Knowledge Base

What the knowledge base is

The knowledge base (KB) is a curated library of domain-expertise entries under content/knowledge/, organized into category directories (backend, core, web-app, web3, ml, …). Each entry is a single Markdown file with YAML frontmatter and a body. During prompt assembly, the entries a pipeline step declares are spliced into that step's Knowledge Base section so the agent running the step gets the relevant expertise inline — no web lookup, no guessing.

The KB is orthogonal to the pipeline: a step references entries by name, and the same entry can be reused by many steps. Entries are versioned as a set via content/knowledge/VERSION (bumped on merge by the freshness workflow).

Scope of this guide. This guide covers authoring, injection, and overriding entries. The freshness lifecycle — volatility cadence, the daily audit cron, the five PR gates, Lens-I gap detection, and the source allowlist — lives in the Knowledge Freshness guide, along with the live entry/category counts (see its KB inventory section). Counts and tiers are not restated here.

How entries are injected during assembly

When scaffold run <step> assembles a prompt, it builds a merged name→path index of every entry, loads the names the step declares, and renders each into the step's Knowledge Base section.

#my-svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#000000;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#my-svg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#my-svg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#my-svg .error-icon{fill:#552222;}#my-svg .error-text{fill:#552222;stroke:#552222;}#my-svg .edge-thickness-normal{stroke-width:1px;}#my-svg .edge-thickness-thick{stroke-width:3.5px;}#my-svg .edge-pattern-solid{stroke-dasharray:0;}#my-svg .edge-thickness-invisible{stroke-width:0;fill:none;}#my-svg .edge-pattern-dashed{stroke-dasharray:3;}#my-svg .edge-pattern-dotted{stroke-dasharray:2;}#my-svg .marker{fill:#666;stroke:#666;}#my-svg .marker.cross{stroke:#666;}#my-svg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#my-svg p{margin:0;}#my-svg .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#000000;}#my-svg .cluster-label text{fill:#333;}#my-svg .cluster-label span{color:#333;}#my-svg .cluster-label span p{background-color:transparent;}#my-svg .label text,#my-svg span{fill:#000000;color:#000000;}#my-svg .node rect,#my-svg .node circle,#my-svg .node ellipse,#my-svg .node polygon,#my-svg .node path{fill:#eee;stroke:#999;stroke-width:1px;}#my-svg .rough-node .label text,#my-svg .node .label text,#my-svg .image-shape .label,#my-svg .icon-shape .label{text-anchor:middle;}#my-svg .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#my-svg .rough-node .label,#my-svg .node .label,#my-svg .image-shape .label,#my-svg .icon-shape .label{text-align:center;}#my-svg .node.clickable{cursor:pointer;}#my-svg .root .anchor path{fill:#666!important;stroke-width:0;stroke:#666;}#my-svg .arrowheadPath{fill:#333333;}#my-svg .edgePath .path{stroke:#666;stroke-width:1px;}#my-svg .flowchart-link{stroke:#666;fill:none;}#my-svg .edgeLabel{background-color:white;text-align:center;}#my-svg .edgeLabel p{background-color:white;}#my-svg .edgeLabel rect{opacity:0.5;background-color:white;fill:white;}#my-svg .labelBkg{background-color:rgba(255, 255, 255, 0.5);}#my-svg .cluster rect{fill:hsl(0, 0%, 98.9215686275%);stroke:#707070;stroke-width:1px;}#my-svg .cluster text{fill:#333;}#my-svg .cluster span{color:#333;}#my-svg div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(-160, 0%, 93.3333333333%);border:1px solid #707070;border-radius:2px;pointer-events:none;z-index:100;}#my-svg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#000000;}#my-svg rect.text{fill:none;stroke-width:0;}#my-svg .icon-shape,#my-svg .image-shape{background-color:white;text-align:center;}#my-svg .icon-shape p,#my-svg .image-shape p{background-color:white;padding:2px;}#my-svg .icon-shape .label rect,#my-svg .image-shape .label rect{opacity:0.5;background-color:white;fill:white;}#my-svg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#my-svg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#my-svg .node .neo-node{stroke:#999;}#my-svg [data-look="neo"].node rect,#my-svg [data-look="neo"].cluster rect,#my-svg [data-look="neo"].node polygon{stroke:url(#my-svg-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg [data-look="neo"].node path{stroke:url(#my-svg-gradient);stroke-width:1px;}#my-svg [data-look="neo"].node .outer-path{filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg [data-look="neo"].node .neo-line path{stroke:#999;filter:none;}#my-svg [data-look="neo"].node circle{stroke:url(#my-svg-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg [data-look="neo"].node circle .state-start{fill:#000000;}#my-svg [data-look="neo"].icon-shape .icon{fill:url(#my-svg-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg [data-look="neo"].icon-shape .icon-neo path{stroke:url(#my-svg-gradient);filter:drop-shadow( 1px 2px 2px rgba(185,185,185,1));}#my-svg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}pipeline stepfrontmatter:knowledge-base: [names]loadEntries(index, names)resolvedoverlay.knowledge[step](or step frontmatter)buildKnowledgeBaseSection## name: description +bodybuildIndexWithOverridesglobal +.scaffold/knowledgerenderGapSignalTail(unless QUIET)Knowledge Base sectionin the assembled prompt

