@isaacriehm/cairn-core 0.1.10 → 0.3.0
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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/claude/runner.d.ts +1 -1
- package/dist/claude/runner.js +62 -1
- package/dist/claude/runner.js.map +1 -1
- package/dist/claude/types.d.ts +14 -7
- package/dist/claude/types.js +5 -7
- package/dist/claude/types.js.map +1 -1
- package/dist/context/index.d.ts +4 -4
- package/dist/context/index.js +3 -4
- package/dist/context/index.js.map +1 -1
- package/dist/context/spec-delta.js +3 -4
- package/dist/context/spec-delta.js.map +1 -1
- package/dist/context/task-summary.d.ts +18 -0
- package/dist/context/task-summary.js +89 -0
- package/dist/context/task-summary.js.map +1 -0
- package/dist/decision-capture/id.d.ts +21 -4
- package/dist/decision-capture/id.js +56 -5
- package/dist/decision-capture/id.js.map +1 -1
- package/dist/decision-capture/index.d.ts +7 -22
- package/dist/decision-capture/index.js +7 -17
- package/dist/decision-capture/index.js.map +1 -1
- package/dist/doctor/index.d.ts +2 -2
- package/dist/doctor/index.js +5 -60
- package/dist/doctor/index.js.map +1 -1
- package/dist/events/index.d.ts +1 -1
- package/dist/events/index.js +1 -1
- package/dist/events/writer.d.ts +1 -1
- package/dist/events/writer.js +1 -1
- package/dist/gc/canary.d.ts +13 -23
- package/dist/gc/canary.js +36 -92
- package/dist/gc/canary.js.map +1 -1
- package/dist/gc/citation-integrity.d.ts +6 -6
- package/dist/gc/citation-integrity.js +64 -20
- package/dist/gc/citation-integrity.js.map +1 -1
- package/dist/gc/frontmatter.js +1 -1
- package/dist/gc/generator-drift.js +1 -1
- package/dist/gc/generator-drift.js.map +1 -1
- package/dist/gc/index.d.ts +2 -2
- package/dist/gc/index.js +1 -1
- package/dist/gc/index.js.map +1 -1
- package/dist/gc/quality-update.d.ts +4 -4
- package/dist/gc/quality-update.js +5 -5
- package/dist/gc/quality-update.js.map +1 -1
- package/dist/gc/types.d.ts +1 -30
- package/dist/ground/index.d.ts +2 -2
- package/dist/ground/index.js +1 -1
- package/dist/ground/index.js.map +1 -1
- package/dist/ground/ledgers.d.ts +0 -4
- package/dist/ground/ledgers.js +10 -8
- package/dist/ground/ledgers.js.map +1 -1
- package/dist/ground/manifest.js +0 -4
- package/dist/ground/manifest.js.map +1 -1
- package/dist/ground/paths.js +0 -1
- package/dist/ground/paths.js.map +1 -1
- package/dist/ground/quality-grades.js +3 -1
- package/dist/ground/quality-grades.js.map +1 -1
- package/dist/ground/schemas.d.ts +2 -7
- package/dist/ground/schemas.js +7 -2
- package/dist/ground/schemas.js.map +1 -1
- package/dist/ground/scope-index.d.ts +62 -14
- package/dist/ground/scope-index.js +215 -45
- package/dist/ground/scope-index.js.map +1 -1
- package/dist/hooks/bypass-detection.js +14 -17
- package/dist/hooks/bypass-detection.js.map +1 -1
- package/dist/hooks/defer.d.ts +50 -0
- package/dist/hooks/defer.js +87 -0
- package/dist/hooks/defer.js.map +1 -0
- package/dist/hooks/post-tool-use/citation-scanner.d.ts +7 -5
- package/dist/hooks/post-tool-use/citation-scanner.js +15 -9
- package/dist/hooks/post-tool-use/citation-scanner.js.map +1 -1
- package/dist/hooks/post-tool-use/copy-scanner.js +1 -1
- package/dist/hooks/post-tool-use/copy-scanner.js.map +1 -1
- package/dist/hooks/post-tool-use/ledger-cache.d.ts +10 -2
- package/dist/hooks/post-tool-use/ledger-cache.js +58 -4
- package/dist/hooks/post-tool-use/ledger-cache.js.map +1 -1
- package/dist/hooks/post-tool-use/legend-builder.d.ts +3 -3
- package/dist/hooks/post-tool-use/legend-builder.js +30 -8
- package/dist/hooks/post-tool-use/legend-builder.js.map +1 -1
- package/dist/hooks/post-tool-use/read-enricher.d.ts +1 -1
- package/dist/hooks/post-tool-use/read-enricher.js +68 -21
- package/dist/hooks/post-tool-use/read-enricher.js.map +1 -1
- package/dist/hooks/post-tool-use/write-guardian.js +148 -18
- package/dist/hooks/post-tool-use/write-guardian.js.map +1 -1
- package/dist/hooks/runners/index.d.ts +3 -0
- package/dist/hooks/runners/index.js +2 -0
- package/dist/hooks/runners/index.js.map +1 -1
- package/dist/hooks/runners/payload.d.ts +1 -1
- package/dist/hooks/runners/payload.js +27 -12
- package/dist/hooks/runners/payload.js.map +1 -1
- package/dist/hooks/runners/session-start.js +250 -75
- package/dist/hooks/runners/session-start.js.map +1 -1
- package/dist/hooks/runners/stop.d.ts +9 -12
- package/dist/hooks/runners/stop.js +129 -41
- package/dist/hooks/runners/stop.js.map +1 -1
- package/dist/hooks/runners/user-prompt-submit.d.ts +15 -0
- package/dist/hooks/runners/user-prompt-submit.js +149 -0
- package/dist/hooks/runners/user-prompt-submit.js.map +1 -0
- package/dist/hooks/seed-attested.d.ts +25 -0
- package/dist/hooks/seed-attested.js +84 -0
- package/dist/hooks/seed-attested.js.map +1 -0
- package/dist/hooks/user-prompt-submit.d.ts +7 -0
- package/dist/hooks/user-prompt-submit.js +12 -0
- package/dist/hooks/user-prompt-submit.js.map +1 -0
- package/dist/index.d.ts +3 -6
- package/dist/index.js +14 -12
- package/dist/index.js.map +1 -1
- package/dist/init/baseline-audit.d.ts +5 -1
- package/dist/init/baseline-audit.js +25 -2
- package/dist/init/baseline-audit.js.map +1 -1
- package/dist/init/glob-inference.d.ts +16 -0
- package/dist/init/glob-inference.js +108 -0
- package/dist/init/glob-inference.js.map +1 -0
- package/dist/init/index.d.ts +4 -0
- package/dist/init/index.js +2 -0
- package/dist/init/index.js.map +1 -1
- package/dist/init/ingest-docs.d.ts +1 -1
- package/dist/init/ingest-docs.js +19 -6
- package/dist/init/ingest-docs.js.map +1 -1
- package/dist/init/init.js +17 -94
- package/dist/init/init.js.map +1 -1
- package/dist/init/mapper-merge.d.ts +8 -2
- package/dist/init/mapper-merge.js +50 -32
- package/dist/init/mapper-merge.js.map +1 -1
- package/dist/init/mapper-parallel.d.ts +2 -3
- package/dist/init/mapper-parallel.js +9 -38
- package/dist/init/mapper-parallel.js.map +1 -1
- package/dist/init/mapper-prompts.js +1 -1
- package/dist/init/mapper-prompts.js.map +1 -1
- package/dist/init/mapper.d.ts +7 -128
- package/dist/init/mapper.js +30 -7
- package/dist/init/mapper.js.map +1 -1
- package/dist/init/module-slicer.d.ts +1 -1
- package/dist/init/module-slicer.js +1 -1
- package/dist/init/multi-dev/install.d.ts +9 -10
- package/dist/init/multi-dev/install.js +15 -84
- package/dist/init/multi-dev/install.js.map +1 -1
- package/dist/init/overlay.d.ts +14 -0
- package/dist/init/overlay.js +65 -0
- package/dist/init/overlay.js.map +1 -0
- package/dist/init/phases/1-detect.d.ts +10 -0
- package/dist/init/phases/1-detect.js +36 -0
- package/dist/init/phases/1-detect.js.map +1 -0
- package/dist/init/phases/10-strip.d.ts +11 -0
- package/dist/init/phases/10-strip.js +79 -0
- package/dist/init/phases/10-strip.js.map +1 -0
- package/dist/init/phases/12-multidev.d.ts +6 -0
- package/dist/init/phases/12-multidev.js +34 -0
- package/dist/init/phases/12-multidev.js.map +1 -0
- package/dist/init/phases/2-walker.d.ts +8 -0
- package/dist/init/phases/2-walker.js +34 -0
- package/dist/init/phases/2-walker.js.map +1 -0
- package/dist/init/phases/3-mapper.d.ts +14 -0
- package/dist/init/phases/3-mapper.js +56 -0
- package/dist/init/phases/3-mapper.js.map +1 -0
- package/dist/init/phases/3b-seed.d.ts +18 -0
- package/dist/init/phases/3b-seed.js +140 -0
- package/dist/init/phases/3b-seed.js.map +1 -0
- package/dist/init/phases/4-pilot.d.ts +10 -0
- package/dist/init/phases/4-pilot.js +108 -0
- package/dist/init/phases/4-pilot.js.map +1 -0
- package/dist/init/phases/5-brand.d.ts +10 -0
- package/dist/init/phases/5-brand.js +66 -0
- package/dist/init/phases/5-brand.js.map +1 -0
- package/dist/init/phases/6-docs-ingest.d.ts +11 -0
- package/dist/init/phases/6-docs-ingest.js +39 -0
- package/dist/init/phases/6-docs-ingest.js.map +1 -0
- package/dist/init/phases/7b-source-comments.d.ts +6 -0
- package/dist/init/phases/7b-source-comments.js +34 -0
- package/dist/init/phases/7b-source-comments.js.map +1 -0
- package/dist/init/phases/7c-rules-merge.d.ts +6 -0
- package/dist/init/phases/7c-rules-merge.js +34 -0
- package/dist/init/phases/7c-rules-merge.js.map +1 -0
- package/dist/init/phases/8-baseline.d.ts +10 -0
- package/dist/init/phases/8-baseline.js +52 -0
- package/dist/init/phases/8-baseline.js.map +1 -0
- package/dist/init/phases/index.d.ts +15 -0
- package/dist/init/phases/index.js +16 -0
- package/dist/init/phases/index.js.map +1 -0
- package/dist/init/phases/orchestrator.d.ts +49 -0
- package/dist/init/phases/orchestrator.js +85 -0
- package/dist/init/phases/orchestrator.js.map +1 -0
- package/dist/init/phases/state-io.d.ts +19 -0
- package/dist/init/phases/state-io.js +73 -0
- package/dist/init/phases/state-io.js.map +1 -0
- package/dist/init/phases/types.d.ts +93 -0
- package/dist/init/phases/types.js +30 -0
- package/dist/init/phases/types.js.map +1 -0
- package/dist/init/prompts.d.ts +6 -8
- package/dist/init/prompts.js +3 -6
- package/dist/init/prompts.js.map +1 -1
- package/dist/init/rules-merge/ingest.js +1 -1
- package/dist/init/seed.js +11 -2
- package/dist/init/seed.js.map +1 -1
- package/dist/init/source-comments/classify.d.ts +1 -41
- package/dist/init/source-comments/classify.js +68 -37
- package/dist/init/source-comments/classify.js.map +1 -1
- package/dist/init/source-comments/index.d.ts +2 -2
- package/dist/init/source-comments/index.js +2 -2
- package/dist/init/source-comments/index.js.map +1 -1
- package/dist/init/source-comments/ingest.d.ts +38 -2
- package/dist/init/source-comments/ingest.js +237 -4
- package/dist/init/source-comments/ingest.js.map +1 -1
- package/dist/init/source-comments/strip-replace.d.ts +10 -19
- package/dist/init/source-comments/strip-replace.js +26 -2
- package/dist/init/source-comments/strip-replace.js.map +1 -1
- package/dist/init/source-comments/walker.d.ts +9 -2
- package/dist/init/source-comments/walker.js +38 -19
- package/dist/init/source-comments/walker.js.map +1 -1
- package/dist/init/submodules.d.ts +3 -2
- package/dist/init/types.d.ts +3 -5
- package/dist/init/types.js +3 -5
- package/dist/init/types.js.map +1 -1
- package/dist/init/visual.d.ts +4 -9
- package/dist/init/visual.js +1 -16
- package/dist/init/visual.js.map +1 -1
- package/dist/init/walker.d.ts +0 -6
- package/dist/init/walker.js +2 -3
- package/dist/init/walker.js.map +1 -1
- package/dist/join/index.js +49 -2
- package/dist/join/index.js.map +1 -1
- package/dist/logger.d.ts +1 -2
- package/dist/logger.js +5 -6
- package/dist/logger.js.map +1 -1
- package/dist/mcp/context.d.ts +2 -3
- package/dist/mcp/context.js.map +1 -1
- package/dist/mcp/errors.d.ts +1 -1
- package/dist/mcp/errors.js.map +1 -1
- package/dist/mcp/history/prompt.d.ts +2 -1
- package/dist/mcp/history/walker.js +1 -1
- package/dist/mcp/history/walker.js.map +1 -1
- package/dist/mcp/path-allowlist.d.ts +0 -6
- package/dist/mcp/path-allowlist.js +0 -10
- package/dist/mcp/path-allowlist.js.map +1 -1
- package/dist/mcp/schemas.d.ts +34 -72
- package/dist/mcp/schemas.js +40 -70
- package/dist/mcp/schemas.js.map +1 -1
- package/dist/mcp/serve.d.ts +1 -1
- package/dist/mcp/serve.js +2 -5
- package/dist/mcp/serve.js.map +1 -1
- package/dist/mcp/server.js +10 -4
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/telemetry.d.ts +8 -1
- package/dist/mcp/telemetry.js +16 -0
- package/dist/mcp/telemetry.js.map +1 -1
- package/dist/mcp/tools/decision-get.js +37 -29
- package/dist/mcp/tools/decision-get.js.map +1 -1
- package/dist/mcp/tools/decisions-in-scope.js +22 -3
- package/dist/mcp/tools/decisions-in-scope.js.map +1 -1
- package/dist/mcp/tools/ground-get.js +2 -2
- package/dist/mcp/tools/ground-get.js.map +1 -1
- package/dist/mcp/tools/index.js +6 -2
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/mcp/tools/init-phases.d.ts +24 -0
- package/dist/mcp/tools/init-phases.js +140 -0
- package/dist/mcp/tools/init-phases.js.map +1 -0
- package/dist/mcp/tools/invariant-get.js +1 -1
- package/dist/mcp/tools/invariant-get.js.map +1 -1
- package/dist/mcp/tools/invariants-in-scope.js +26 -7
- package/dist/mcp/tools/invariants-in-scope.js.map +1 -1
- package/dist/mcp/tools/query-history.js +3 -3
- package/dist/mcp/tools/query-history.js.map +1 -1
- package/dist/mcp/tools/resolve-attention.d.ts +6 -4
- package/dist/mcp/tools/resolve-attention.js +253 -24
- package/dist/mcp/tools/resolve-attention.js.map +1 -1
- package/dist/mcp/tools/search.js +3 -4
- package/dist/mcp/tools/search.js.map +1 -1
- package/dist/mcp/tools/task-create.d.ts +15 -0
- package/dist/mcp/tools/task-create.js +96 -0
- package/dist/mcp/tools/task-create.js.map +1 -0
- package/dist/mcp/tools/timeline.js +3 -7
- package/dist/mcp/tools/timeline.js.map +1 -1
- package/dist/profiles/types.d.ts +2 -2
- package/dist/profiles/types.js +1 -1
- package/dist/sensors/catalog.js +8 -3
- package/dist/sensors/catalog.js.map +1 -1
- package/dist/session/id.d.ts +1 -3
- package/dist/session/id.js +1 -1
- package/dist/session/id.js.map +1 -1
- package/dist/session-start/build.d.ts +7 -11
- package/dist/session-start/build.js +62 -34
- package/dist/session-start/build.js.map +1 -1
- package/dist/session-start/index.d.ts +2 -2
- package/dist/session-start/index.js +2 -2
- package/dist/session-start/index.js.map +1 -1
- package/dist/session-start/templates.d.ts +20 -4
- package/dist/session-start/templates.js +45 -35
- package/dist/session-start/templates.js.map +1 -1
- package/dist/status-line/format.d.ts +20 -9
- package/dist/status-line/format.js +45 -35
- package/dist/status-line/format.js.map +1 -1
- package/dist/status-line/index.d.ts +3 -4
- package/dist/status-line/index.js +1 -1
- package/dist/status-line/index.js.map +1 -1
- package/dist/status-line/reader.d.ts +9 -6
- package/dist/status-line/reader.js +50 -18
- package/dist/status-line/reader.js.map +1 -1
- package/dist/status-line/writer.d.ts +6 -9
- package/dist/status-line/writer.js +10 -12
- package/dist/status-line/writer.js.map +1 -1
- package/dist/trace/index.d.ts +44 -0
- package/dist/trace/index.js +51 -0
- package/dist/trace/index.js.map +1 -0
- package/package.json +2 -3
- package/templates/.archive/README.md +3 -3
- package/templates/.cairn/JOIN.md +3 -9
- package/templates/.cairn/config/sensors.yaml +16 -19
- package/templates/.cairn/config/stub-patterns.yaml +4 -4
- package/templates/.cairn/config/trust-policy.yaml +3 -35
- package/templates/.cairn/config/workflow.md +13 -194
- package/templates/.cairn/git-hooks/commit-msg +15 -4
- package/templates/.cairn/git-hooks/pre-commit +22 -13
- package/templates/.cairn/ground/canonical-map/topics.yaml +3 -26
- package/templates/.cairn/ground/capabilities/snippets.yaml +0 -1
- package/templates/.cairn/ground/manifest.yaml +5 -8
- package/templates/.cairn/ground/product/personas.yaml +0 -1
- package/dist/context/checkpoint.d.ts +0 -10
- package/dist/context/checkpoint.js +0 -29
- package/dist/context/checkpoint.js.map +0 -1
- package/dist/decision-capture/capture.d.ts +0 -57
- package/dist/decision-capture/capture.js +0 -186
- package/dist/decision-capture/capture.js.map +0 -1
- package/dist/decision-capture/extractor.d.ts +0 -20
- package/dist/decision-capture/extractor.js +0 -103
- package/dist/decision-capture/extractor.js.map +0 -1
- package/dist/decision-capture/prompt.d.ts +0 -15
- package/dist/decision-capture/prompt.js +0 -68
- package/dist/decision-capture/prompt.js.map +0 -1
- package/dist/decision-capture/refinement-prompt.d.ts +0 -25
- package/dist/decision-capture/refinement-prompt.js +0 -146
- package/dist/decision-capture/refinement-prompt.js.map +0 -1
- package/dist/decision-capture/refinement-schema.d.ts +0 -52
- package/dist/decision-capture/refinement-schema.js +0 -61
- package/dist/decision-capture/refinement-schema.js.map +0 -1
- package/dist/decision-capture/refinement.d.ts +0 -60
- package/dist/decision-capture/refinement.js +0 -439
- package/dist/decision-capture/refinement.js.map +0 -1
- package/dist/decision-capture/schema.d.ts +0 -70
- package/dist/decision-capture/schema.js +0 -71
- package/dist/decision-capture/schema.js.map +0 -1
- package/dist/decision-capture/types.d.ts +0 -201
- package/dist/decision-capture/types.js +0 -20
- package/dist/decision-capture/types.js.map +0 -1
- package/dist/decision-capture/writer.d.ts +0 -90
- package/dist/decision-capture/writer.js +0 -267
- package/dist/decision-capture/writer.js.map +0 -1
- package/dist/frontend-types.d.ts +0 -243
- package/dist/frontend-types.js +0 -15
- package/dist/frontend-types.js.map +0 -1
- package/dist/hooks/index.d.ts +0 -12
- package/dist/hooks/index.js +0 -13
- package/dist/hooks/index.js.map +0 -1
- package/dist/inbox.d.ts +0 -17
- package/dist/inbox.js +0 -30
- package/dist/inbox.js.map +0 -1
- package/dist/mcp/tools/append-run-note.d.ts +0 -18
- package/dist/mcp/tools/append-run-note.js +0 -47
- package/dist/mcp/tools/append-run-note.js.map +0 -1
- package/dist/mcp/tools/append.d.ts +0 -8
- package/dist/mcp/tools/append.js +0 -37
- package/dist/mcp/tools/append.js.map +0 -1
- package/dist/mcp/tools/ask-operator.d.ts +0 -34
- package/dist/mcp/tools/ask-operator.js +0 -97
- package/dist/mcp/tools/ask-operator.js.map +0 -1
- package/dist/mcp/tools/drop-task.d.ts +0 -12
- package/dist/mcp/tools/drop-task.js +0 -68
- package/dist/mcp/tools/drop-task.js.map +0 -1
- package/dist/mcp/tools/record-run-event.d.ts +0 -10
- package/dist/mcp/tools/record-run-event.js +0 -32
- package/dist/mcp/tools/record-run-event.js.map +0 -1
- package/dist/prompt.d.ts +0 -19
- package/dist/prompt.js +0 -50
- package/dist/prompt.js.map +0 -1
- package/dist/tier0/classify.d.ts +0 -10
- package/dist/tier0/classify.js +0 -110
- package/dist/tier0/classify.js.map +0 -1
- package/dist/tier0/index.d.ts +0 -2
- package/dist/tier0/index.js +0 -2
- package/dist/tier0/index.js.map +0 -1
- package/dist/tier0/types.d.ts +0 -24
- package/dist/tier0/types.js +0 -9
- package/dist/tier0/types.js.map +0 -1
- package/dist/tightener/index.d.ts +0 -4
- package/dist/tightener/index.js +0 -4
- package/dist/tightener/index.js.map +0 -1
- package/dist/tightener/prompt.d.ts +0 -3
- package/dist/tightener/prompt.js +0 -67
- package/dist/tightener/prompt.js.map +0 -1
- package/dist/tightener/schema.d.ts +0 -68
- package/dist/tightener/schema.js +0 -44
- package/dist/tightener/schema.js.map +0 -1
- package/dist/tightener/tighten.d.ts +0 -2
- package/dist/tightener/tighten.js +0 -66
- package/dist/tightener/tighten.js.map +0 -1
- package/dist/tightener/types.d.ts +0 -74
- package/dist/tightener/types.js +0 -6
- package/dist/tightener/types.js.map +0 -1
- package/templates/.claude/settings.json +0 -57
- package/templates/.mcp.json +0 -8
- package/templates/README.md +0 -24
|
@@ -2,20 +2,50 @@
|
|
|
2
2
|
* Scope index — forward map from every file path in the repo to the decisions
|
|
3
3
|
* and invariants that apply to that file.
|
|
4
4
|
*
|
|
5
|
-
* Built at init by the Tier-2 mapper LLM, maintained by the
|
|
6
|
-
*
|
|
5
|
+
* Built at init by the Tier-2 mapper LLM, maintained by the GC sweep + the
|
|
6
|
+
* MCP record-decision tool when scope edits land. Read by the read-enricher
|
|
7
|
+
* / write-guardian hooks (via cached accessor in
|
|
7
8
|
* `hooks/post-tool-use/ledger-cache.ts`) and by the GC scope-coverage pass.
|
|
8
9
|
*
|
|
9
|
-
* Spec: docs/
|
|
10
|
+
* Spec: docs/FILESYSTEM_LAYOUT.md §2.1.
|
|
10
11
|
*/
|
|
12
|
+
import { execFileSync } from "node:child_process";
|
|
11
13
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
14
|
import { dirname, join } from "node:path";
|
|
13
15
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
14
|
-
import { detectAll } from "../init/detect.js";
|
|
15
|
-
import { buildMapperUserPrompt, MAPPER_OUTPUT_SCHEMA, MAPPER_SYSTEM_PROMPT, validateMapperOutput, } from "../init/mapper.js";
|
|
16
|
-
import { buildRepoSummary } from "../init/walker.js";
|
|
17
16
|
import { logger } from "../logger.js";
|
|
18
|
-
|
|
17
|
+
const DEC_ID_RE = /\bDEC-\d{4,}\b/;
|
|
18
|
+
const INV_ID_RE = /\bINV-\d{4,}\b/;
|
|
19
|
+
/**
|
|
20
|
+
* Mapper LLMs occasionally emit ledger-entry PROSE ("HTTP layer is the only
|
|
21
|
+
* public surface…") into `decisions[]` / `invariants[]` instead of bare IDs,
|
|
22
|
+
* because the user prompt lists them as `${id} — ${title}` and the JSON-mode
|
|
23
|
+
* schema only constrains the type to `string`. This coercer extracts the
|
|
24
|
+
* first ID-shaped token from each string and silently drops anything that
|
|
25
|
+
* doesn't match — IDs only, deduplicated, order preserved.
|
|
26
|
+
*/
|
|
27
|
+
export function coerceDecisionIds(raw) {
|
|
28
|
+
return coerceIds(raw, DEC_ID_RE);
|
|
29
|
+
}
|
|
30
|
+
export function coerceInvariantIds(raw) {
|
|
31
|
+
return coerceIds(raw, INV_ID_RE);
|
|
32
|
+
}
|
|
33
|
+
function coerceIds(raw, re) {
|
|
34
|
+
const out = [];
|
|
35
|
+
const seen = new Set();
|
|
36
|
+
for (const s of raw) {
|
|
37
|
+
if (typeof s !== "string")
|
|
38
|
+
continue;
|
|
39
|
+
const m = s.match(re);
|
|
40
|
+
if (m === null)
|
|
41
|
+
continue;
|
|
42
|
+
if (seen.has(m[0]))
|
|
43
|
+
continue;
|
|
44
|
+
seen.add(m[0]);
|
|
45
|
+
out.push(m[0]);
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
19
49
|
export function scopeIndexPath(repoRoot) {
|
|
20
50
|
return join(repoRoot, ".cairn", "ground", "scope-index.yaml");
|
|
21
51
|
}
|
|
@@ -68,53 +98,193 @@ export function writeScopeIndex(repoRoot, index) {
|
|
|
68
98
|
writeFileSync(path, stringifyYaml(index), "utf8");
|
|
69
99
|
}
|
|
70
100
|
const log = logger("ground.scope-index");
|
|
101
|
+
/* -------------------------------------------------------------------------- */
|
|
102
|
+
/* Deterministic citation rescan */
|
|
103
|
+
/* -------------------------------------------------------------------------- */
|
|
104
|
+
const CITATION_RE = /§(?:INV|DEC)-\d{4,}/g;
|
|
105
|
+
const RESCAN_SOURCE_EXT_RE = /\.(?:ts|tsx|cts|mts|js|jsx|cjs|mjs|py|rb|go|rs|java|kt|swift|c|cc|cpp|h|hpp|cs|php|sql|sh|bash)$/i;
|
|
106
|
+
const RESCAN_SKIP_DIR_RE = /(?:^|\/)(?:node_modules|dist|build|out|\.next|\.turbo|\.cache|coverage|\.cairn|\.archive|\.git)(?:\/|$)/;
|
|
71
107
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
108
|
+
* Deterministic regex sweep — walk every git-tracked source file, parse
|
|
109
|
+
* `§INV-NNNN` / `§DEC-NNNN` cite tokens, and sync the scope-index so the
|
|
110
|
+
* in-scope tools never lag behind source-cite reality. No LLM, no
|
|
111
|
+
* incremental tracking complexity. Cheap enough to run on every
|
|
112
|
+
* SessionStart (~100ms on 50k files).
|
|
76
113
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
114
|
+
* Honors `unscoped: true` entries — never touches them. Skips files with
|
|
115
|
+
* no citations and no prior entry to avoid polluting the index. When a
|
|
116
|
+
* file's cite set differs from its scope-index entry (added, removed, or
|
|
117
|
+
* stale ids), the entry is rewritten with the deterministically-sorted
|
|
118
|
+
* current set.
|
|
79
119
|
*/
|
|
80
|
-
export
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
120
|
+
export function rescanScopeIndex(repoRoot) {
|
|
121
|
+
const sourceFiles = listGitTrackedSourceFiles(repoRoot);
|
|
122
|
+
const existing = readScopeIndex(repoRoot) ?? {
|
|
123
|
+
generated: new Date().toISOString(),
|
|
124
|
+
files: {},
|
|
125
|
+
};
|
|
126
|
+
let entriesAdded = 0;
|
|
127
|
+
let entriesUpdated = 0;
|
|
128
|
+
let entriesUnchanged = 0;
|
|
129
|
+
let dirty = false;
|
|
130
|
+
for (const rel of sourceFiles) {
|
|
131
|
+
const prior = existing.files[rel];
|
|
132
|
+
if (prior?.unscoped === true)
|
|
133
|
+
continue;
|
|
134
|
+
const found = scanFileCitations(join(repoRoot, rel));
|
|
135
|
+
if (found === null)
|
|
136
|
+
continue;
|
|
137
|
+
if (found.decisions.length === 0 &&
|
|
138
|
+
found.invariants.length === 0 &&
|
|
139
|
+
prior === undefined) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (prior === undefined) {
|
|
143
|
+
existing.files[rel] = {
|
|
144
|
+
decisions: found.decisions,
|
|
145
|
+
invariants: found.invariants,
|
|
146
|
+
};
|
|
147
|
+
entriesAdded += 1;
|
|
148
|
+
dirty = true;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const sameDecs = prior.decisions.length === found.decisions.length &&
|
|
152
|
+
prior.decisions.every((d, i) => d === found.decisions[i]);
|
|
153
|
+
const sameInvs = prior.invariants.length === found.invariants.length &&
|
|
154
|
+
prior.invariants.every((v, i) => v === found.invariants[i]);
|
|
155
|
+
if (sameDecs && sameInvs) {
|
|
156
|
+
entriesUnchanged += 1;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
existing.files[rel] = {
|
|
160
|
+
decisions: found.decisions,
|
|
161
|
+
invariants: found.invariants,
|
|
103
162
|
};
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
163
|
+
entriesUpdated += 1;
|
|
164
|
+
dirty = true;
|
|
165
|
+
}
|
|
166
|
+
if (dirty) {
|
|
167
|
+
writeScopeIndex(repoRoot, {
|
|
168
|
+
generated: new Date().toISOString(),
|
|
169
|
+
files: existing.files,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
filesScanned: sourceFiles.length,
|
|
174
|
+
entriesAdded,
|
|
175
|
+
entriesUpdated,
|
|
176
|
+
entriesUnchanged,
|
|
177
|
+
dirty,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function scanFileCitations(absPath) {
|
|
181
|
+
let body;
|
|
182
|
+
try {
|
|
183
|
+
body = readFileSync(absPath, "utf8");
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return null;
|
|
107
187
|
}
|
|
108
|
-
|
|
188
|
+
return parseCitations(body);
|
|
189
|
+
}
|
|
190
|
+
function parseCitations(body) {
|
|
191
|
+
const decs = new Set();
|
|
192
|
+
const invs = new Set();
|
|
193
|
+
for (const m of body.matchAll(CITATION_RE)) {
|
|
194
|
+
const tok = m[0].slice(1);
|
|
195
|
+
if (tok.startsWith("DEC-"))
|
|
196
|
+
decs.add(tok);
|
|
197
|
+
else if (tok.startsWith("INV-"))
|
|
198
|
+
invs.add(tok);
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
decisions: [...decs].sort(),
|
|
202
|
+
invariants: [...invs].sort(),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Single-file scope-index sync from in-memory content. Called by the
|
|
207
|
+
* PostToolUse(Write/Edit) hook so an agent's writes don't leave the
|
|
208
|
+
* scope-index stale until the next SessionStart rescan. Same regex
|
|
209
|
+
* parser as `rescanScopeIndex`, but bounded to one file — O(1) cost,
|
|
210
|
+
* no walker.
|
|
211
|
+
*
|
|
212
|
+
* Honors `unscoped: true` entries (skips them). Honors the same skip
|
|
213
|
+
* rule the rescan does: file with no citations and no prior entry is
|
|
214
|
+
* a no-op so we don't pollute the index with empty rows.
|
|
215
|
+
*/
|
|
216
|
+
export function syncFileScopeFromContent(repoRoot, repoRelPath, content) {
|
|
217
|
+
const found = parseCitations(content);
|
|
218
|
+
const existing = readScopeIndex(repoRoot) ?? {
|
|
109
219
|
generated: new Date().toISOString(),
|
|
110
|
-
files,
|
|
220
|
+
files: {},
|
|
221
|
+
};
|
|
222
|
+
const prior = existing.files[repoRelPath];
|
|
223
|
+
if (prior?.unscoped === true)
|
|
224
|
+
return { dirty: false };
|
|
225
|
+
if (found.decisions.length === 0 &&
|
|
226
|
+
found.invariants.length === 0 &&
|
|
227
|
+
prior === undefined) {
|
|
228
|
+
return { dirty: false };
|
|
229
|
+
}
|
|
230
|
+
if (prior !== undefined) {
|
|
231
|
+
const sameDecs = prior.decisions.length === found.decisions.length &&
|
|
232
|
+
prior.decisions.every((d, i) => d === found.decisions[i]);
|
|
233
|
+
const sameInvs = prior.invariants.length === found.invariants.length &&
|
|
234
|
+
prior.invariants.every((v, i) => v === found.invariants[i]);
|
|
235
|
+
if (sameDecs && sameInvs)
|
|
236
|
+
return { dirty: false };
|
|
237
|
+
}
|
|
238
|
+
existing.files[repoRelPath] = {
|
|
239
|
+
decisions: found.decisions,
|
|
240
|
+
invariants: found.invariants,
|
|
111
241
|
};
|
|
112
|
-
writeScopeIndex(
|
|
242
|
+
writeScopeIndex(repoRoot, {
|
|
243
|
+
generated: new Date().toISOString(),
|
|
244
|
+
files: existing.files,
|
|
245
|
+
});
|
|
246
|
+
return { dirty: true };
|
|
247
|
+
}
|
|
248
|
+
function listGitTrackedSourceFiles(repoRoot) {
|
|
249
|
+
let out;
|
|
250
|
+
try {
|
|
251
|
+
out = execFileSync("git", ["ls-files", "--cached", "--others", "--exclude-standard", "-z"], { cwd: repoRoot, maxBuffer: 100 * 1024 * 1024 }).toString("utf8");
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
return [];
|
|
255
|
+
}
|
|
256
|
+
const acc = [];
|
|
257
|
+
for (const path of out.split("\0")) {
|
|
258
|
+
if (path.length === 0)
|
|
259
|
+
continue;
|
|
260
|
+
if (RESCAN_SKIP_DIR_RE.test(path))
|
|
261
|
+
continue;
|
|
262
|
+
if (!RESCAN_SOURCE_EXT_RE.test(path))
|
|
263
|
+
continue;
|
|
264
|
+
acc.push(path);
|
|
265
|
+
}
|
|
266
|
+
return acc;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* `cairn scope rebuild` — full deterministic resync of `.cairn/ground/
|
|
270
|
+
* scope-index.yaml` from current source-cite reality. Used to be a
|
|
271
|
+
* Sonnet call (`runMapper`-style) that re-classified files via the
|
|
272
|
+
* Tier-2 mapper, but classification was always the wrong abstraction
|
|
273
|
+
* — bare-symbol citations in source are the canonical source of truth,
|
|
274
|
+
* `rescanScopeIndex` parses them deterministically, and the result
|
|
275
|
+
* matches what the read-enricher legend actually shows.
|
|
276
|
+
*
|
|
277
|
+
* No LLM. No tokens. No mapper. Just a regex sweep over git-tracked
|
|
278
|
+
* source files and an atomic write.
|
|
279
|
+
*/
|
|
280
|
+
export async function rebuildScopeIndex(opts) {
|
|
281
|
+
log.info({ repo_root: opts.repoRoot }, "scope rebuild — deterministic rescan");
|
|
282
|
+
const startedAt = Date.now();
|
|
283
|
+
const result = rescanScopeIndex(opts.repoRoot);
|
|
113
284
|
return {
|
|
114
285
|
path: scopeIndexPath(opts.repoRoot),
|
|
115
|
-
filesClassified:
|
|
116
|
-
|
|
117
|
-
model: result.model,
|
|
286
|
+
filesClassified: result.entriesAdded + result.entriesUpdated + result.entriesUnchanged,
|
|
287
|
+
durationMs: Date.now() - startedAt,
|
|
118
288
|
};
|
|
119
289
|
}
|
|
120
290
|
//# sourceMappingURL=scope-index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scope-index.js","sourceRoot":"","sources":["../../src/ground/scope-index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"scope-index.js","sourceRoot":"","sources":["../../src/ground/scope-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,MAAM,CAAC;AAWtE,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAStC,MAAM,SAAS,GAAG,gBAAgB,CAAC;AACnC,MAAM,SAAS,GAAG,gBAAgB,CAAC;AAEnC;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAuB;IACvD,OAAO,SAAS,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,GAAuB;IACxD,OAAO,SAAS,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;AACnC,CAAC;AAED,SAAS,SAAS,CAAC,GAAuB,EAAE,EAAU;IACpD,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,GAAG,EAAE,CAAC;QACpB,IAAI,OAAO,CAAC,KAAK,QAAQ;YAAE,SAAS;QACpC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACtB,IAAI,CAAC,KAAK,IAAI;YAAE,SAAS;QACzB,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAAE,SAAS;QAC7B,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACf,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAOD,MAAM,UAAU,cAAc,CAAC,QAAgB;IAC7C,OAAO,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,kBAAkB,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,QAAgB;IAC7C,MAAM,IAAI,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;IACtC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/D,MAAM,SAAS,GACb,OAAQ,MAAkC,CAAC,WAAW,CAAC,KAAK,QAAQ;QAClE,CAAC,CAAG,MAAkC,CAAC,WAAW,CAAY;QAC9D,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC/B,MAAM,QAAQ,GAAI,MAAkC,CAAC,OAAO,CAAC,CAAC;IAC9D,MAAM,KAAK,GAAoC,EAAE,CAAC;IAClD,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtD,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC9C,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI;gBAAE,SAAS;YAClD,MAAM,CAAC,GAAG,CAA4B,CAAC;YACvC,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;gBAC7C,CAAC,CAAE,CAAC,CAAC,WAAW,CAAe,CAAC,MAAM,CAClC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAC1C;gBACH,CAAC,CAAC,EAAE,CAAC;YACP,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;gBAC/C,CAAC,CAAE,CAAC,CAAC,YAAY,CAAe,CAAC,MAAM,CACnC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAC1C;gBACH,CAAC,CAAC,EAAE,CAAC;YACP,MAAM,KAAK,GAAoB,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC;YACzD,IAAI,CAAC,CAAC,UAAU,CAAC,KAAK,IAAI;gBAAE,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC;YAClD,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;QACnB,CAAC;IACH,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,KAAiB,EACjB,gBAAwB;IAExB,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAC5C,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACrC,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,QAAgB,EAAE,KAAiB;IACjE,MAAM,IAAI,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;IACtC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,aAAa,CAAC,IAAI,EAAE,aAAa,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,GAAG,GAAG,MAAM,CAAC,oBAAoB,CAAC,CAAC;AAEzC,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAEhF,MAAM,WAAW,GAAG,sBAAsB,CAAC;AAE3C,MAAM,oBAAoB,GACxB,mGAAmG,CAAC;AAEtG,MAAM,kBAAkB,GACtB,yGAAyG,CAAC;AAW5G;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,MAAM,WAAW,GAAG,yBAAyB,CAAC,QAAQ,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAG,cAAc,CAAC,QAAQ,CAAC,IAAI;QAC3C,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,KAAK,EAAE,EAAE;KACV,CAAC;IACF,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,cAAc,GAAG,CAAC,CAAC;IACvB,IAAI,gBAAgB,GAAG,CAAC,CAAC;IACzB,IAAI,KAAK,GAAG,KAAK,CAAC;IAElB,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,KAAK,EAAE,QAAQ,KAAK,IAAI;YAAE,SAAS;QACvC,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;QACrD,IAAI,KAAK,KAAK,IAAI;YAAE,SAAS;QAE7B,IACE,KAAK,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC;YAC5B,KAAK,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC;YAC7B,KAAK,KAAK,SAAS,EACnB,CAAC;YACD,SAAS;QACX,CAAC;QAED,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG;gBACpB,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,UAAU,EAAE,KAAK,CAAC,UAAU;aAC7B,CAAC;YACF,YAAY,IAAI,CAAC,CAAC;YAClB,KAAK,GAAG,IAAI,CAAC;YACb,SAAS;QACX,CAAC;QAED,MAAM,QAAQ,GACZ,KAAK,CAAC,SAAS,CAAC,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC,MAAM;YACjD,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5D,MAAM,QAAQ,GACZ,KAAK,CAAC,UAAU,CAAC,MAAM,KAAK,KAAK,CAAC,UAAU,CAAC,MAAM;YACnD,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9D,IAAI,QAAQ,IAAI,QAAQ,EAAE,CAAC;YACzB,gBAAgB,IAAI,CAAC,CAAC;YACtB,SAAS;QACX,CAAC;QAED,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG;YACpB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,UAAU,EAAE,KAAK,CAAC,UAAU;SAC7B,CAAC;QACF,cAAc,IAAI,CAAC,CAAC;QACpB,KAAK,GAAG,IAAI,CAAC;IACf,CAAC;IAED,IAAI,KAAK,EAAE,CAAC;QACV,eAAe,CAAC,QAAQ,EAAE;YACxB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,KAAK,EAAE,QAAQ,CAAC,KAAK;SACtB,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,YAAY,EAAE,WAAW,CAAC,MAAM;QAChC,YAAY;QACZ,cAAc;QACd,gBAAgB;QAChB,KAAK;KACN,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CACxB,OAAe;IAEf,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC;AAED,SAAS,cAAc,CAAC,IAAY;IAIlC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QAC3C,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC1B,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;aACrC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACjD,CAAC;IACD,OAAO;QACL,SAAS,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,EAAE;QAC3B,UAAU,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,EAAE;KAC7B,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,wBAAwB,CACtC,QAAgB,EAChB,WAAmB,EACnB,OAAe;IAEf,MAAM,KAAK,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,cAAc,CAAC,QAAQ,CAAC,IAAI;QAC3C,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,KAAK,EAAE,EAAE;KACV,CAAC;IACF,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC1C,IAAI,KAAK,EAAE,QAAQ,KAAK,IAAI;QAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IACtD,IACE,KAAK,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC;QAC5B,KAAK,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC;QAC7B,KAAK,KAAK,SAAS,EACnB,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAC1B,CAAC;IACD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,QAAQ,GACZ,KAAK,CAAC,SAAS,CAAC,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC,MAAM;YACjD,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5D,MAAM,QAAQ,GACZ,KAAK,CAAC,UAAU,CAAC,MAAM,KAAK,KAAK,CAAC,UAAU,CAAC,MAAM;YACnD,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9D,IAAI,QAAQ,IAAI,QAAQ;YAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IACpD,CAAC;IACD,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG;QAC5B,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,UAAU,EAAE,KAAK,CAAC,UAAU;KAC7B,CAAC;IACF,eAAe,CAAC,QAAQ,EAAE;QACxB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,KAAK,EAAE,QAAQ,CAAC,KAAK;KACtB,CAAC,CAAC;IACH,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACzB,CAAC;AAED,SAAS,yBAAyB,CAAC,QAAgB;IACjD,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAChB,KAAK,EACL,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,oBAAoB,EAAE,IAAI,CAAC,EAChE,EAAE,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,GAAG,IAAI,GAAG,IAAI,EAAE,CAChD,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAChC,IAAI,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QAC5C,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QAC/C,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAiBD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAA8B;IAE9B,GAAG,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,sCAAsC,CAAC,CAAC;IAC/E,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC/C,OAAO;QACL,IAAI,EAAE,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC;QACnC,eAAe,EACb,MAAM,CAAC,YAAY,GAAG,MAAM,CAAC,cAAc,GAAG,MAAM,CAAC,gBAAgB;QACvE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;KACnC,CAAC;AACJ,CAAC"}
|
|
@@ -16,6 +16,7 @@ import { execFileSync } from "node:child_process";
|
|
|
16
16
|
import { existsSync, readFileSync } from "node:fs";
|
|
17
17
|
import { join } from "node:path";
|
|
18
18
|
const HEAD_LOOKBACK = 5;
|
|
19
|
+
const NUL = "\x00";
|
|
19
20
|
export function scanBypassedCommits(repoRoot) {
|
|
20
21
|
if (!existsSync(join(repoRoot, ".git"))) {
|
|
21
22
|
return { bypassed: [], inspected: 0, attestedFileExists: false };
|
|
@@ -39,24 +40,16 @@ export function scanBypassedCommits(repoRoot) {
|
|
|
39
40
|
}
|
|
40
41
|
export function renderBypassHint(bypassed) {
|
|
41
42
|
const lines = [];
|
|
42
|
-
|
|
43
|
+
const noun = bypassed.length === 1 ? "commit" : "commits";
|
|
44
|
+
lines.push(`## Cairn — ${bypassed.length} ${noun} not attested`);
|
|
43
45
|
lines.push("");
|
|
44
|
-
lines.push("
|
|
45
|
-
(bypassed.length === 1 ? " was" : "s were") +
|
|
46
|
-
" not recorded by cairn's post-commit hook. Likely cause: " +
|
|
47
|
-
"`git commit --no-verify` (or the hook ran before `cairn join` " +
|
|
48
|
-
"set `core.hooksPath`).");
|
|
46
|
+
lines.push("Likely a `--no-verify` commit or a missing per-clone bootstrap.");
|
|
49
47
|
lines.push("");
|
|
50
48
|
for (const c of bypassed) {
|
|
51
49
|
lines.push(`- \`${c.shortSha}\` — ${c.subject}`);
|
|
52
50
|
}
|
|
53
51
|
lines.push("");
|
|
54
|
-
lines.push("
|
|
55
|
-
lines.push("");
|
|
56
|
-
lines.push("> Some recent commits weren't attested by cairn. Pick one:");
|
|
57
|
-
lines.push("> `[a]` backfill — run `cairn sweep` to attest sensors retroactively");
|
|
58
|
-
lines.push("> `[b]` accept — record a DEC noting the bypass + reason");
|
|
59
|
-
lines.push("> `[c]` defer — keep the warning, address later");
|
|
52
|
+
lines.push("Invoke the `cairn-attention` skill on the next turn so the operator can pick record bypass / acknowledge / defer through `AskUserQuestion`.");
|
|
60
53
|
return lines.join("\n");
|
|
61
54
|
}
|
|
62
55
|
/* -------------------------------------------------------------------------- */
|
|
@@ -64,16 +57,20 @@ export function renderBypassHint(bypassed) {
|
|
|
64
57
|
/* -------------------------------------------------------------------------- */
|
|
65
58
|
function readRecentHead(repoRoot) {
|
|
66
59
|
try {
|
|
67
|
-
|
|
60
|
+
// NUL (%x00) as the SHA/subject separator. Tabs and any other
|
|
61
|
+
// printable byte can legitimately appear inside a commit subject;
|
|
62
|
+
// NUL cannot. Records are newline-separated; the subject (`%s`) is
|
|
63
|
+
// a single line by definition, so split("\n") is safe.
|
|
64
|
+
const out = execFileSync("git", ["log", `-n${HEAD_LOOKBACK}`, "--format=%H%x00%s"], { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
68
65
|
const result = [];
|
|
69
66
|
for (const line of out.split("\n")) {
|
|
70
67
|
if (line.length === 0)
|
|
71
68
|
continue;
|
|
72
|
-
const
|
|
73
|
-
if (
|
|
69
|
+
const sepIdx = line.indexOf(NUL);
|
|
70
|
+
if (sepIdx === -1)
|
|
74
71
|
continue;
|
|
75
|
-
const sha = line.slice(0,
|
|
76
|
-
const subject = line.slice(
|
|
72
|
+
const sha = line.slice(0, sepIdx);
|
|
73
|
+
const subject = line.slice(sepIdx + 1);
|
|
77
74
|
if (sha.length < 7)
|
|
78
75
|
continue;
|
|
79
76
|
result.push({ sha, shortSha: sha.slice(0, 7), subject });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bypass-detection.js","sourceRoot":"","sources":["../../src/hooks/bypass-detection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAgBjC,MAAM,aAAa,GAAG,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"bypass-detection.js","sourceRoot":"","sources":["../../src/hooks/bypass-detection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stop-hook signal debounce.
|
|
3
|
+
*
|
|
4
|
+
* Per-kind defer file: when the operator picks `[c]` defer on the
|
|
5
|
+
* inline A/B/C, cairn_resolve_attention writes
|
|
6
|
+
* `.cairn/.{bypass,review}-deferred-until` with the snapshot of
|
|
7
|
+
* SHAs / task-ids that were flagged. Subsequent Stop hooks suppress
|
|
8
|
+
* the warning while:
|
|
9
|
+
* 1. now < deferred_at + deferred_for_hours, AND
|
|
10
|
+
* 2. the current scan's flagged set ⊆ the deferred set
|
|
11
|
+
* (anything new shows up).
|
|
12
|
+
*
|
|
13
|
+
* Pure read/write/check helpers — no side effects beyond filesystem.
|
|
14
|
+
*/
|
|
15
|
+
export type DeferKind = "bypass" | "review";
|
|
16
|
+
export interface DeferState {
|
|
17
|
+
/** ISO timestamp when the operator chose defer. */
|
|
18
|
+
deferred_at: string;
|
|
19
|
+
/** Hours from `deferred_at` until the suppression window expires. */
|
|
20
|
+
deferred_for_hours: number;
|
|
21
|
+
/** Bypass commit SHAs at the time of defer (full SHAs, not short). */
|
|
22
|
+
flagged_shas: string[];
|
|
23
|
+
/** Reviewer-pending task ids at the time of defer. */
|
|
24
|
+
flagged_task_ids: string[];
|
|
25
|
+
}
|
|
26
|
+
export declare function deferStatePath(repoRoot: string, kind: DeferKind): string;
|
|
27
|
+
export declare function readDeferState(repoRoot: string, kind: DeferKind): DeferState | null;
|
|
28
|
+
export declare function writeDeferState(repoRoot: string, kind: DeferKind, snapshot: {
|
|
29
|
+
flagged_shas?: string[];
|
|
30
|
+
flagged_task_ids?: string[];
|
|
31
|
+
/** Override defer window. Default 24h. */
|
|
32
|
+
hours?: number;
|
|
33
|
+
/** ISO timestamp; defaults to new Date().toISOString(). */
|
|
34
|
+
nowIso?: string;
|
|
35
|
+
}): DeferState;
|
|
36
|
+
export declare function clearDeferState(repoRoot: string, kind: DeferKind): void;
|
|
37
|
+
/**
|
|
38
|
+
* Returns true when `state` is still suppressing — within the time
|
|
39
|
+
* window AND every currently-flagged item is in the deferred snapshot
|
|
40
|
+
* (no new bypasses / new pending reviews have appeared).
|
|
41
|
+
*
|
|
42
|
+
* If anything new appears, the suppression breaks and the Stop hook
|
|
43
|
+
* surfaces the warning again — which is the right behavior, because
|
|
44
|
+
* "defer for 24h" was a promise about the items the operator saw,
|
|
45
|
+
* not a blanket mute.
|
|
46
|
+
*/
|
|
47
|
+
export declare function isDeferActive(state: DeferState, now: Date, currentItems: {
|
|
48
|
+
kind: "shas" | "task_ids";
|
|
49
|
+
values: string[];
|
|
50
|
+
}): boolean;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stop-hook signal debounce.
|
|
3
|
+
*
|
|
4
|
+
* Per-kind defer file: when the operator picks `[c]` defer on the
|
|
5
|
+
* inline A/B/C, cairn_resolve_attention writes
|
|
6
|
+
* `.cairn/.{bypass,review}-deferred-until` with the snapshot of
|
|
7
|
+
* SHAs / task-ids that were flagged. Subsequent Stop hooks suppress
|
|
8
|
+
* the warning while:
|
|
9
|
+
* 1. now < deferred_at + deferred_for_hours, AND
|
|
10
|
+
* 2. the current scan's flagged set ⊆ the deferred set
|
|
11
|
+
* (anything new shows up).
|
|
12
|
+
*
|
|
13
|
+
* Pure read/write/check helpers — no side effects beyond filesystem.
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { dirname, join } from "node:path";
|
|
17
|
+
const DEFAULT_DEFER_HOURS = 24;
|
|
18
|
+
export function deferStatePath(repoRoot, kind) {
|
|
19
|
+
return join(repoRoot, ".cairn", `.${kind}-deferred-until`);
|
|
20
|
+
}
|
|
21
|
+
export function readDeferState(repoRoot, kind) {
|
|
22
|
+
const path = deferStatePath(repoRoot, kind);
|
|
23
|
+
if (!existsSync(path))
|
|
24
|
+
return null;
|
|
25
|
+
let parsed;
|
|
26
|
+
try {
|
|
27
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
if (!isDeferState(parsed))
|
|
33
|
+
return null;
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
36
|
+
export function writeDeferState(repoRoot, kind, snapshot) {
|
|
37
|
+
const state = {
|
|
38
|
+
deferred_at: snapshot.nowIso ?? new Date().toISOString(),
|
|
39
|
+
deferred_for_hours: snapshot.hours ?? DEFAULT_DEFER_HOURS,
|
|
40
|
+
flagged_shas: [...(snapshot.flagged_shas ?? [])],
|
|
41
|
+
flagged_task_ids: [...(snapshot.flagged_task_ids ?? [])],
|
|
42
|
+
};
|
|
43
|
+
const path = deferStatePath(repoRoot, kind);
|
|
44
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
45
|
+
writeFileSync(path, JSON.stringify(state, null, 2), "utf8");
|
|
46
|
+
return state;
|
|
47
|
+
}
|
|
48
|
+
export function clearDeferState(repoRoot, kind) {
|
|
49
|
+
rmSync(deferStatePath(repoRoot, kind), { force: true });
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Returns true when `state` is still suppressing — within the time
|
|
53
|
+
* window AND every currently-flagged item is in the deferred snapshot
|
|
54
|
+
* (no new bypasses / new pending reviews have appeared).
|
|
55
|
+
*
|
|
56
|
+
* If anything new appears, the suppression breaks and the Stop hook
|
|
57
|
+
* surfaces the warning again — which is the right behavior, because
|
|
58
|
+
* "defer for 24h" was a promise about the items the operator saw,
|
|
59
|
+
* not a blanket mute.
|
|
60
|
+
*/
|
|
61
|
+
export function isDeferActive(state, now, currentItems) {
|
|
62
|
+
const deferredAt = Date.parse(state.deferred_at);
|
|
63
|
+
if (Number.isNaN(deferredAt))
|
|
64
|
+
return false;
|
|
65
|
+
const expiresAt = deferredAt + state.deferred_for_hours * 60 * 60 * 1000;
|
|
66
|
+
if (now.getTime() >= expiresAt)
|
|
67
|
+
return false;
|
|
68
|
+
const snapshotSet = new Set(currentItems.kind === "shas" ? state.flagged_shas : state.flagged_task_ids);
|
|
69
|
+
for (const item of currentItems.values) {
|
|
70
|
+
if (!snapshotSet.has(item))
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
function isStringArray(v) {
|
|
76
|
+
return Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
77
|
+
}
|
|
78
|
+
function isDeferState(x) {
|
|
79
|
+
if (x === null || typeof x !== "object")
|
|
80
|
+
return false;
|
|
81
|
+
const o = x;
|
|
82
|
+
return (typeof o["deferred_at"] === "string" &&
|
|
83
|
+
typeof o["deferred_for_hours"] === "number" &&
|
|
84
|
+
isStringArray(o["flagged_shas"]) &&
|
|
85
|
+
isStringArray(o["flagged_task_ids"]));
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=defer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defer.js","sourceRoot":"","sources":["../../src/hooks/defer.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Scans the visible body of a Read tool's content for cairn citation
|
|
3
|
-
* patterns: §
|
|
4
|
-
* DEC-
|
|
3
|
+
* patterns: §INV invariants, §DEC decisions, and TODO(TSK-...) linked
|
|
4
|
+
* todos. Both `§INV-NNNN` and `§DEC-NNNN` are the canonical bare-symbol
|
|
5
|
+
* citation forms produced by the strip-replace phase and resolved by
|
|
6
|
+
* the read-enricher into the legend.
|
|
5
7
|
*
|
|
6
8
|
* Per READ_ENRICHER_SPEC §3 — line numbers in the legend should reflect
|
|
7
9
|
* the original source line, so when content is `cat -n`-prefixed
|
|
@@ -9,7 +11,7 @@
|
|
|
9
11
|
* number as the reported line. The body text itself is NEVER mutated.
|
|
10
12
|
*/
|
|
11
13
|
export interface CitationMatch {
|
|
12
|
-
/** Citation id, e.g. "
|
|
14
|
+
/** Citation id, e.g. "INV-0023" or "TSK-auth-refactor" or "DEC-0042". */
|
|
13
15
|
id: string;
|
|
14
16
|
/** 1-indexed line number from cat -n prefix, or iteration index. */
|
|
15
17
|
line: number;
|
|
@@ -17,7 +19,7 @@ export interface CitationMatch {
|
|
|
17
19
|
export interface ScannedCitations {
|
|
18
20
|
invariants: CitationMatch[];
|
|
19
21
|
todos: CitationMatch[];
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
+
/** §DEC-NNNN citations resolved against the decisions ledger. */
|
|
23
|
+
decisions: CitationMatch[];
|
|
22
24
|
}
|
|
23
25
|
export declare function scanCitations(content: string): ScannedCitations;
|
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Scans the visible body of a Read tool's content for cairn citation
|
|
3
|
-
* patterns: §
|
|
4
|
-
* DEC-
|
|
3
|
+
* patterns: §INV invariants, §DEC decisions, and TODO(TSK-...) linked
|
|
4
|
+
* todos. Both `§INV-NNNN` and `§DEC-NNNN` are the canonical bare-symbol
|
|
5
|
+
* citation forms produced by the strip-replace phase and resolved by
|
|
6
|
+
* the read-enricher into the legend.
|
|
5
7
|
*
|
|
6
8
|
* Per READ_ENRICHER_SPEC §3 — line numbers in the legend should reflect
|
|
7
9
|
* the original source line, so when content is `cat -n`-prefixed
|
|
8
10
|
* (`<num>\t<line>`), strip the prefix for matching but use the prefix
|
|
9
11
|
* number as the reported line. The body text itself is NEVER mutated.
|
|
10
12
|
*/
|
|
11
|
-
const INVARIANT_RE = /§
|
|
13
|
+
const INVARIANT_RE = /§INV-(\d+)/g;
|
|
12
14
|
const TODO_RE = /TODO\(TSK-([^)]+)\)/g;
|
|
13
|
-
|
|
15
|
+
// Require `§` prefix so plain `DEC-NNNN` strings (URL fragments, prose
|
|
16
|
+
// citations in markdown bodies, GitHub-style refs) don't false-match.
|
|
17
|
+
// The strip-replace phase ALWAYS emits the `§` prefix on accepted
|
|
18
|
+
// decision tokens (per LENS_SPEC + CLAUDE.md "bare symbols only").
|
|
19
|
+
const DEC_RE = /§DEC-(\d+)/g;
|
|
14
20
|
const CAT_N_PREFIX_RE = /^(\d+)\t/;
|
|
15
21
|
export function scanCitations(content) {
|
|
16
22
|
const invariants = [];
|
|
17
23
|
const todos = [];
|
|
18
|
-
const
|
|
24
|
+
const decisions = [];
|
|
19
25
|
if (content.length === 0) {
|
|
20
|
-
return { invariants, todos,
|
|
26
|
+
return { invariants, todos, decisions };
|
|
21
27
|
}
|
|
22
28
|
const lines = content.split(/\r?\n/);
|
|
23
29
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -39,7 +45,7 @@ export function scanCitations(content) {
|
|
|
39
45
|
const digits = m[1];
|
|
40
46
|
if (digits === undefined)
|
|
41
47
|
continue;
|
|
42
|
-
invariants.push({ id: `
|
|
48
|
+
invariants.push({ id: `INV-${digits}`, line: lineNumber });
|
|
43
49
|
}
|
|
44
50
|
for (const m of lineText.matchAll(TODO_RE)) {
|
|
45
51
|
const tail = m[1];
|
|
@@ -51,9 +57,9 @@ export function scanCitations(content) {
|
|
|
51
57
|
const digits = m[1];
|
|
52
58
|
if (digits === undefined)
|
|
53
59
|
continue;
|
|
54
|
-
|
|
60
|
+
decisions.push({ id: `DEC-${digits}`, line: lineNumber });
|
|
55
61
|
}
|
|
56
62
|
}
|
|
57
|
-
return { invariants, todos,
|
|
63
|
+
return { invariants, todos, decisions };
|
|
58
64
|
}
|
|
59
65
|
//# sourceMappingURL=citation-scanner.js.map
|