/agent-context
Self-contained execution runbook. Read top-to-bottom. Do not backtrack.
Every output file reads like a human wrote it from real evidence. No [to fill] residue except in
docs/agents/testing.md
Mock stance. If the graph does not support a section, omit the section entirely — do not scaffold.
Reference files (read on-demand, not required for normal execution):
references/SCHEMAS.md— full graph schemas, 20-rule lint spec, sample datareferences/TEMPLATES.md— verbatim output file templates numbered 1–28 matching write order
Variables
All variables set during execution. Phases that set each variable are noted.
| Variable | Type | Set in | Description |
|---|---|---|---|
PROJECT_ROOT |
string | Phase 0 | Absolute path to target repo |
FORCE |
bool | Phase 0 | Overwrite existing files |
DRY_RUN |
bool | Phase 0 | Print content, write nothing |
WITH_CI |
bool | Phase 0 | Generate CI workflow and hook |
MINIMAL |
bool | Phase 0 | Emit only AGENTS.md + CLAUDE.md + .claude/settings.json; skip docs/agents/ and cross-vendor files |
IS_GIT_REPO |
bool | Phase 0 | Whether PROJECT_ROOT is a git repo |
KNOWLEDGE_GRAPH |
object | Phase 1 | Parsed knowledge-graph.json |
DOMAIN_GRAPH |
object or null | Phase 1 | Parsed domain-graph.json or null |
GRAPH_STALE |
bool | Phase 1 | HEAD != graph commit hash |
DOMAIN_QUALITY |
enum | Phase 1 | "high", "mixed", "low", or "missing" |
EXISTING_CONVENTIONS |
string or null | Phase 1 | Content of existing CONVENTIONS.md |
nodesById |
Map | Phase 2 | node.id → Node |
functionsByFile |
Map | Phase 2 | filePath → function Node[] |
importsOut |
Map | Phase 2 | node.id → target id[] |
importsIn |
Map | Phase 2 | node.id → source id[] |
containsOut |
Map | Phase 2 | file node.id → function node.id[] |
layersByNodeId |
Map | Phase 2 | node.id → layer name |
nodesByLayer |
Map | Phase 2 | layer name → node.id[] |
COMMANDS |
dict | Phase 3 | keys: install, dev, test, lint, build (any subset) |
FRAMEWORK |
string | Phase 3 | Primary framework name (e.g. "Nuxt.js", "FastAPI") or "unknown" if manifest found but no match |
LANG |
string | Phase 3 | Primary language (e.g. "TypeScript/JavaScript", "Python", "Rust", "Go") or empty if no manifest found |
PROJECT_SUMMARY |
string | Phase 3 | One-line project summary from knowledge-graph project.description or first tour step (120 chars max) |
MONOREPO_TOOL |
string or null | Phase 3 | Detected monorepo manager: "pnpm-workspaces", "npm-workspaces", "yarn-workspaces", "lerna", "nx", "turborepo", "cargo-workspace", or null |
MONOREPO_WORKSPACES |
string[] | Phase 3 | Package paths from workspace manifest; empty if not a monorepo |
CHANGELOG_SNIPPET |
string or null | Phase 3 | Last 3–5 entries extracted from CHANGELOG.md; null if file absent |
NON_OBVIOUS |
string[] | Phase 4 | Up to 5 phrased bullet strings from topology signals (4a–4e) |
CONTENT_SIGNALS |
string[] | Phase 4 | Up to 3 phrased bullet strings from content signal (4g); may be empty |
CONVENTIONS_DIRECTIVES |
map or null | Phase 4 | Extracted directives from CONVENTIONS.md, grouped by category (safety, naming, patterns, workflow, other) |
CONVENTIONS_ACTION |
enum or null | Phase 1 | "stub", "skip", or "legacy" when EXISTING_CONVENTIONS is null; null otherwise |
total_flow_count |
int | Phase 2 | Count of type=="flow" nodes in DOMAIN_GRAPH; 0 if DOMAIN_GRAPH is null |
flowsByDomain |
Map | Phase 2 | domain.id → flow Node[] via contains_flow edges |
cross_domain_edges |
tuple[] | Phase 2 | Ordered (source.name, target.name) pairs from cross_domain edges |
LAYER_GLOBS |
Map | Phase 5 | layer.name → glob pattern derived from common path prefix of layer's file nodes |
Phase 0 — Argument parsing
- Tokenise
$ARGUMENTSon whitespace. - Extract flags:
--force→FORCE=true,--dry-run→DRY_RUN=true,--with-ci→WITH_CI=true,--minimal→MINIMAL=true. Defaults: all false. - First non-flag token → target path. Resolve relative to CWD. If absent →
PROJECT_ROOT = CWD(current working directory becomes$PROJECT_ROOT). - Verify
PROJECT_ROOTis a directory. If not → printagent-context: <path> is not a directory.→ stop. - Run
git -C $PROJECT_ROOT rev-parse --git-dir. Success →IS_GIT_REPO=true. Failure →IS_GIT_REPO=false(warn, continue). - If
MINIMAL=trueandWITH_CI=true: printagent-context: --minimal and --with-ci are mutually exclusive. --with-ci ignored.SetWITH_CI=false.
Phase 1 — Prerequisite gates
Run gates A → B → C → D in order. Gate A is hard (stop on failure). Gates B, C, D are soft (warn, continue).
Gate A — Knowledge graph (HARD)
Check $PROJECT_ROOT/.understand-anything/knowledge-graph.json.
Missing — print verbatim and stop:
agent-context: knowledge graph not found.
This plugin needs $PROJECT_ROOT/.understand-anything/knowledge-graph.json in the target
repo to generate useful context files. If you have not set up
Understand-Anything yet, run:
/plugin marketplace add Lum1104/understand-anything
/plugin install understand-anything
Then, in the repo you want to generate context for, run:
/understand
That will produce $PROJECT_ROOT/.understand-anything/knowledge-graph.json. Re-run
/agent-context once it is present.
Present but not valid JSON — print verbatim and stop:
agent-context: knowledge graph is not valid JSON.
$PROJECT_ROOT/.understand-anything/knowledge-graph.json exists but cannot be parsed.
This usually means /understand was interrupted. Re-run:
/understand
and try again.
Missing required top-level keys (version, project, nodes, edges, layers) — print verbatim and stop:
agent-context: knowledge graph schema does not match what this plugin
expects. Required top-level keys: version, project, nodes, edges, layers.
Missing: <list>.
Update Understand-Anything (/plugin update understand-anything) or file
an issue at github.com/jonaskahn/agent-context describing the schema
mismatch.
Valid — set KNOWLEDGE_GRAPH to parsed object.
Gate B — Freshness (SOFT)
Skip if IS_GIT_REPO=false. Compare git rev-parse HEAD vs project.gitCommitHash.
Mismatch — set GRAPH_STALE=true. Print:
agent-context: knowledge graph was generated against commit <graph_hash>
but the repo is at <head_hash>. Generated files may be out of date.
Re-run /understand for the best results.
Match or no git — set GRAPH_STALE=false.
Also check project.analyzedAt: if older than 14 days from now, print a staleness warning. Continue either way.
Gate C — Domain graph (SOFT)
Check $PROJECT_ROOT/.understand-anything/domain-graph.json.
Missing — set DOMAIN_QUALITY="missing", DOMAIN_GRAPH=null. Print:
agent-context: domain graph not found. Glossary will be stubbed.
Run /understand-domain to populate docs/agents/glossary.md with
domain-level content.
Present — parse it. Set DOMAIN_GRAPH to parsed object. Compute quality grade:
HEURISTIC_COUNT = count of domain-type nodes whose summary contains "Heuristic"
TOTAL_COUNT = count of domain-type nodes (type == "domain")
if TOTAL_COUNT == 0: DOMAIN_QUALITY = "missing"
elif HEURISTIC_COUNT / TOTAL_COUNT >= 0.5: DOMAIN_QUALITY = "low"
elif HEURISTIC_COUNT / TOTAL_COUNT >= 0.1: DOMAIN_QUALITY = "mixed"
else: DOMAIN_QUALITY = "high"
Gate D — Existing CONVENTIONS.md (SOFT)
Search for CONVENTIONS.md (case-insensitive) in expected locations and report findings.
Step D.1: Grep search in standard locations
Run grep -ri "^" $PROJECT_ROOT/CONVENTIONS.md $PROJECT_ROOT/docs/CONVENTIONS.md 2>/dev/null to find files.
Check in this order:
$PROJECT_ROOT/CONVENTIONS.md(case-insensitive)$PROJECT_ROOT/docs/CONVENTIONS.md(case-insensitive)
Step D.2: File found in expected location
Found in root or docs/ — read content. Set EXISTING_CONVENTIONS to content string. Set CONVENTIONS_ACTION=null. Print:
agent-context: found existing CONVENTIONS.md (<N> lines).
Existing conventions will be transformed into AI-targeted directives in docs/agents/conventions.md.
Location: <root or docs/>
Existing conventions are parsed and distilled into docs/agents/conventions.md (terse AI directives, not verbatim
copy) and linked from AGENTS.md §6 (Deeper Context).
They are NOT inlined into AGENTS.md — the 100-line cap forbids it.
Step D.3: Fallback grep search
If not found in standard locations, run:
find $PROJECT_ROOT -iname "CONVENTIONS.md" -not -path "*/node_modules/*" -not -path "*/.git/*"
Found elsewhere — print diagnostic:
⚠️ CONVENTIONS.md found at unexpected location
Searched the repository — CONVENTIONS.md was found at:
- <path>
Expected location: $PROJECT_ROOT or docs/ folder
If this is your conventions file, please either:
1. Move it to $PROJECT_ROOT (recommended)
2. Move it to the docs/ folder
3. Provide the exact location in your project context
agent-context will skip loading conventions and use default stub.
Set EXISTING_CONVENTIONS=null. Continue to Step D.4.
Not found anywhere — set EXISTING_CONVENTIONS=null. Print diagnostic:
⚠️ CONVENTIONS.md not found
Searched the repository — no CONVENTIONS.md file exists in:
- $PROJECT_ROOT: ❌
- docs/ folder: ❌
- Anywhere else in the project: ❌
According to your README and SKILL.md documentation, the agent-context plugin expects a CONVENTIONS.md file in $PROJECT_ROOT (case-insensitive lookup). This file should contain team coding standards and directives.
If you have a conventions file:
• Move it to $PROJECT_ROOT, or
• Move it to docs/, or
• Specify its location below
If not, agent-context will create a starter stub.
Continue to Step D.4.
Step D.4: Handle missing CONVENTIONS.md
Only reached if EXISTING_CONVENTIONS=null. Determine CONVENTIONS_ACTION:
- If
DRY_RUN=trueOR AskUserQuestion is unavailable (headless / script invocation): setCONVENTIONS_ACTION="stub". Print:agent-context: no CONVENTIONS.md — defaulting to starter stub. - Otherwise call AskUserQuestion with question
"No CONVENTIONS.md found. How should /agent-context handle CONVENTIONS.md?"and four options:- (a) (default, recommended)
Create a starter stub with empty Safety / Naming / Patterns / Workflow sections.→CONVENTIONS_ACTION="stub" - (b)
Skip — do not create CONVENTIONS.md.→CONVENTIONS_ACTION="skip" - (c)
Legacy — write a verbatim copy of AGENTS.md.→CONVENTIONS_ACTION="legacy" - (d)
Provide custom path — I have a CONVENTIONS file elsewhere.→ prompt user for path with text input, then attempt to read that file and treat as found.
- (a) (default, recommended)
Do NOT mutate EXISTING_CONVENTIONS. It stays null for this run. The stub written by option (a) is not parsed back as
conventions on the same run; on the next run, Gate D detects it via the Found branch.
Phase 2 — Graph indexing
Schema quick-ref
Node key fields: id (opaque, file: or func: prefix), type ("file"|"function"), filePath (always present),
lineRange ([start,end], function nodes only), summary (generic if starts with "Source file " or ends with
"— function in this module."), complexity ("simple"|"moderate"|"complex").
Edge types: contains (file→function), imports (file→file, one-directional).
Layer: { id, name, description, nodeIds[] }. Pre-computed — do not recompute.
Tour: { order, title, description, nodeIds[] }. Dependency-ordered onboarding steps.
For full field-level documentation, see references/SCHEMAS.md §1–§6.
Build indexes
One pass each:
nodes[]→nodesById(Map: node.id → Node).edges[]:type == "contains"→ push target intocontainsOut[source]type == "imports"→ push target intoimportsOut[source], push source intoimportsIn[target]
layers[]→nodesByLayer(Map: layer.name → node.id[]), and for each nodeId:layersByNodeId[nodeId] = layer.name.- Filter
nodesByIdvalues wheretype == "function"→ group byfilePathintofunctionsByFile. - If
DOMAIN_GRAPHis non-null:total_flow_count= count ofDOMAIN_GRAPH.nodeswheretype == "flow". IfDOMAIN_GRAPHis null, set to 0.flowsByDomain= Map ofdomain.id→ flow Node[], built by walkingDOMAIN_GRAPH.edgesof typecontains_flow.cross_domain_edges= ordered list of(source.name, target.name)tuples from edges of typecross_domain, usingnodesByIdlookups againstDOMAIN_GRAPH.nodes. Skip if either endpoint is missing.
Integrity assertions (warn, do not stop)
- All edge source/target ids exist in
nodesById. Print warning with orphan count if any. - All
layer.nodeIdsentries exist innodesById. Same. - All
tour[].nodeIdsentries exist innodesById. Same. - No node in more than one layer. If conflict, use first occurrence, warn.
Edge case: empty arrays
nodes[]empty → all indexes are empty maps. Phase 4 produces zero signals. Phase 5 emits AGENTS.md with header + §3 (if commands found) + §5 (Safety) + §6 (Deeper Context pointing to stubs). Omit §1, §2, §4.layers[]empty →layersByNodeIdandnodesByLayerare empty. AGENTS.md §2 tagline becomes:**No layers detected. Run /understand with more source files.**tour[]empty → AGENTS.md §1 has tagline and test sentence but zero bullets. Tagline becomes:**Layers are the architecture. Read the module map below.**
Phase 3 — Shallow command discovery
The graph does not contain build commands. Read manifests directly from PROJECT_ROOT:
package.json (check first)
If package.json exists, parse it:
scripts.dev→COMMANDS.devscripts.test→COMMANDS.testscripts.lint→COMMANDS.lintscripts.build→COMMANDS.build
Infer install command from lockfile. Check in this order, use first found:
bun.lockb→bun installpnpm-lock.yaml→pnpm installyarn.lock→yarn installpackage-lock.json→npm install- None found →
npm install(default)
Set COMMANDS.install to the inferred command.
pyproject.toml (if no package.json)
- Install:
pip install -e . - Test:
pytest
Cargo.toml (if no package.json or pyproject.toml)
- Build:
cargo build - Test:
cargo test - Lint:
cargo check
go.mod (if none of the above)
- Build:
go build ./... - Test:
go test ./... - Lint:
go vet ./...
No manifest found
COMMANDS is empty. Omit AGENTS.md §3 entirely.
Framework and language detection
Run this step using whichever manifest was found above (or "none" if none found). Set FRAMEWORK, LANG, and
PROJECT_SUMMARY.
LANG — infer from which manifest was found:
package.json→"TypeScript/JavaScript"pyproject.toml→"Python"Cargo.toml→"Rust"go.mod→"Go"- None found →
""(empty — omit Stack line from architecture.md)
FRAMEWORK — scan the manifest found above for these keys (check in order listed, use first match):
package.json — inspect dependencies and devDependencies keys:
| Key present | FRAMEWORK |
|---|---|
nuxt or any @nuxtjs/* |
"Nuxt.js" |
next |
"Next.js" |
@nestjs/core |
"NestJS" |
astro |
"Astro" |
svelte |
"Svelte" |
solid-js |
"SolidJS" |
hono |
"Hono" |
fastify |
"Fastify" |
express |
"Express" |
react |
"React" |
vue |
"Vue.js" |
| None of the above | "unknown" |
pyproject.toml — inspect [tool.poetry.dependencies] or [project.dependencies]:
| Key present | FRAMEWORK |
|---|---|
fastapi |
"FastAPI" |
django |
"Django" |
litestar |
"Litestar" |
flask |
"Flask" |
| None of the above | "unknown" |
Cargo.toml — inspect [dependencies]:
| Key present | FRAMEWORK |
|---|---|
actix-web |
"Actix Web" |
axum |
"Axum" |
rocket |
"Rocket" |
| None of the above | "unknown" |
go.mod — inspect require block:
| Module present | FRAMEWORK |
|---|---|
github.com/gin-gonic/gin |
"Gin" |
github.com/labstack/echo |
"Echo" |
github.com/gofiber/fiber |
"Fiber" |
| None of the above | "unknown" |
No manifest → FRAMEWORK = "" (empty).
PROJECT_SUMMARY — derive from KNOWLEDGE_GRAPH:
- If
project.descriptionis non-empty → use it (truncate to 120 chars at word boundary). - Else if
touris non-empty → usetour[0].description(truncate to 120 chars at word boundary). - Else →
""(empty).
Monorepo detection
Run after manifest discovery. Check these signals in order:
| Signal | Condition | MONOREPO_TOOL |
|---|---|---|
nx.json exists |
— | "nx" |
turbo.json exists |
— | "turborepo" |
lerna.json exists |
— | "lerna" |
package.json found |
workspaces key is an array |
"pnpm-workspaces" (if pnpm lockfile) / "yarn-workspaces" (if yarn lockfile) / "npm-workspaces" |
Cargo.toml found |
[workspace] section present |
"cargo-workspace" |
| None of the above | — | null |
If MONOREPO_TOOL is not null: set MONOREPO_WORKSPACES to the list of workspace package paths from the relevant
manifest's workspaces / members field. If the field uses glob patterns rather than explicit paths, keep the
patterns as-is (e.g., packages/*). If the manifest has no such field, set to [].
If MONOREPO_TOOL is null: set MONOREPO_WORKSPACES = [].
When MONOREPO_TOOL is not null, append a note to COMMANDS.install (if present):
<install_command> # monorepo root — see {MONOREPO_TOOL} workspaces
CHANGELOG extraction
Check for CHANGELOG.md, CHANGELOG, CHANGES.md, or HISTORY.md at PROJECT_ROOT (case-insensitive). Use the
first match found.
Found — extract the last 3–5 changelog entries. An "entry" is any ## heading block (version or date heading)
plus the bullets/lines immediately following it until the next ## heading. Extract text only — strip any markdown
formatting except backticks. Truncate each entry to 300 chars. Set CHANGELOG_SNIPPET to the joined entries as a
plain-text string.
Not found — set CHANGELOG_SNIPPET = null.
Phase 4 — Convention mining
Apply 5 signals. Each produces zero or more candidate bullets. Rank all candidates by rarity. Take top 3–5. Phrase every bullet negation-forward (lead with "Don't", "No", "Never", or "If X, stop").
Signal 4a — Cross-layer import anomalies
For each imports edge, look up source and target layers via layersByNodeId. For each (source_layer, target_layer)
pair, count edges. Pairs with count == 1 or count == 2 are anomalies.
Phrase each anomaly as:
- {source_layer} rarely imports {target_layer} directly — the one exception is \{source.filePath}` importing
`{target.filePath}`. Don't remove it.`
Rarity per candidate: 1.0 / count (where count is the pair's edge count).
Signal 4b — Naming deviators within a layer
For each layer with >=5 file nodes:
- Tokenise each file's basename on
.,-, and_. - Find the modal (most frequent) last-token (e.g.
.vue,.ts). - If >80% of files share the modal token, the remaining files are deviators.
Phrase as:
- Most {layer} files end in \{token}`. Don't rename the exceptions — {list of up to 3 deviators} — they are
intentional.`
Rarity per candidate: 1.0 / deviator_count.
Signal 4c — Layer/path disagreements
For each file node, predict its layer from filePath top-level directory prefix:
app/pages/orpages/→ "Pages"app/components/orcomponents/→ "Components"app/composables/orcomposables/→ "Composables"server/→ "Server"app/utils/orutils/orlib/→ "App utils" or "Utility"src/→ infer from next directory segment
Compare predicted layer to actual layer from layersByNodeId. Collect disagreements.
If total disagreements > 3: skip this signal entirely (bulk disagreements indicate graph-level misclassification, not conventions).
If <= 3 disagreements, phrase each as:
- \{filePath}` lives under {predicted} by path but is classified as {actual}. Don't reorganise it.`
Rarity per candidate: 1.0 / total_disagreement_count.
Signal 4d — Dependency direction violations
Select precedence array based on project.frameworks:
- If frameworks contain "Nuxt" or "Vue" (case-insensitive):
["Pages", "Components", "Composables", "App utils", "Server"] - Otherwise:
["API", "Service", "Data", "Utility"]
For each imports edge, get source and target layer positions in the precedence array. If target position < source
position (target is "higher" than source), it is an upward violation.
Skip edges where either layer is not in the precedence array.
Phrase as:
- {source_layer} normally does not import from {target_layer}, but \{source.filePath}` does. If removing it, verify
it is not an event callback.`
Rarity per candidate: 1.0 / violation_count_for_this_layer_pair.
Signal 4e — Rarity ranking and selection
- Collect all candidates from 4a–4d.
- Sort by rarity descending (highest rarity = rarest = most worth surfacing).
- Tie-breaker when rarity scores are equal: prefer 4a > 4c > 4d > 4b.
- Take top 5. If fewer than 3 candidates total, emit only what exists — do not pad.
- Set
NON_OBVIOUSto the selected bullet strings.
Signal 4f — Convention directive extraction (only if EXISTING_CONVENTIONS not null)
Parse EXISTING_CONVENTIONS into AI-targeted directives and set CONVENTIONS_DIRECTIVES.
Step 1 — Identify imperative lines. For each line or bullet in EXISTING_CONVENTIONS, check if it contains any
of the following keywords (case-insensitive): must, must not, never, always, don't, do not, avoid,
use, prefer, require. Lines matching at least one keyword are rule candidates. Lines that are headings
(#-prefixed), blank, or pure prose narrative (no imperative verb) are skipped.
Step 2 — Strip rationale. For each candidate, truncate at the first occurrence of —, because, (this,
or . that follows the core directive clause. Keep only the imperative clause.
Step 3 — Normalize prefix.
- If the line contains a negation (
must not,never,don't,do not,avoid,no): normalize prefix toMUST NOT. - If the line expresses obligation (
must,always,require,use,prefer): normalize prefix toMUST. - Otherwise: keep the original imperative verb.
Step 4 — Categorize each directive into exactly one bucket:
safety— keywords: secret, credential, env, migration, destructive, delete, drop, test disab, commitnaming— keywords: name, naming, case, suffix, prefix, file name, variable, module name, casingpatterns— keywords: import, abstract, class, struct, coupling, layer, circular, dependworkflow— keywords: phase, PR, pull request, review, gate, branch, deploy, merge, approvalother— anything not matching the above
Step 5 — Deduplicate. For each directive, check if it semantically overlaps with any string in NON_OBVIOUS
(same file path mentioned, or >70% word overlap). If so, skip it — NON_OBVIOUS already surfaces it.
Step 6 — Set result. If at least one directive was extracted: set CONVENTIONS_DIRECTIVES to a map of
{safety: [...], naming: [...], patterns: [...], workflow: [...], other: [...]} (omit empty categories).
If zero directives were extracted: set CONVENTIONS_DIRECTIVES = null.
Signal 4g — Content-based pattern extraction (from function summaries)
Complements topology signals (4a–4e) with semantic patterns inferred from non-generic function summaries.
Step 1 — Collect substantive function summaries. From nodesById, filter to function nodes where summary is
non-generic (does not end with "— function in this module."). Require at least 10 such nodes to proceed; if fewer,
set CONTENT_SIGNALS = [] and skip remaining steps.
Step 2 — Identify recurring return-shape patterns. Scan summaries for these phrases (case-insensitive). For each that appears in ≥3 distinct function summaries, record a candidate:
| Phrase pattern | Candidate bullet |
|---|---|
returns {data, error} or returns (data, error) |
- Functions return \{data, error}` pairs — don't throw, return the error field.` |
throws or raise |
- Errors are thrown, not returned as values — don't swallow exceptions. |
async / await / Promise |
- Most functions are async — don't mix sync I/O in async call chains. |
decorator pattern: @ prefix in summary |
- Decorators are the extension point — don't subclass where a decorator fits. |
middleware |
- Middleware is the primary composition pattern — chain it, don't fork it. |
Step 3 — Identify naming conventions from function names. Among the substantive function nodes, tokenise
node.name by camelCase / snake_case / kebab-case boundaries. Find the modal prefix (appears in ≥4 function names
within any single layer). If that prefix is not already surfaced in NON_OBVIOUS, add:
- Functions in {layer} start with \{prefix}` — match it when adding new ones.`
Step 4 — Deduplicate against NON_OBVIOUS. Skip any candidate that overlaps >70% with an existing
NON_OBVIOUS bullet (word-overlap check).
Step 5 — Set result. Take up to 3 candidates from Steps 2–3 (Step 2 first). Set CONTENT_SIGNALS to the
selected bullet strings. If zero candidates, set CONTENT_SIGNALS = [].
CONTENT_SIGNALS are appended to NON_OBVIOUS after the top-5 topology picks, capped so the total
NON_OBVIOUS + CONTENT_SIGNALS list does not exceed 7 bullets going into Phase 5.
Phase 5 — File generation
Write-or-skip decision table
| Condition | Action |
|---|---|
| File does not exist | Create |
File exists + FORCE=false |
Skip. Add to "skipped" list for Phase 7. |
File exists + FORCE=true |
Overwrite |
AGENTS.md exists + contains ## Absolute rules + FORCE=true |
Overwrite all sections except ## Absolute rules — read the existing file, extract the section from its heading to the next ## heading, and splice it into the freshly-generated content in place of the template's §5. This protects user customisations across forced refreshes. |
.claude/settings.json (any state) |
Always merge (union deny arrays, preserve existing hooks). Never overwrite. |
.gitignore (any state, DRY_RUN=false only) |
Append CLAUDE.local.md if line not already present. Under DRY_RUN=true, print the would-be append but do not write. |
.aider.conf.yml exists |
Merge: append to read list if not present. |
DRY_RUN=true |
For all files including merge targets: compute final content (reading existing files for merges), print between ===== FILE: <path> ===== separators, write nothing. |
MINIMAL=true |
Skip items #1–7 (docs/agents/), #13–#19 (cross-vendor), #20–#28 (new vendor/rules). Write only #8–#12 + #20 (.claude/rules/) and AGENTS.md/CLAUDE.md/.claude/settings.json. |
Write order (process sequentially, 1–28)
Items #1–7 are skipped when MINIMAL=true. Items #13–19 and #22–28 are skipped when MINIMAL=true.
| # | File | Condition | Template |
|---|---|---|---|
| 1 | docs/agents/tech-debt.md |
skip if MINIMAL |
TEMPLATES.md §11 |
| 2 | docs/agents/testing.md |
skip if MINIMAL |
TEMPLATES.md §10 |
| 3 | docs/agents/glossary.md |
skip if MINIMAL |
TEMPLATES.md §7 (if quality "high"/"mixed") or §8 (if "low"/"missing") |
| 4 | docs/agents/conventions.md |
only if EXISTING_CONVENTIONS not null; skip if MINIMAL |
TEMPLATES.md §9 |
| 5 | docs/agents/patterns.md |
skip if MINIMAL |
TEMPLATES.md §6 |
| 6 | docs/agents/architecture.md |
skip if MINIMAL |
TEMPLATES.md §5 |
| 7 | docs/agents/flow.md or docs/agents/flows/ |
skip if MINIMAL (mode below) |
TEMPLATES.md §19 (single) or §20+§21 (folder) |
| 8 | .claude/settings.json |
always (merge) | TEMPLATES.md §4 |
| 9 | .gitignore |
always (append, no-op under DRY_RUN) |
— |
| 10 | CLAUDE.local.md |
always | TEMPLATES.md §3 |
| 11 | CLAUDE.md |
always | TEMPLATES.md §2 |
| 12 | AGENTS.md |
always | TEMPLATES.md §1 |
| 13 | .cursor/rules/agents.mdc |
skip if MINIMAL |
TEMPLATES.md §12 |
| 14 | .cursor/rules/<layer>.mdc (one per layer) |
skip if MINIMAL; skip if LAYER_GLOBS is empty |
TEMPLATES.md §26 |
| 15 | .github/copilot-instructions.md |
skip if MINIMAL |
TEMPLATES.md §13 |
| 16 | .github/instructions/<layer>.instructions.md |
skip if MINIMAL; one per layer in LAYER_GLOBS |
TEMPLATES.md §27 |
| 17 | .codex/instructions.md |
skip if MINIMAL |
TEMPLATES.md §14 |
| 18 | GEMINI.md |
skip if MINIMAL |
TEMPLATES.md §24 |
| 19 | .windsurf/rules/agents.md |
skip if MINIMAL |
TEMPLATES.md §25 |
| 20 | .claude/rules/<layer>.md (one per layer) |
always (even with MINIMAL); skip if no layers |
TEMPLATES.md §23 |
| 21 | CONVENTIONS.md |
only if EXISTING_CONVENTIONS is null AND CONVENTIONS_ACTION != "skip"; skip if MINIMAL |
TEMPLATES.md §22 if CONVENTIONS_ACTION=="stub"; §15 if "legacy" |
| 22 | .aider.conf.yml |
skip if MINIMAL (merge if exists) |
TEMPLATES.md §16 |
| 23 | .github/workflows/agent-context-freshness.yml |
only if WITH_CI=true |
TEMPLATES.md §17 |
| 24 | hooks/check-freshness.sh |
only if WITH_CI=true |
TEMPLATES.md §18 (chmod +x) |
| 25 | .agent-context/manifest.json |
always | TEMPLATES.md §28 |
Flow output mode (Step 7)
Decide between single-file and folder mode using the indexes from Phase 2:
total_flow_count <= 8(orDOMAIN_GRAPHis null): write a singledocs/agents/flow.mdfrom TEMPLATES.md §19. IfDOMAIN_GRAPHis null ortotal_flow_count == 0, the template emits the "no flows extracted" stub branch.total_flow_count > 8: write a folderdocs/agents/flows/containing:docs/agents/flows/index.md(TEMPLATES.md §20)docs/agents/flows/<domain.slug>.mdfor each domain indomains_with_flows(TEMPLATES.md §21)
Domain slug derivation (used by §20 and §21):
- Lowercase
domain.name. - Strip diacritics. Replace any run of
[^a-z0-9]+with-. Trim leading/trailing-. - Reserved word: never emit
index. If a domain slugs toindex, suffix with-domain. - Collisions across domains: append
-2,-3, … in domain iteration order. Record the final slug on the domain object so cross-references (§21 "See also") stay consistent.
Format migration (automatic, not gated by --force)
Before writing flow output, detect and clean up leftovers from a prior run that used the other mode:
- Folder mode active and
docs/agents/flow.mdexists → recognise it as the old single-file version, delete it, then write the folder. Report `↻ docs/agents/flow.md → docs/agents/flows/ (migra
…(truncated)