The injection runs in three stages:

  1. Index. buildIndexWithOverrides(projectRoot, globalKnowledgeDir) walks the global KB and the project-local override dir, returning a map of entry name → file path (src/core/assembly/knowledge-loader.ts:217). The global dir is resolved by getPackageKnowledgeDir, which prefers a repo-local content/knowledge and otherwise falls back to the package's bundled copy (src/utils/fs.ts:55).
  2. Select + load. scaffold run reads the entry names from the pipeline overlay (if set) or the step's knowledge-base frontmatter, then calls loadEntries (src/cli/commands/run.ts:370-374). A name that isn't in the index becomes a non-fatal FRONTMATTER_KB_ENTRY_MISSING warning rather than an error.
  3. Render. buildKnowledgeBaseSection emits one ## <name>: <description> block per entry followed by the entry content (src/core/assembly/engine.ts:174-185).

Deep Guidance vs. Summary (the dual-channel split)

An entry body may contain a ## Summary section and a ## Deep Guidance section. For runtime CLI assembly, loadEntries injects only the Deep Guidance — extractDeepGuidance finds the ## Deep Guidance heading and returns everything after it, falling back to the full body when the heading is absent (src/core/assembly/knowledge-loader.ts:156). The loader stores deepOnly ?? fullBody as the entry content (src/core/assembly/knowledge-loader.ts:315). This avoids re-stating guidance the command prompt already shows the user. The scaffold build path (self-contained generated commands) instead uses loadFullEntries, which keeps the complete body since no runtime assembly is available downstream.

The gap-signal tail

After the entry bodies, assembly appends a gap-signal tail that tells the agent to emit a knowledge_gap_signal observability event if it searches the section and finds nothing useful (src/core/assembly/engine.ts:183). The template lives in renderGapSignalTail, which returns the empty string when SCAFFOLD_GAP_SIGNAL_QUIET=1 so tests and CI stay deterministic (src/core/assembly/gap-signal-tail.ts:45). Those signals feed Lens I — see the Knowledge Freshness guide{mode=advisory} for what happens to them.

Browsing and overriding entries

The KB resolves in two layers. Global entries ship with Scaffold under content/knowledge/. Local overrides live under a project's .scaffold/knowledge/ directory (src/core/assembly/knowledge-loader.ts:225). When both define the same name, the local override wins — the merge layers local on top of global (src/core/assembly/knowledge-loader.ts:265).

Use the scaffold knowledge subcommands to inspect and manage the effective set:

CommandWhat it doesSource
scaffold knowledge listList every entry — global plus local overrides — with a global / local override source label and description. --format json for machine output.src/cli/commands/knowledge.ts:45
scaffold knowledge show <name>Print the effective content of an entry (the local override if present, else the global entry), prefixed with its source path.src/cli/commands/knowledge.ts:109
scaffold knowledge reset <name>Remove a local override, reverting to the global entry. Refuses to delete a dirty override unless --auto is passed.src/cli/commands/knowledge.ts:167
scaffold knowledge update <target> [instructions..]Assemble a knowledge-update prompt for one entry or every entry in a pipeline step (--step). Unlike the other subcommands, it resolves targets from the global KB index and reads step lists from base step frontmatter — not local-only overrides or pipeline overlays.src/cli/commands/knowledge.ts:251

