@c4a/context-cli 0.5.35-beta.3 → 0.5.38

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.
@@ -0,0 +1,515 @@
1
+ # Aspect Extension Guide
2
+
3
+ > Generated by `context init`. This document is business-agnostic — a complete reference for extending knowledge graphs via Aspects.
4
+
5
+ For the protocol and bundled code runner package, see [`@c4a/extract`](https://www.npmjs.com/package/@c4a/extract).
6
+
7
+ ## What Is an Aspect
8
+
9
+ An Aspect is a **structured data source** for the knowledge graph. Each aspect extracts information from a specific origin (source code, docs, APIs, config files, etc.) and outputs standardized Sections, anchored to Entity nodes in the knowledge graph.
10
+
11
+ Unlike the align/compile pipeline (LLM-driven, designed for unstructured documents), aspects are **fully deterministic, zero LLM**, suitable for extraction tasks with stable, well-defined rules.
12
+
13
+ ### Aspect vs Align/Compile
14
+
15
+ | | Aspect Projection | Align/Compile |
16
+ |---|---|---|
17
+ | Input | Structured sources (code, DSL, YAML, token tables) | Unstructured sources (Markdown, docs, chat logs) |
18
+ | Extraction | `extract.ts` script, pure algorithm | LLM semantic classification + alignment |
19
+ | Speed | Seconds | Minutes (depends on LLM call count) |
20
+ | Repeatability / traceability | Repeatable output with source-bound evidence | Depends on LLM quality and human review |
21
+ | Best for | Source symbols, API references, design tokens, structured FAQ | Product docs, meeting notes, articles |
22
+
23
+ ---
24
+
25
+ ## Directory Structure
26
+
27
+ ```text
28
+ .context/aspects/
29
+ ├── README.md ← This file
30
+ ├── code/
31
+ │ ├── aspect.yaml # Built-in code aspect (generated when selected during init)
32
+ │ └── prompt.md # Agent/human hint only; CLI does not read it
33
+ ├── <your-aspect>/
34
+ │ ├── aspect.yaml # Required: aspect definition
35
+ │ ├── extract.ts # Aspect plugin module (local mode)
36
+ │ └── prompt.md # Optional: hint for humans/agents
37
+ └── <another-aspect>/
38
+ └── ...
39
+
40
+ CLI runtime
41
+ └── C4A_ASPECT_RUNNER_SDK # env var containing the CLI-provided SDK module URL
42
+ ```
43
+
44
+ `context init` generates `.context/aspects/README.md` from this guide. The runtime SDK is provided by CLI at capture time; teams should not copy protocol IO helpers into every business aspect.
45
+
46
+ ---
47
+
48
+ ## aspect.yaml — Complete Reference
49
+
50
+ ### Local Script Mode (`schema_version: "context.aspect.v2"`)
51
+
52
+ ```yaml
53
+ schema_version: "context.aspect.v2"
54
+ name: my-aspect # Unique identifier; recommended to match directory name
55
+
56
+ runner:
57
+ script: ./extract.ts # Relative to the aspect.yaml directory
58
+
59
+ output:
60
+ bucket: raw/aspect/<name>/<source-slug>/<snapshot-id>/
61
+ files: # Final bucket file list
62
+ - source.yaml # Generated by CLI
63
+ - manifest.json # Generated by CLI
64
+ - source-files.jsonl # source_path/hash_id/bucket_path mapping
65
+ - sections.jsonl # section-projection.v2 rows
66
+
67
+ projection:
68
+ protocol: "section-projection.v2"
69
+ anchor_policy: best-effort # best-effort = warn and skip missing nodes; strict = fail
70
+ allow_empty: false # reject row_count=0 by default
71
+ large_deprecate_threshold: 0.5 # reject >50% row drop by default
72
+
73
+ evidence:
74
+ mode: bucket-files # bucket-files: source_ref resolves against bucket files
75
+ ```
76
+
77
+ ### Bundled Runner Mode (`schema_version: "context.aspect.v2"` — code aspect only)
78
+
79
+ ```yaml
80
+ schema_version: "context.aspect.v2"
81
+ name: code
82
+
83
+ runner:
84
+ package: "@c4a/extract" # npm package name
85
+ bin: c4a-extract-code # binary name
86
+
87
+ plugins: # Optional: language plugins
88
+ - package: "@c4a/extract-ts"
89
+ export: TypeScriptPlugin
90
+
91
+ output:
92
+ bucket: raw/aspect/code/<source-slug>/<snapshot-id>/
93
+ files:
94
+ - source.yaml
95
+ - manifest.json
96
+ - digests.jsonl
97
+ - source-files.jsonl
98
+ - packages.jsonl
99
+ - symbols.jsonl
100
+ - edges.jsonl
101
+ - _meta.yaml
102
+
103
+ evidence:
104
+ mode: none # none: no evidence files; uses built-in code projection
105
+ ```
106
+
107
+ > Custom aspects and the built-in `code` aspect both use `schema_version: "context.aspect.v2"`. Local script mode is expressed by `runner.script`; bundled runner mode is expressed by `runner.package` + `runner.bin`. Generic Section projection is selected by `projection.protocol: "section-projection.v2"`.
108
+
109
+ ### Field Reference
110
+
111
+ | Field | Required | Description |
112
+ |---|---|---|
113
+ | `name` | Yes | Unique aspect identifier; used as the argument to `capture --aspect <name>` and `compile --aspect <name>` |
114
+ | `runner.script` | Yes for local plugin mode | Local plugin module path, relative to aspect.yaml directory; CLI loads it through the aspect host |
115
+ | `runner.package` | Yes for bundled runner mode | npm package name |
116
+ | `runner.bin` | Yes for bundled runner mode | Binary name from the npm package |
117
+ | `output.bucket` | Yes | Raw snapshot storage path template; `<source-slug>` and `<snapshot-id>` substituted by CLI |
118
+ | `output.files` | Yes | Final bucket file list. CLI generates `source.yaml`, `manifest.json`, `source-files.jsonl`, and `sections.jsonl` from rows emitted by the aspect plugin |
119
+ | `projection.protocol` | Yes for Section projection | Fixed value `"section-projection.v2"`; absence is only valid for built-in code projection |
120
+ | `projection.anchor_policy` | Yes | `strict` = fail on unknown node; `best-effort` = warn, record `skipped_anchor_missing[]`, and skip the row |
121
+ | `projection.allow_empty` | Optional | Defaults to `false`; reject empty `sections.jsonl` to prevent accidental mass deprecations. This does not bypass `large_deprecate_threshold` |
122
+ | `projection.large_deprecate_threshold` | Optional | Defaults to `0.5`; reject a `sections.jsonl` row-count drop beyond this ratio unless `--allow-large-deprecate` is explicit |
123
+ | `evidence.mode` | Yes | `bucket-files` = resolve source_ref from `source-files.jsonl` and read copied evidence files from the bucket; `none` = no evidence |
124
+
125
+ ---
126
+
127
+ ## Aspect Plugin Contract
128
+
129
+ ### Invocation
130
+
131
+ CLI starts the CLI-provided aspect host and passes the aspect module as a plugin. The aspect file is not a self-executing script; it exports a lifecycle object:
132
+
133
+ ```typescript
134
+ export default defineAspect({
135
+ async capture(ctx) {
136
+ // read source files through ctx.source
137
+ // emit rows through ctx.emit.section
138
+ },
139
+ });
140
+ ```
141
+
142
+ ### Plugin Context
143
+
144
+ The plugin does not receive `workspace_root`, `repo_root`, `bucket_dir`, raw bucket paths, or knowledge file paths. Those are host-internal details. The only workspace access surface is the host API:
145
+
146
+ | Context API | Description |
147
+ |---|---|
148
+ | `ctx.aspect` | Logical aspect metadata: `name`, `source_slug`, `source_id`, `snapshot_id` |
149
+ | `ctx.source.glob({ root, extensions, recursive })` | List source files by normalized POSIX-relative path under the configured repo root |
150
+ | `ctx.source.immediateFiles(root, extensions)` | List immediate files under one source directory |
151
+ | `ctx.source.readText(fileOrPath)` | Read a source file through the CLI host boundary |
152
+ | `ctx.source.exists(fileOrPath)` | Check whether a source file exists through the CLI host boundary |
153
+ | `ctx.code.packages` / `ctx.code.symbols` | Active code aspect index for resolving package and symbol node slugs; may be empty before code compile |
154
+ | `ctx.code.findPackage(...)` / `ctx.code.findSymbol(...)` | Convenience lookup over the active code index |
155
+ | `ctx.emit.node(row)` | Emit one deterministic documentation node. CLI later materializes `nodes.jsonl` and upserts the node before Section projection |
156
+ | `ctx.emit.section(row)` | Emit one projected Section row. CLI later materializes `sections.jsonl`, `source-files.jsonl`, hashes, and `source_ref` |
157
+ | `ctx.emit.warning(...)` | Emit a non-fatal diagnostic for the capture result |
158
+
159
+ ### Plugin Responsibilities
160
+
161
+ Business aspect plugins own only source selection and business mapping:
162
+
163
+ 1. Import the CLI SDK URL from `process.env.C4A_ASPECT_RUNNER_SDK`
164
+ 2. Export `defineAspect({ capture(ctx) { ... } })`
165
+ 3. Read source content only through `ctx.source`
166
+ 4. Emit deterministic documentation nodes through `ctx.emit.node({ node_slug, title, type, tags, parent_slug })` when source files should become standalone knowledge files
167
+ 5. Emit rows through `ctx.emit.section({ node_slug, kind, summary, content, source/source_path, artifact })`
168
+ 6. Return normally for success; throw for failure
169
+
170
+ Plugins must not read or write `.context`, raw buckets, knowledge files, `source-files.jsonl`, or `sections.jsonl` directly. Local execution cannot fully sandbox arbitrary Node APIs, but the protocol boundary deliberately avoids passing workspace file-system paths to the plugin so the same aspect can later run behind a remote or isolated host.
171
+
172
+ The SDK does not export product-specific constants such as package slug maps. Keep those mappings in the business aspect, or derive them from `ctx.code.packages`.
173
+
174
+ ### CLI Host Responsibilities
175
+
176
+ | Host capability | Why it belongs in CLI |
177
+ |---|---|
178
+ | Source file I/O | Keeps workspace access behind one boundary and allows future remote/sandbox hosts |
179
+ | source path normalization and boundary checks | Prevents `../`, absolute paths, symlink escapes, and platform-specific drift |
180
+ | hash and `source_ref` generation | Keeps `file:<path>#<artifact>@<hash12>` canonical |
181
+ | `nodes.jsonl`, `sections.jsonl`, and `source-files.jsonl` generation | Prevents each business plugin from reimplementing JSONL and bucket rules |
182
+ | raw bucket publish | Ensures validation passes before atomic rename to `raw/aspect/...` |
183
+ | `code_index` package/symbol lookup | Lets custom aspects attach to code aspect nodes without parsing `_sources.yaml` |
184
+ | common AST helpers | Keeps TSX/JS/MDX/SCSS parsing deterministic and reusable |
185
+
186
+ ### Example: Lifecycle-Based extract.ts
187
+
188
+ ```typescript
189
+ const sdkUrl = process.env.C4A_ASPECT_RUNNER_SDK;
190
+ if (!sdkUrl) throw new Error("C4A_ASPECT_RUNNER_SDK is required; run through context capture --aspect");
191
+
192
+ const { artifactFromRelPath, defineAspect, parseMdxDocument } = await import(sdkUrl);
193
+
194
+ export default defineAspect({
195
+ async capture(ctx) {
196
+ for (const file of await ctx.source.glob({ root: "docs", extensions: [".md", ".mdx"] })) {
197
+ const doc = parseMdxDocument(await ctx.source.readText(file), file.path);
198
+ ctx.emit.section({
199
+ node_slug: "target-node",
200
+ kind: "description",
201
+ summary: doc.title,
202
+ content: doc.markdown,
203
+ source: file,
204
+ artifact: artifactFromRelPath(file.path),
205
+ });
206
+ }
207
+ },
208
+ });
209
+ ```
210
+
211
+ ---
212
+
213
+ ## Section Projection Protocol (`section-projection.v2`)
214
+
215
+ ### nodes.jsonl Row Schema (Optional)
216
+
217
+ ```jsonc
218
+ {
219
+ "node_slug": "pkg-slug/colors",
220
+ "title": "Colors",
221
+ "type": "entity",
222
+ "tags": ["module"],
223
+ "summary": "Color design language for this package.",
224
+ "parent_slug": "pkg-slug",
225
+ "code_package": "pkg-slug",
226
+ "language": "English"
227
+ }
228
+ ```
229
+
230
+ Use `nodes.jsonl` only for deterministic documentation nodes whose slug and parent are derived mechanically from source paths or fixed project rules. `language` may be set when source-bound documentation uses a different language than the workspace generation language. Compile upserts these nodes before projecting Sections. Missing anchors that are referenced only by `sections.jsonl` are still handled by `anchor_policy`; they are not created implicitly.
231
+
232
+ ### sections.jsonl Row Schema
233
+
234
+ ```jsonc
235
+ {
236
+ "node_slug": "pkg-slug/symbol/target", // Target Entity slug
237
+ "kind": "example", // Existing SectionKind enum value
238
+ "summary": "basic.tsx", // Optional: existing Section.summary
239
+ "content": "# basic.tsx\n\n...", // Full section text; enters the full-text index
240
+ "source_ref": "file:rel/path/file.tsx#basic@ab12cd34ef56" // Traceability reference
241
+ }
242
+ ```
243
+
244
+ `kind` must be one of the existing C4A SectionKind values: `description`, `spec`, `warning`, `principle`, `decision`, `incident`, `example`, `changelog`, `comparison`, or `faq`. Project-specific labels such as `design-system-color` or `component-description` belong in `summary` / `content` or the `source_ref` fragment, not in `Section.kind`.
245
+
246
+ `source_ref` uses an alias-free existing code-style locator: `file:<rel-path>[#artifact]@<hash12>`. The fragment is required when one file produces more than one Section. During compile, C4A adds the aspect source to the target node's `sources[]` and persists a canonical reference like `src-N#file:<rel-path>[#artifact]@<hash12>`.
247
+
248
+ This does not add a new persisted `source_ref` format. Persisted Sections still use the existing code-style `src-N#file:...@hash` form. The plugin emits `source_path + artifact`; CLI generates the alias-free `file:...@hash` form and compile assigns `src-N#` because that alias depends on the target node's `sources[]` order.
249
+
250
+ Canonical grammar:
251
+
252
+ ```text
253
+ file:<escaped-posix-rel-path>[#<escaped-fragment>]@<hash12>
254
+ ```
255
+
256
+ The verifier must percent-decode the source_ref path first, then compare it by byte-for-byte string equality: `decode(path) === source-files.jsonl.source_path`. Decode failure, a decoded path that is not a normalized POSIX relative path, absolute paths, `..`, NUL, or no matching `source-files.jsonl` row must fail fast. `/` remains the path separator; reserved characters inside path segments and fragments, including whitespace, `#`, `@`, and `%`, must use URL-style percent encoding. Windows backslashes, unescaped whitespace, unescaped `#` / `@`, and hashes other than 12 lowercase hex characters are protocol errors.
257
+
258
+ Verify, query, source_ref resolution, and code projection drift checks must not classify `src-N#file:...@hash` as a code source_ref by syntax alone. They must first read the target node's `sources[src-N]`: `aspect:code:*` uses the existing code verifier, while `aspect:<custom>:*` uses the `section-projection.v2` bucket-files verifier. Code ownership must not be inferred from `parseCodeSourceRef(...) !== null` alone.
259
+
260
+ ### Anchor Levels
261
+
262
+ `node_slug` can target existing code/manual nodes or deterministic documentation nodes declared in `nodes.jsonl`:
263
+
264
+ | Level | slug Example | Suitable Content |
265
+ |---|---|---|
266
+ | package root | `my-pkg-slug` | FAQ and short package-level notes |
267
+ | deterministic doc | `my-pkg-slug/colors` | Design language pages, token tables, guides |
268
+ | module | `my-pkg-slug/module-name` | Code aspect module notes |
269
+ | symbol | `my-pkg-slug/symbol/myfunction` | Function/class/component examples and descriptions |
270
+
271
+ ### Projection Identity
272
+
273
+ No separate projection key field is added to the persisted Section schema. Compile derives identity from existing durable fields:
274
+
275
+ ```text
276
+ projection_key = source_id + "\0" + node_slug + "\0" + kind + "\0" + alias_free_locator
277
+ alias_free_locator = file:<rel-path>[#artifact]
278
+ ```
279
+
280
+ `alias_free_locator` comes from the CLI-generated alias-free `source_ref` without the trailing `@hash12`; it does not include `src-N#`. `src-N` is a persisted alias derived from node `sources[]` and can change when source ordering changes, so it must not enter the projection key. Projection keys must be unique within the same `sections.jsonl`. A duplicate key is a plugin conflict, not a merge or last-wins case; compile must fail fast and report the duplicate key with row numbers.
281
+
282
+ During compile, each projection key is compared against existing knowledge Sections:
283
+
284
+ | in jsonl? | in knowledge? | projected Section payload | Action |
285
+ |---|---|---|---|
286
+ | Yes | No | — | add (create) |
287
+ | Yes | Yes | same | noop (skip) |
288
+ | Yes | Yes | different | update |
289
+ | No | Yes | — | deprecate (reason: source-removed) |
290
+
291
+ Projection is applied as a batch. CLI reads workspace nodes once, computes the diff, mutates sources and Sections in memory, sorts touched nodes, and then atomically rewrites each touched knowledge root file once. It must not call row-level mdrive write commands for every projected row. Protocol or validation failures stop before any knowledge rewrite; unchanged rows do not rewrite knowledge files.
292
+
293
+ To avoid mass deprecations caused by plugin or host bugs, compile applies mechanical guards before diffing: `row_count=0` is rejected by default unless `projection.allow_empty: true`; a `sections.jsonl` row-count drop beyond `projection.large_deprecate_threshold` is still rejected unless the user explicitly passes `--allow-large-deprecate`. `projection.allow_empty` only says that an empty capture result is valid; it does not authorize deleting a large set of existing Sections. When the large-deprecate guard fires, the error envelope's `next_action.command` must provide the same-aspect command: `context compile --aspect <name> --allow-large-deprecate`.
294
+
295
+ ### kind Convention
296
+
297
+ `kind` is not a free-form string. It controls renderer grouping, mount validation, verification, and query behavior. Put project-specific taxonomy in `summary`, content headings, or the `source_ref` fragment.
298
+
299
+ ---
300
+
301
+ ## config.yaml Variable Mechanism
302
+
303
+ ### Syntax
304
+
305
+ ```yaml
306
+ # .context/config.yaml
307
+ workspace:
308
+ name: my-project
309
+ language: English
310
+ repo_root: ${REPO_ROOT:-../}
311
+ ```
312
+
313
+ `${VAR:-default}` — uses `default` when `VAR` is not set or is an empty string. Relative values are resolved to absolute paths relative to the workspace root, i.e. the parent directory of `.context/` in embedded layout.
314
+
315
+ ### Variable Source Priority
316
+
317
+ 1. Shell environment variables (`process.env`) — highest priority, for temporary overrides
318
+ 2. `.context/.env` file — persistent defaults
319
+ 3. `${VAR:-default}` fallback — last resort
320
+
321
+ `.context/.env` is loaded into a scoped env map only; CLI must not mutate global `process.env`. Expansion uses an effective env map (`.context/.env` as the base, shell env overriding duplicate keys), and the same effective env is passed to the aspect host subprocess.
322
+
323
+ ### `.env` File
324
+
325
+ ```bash
326
+ # .context/.env
327
+ # Generated by context init. Leave empty to use config.yaml's fallback.
328
+ REPO_ROOT=
329
+
330
+ # Override with an absolute source repository path when needed.
331
+ # REPO_ROOT=/abs/path/to/source/repo
332
+ ```
333
+
334
+ ### Usage in Plugins
335
+
336
+ Plugins do not receive `repo_root`. They use POSIX-relative source paths through `ctx.source`; CLI resolves those paths against the configured repo root:
337
+
338
+ ```typescript
339
+ const files = await ctx.source.glob({ root: "docs", extensions: [".mdx"] });
340
+ const text = await ctx.source.readText(files[0]);
341
+ ```
342
+
343
+ ---
344
+
345
+ ## Command Reference
346
+
347
+ ### capture
348
+
349
+ ```bash
350
+ # Capture a single aspect:
351
+ context capture --aspect my-aspect
352
+
353
+ # Capture multiple aspects (sequential):
354
+ context capture --aspect my-aspect another-aspect
355
+
356
+ # Code capture keeps its existing command and options:
357
+ context capture --code <target-or-module-options>
358
+ # Requires `.context/aspects/code/aspect.yaml`; if it is missing, CLI asks to install the code aspect.
359
+
360
+ # --force: re-run plugin even if snapshot unchanged
361
+ context capture --aspect my-aspect --force
362
+
363
+ # --quiet: suppress progress output
364
+ context capture --aspect my-aspect --quiet
365
+ ```
366
+
367
+ ### compile (Project to Knowledge Graph)
368
+
369
+ ```bash
370
+ # Compile a single aspect:
371
+ context compile --aspect my-aspect
372
+
373
+ # Global compile orchestrator: project all aspects, then inspect/advance existing LLM compile work:
374
+ context compile --all
375
+
376
+ # The only entry for code projection:
377
+ context compile --aspect code
378
+
379
+ # Explicitly allow this run after confirming a large deprecate is expected:
380
+ context compile --aspect my-aspect --allow-large-deprecate
381
+
382
+ # Compile specific aspects:
383
+ context compile --aspect my-aspect
384
+
385
+ # No argument to --aspect → list all available aspects:
386
+ context compile --aspect
387
+ # → Available aspects: code, my-aspect, another-aspect
388
+ ```
389
+
390
+ `compile --aspect <name...>` is pure TypeScript, zero LLM: read `sections.jsonl` → diff by projection key → batch-apply add/update/deprecate/noop in memory → atomically rewrite touched knowledge roots + append changelog.
391
+
392
+ `context compile --all` is the global compile orchestrator. It first runs the aspect projection subphase in a fixed order: `code` first, then custom aspects by lexicographic `aspect.name`; then it inspects the current workspace for an existing LLM compile workflow / pending semantic compile work. If there is no remaining work, it returns a noop summary and exits 0. If the remaining state can be mechanically closed or advanced, CLI does so. If Agent-authored draft, judge input, or semantic answers are required, CLI stops, returns the single canonical `next_action.command` plus `input_schema`, and exits non-zero; it does not make semantic decisions for the Agent.
393
+
394
+ > `context compile` (without flags) is the existing LLM-driven compile entry/help. `context compile --all` orchestrates both aspect projection and the LLM compile workflow; it is not an aspect-only shortcut.
395
+ > Agent command/router may accept inputs such as `compile -all` or `compile all`, but must normalize them to the CLI command `context compile --all`.
396
+
397
+ **Code aspect note**: `code` has no `projection` section in its aspect.yaml. CLI detects this and uses the built-in code projection (maps `symbols.jsonl` / `packages.jsonl` / `edges.jsonl` to Sections). The command form, projection identity model, and zero-LLM guarantee are identical to `section-projection.v2`.
398
+
399
+ ### Inspect Aspect State
400
+
401
+ ```bash
402
+ # List all sources (including aspects):
403
+ context source list
404
+
405
+ # View a specific source:
406
+ context source get aspect:<name>:<slug>
407
+
408
+ # Workspace status (includes knowledge stats):
409
+ context status
410
+
411
+ # Validate integrity:
412
+ context verify
413
+ ```
414
+
415
+ ### Query
416
+
417
+ ```bash
418
+ # Aspect-produced Sections are queryable just like any other Section:
419
+ context query "button example basic"
420
+ context query "color tokens"
421
+ ```
422
+
423
+ ---
424
+
425
+ ## Relationship with Align / Compile / Query
426
+
427
+ ```
428
+ ┌──────────────────────────────────────────────────────────┐
429
+ │ Knowledge Graph Consumption │
430
+ │ context query ←── Full-text index ──→ All Sections │
431
+ └──────────────────────────────────────────────────────────┘
432
+
433
+ ┌──────────────────┴──────────────────┐
434
+ │ │
435
+ ┌───────┴────────┐ ┌───────┴────────┐
436
+ │ Aspect Project │ │ Align/Compile │
437
+ │ (deterministic)│ │ (LLM-driven) │
438
+ │ │ │ │
439
+ │ extract.ts │ │ capture raw │
440
+ │ ↓ │ │ ↓ │
441
+ │ sections.jsonl │ │ align │
442
+ │ ↓ │ │ ↓ │
443
+ │ compile --asp │ │ compile │
444
+ │ ↓ │ │ ↓ │
445
+ │ Section[] │ │ Section[] │
446
+ └───────┬────────┘ └───────┬────────┘
447
+ │ │
448
+ └──────────────────┬──────────────────┘
449
+
450
+ ┌───────────────────────┐
451
+ │ knowledge/entity/ │
452
+ │ <slug>.md │
453
+ │ changelog.md │
454
+ └───────────────────────┘
455
+ ```
456
+
457
+ - **capture**: Acquire raw data. For aspects, this means running `extract.ts` and writing to `raw/`. For align/compile, this means capturing URLs, documents, or Markdown.
458
+ - **compile**: Raw data → knowledge Sections. Aspects use deterministic diff; align outputs go through an LLM review pipeline.
459
+ - **query**: Sections from both sources are indexed and retrieved uniformly — consumers do not distinguish provenance.
460
+
461
+ ---
462
+
463
+ ## Updating the Code Aspect Template
464
+
465
+ When the user chooses to install the code aspect, `context init` generates `aspects/code/aspect.yaml` from the template bundled with the CLI. When you upgrade the CLI, the template may have been updated.
466
+
467
+ ```bash
468
+ # View the current CLI-bundled template (read-only):
469
+ context aspect template code
470
+
471
+ # Write the latest template to aspects/code/ (overwrites aspect.yaml, preserves prompt.md):
472
+ context aspect template code --write
473
+
474
+ # Diff current file against the latest template:
475
+ context aspect template code --diff
476
+ ```
477
+
478
+ > `--write` will not overwrite `prompt.md` (user-edited agent hints). To reset prompt.md, delete it first, then `--write`.
479
+
480
+ ---
481
+
482
+ ## FAQ
483
+
484
+ ### Can aspects and align/compile coexist?
485
+
486
+ Yes. The same Entity can have Sections from both aspect projection and align/compile. Aspect compile only touches Sections whose source namespace belongs to the selected aspect.
487
+
488
+ ### Can an aspect create new Entities?
489
+
490
+ Yes, but only when the plugin explicitly emits deterministic documentation nodes through `ctx.emit.node`, which become `nodes.jsonl`. This is for mechanically named pages such as `pkg/colors` or `pkg/quick-start`.
491
+
492
+ `sections.jsonl` alone never creates missing nodes. If a Section points at a node that is neither pre-existing nor declared in `nodes.jsonl`, `anchor_policy: strict` fails and `best-effort` records `skipped_anchor_missing[]`.
493
+
494
+ ### Can sections.jsonl be incrementally updated?
495
+
496
+ No. Every `context capture --aspect` produces a **full** `sections.jsonl`. The compile step automatically diffs by projection key: unchanged Sections are untouched, new rows are added, changed rows are updated, and removed rows are deprecated.
497
+
498
+ ### How do I debug extract.ts?
499
+
500
+ ```bash
501
+ # Run through the CLI host so ctx.source and ctx.emit are available:
502
+ context capture --aspect my-aspect --force --verbose
503
+
504
+ # Inspect the materialized snapshot:
505
+ cat .context/raw/aspect/my-aspect/my-aspect/latest/nodes.jsonl | jq .
506
+ cat .context/raw/aspect/my-aspect/my-aspect/latest/sections.jsonl | jq .
507
+ ```
508
+
509
+ ### Are aspect-produced Sections searchable?
510
+
511
+ Yes. Aspect-created Node title/summary and all Section `content` enter the index. Retrieval does not distinguish between aspect, align, or mdrive provenance.
512
+
513
+ ### Can multiple aspects run in parallel?
514
+
515
+ `context capture --aspect a b c` and `context compile --aspect a b c` execute sequentially. Different aspects operate on different namespaces and could theoretically run in parallel — a future CLI version may add a `--parallel` flag.
@@ -1,4 +1,4 @@
1
- schema_version: "1.0"
1
+ schema_version: context.aspect.v2
2
2
  name: code
3
3
  runner:
4
4
  package: "@c4a/extract"
@@ -7,7 +7,7 @@ plugins:
7
7
  - package: "@c4a/extract-ts"
8
8
  export: TypeScriptPlugin
9
9
  output:
10
- bucket: raw/aspect/code/<source-slug>/<snapshot-id>/
10
+ bucket: raw/aspect/code/{source_slug}/{snapshot_id}
11
11
  files:
12
12
  - source.yaml
13
13
  - manifest.json
@@ -5,8 +5,9 @@
5
5
  ## Target
6
6
 
7
7
  Extract package, symbol, edge, digest, and source-file rows from the local
8
- worktree. The runner reads JSON from stdin and returns NDJSON events; the
9
- context CLI owns `.context` writes.
8
+ worktree. This aspect uses bundled runner mode (`runner.package` + `runner.bin`),
9
+ not local plugin mode. The context CLI owns `.context` writes and publishes the
10
+ raw bucket atomically.
10
11
 
11
12
  ## Raw Snapshot
12
13
 
@@ -18,14 +19,14 @@ raw/aspect/code/<source-slug>/<snapshot-id>/
18
19
 
19
20
  The bucket contains:
20
21
 
21
- - `source.yaml` local source identity and publish upsert hints
22
- - `manifest.json` snapshot identity, runner hash, counts, and content hash
23
- - `digests.jsonl` module digest rows with `hash_id`, `module_path`, and `dir_commit`
24
- - `source-files.jsonl` service-compatible source file mapping
25
- - `packages.jsonl` flat package/module view
26
- - `symbols.jsonl` flat symbol view, with nested members flattened
27
- - `edges.jsonl` flat code relation view
28
- - `_meta.yaml` compact copy of snapshot metadata for legacy readers
22
+ - `source.yaml` - local source identity and publish upsert hints
23
+ - `manifest.json` - snapshot identity, runner hash, counts, and content hash
24
+ - `digests.jsonl` - module digest rows with `hash_id`, `module_path`, and `dir_commit`
25
+ - `source-files.jsonl` - service-compatible source file mapping
26
+ - `packages.jsonl` - flat package/module view
27
+ - `symbols.jsonl` - flat symbol view, with nested members flattened
28
+ - `edges.jsonl` - flat code relation view
29
+ - `_meta.yaml` - compact copy of snapshot metadata for legacy readers
29
30
 
30
31
  ## Notes
31
32
 
@@ -37,8 +38,8 @@ The bucket contains:
37
38
  - The code aspect uses `evidence.mode: none`: it does not generate
38
39
  `raw/.evidence` block manifests. Symbols, files, and edges are already
39
40
  represented by the bucket JSONL indexes.
40
- - `context compile --code` converts package and symbol rows into code-owned
41
- Sections with code `source_ref` values such as
41
+ - `context compile --aspect code` converts package and symbol rows into
42
+ code-owned Sections with code `source_ref` values such as
42
43
  `src-1#package:<package>@<hash>` and
43
44
  `src-1#symbol:<locator>:<kind>@<hash>`. These refs resolve against the raw
44
45
  code snapshot JSONL indexes, not against prose evidence blocks.