@c4a/context-cli 0.5.29-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +122 -0
  2. package/cli.js +37711 -0
  3. package/package.json +18 -0
  4. package/plugin/.claude-plugin/plugin.json +16 -0
  5. package/plugin/.claude-plugin/plugin.json.template +16 -0
  6. package/plugin/.codex-plugin/plugin.json +35 -0
  7. package/plugin/.codex-plugin/plugin.json.template +35 -0
  8. package/plugin/commands/align.md +54 -0
  9. package/plugin/commands/capture-aspect.md +17 -0
  10. package/plugin/commands/capture-code.md +25 -0
  11. package/plugin/commands/capture.md +67 -0
  12. package/plugin/commands/compile.md +89 -0
  13. package/plugin/commands/context.md +30 -0
  14. package/plugin/commands/drop.md +17 -0
  15. package/plugin/commands/extract.md +18 -0
  16. package/plugin/commands/init.md +95 -0
  17. package/plugin/commands/purge.md +18 -0
  18. package/plugin/commands/query.md +13 -0
  19. package/plugin/commands/status.md +21 -0
  20. package/plugin/skills/align-finalize/SKILL.md +137 -0
  21. package/plugin/skills/align-propose/SKILL.md +142 -0
  22. package/plugin/skills/align-scan/SKILL.md +161 -0
  23. package/plugin/skills/align-scan/references/data-model.md +343 -0
  24. package/plugin/skills/align-scan/references/user-question-contract.md +155 -0
  25. package/plugin/skills/compile-close/SKILL.md +122 -0
  26. package/plugin/skills/compile-draft/SKILL.md +246 -0
  27. package/plugin/skills/context-query/SKILL.md +166 -0
  28. package/plugin/skills/drop/SKILL.md +163 -0
  29. package/plugin/skills/semantic-reconcile/SKILL.md +106 -0
  30. package/scripts/build-plugin.ts +70 -0
  31. package/scripts/postinstall.mjs +183 -0
  32. package/templates/aspects/code/prompt.md +29 -0
  33. package/templates/aspects/design-system/prompt.md +27 -0
  34. package/templates/aspects/graphql/prompt.md +24 -0
  35. package/templates/aspects/openapi/prompt.md +24 -0
  36. package/wasm/tree-sitter-tsx.wasm +0 -0
  37. package/wasm/tree-sitter-typescript.wasm +0 -0
  38. package/wasm/tree-sitter.wasm +0 -0