To author a local override, create .scaffold/knowledge/<category>/<name>.md with the same name: as the global entry you want to replace, using the same frontmatter shape described below. A duplicate name within the local dir emits a warn: duplicate knowledge override name to stderr and the last file wins.

--knowledge-root (a separate resolver)

The scaffold knowledge commands resolve the global dir via getPackageKnowledgeDir and the local dir via .scaffold/knowledge/. The --knowledge-root flag is a different mechanism: it belongs to the observability audit's Lens-I suppression, which needs to locate a KB to know which gap topics are already covered. It does not redirect prompt assembly or the scaffold knowledge subcommands. Its three-tier resolution (CLI flag → yaml → auto-detect) is documented in the Knowledge Freshness guide{mode=advisory}.

Authoring a new entry

  1. Pick a category directory under content/knowledge/<category>/. Prefer an existing category; a brand-new category is a larger change.
  2. Name the file after the slug. The basename should match the name: field (e.g. backend-api-design.mdname: backend-api-design). The index keys on the frontmatter name, not the filename (src/core/assembly/knowledge-loader.ts:198), but keeping them aligned prevents confusion and keeps freshness/suppression matching predictable.
  3. Write the frontmatter (fields below).
  4. Structure the body with an optional intro paragraph, then ## Summary and ## Deep Guidance. The Deep Guidance section is what gets injected into runtime prompts.
  5. Wire it into a step by adding the name to that pipeline step's knowledge-base: frontmatter list (or a pipeline overlay).
  6. Validate: make validate-knowledge runs the frontmatter validator over every entry (src/cli/commands/validate-knowledge.ts:20).

Frontmatter fields

The frontmatter is YAML between --- fences. The runtime loader coerces these fields (src/core/assembly/knowledge-loader.ts:65); the freshness validator enforces them as a Zod schema (src/validation/knowledge-frontmatter-validator.ts:42-50). Only name is strictly required for an entry to be indexed at all (src/core/assembly/knowledge-loader.ts:102).

FieldRequiredShapeNotes
nameyes/^[a-z][a-z0-9-]*$/ (must start with a letter)The index key. Steps reference entries by this. An entry with no name is silently dropped from the index.
descriptionyesstringRendered into the ## <name>: <description> heading at injection. The validator warns past ~200 chars but does not fail.
topicsnostring[] (default [])Free-form tags carried as discovery and audit metadata. They are parsed and stored on the entry but do not drive selection — injection is always by explicit name (see above).
volatilitynostable|evolving|fast-moving (default evolving)Drives the freshness cadence — see the freshness guide.
last-reviewednoYYYY-MM-DD or nullISO date; unquoted dates are coerced to strings. Audited entries advance this on a current/drift verdict.
version-pinnostring or nullPins the entry to an edition (e.g. "OWASP Top 10 2021").
sourcesnoobject[] (default [])Each: url (SSRF-checked), optional anchor (must start with #), retrieved (ISO date), hash (sha256). An entry with no sources is skipped by the freshness cron.

A minimal entry:

---
name: retry-with-jitter
description: Exponential backoff with full jitter for resilient client retries
topics: [resilience, networking, retries]
volatility: stable
last-reviewed: null
version-pin: null
sources:
  - url: https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/
---

One-paragraph framing of why this matters.

## Summary

A short overview that may overlap with the command prompt's own text.

## Deep Guidance

The supplementary expertise that gets injected into runtime prompts. This is
the section assembly keeps for CLI prompts (see the dual-channel split above).

Want the freshness cron to keep it current? Add at least one sources[] entry and pick a volatility tier. Entries without sources are still injected into prompts, but the daily audit skips them. The cadence model, source allowlist, and audit verdicts are all in the Knowledge Freshness guide.

See also