@@ -0,0 +1,137 @@
1
+ ---
2
+ name: align-finalize
3
+ description: >
4
+ Third skill of `/context:align`. Reconciles `WORKSPACE_DIR/output/align.propose.yaml`
5
+ with the user's `WORKSPACE_DIR/output/align.user-decisions.yaml`, applies every pick
6
+ (ambiguous / orphan / accept-reject / addition / rename), re-validates
7
+ the tree against identity / establishment / tag / mount / layout rules,
8
+ and writes the final AlignRenderInput YAML. The CLI renders
9
+ `WORKSPACE_DIR/output/align.md` and binds raw mentions to `WORKSPACE_DIR/output/bind.yaml`.
10
+ Activates after the user has reviewed `align.propose.md` and written
11
+ their decisions file.
12
+ tools:
13
+ - Read
14
+ - Bash
15
+ ---
16
+
17
+ # align-finalize — apply user decisions, emit the final plan
18
+
19
+ Reconcile propose YAML with user-decisions YAML, settle every
20
+ ambiguity, and produce the final `AlignRenderInput`. The CLI does
21
+ all rendering.
22
+
23
+ `WORKSPACE_DIR` is provided by the caller from `context workspace locate --format json` (`ctxDir`). It is `.context/` in embedded layout and the repository root in root layout.
24
+
25
+ ## TL;DR — Non-negotiables
26
+
27
+ - Inputs must both exist: `output/align.propose.yaml` (CLI render) + `output/align.user-decisions.yaml` (user pick), read through `context workspace read`. Missing → stop and say which one.
28
+ - Output: final `AlignRenderInput` passed to `context align --finalize -`; add `--save-input` when the caller wants a durable scratch copy.
29
+ - Respect user decisions literally; a demoted candidate does not appear in `nodes[]` even if you disagreed during propose.
30
+ - `type` values are closed to `domain / entity / action / concept` (see [Node types](${CLAUDE_PLUGIN_ROOT}/skills/align-scan/references/data-model.md#node-types)); reject anything else rather than silently mapping.
31
+ - Domain hard rule: no Node sits inline under a Domain file; every child of a Domain is a standalone root (see [Layout rules](${CLAUDE_PLUGIN_ROOT}/skills/align-scan/references/data-model.md#layout-rules)).
32
+ - `planned_sections[]` must satisfy the [Section mount matrix](${CLAUDE_PLUGIN_ROOT}/skills/align-scan/references/data-model.md#section-mount-matrix) for each Node's type.
33
+ - Slugs reuse across loops for `existing` / `modified`; new slugs only for `new`; user rename decisions carry through verbatim.
34
+ - Every edge endpoint references a slug in `nodes[]`; no dangling edges.
35
+ - No cycles across `contains` + `refines`; user-decision-induced cycles reject with the cycle path.
36
+ - User decisions are business-intent answers; map them back to the closed model yourself instead of asking follow-up questions with model terms.
37
+ - Output language: user-facing prose and `decisions.notes` match the user's language; slug / tag / type / edge values stay English.
38
+
39
+ <reference>
40
+
41
+ ## User-decisions shape
42
+
43
+ ```yaml
44
+ decisions:
45
+ ambiguous:
46
+ - phrase: "..."
47
+ choice: A # A / B / other
48
+ custom: { ... } # optional for "other"
49
+ orphans:
50
+ - slug: "..."
51
+ choice: deprecate # deprecate | keep | modify | supersede
52
+ new_status: deprecated
53
+ valid_until: <date>
54
+ supersede_with: <slug> # when choice=supersede
55
+ new_candidates:
56
+ - name: "..."
57
+ choice: accept # accept | reject | reclassify
58
+ reclassify: { type: ..., tags: [...] }
59
+ rename: <new-slug> # optional
60
+ additions:
61
+ - name: "..."
62
+ suggested_type: ...
63
+ suggested_tags: [...]
64
+ ```
65
+
66
+ Missing sections = "no change from propose". Read tolerantly.
67
+
68
+ ## Action establishment recheck
69
+
70
+ User decisions can drop an Action below its establishment bar (e.g.
71
+ rejecting a sub-Action leaves the parent with a single Section
72
+ intent). After applying all decisions, re-verify every `action` Node
73
+ against [Node establishment thresholds](${CLAUDE_PLUGIN_ROOT}/skills/align-scan/references/data-model.md#node-establishment-thresholds). Below bar → leave the Node in place (the user made the call) but flag the weakness in `decisions.notes`. The caller's next `context doctor` pass (run at the end of `/context:align`) surfaces the note via the output-align diagnostic group, giving the user a chance to reopen the decision on the next loop.
74
+
75
+ ## Alias carry-through
76
+
77
+ For every `new` Node, carry over the aliases the raw called it by
78
+ (derived from scan mentions) so the close-time glossary build picks
79
+ them up. Without aliases, later retrieval misses language variants.
80
+
81
+ </reference>
82
+
83
+ <procedures>
84
+
85
+ ### Step 1 — Load inputs
86
+
87
+ Read `output/align.propose.yaml` and `output/align.user-decisions.yaml` in full with `context workspace read <path> --format text`. Build a `decisions` lookup keyed by slug / phrase / name. If either file is missing, stop and name it.
88
+
89
+ ### Step 2 — Apply `new_candidates` decisions
90
+
91
+ For each propose `new` candidate:
92
+
93
+ - `accept` → keep in the plan.
94
+ - `reject` → drop from the plan; its mentions still show in `bind.yaml` as unbound raw.
95
+ - `reclassify` → swap `type` + `tags`; re-check tags per the [Entity tag groups](${CLAUDE_PLUGIN_ROOT}/skills/align-scan/references/data-model.md#entity-tag-groups) and concept gate rules.
96
+ - `rename` → change `slug` / `title`; propagate to every edge + parent pointer.
97
+
98
+ ### Step 3 — Apply `ambiguous` decisions
99
+
100
+ Pick the matching candidate per the user's `choice`; drop the others. `custom` branches go through Step 2's `reclassify` path.
101
+
102
+ ### Step 4 — Apply `orphans` decisions
103
+
104
+ - `keep` → `status: existing`, no change.
105
+ - `deprecate` → `status: orphan` + copy `new_status` / `valid_until`.
106
+ - `modify` → `status: modified` + record intent in `decisions.notes`.
107
+ - `supersede` → `status: orphan` on the old slug + add a new Node for the supersession target from `supersede_with`.
108
+
109
+ ### Step 5 — Apply `additions`
110
+
111
+ For every user addition, run it through the same gates as propose ([establishment](${CLAUDE_PLUGIN_ROOT}/skills/align-scan/references/data-model.md#node-establishment-thresholds), [tag rules](${CLAUDE_PLUGIN_ROOT}/skills/align-scan/references/data-model.md#entity-tag-groups), [concept gate](${CLAUDE_PLUGIN_ROOT}/skills/align-scan/references/data-model.md#concept-three-test-gate)). Gate failure → surface to the caller; the user needs to revise. Do not silently drop.
112
+
113
+ ### Step 6 — Fill and verify `planned_sections[]`
114
+
115
+ Carry propose's hint when set; extend from raw evidence only with kinds that satisfy the [Section mount matrix](${CLAUDE_PLUGIN_ROOT}/skills/align-scan/references/data-model.md#section-mount-matrix) for the Node's type. Drop mismatches and note them in `decisions.notes`.
116
+
117
+ ### Step 7 — Layout + cycle check
118
+
119
+ - Confirm no Node carries a `contains_parent` that would require inline nesting under a Domain (see [Layout rules](${CLAUDE_PLUGIN_ROOT}/skills/align-scan/references/data-model.md#layout-rules)).
120
+ - Run a cycle check over `contains` + `refines`. Any cycle → reject with the offending path in the output; do not auto-break.
121
+
122
+ ### Step 8 — Write the final input
123
+
124
+ Emit the confirmed `AlignRenderInput` shape (same shape as align-propose's output) for the caller to pass to `context align --finalize -` (or `context align --finalize - --save-input` when a durable scratch copy is needed). Return control to the caller.
125
+
126
+ ### Step 9 — Self-verify
127
+
128
+ - [ ] Every `type` is one of the four closed values — if not, **Step 2/3/5**.
129
+ - [ ] Every `entity` has ≥1 A/B tag — if not, **Step 2/3/5**.
130
+ - [ ] Every `action` cleared the establishment bar OR carries a `decisions.notes` override — if not, **Step 2**.
131
+ - [ ] Every `planned_sections[]` entry is legal for the Node type — if not, **Step 6**.
132
+ - [ ] No Node sits inline under a Domain — if any, **Step 7**.
133
+ - [ ] No cycles — if any, **Step 7**.
134
+ - [ ] Every edge endpoint appears in `nodes[]` — if not, **Step 8**.
135
+ - [ ] No Read / Glob / Grep / Write was used against `WORKSPACE_DIR` — if any, restart with `context workspace` / `context mdrive` commands.
136
+
137
+ </procedures>
@@ -0,0 +1,142 @@
1
+ ---
2
+ name: align-propose
3
+ description: >
4
+ Second skill of `/context:align`. Reads the align-scan decisions file +
5
+ the existing `knowledge/` contains tree from `context mdrive query
6
+ --contains-tree --format json`, assembles a candidate Node tree, validates every candidate
7
+ against identity / establishment / tag / Edge rules, marks each node
8
+ new / existing / modified / orphan, and writes an AlignRenderInput
9
+ YAML. The CLI renders `WORKSPACE_DIR/output/align.propose.md` + `.yaml` for the user
10
+ to review. Activates when align-scan decisions are on disk and the
11
+ pipeline needs a reviewable tree before align-finalize.
12
+ tools:
13
+ - Read
14
+ - Bash
15
+ ---
16
+
17
+ # align-propose — assemble a reviewable Node tree
18
+
19
+ Promote classified mentions into a contains-rooted tree with explicit
20
+ Edges, applying the identity / establishment / tag rules. Output is
21
+ pure YAML; the CLI does the rendering.
22
+
23
+ `WORKSPACE_DIR` is provided by the caller from `context workspace locate --format json` (`ctxDir`). It is `.context/` in embedded layout and the repository root in root layout.
24
+
25
+ ## TL;DR — Non-negotiables
26
+
27
+ - Input: `output/align.scan-decisions.json` read through `context workspace read` + the current contains tree from `context mdrive query --contains-tree --format json`.
28
+ - Output: an `AlignRenderInput` passed to `context align --propose -`; add `--save-input` when the caller wants a durable scratch copy.
29
+ - Every candidate carries a `status` (see [Status markers](#status-markers)); `new` is never the default.
30
+ - Entity candidates pass [Entity tag groups](${CLAUDE_PLUGIN_ROOT}/skills/align-scan/references/data-model.md#entity-tag-groups); re-route false Entities per [False-Entity smell](${CLAUDE_PLUGIN_ROOT}/skills/align-scan/references/data-model.md#false-entity-smell).
31
+ - Action candidates clear [Node establishment thresholds](${CLAUDE_PLUGIN_ROOT}/skills/align-scan/references/data-model.md#node-establishment-thresholds); below-bar demote to Section suggestions.
32
+ - Concept candidates pass the [Concept three-test gate](${CLAUDE_PLUGIN_ROOT}/skills/align-scan/references/data-model.md#concept-three-test-gate); failures demote.
33
+ - Edges come from the five logical types only (see [Edge types](${CLAUDE_PLUGIN_ROOT}/skills/align-scan/references/data-model.md#edge-types)); `refers_to_nodes` is a Section field, not an Edge.
34
+ - No `contains` edge from a Domain to a Node that would nest inline — Domain children are always standalone files (see [Layout rules](${CLAUDE_PLUGIN_ROOT}/skills/align-scan/references/data-model.md#layout-rules)).
35
+ - No cycles in `contains` + `refines`.
36
+ - Slugs are kebab-case, ≤60 chars, stable across loops.
37
+ - User review questions follow `${CLAUDE_PLUGIN_ROOT}/skills/align-scan/references/user-question-contract.md`: ask for business intent, never storage mechanics.
38
+ - Output language: user-facing prose and `decisions.notes` match the user's language; slugs / tag / type / edge values stay English.
39
+
40
+ <reference>
41
+
42
+ ## Status markers
43
+
44
+ | marker | When to use |
45
+ |---|---|
46
+ | `existing` | Exact slug / title match in workspace AND raw does not contradict the glossary entry |
47
+ | `new` | No existing Node; raw material justifies creating it |
48
+ | `modified` | Matches an existing Node AND raw adds information (new Sections incoming, or raw flips a decision) |
49
+ | `orphan` | Existing Node whose raw footprint says sunset / deprecated / superseded; user decides deprecate / keep / supersede |
50
+
51
+ ## AlignRenderInput shape
52
+
53
+ ```yaml
54
+ workspace: <workspace-name>
55
+ nodes:
56
+ - slug: <kebab-case>
57
+ type: domain | entity | action | concept
58
+ tags: [...]
59
+ title: <human title>
60
+ sources: [<source-id>, ...]
61
+ aliases: [...]
62
+ summary: <optional one-liner>
63
+ status: new | existing | modified | orphan
64
+ contains_parent: <slug, optional>
65
+ planned_sections: [description, spec, warning]
66
+ edges:
67
+ - type: contains | depends_on | realizes | refines | related_to
68
+ from: <slug>
69
+ to: <slug>
70
+ note: <optional>
71
+ decisions:
72
+ notes: |
73
+ <brief rationale + demotions + unresolved questions>
74
+ ambiguous: [ ... carried from scan + discovered re-routes ... ]
75
+ orphans: [ ... carried from scan ... ]
76
+ ```
77
+
78
+ ## User-review shape
79
+
80
+ The CLI renders the proposal to `align.propose.md`. Shape your output
81
+ so the caller can ask a small number of business-intent questions. The
82
+ user should never need to know the internal Node / Section / Edge model.
83
+
84
+ 1. **Same thing or separate topic?** Pre-resolve with the identity tests.
85
+ Surface only hard cases in `decisions.ambiguous[]`.
86
+ 2. **Standalone lookup or parent detail?** Set the internal parent / demotion
87
+ fields yourself. Ask the user only when future retrieval behavior is unclear.
88
+ 3. **Business relationship?** Ask only when raw evidence leaves multiple
89
+ plausible meanings, such as alternatives vs dependency vs co-location.
90
+ 4. **Validity change?** Ask when old and new source documents conflict about
91
+ what readers should trust now.
92
+ 5. **Navigation-only duplication?** Do not ask. Repeated "Related links",
93
+ "References", and similar blocks are handled as structure unless they carry
94
+ new business prose.
95
+
96
+ </reference>
97
+
98
+ <procedures>
99
+
100
+ ### Step 1 — Load inputs
101
+
102
+ 1. Read `output/align.scan-decisions.json` with `context workspace read output/align.scan-decisions.json --format text`.
103
+ 2. Run `context mdrive query --contains-tree --format json` and use the returned `nodes[]` + `edges[]` as the current workspace skeleton. `contains` edges encode both same-file heading children and cross-file `## Contains` links; do not rebuild the tree with Glob + Read.
104
+
105
+ ### Step 2 — Seat `existing_nodes`
106
+
107
+ Copy every `existing` entry into `nodes[]` with its current parent pointer from the tree. Set `status: existing`, or `modified` if new mentions imply incoming Sections. Do not re-parent without explicit raw evidence.
108
+
109
+ ### Step 3 — Promote new candidates
110
+
111
+ For each `new_candidates[]` from `output/align.scan-decisions.json`:
112
+
113
+ 1. Check [Node establishment thresholds](${CLAUDE_PLUGIN_ROOT}/skills/align-scan/references/data-model.md#node-establishment-thresholds). Below bar → skip the Node; record a Section demotion in `decisions.notes`.
114
+ 2. For `entity`, verify tags per the data-model reference. Neither-group hits → re-route via [False-Entity smell](${CLAUDE_PLUGIN_ROOT}/skills/align-scan/references/data-model.md#false-entity-smell) into `decisions.ambiguous[]`.
115
+ 3. For `concept`, run the [Concept three-test gate](${CLAUDE_PLUGIN_ROOT}/skills/align-scan/references/data-model.md#concept-three-test-gate). Any ❌ → demote; do not emit.
116
+ 4. Pick `contains_parent` from the scan's `suggested_parent` or a matching existing Domain.
117
+ 5. Fill `planned_sections[]` only where raw clearly justifies kinds that fit the target type per the [Section mount matrix](${CLAUDE_PLUGIN_ROOT}/skills/align-scan/references/data-model.md#section-mount-matrix).
118
+
119
+ ### Step 4 — Enumerate edges
120
+
121
+ One `edges[]` entry per `contains` edge in the tree. Add `depends_on` / `realizes` / `refines` / `related_to` only where raw explicitly justifies the relationship AND both endpoints appear in `nodes[]`. Never emit `refers_to_nodes`-style edges. Run a cycle check over `contains` + `refines`; reject any cycle.
122
+
123
+ ### Step 5 — Carry ambiguous and orphans
124
+
125
+ Copy `scan.ambiguous[]` into `decisions.ambiguous[]` plus any discoveries from Step 3 re-routes. Copy `scan.orphans[]` into `decisions.orphans[]`. Do not make deprecation calls yourself; the user decides in the next gate.
126
+
127
+ ### Step 6 — Write the proposal input
128
+
129
+ Emit the [AlignRenderInput shape](#alignrenderinput-shape) as YAML for the caller to pass to `context align --propose -` (or `context align --propose - --save-input` when a durable scratch copy is needed). Return control to the caller; the caller renders `align.propose.md` + `.yaml`.
130
+
131
+ ### Step 7 — Self-verify
132
+
133
+ - [ ] Every `entity` has ≥1 A/B tag — if not, **Step 3**.
134
+ - [ ] Every `action` clears the establishment bar OR is demoted — if not, **Step 3**.
135
+ - [ ] Every `concept` passes the three-test gate — if not, **Step 3**.
136
+ - [ ] No cycles in `contains` + `refines` — if any, **Step 4**.
137
+ - [ ] No `refers_to_nodes` edges — if any, **Step 4**.
138
+ - [ ] No `new` slug collides with an existing slug — if any, treat as `modified` in **Step 3**.
139
+ - [ ] `decisions.ambiguous[]` + `decisions.orphans[]` carried through — if not, **Step 5**.
140
+ - [ ] No Read / Glob / Grep / Write was used against `WORKSPACE_DIR` — if any, restart with `context workspace` / `context mdrive` commands.
141
+
142
+ </procedures>
@@ -0,0 +1,161 @@
1
+ ---
2
+ name: align-scan
3
+ description: >
4
+ First skill of `/context:align`. Reads `WORKSPACE_DIR/output/align.scan.yaml` + the
5
+ workspace glossary, inspects the raw outlines/full raw evidence, classifies
6
+ raw material into existing / new / ambiguous / orphan, and writes a
7
+ structured JSON decisions file for align-propose to consume. The CLI only
8
+ provides weak candidate seeds and deterministic context; semantic
9
+ classification is the agent's job. Activates when `/context:align` starts,
10
+ or when an agent has raw material ready and needs a first-cut candidate list before the
11
+ structural proposal step.
12
+ tools:
13
+ - Read
14
+ - Bash
15
+ ---
16
+
17
+ # align-scan — classify raw material
18
+
19
+ Classify raw material in `WORKSPACE_DIR/output/align.scan.yaml` against the
20
+ workspace glossary. The scan file is a context package, not a final classifier:
21
+ `new_candidates[]` are weak seeds and `raw_outlines[]` is the main reading map.
22
+ Output one JSON payload; no markdown.
23
+
24
+ `WORKSPACE_DIR` is provided by the caller from `context workspace locate --format json` (`ctxDir`). It is `.context/` in embedded layout and the repository root in root layout.
25
+
26
+ ## TL;DR — Non-negotiables
27
+
28
+ - Output is JSON only; the CLI owns every rendered markdown file.
29
+ - Input is `WORKSPACE_DIR/output/align.scan.yaml` produced by `context align --scan`; scan v2 is structural-first and deliberately avoids semantic type guesses.
30
+ - Read `raw_outlines[]` for every scanned raw file. When the outline or previews are insufficient, read the full raw file with `context workspace read <path> --format text`.
31
+ - Treat `scan.new_candidates[]` as weak seeds only. `semantic_type: "unresolved"` / `classification_required: true` means the CLI intentionally did not classify it.
32
+ - `type` is one of `domain / entity / action / concept` (see [Node types](references/data-model.md#node-types)); nothing else.
33
+ - Every `entity` candidate carries ≥1 tag from group A or B (see [Entity tag groups](references/data-model.md#entity-tag-groups)).
34
+ - Every candidate has ≥1 `mention` with `{file, line, quote}` (quote ≤180 chars); no fabrication.
35
+ - Glossary hits become `existing`, not new candidates (see [Glossary](references/data-model.md#glossary)).
36
+ - Relational titles ("X affects Y", "Y-side of X", "X migration") are not Entities — re-route via [False-Entity smell](references/data-model.md#false-entity-smell).
37
+ - Raw file paths in the scan are relative to `WORKSPACE_DIR`; read raw evidence only with `context workspace read <path>`. Never use Read / Glob / Grep against workspace files.
38
+ - Save the decisions file only with `context align --save-scan-decisions -`; do not touch `knowledge/`, raw files, or cache files.
39
+ - Output language: user-facing prose matches the user's conversation language; JSON keys / slug / tag values stay English.
40
+
41
+ <reference>
42
+
43
+ ## Output schema (agent decisions)
44
+
45
+ This is the JSON you write with `context align --save-scan-decisions -`.
46
+ It is not the raw CLI scan schema. In `output/align.scan.yaml`,
47
+ `scan.new_candidates[]` may omit `suggested_type` / `suggested_tags`;
48
+ the agent must fill them after reading raw evidence. When a candidate has
49
+ `semantic_type: "unresolved"` and `classification_required: true`, that is the
50
+ expected scan-v2 shape, not an error.
51
+
52
+ ```jsonc
53
+ {
54
+ "scan_version": 2,
55
+ "workspace": "<workspace-name from scan>",
56
+ "existing_nodes": [
57
+ { "slug": "...", "type": "entity|action|concept|domain",
58
+ "confirmed_mentions": [ { "file": "...", "line": 12, "quote": "..." } ] }
59
+ ],
60
+ "new_candidates": [
61
+ { "name": "<human title>", "suggested_slug": "<kebab-case>",
62
+ "suggested_type": "entity|action|concept|domain",
63
+ "suggested_tags": ["lib"],
64
+ "confidence": "high|medium|low",
65
+ "mentions": [ { "file": "...", "line": 45, "quote": "..." } ],
66
+ "suggested_parent": "<slug>",
67
+ "suggested_realizes": "<slug>",
68
+ "notes": "<optional>"
69
+ }
70
+ ],
71
+ "ambiguous": [
72
+ { "phrase": "...",
73
+ "candidates": [
74
+ { "kind": "existing", "slug": "..." },
75
+ { "kind": "new", "name": "...", "suggested_type": "...", "suggested_tags": [] }
76
+ ],
77
+ "mentions": [ ... ],
78
+ "reroute_as": "<optional: Section on target-slug>"
79
+ }
80
+ ],
81
+ "orphans": [ { "slug": "...", "reason": "...", "mentions": [ ... ] } ],
82
+ "decisions": { "notes": "<free-form rationale>" }
83
+ }
84
+ ```
85
+
86
+ ## Confidence calibration
87
+
88
+ | `confidence` | When |
89
+ |---|---|
90
+ | `high` | Multiple direct mentions + clear integration signal (authoritative "X is our X library" prose) |
91
+ | `medium` | ≥2 mentions OR a single authoritative mention (section heading) |
92
+ | `low` | Single peripheral mention; prefer `ambiguous` over a low-confidence `new` |
93
+
94
+ </reference>
95
+
96
+ <procedures>
97
+
98
+ ### Step 1 — Read the scan
99
+
100
+ Read `output/align.scan.yaml` with `context workspace read output/align.scan.yaml --format text`
101
+ and verify `scan_version: 2` (legacy `scan_version: 1` is readable). If missing, stop and tell the user to run `context align --scan` first.
102
+ Skim `glossary[]` to remember the existing slug + alias set.
103
+ For `scan_version: 2`, first inspect `incremental`:
104
+
105
+ - `structure_changed[]` and `new_sources[]` are the only reasons to classify structure.
106
+ - `content_changed_only[]` means compile work, not align work; do not create candidates from those sources.
107
+ - `skipped_sources[]` are unchanged; do not classify them.
108
+ - `unknown_sources[]` cannot be treated as unchanged. Return control to the caller with the reason unless the caller chose a full scan.
109
+
110
+ When source context is needed, run `context workspace read <path> --format text` where
111
+ `<path>` comes from `raw_files_scanned[]` or `mentions[].file`; for example,
112
+ `raw/local/2026-04-25-vmok.md`. Do not attempt to read only `2026-04-25-vmok.md`
113
+ from the current directory, and do not use the Read tool against `WORKSPACE_DIR`.
114
+
115
+ Then inspect every `raw_outlines[]` entry:
116
+
117
+ - `headings[]` shows the full H1-H6 shape so H3/H4 content is not ignored.
118
+ - `relation_lines[]` shows protocol-like navigation hints. Treat them as structure, not content claims, unless the surrounding raw prose adds a business claim.
119
+ - `relation_hints[]` is the structured version of those navigation lines (`parent` / `children` / `related` / `relations` plus link targets). Use it to preserve explicit raw links, but do not infer semantic type from it.
120
+ - `evidence_blocks[]` gives short previews for all parsed blocks. If a candidate decision depends on text outside the preview, read the full raw file through `context workspace read`.
121
+
122
+ ### Step 2 — Promote `existing_nodes`
123
+
124
+ Copy every `scan.existing_nodes[]` entry into `output.existing_nodes[]`
125
+ with its `confirmed_mentions`. If any raw quote contradicts the
126
+ glossary summary (sunset / replaced), also emit an `orphan` entry;
127
+ never silently drop.
128
+
129
+ ### Step 3 — Classify raw material
130
+
131
+ Use `scan.new_candidates[]` as a seed list, then check `raw_outlines[]` and full raw evidence before deciding. A new Node can come from a seed, a heading visible only in `raw_outlines[]`, or a repeated/raw-supported concept discovered while reading the full source. Conversely, a seed can be demoted to a Section or ignored as navigation.
132
+
133
+ For each potential topic:
134
+
135
+ 1. Match against glossary per [Glossary](references/data-model.md#glossary). A hit moves the item to `existing_nodes`.
136
+ 2. Pick `suggested_type` per [Node types](references/data-model.md#node-types). Use [Entity vs Concept test](references/data-model.md#entity-vs-concept-test) when torn between entity and concept.
137
+ 3. For `entity`, pick tags per [Entity tag groups](references/data-model.md#entity-tag-groups). If neither group has a legal tag, route via [False-Entity smell](references/data-model.md#false-entity-smell) into `ambiguous`; do not force a tag.
138
+ 4. Map any deprecated tag from the raw to its canonical form per [Deprecated tags](references/data-model.md#deprecated-tags).
139
+ 5. Assign `confidence` per [Confidence calibration](#confidence-calibration).
140
+ 6. Record demoted headings / seeds in `decisions.notes` so align-propose can explain why they became Sections or navigation instead of Nodes.
141
+
142
+ ### Step 4 — Carry ambiguities and orphans
143
+
144
+ For each `scan.ambiguous[]`, fill in a best-guess `suggested_type + suggested_tags` on the `new`-side candidates so align-propose can render them. Do not resolve ambiguities here — the user owns that decision. Copy `scan.orphans[]` through; add discoveries from Step 2.
145
+
146
+ ### Step 5 — Save the JSON
147
+
148
+ Pass one UTF-8 JSON document to `context align --save-scan-decisions -`.
149
+ No markdown wrapper, no leading prose.
150
+
151
+ ### Step 6 — Self-verify
152
+
153
+ - [ ] Every `new_candidates[]` entry has ≥1 mention — if not, back to **Step 3**.
154
+ - [ ] Every `entity` `new_candidates[]` has an A or B tag — if not, **Step 3** (re-route via False-Entity smell).
155
+ - [ ] Every `ambiguous[]` entry has a candidate that is either `existing` OR a fully-typed `new` — if not, **Step 4**.
156
+ - [ ] Output is pure JSON — if any markdown leaked, **Step 5**.
157
+ - [ ] No Read / Glob / Grep / Write was used against `WORKSPACE_DIR` — if any, restart with `context workspace` commands.
158
+
159
+ Return control to the caller; do not invoke subsequent skills yourself.
160
+
161
+ </procedures>