@event4u/agent-config 17.0.0 → 17.2.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.
Files changed (164) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +157 -468
  5. package/README.md +4 -3
  6. package/dist/agent-src/commands/project/analyze.md +9 -6
  7. package/dist/agent-src/commands/project/map.md +58 -0
  8. package/dist/agent-src/commands/project.md +5 -3
  9. package/dist/agent-src/contexts/execution/host-capability-manifest.md +5 -3
  10. package/dist/agent-src/skills/code-intelligence/SKILL.md +6 -0
  11. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  12. package/dist/agent-src/templates/roadmaps.md +5 -1
  13. package/dist/cli/registry.js +1 -1
  14. package/dist/cli-delegate/{chunk-IROAQ77A.js → chunk-3PK3VZHB.js} +107 -9
  15. package/dist/cli-delegate/{chunk-2SFOOFJJ.js → chunk-BZJ4Y3CI.js} +1 -1
  16. package/dist/cli-delegate/{chunk-CAL76ADI.js → chunk-CMZEKSNN.js} +52 -10
  17. package/dist/cli-delegate/{chunk-ESUNQ5XR.js → chunk-E3CFULNX.js} +22 -4
  18. package/dist/cli-delegate/{chunk-GOMILC3O.js → chunk-QBHEI5N4.js} +4 -3
  19. package/dist/cli-delegate/{chunk-SIOMDAKL.js → chunk-VYZGJLCA.js} +29 -39
  20. package/dist/cli-delegate/chunk-W5JXMF2T.js +77 -0
  21. package/dist/cli-delegate/{chunk-4SHNERBL.js → chunk-XIAPY44N.js} +1 -1
  22. package/dist/cli-delegate/{chunk-JHMMWNJ2.js → chunk-YHAPONWV.js} +1 -1
  23. package/dist/cli-delegate/cmd_conformance.js +32 -47
  24. package/dist/cli-delegate/cmd_doctor.js +4 -4
  25. package/dist/cli-delegate/cmd_doctor_neighbours.js +2 -2
  26. package/dist/cli-delegate/cmd_explain.js +1 -1
  27. package/dist/cli-delegate/cmd_export.js +6 -6
  28. package/dist/cli-delegate/cmd_fleet.js +3 -3
  29. package/dist/cli-delegate/cmd_preflight.js +3 -3
  30. package/dist/cli-delegate/cmd_prune.js +6 -6
  31. package/dist/cli-delegate/cmd_settings_migrate.js +6 -6
  32. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  33. package/dist/cli-delegate/cmd_sync.js +6 -6
  34. package/dist/cli-delegate/cmd_uninstall.js +6 -6
  35. package/dist/cli-delegate/cmd_update.js +9 -13
  36. package/dist/cli-delegate/cmd_validate.js +6 -6
  37. package/dist/discovery/deprecation-report.md +1 -1
  38. package/dist/discovery/discovery-manifest.json +41 -15
  39. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  40. package/dist/discovery/discovery-manifest.summary.md +3 -3
  41. package/dist/discovery/dormancy-report.md +1 -1
  42. package/dist/discovery/orphan-report.md +1 -1
  43. package/dist/discovery/packs.json +6 -5
  44. package/dist/discovery/trust-report.md +2 -2
  45. package/dist/discovery/workspaces.json +5 -4
  46. package/dist/hooks/dispatch.js +163 -148
  47. package/dist/hooks/dispatch.sha256 +1 -1
  48. package/dist/hooks/instructions_observer.chunk.cjs +301 -0
  49. package/dist/hooks/instructions_observer.chunk.cjs.sha256 +1 -0
  50. package/dist/install/claudeRuleRewrite.js +10 -3
  51. package/dist/install/conflict.js +24 -2
  52. package/dist/install/conflictTracker.js +14 -3
  53. package/dist/install/install.mjs +222 -117
  54. package/dist/install/installThinLayer.js +1 -0
  55. package/dist/install/loggedWrites.js +142 -0
  56. package/dist/install/paths.js +12 -2
  57. package/dist/install/preserve.js +25 -4
  58. package/dist/install/txlog.js +10 -6
  59. package/dist/mcp/registry-manifest.json +3 -3
  60. package/dist/mcp/server.json +2 -2
  61. package/dist/mcp/server.mjs +33595 -22916
  62. package/dist/scripts/_lib/egress_rules.js +4 -1
  63. package/dist/scripts/_lib/identifier_redaction.js +35 -2
  64. package/dist/scripts/ai_council/seat_policy.js +7 -1
  65. package/dist/server/schemas/settings.js +1 -1
  66. package/package.json +4 -1
  67. package/src/config/ci-local-parity.yml +1 -1
  68. package/src/config/continuity-surface.json +7 -0
  69. package/src/config/evaluator-budgets.json +4 -4
  70. package/src/config/gate-coverage.yml +27 -7
  71. package/src/config/gate-violation-baselines.json +9 -3
  72. package/src/config/gitignore-block.txt +6 -0
  73. package/src/config/hook-bundle-budget.json +19 -4
  74. package/src/config/hook-token-budget.json +1 -1
  75. package/src/config/packed-binary-manifest.json +27 -0
  76. package/src/config/production-entries.json +18 -0
  77. package/src/config/release-gate-locality.yml +0 -7
  78. package/src/config/skill-body-budget.json +7 -1
  79. package/src/install/claudeRuleRewrite.ts +10 -1
  80. package/src/install/conflict.ts +24 -1
  81. package/src/install/conflictTracker.ts +18 -2
  82. package/src/install/installThinLayer.ts +6 -0
  83. package/src/install/loggedWrites.ts +148 -0
  84. package/src/install/paths.ts +11 -2
  85. package/src/install/preserve.ts +40 -4
  86. package/src/install/txlog.ts +10 -6
  87. package/src/scripts/_cli/cmd_conformance.ts +3 -2
  88. package/src/scripts/_cli/cmd_update.ts +15 -17
  89. package/src/scripts/_lib/bench_ab_complexity.ts +14 -2
  90. package/src/scripts/_lib/collector_record.ts +1 -0
  91. package/src/scripts/_lib/council_settings_block.ts +9 -2
  92. package/src/scripts/_lib/egress_rules.ts +4 -1
  93. package/src/scripts/_lib/host_capability.ts +10 -5
  94. package/src/scripts/_lib/identifier_redaction.ts +38 -2
  95. package/src/scripts/_lib/price_freshness.ts +112 -0
  96. package/src/scripts/_lib/production_reach.ts +194 -0
  97. package/src/scripts/_lib/quota_parked.ts +17 -28
  98. package/src/scripts/_lib/release_findings_ingest.ts +13 -335
  99. package/src/scripts/_lib/runtime_journal.ts +7 -7
  100. package/src/scripts/_lib/tier_downshift_verdict.ts +81 -35
  101. package/src/scripts/ai_council/chairman.ts +5 -2
  102. package/src/scripts/ai_council/clients.ts +4 -3
  103. package/src/scripts/ai_council/free_hand_shadow.ts +24 -12
  104. package/src/scripts/ai_council/free_route_catalog.ts +142 -5
  105. package/src/scripts/ai_council/free_route_client.ts +4 -0
  106. package/src/scripts/ai_council/hand_payload.ts +115 -7
  107. package/src/scripts/ai_council/orchestrator.ts +7 -10
  108. package/src/scripts/ai_council/route_chooser.ts +1 -1
  109. package/src/scripts/ai_council/route_member.ts +66 -4
  110. package/src/scripts/ai_council/seat_policy.ts +8 -1
  111. package/src/scripts/ai_council/self_hosted_probe.ts +327 -0
  112. package/src/scripts/ai_council/status_surface.ts +2 -0
  113. package/src/scripts/ask_transport.ts +137 -15
  114. package/src/scripts/check_finding_dispositions.ts +25 -71
  115. package/src/scripts/check_release_pr_shape.ts +3 -3
  116. package/src/scripts/check_step_production_reach.ts +257 -0
  117. package/src/scripts/code_graph/build.ts +210 -10
  118. package/src/scripts/code_graph/cli.ts +168 -46
  119. package/src/scripts/code_graph/detect.ts +5 -4
  120. package/src/scripts/code_graph/docs_layer.ts +57 -0
  121. package/src/scripts/code_graph/extract.ts +58 -13
  122. package/src/scripts/code_graph/extract_more.ts +314 -0
  123. package/src/scripts/code_graph/laravel_overlay.ts +454 -0
  124. package/src/scripts/code_graph/map.ts +846 -0
  125. package/src/scripts/code_graph/map_model.ts +977 -0
  126. package/src/scripts/code_graph/map_verify.ts +140 -0
  127. package/src/scripts/code_graph/map_viewer/viewer.css +278 -0
  128. package/src/scripts/code_graph/map_viewer/viewer.js +756 -0
  129. package/src/scripts/code_graph/scip_reader.ts +201 -0
  130. package/src/scripts/code_graph/sqlite_store.ts +8 -1
  131. package/src/scripts/code_graph/types.ts +81 -15
  132. package/src/scripts/code_graph/validate.ts +16 -26
  133. package/src/scripts/code_graph/verbs.ts +7 -0
  134. package/src/scripts/council_cli.ts +10 -8
  135. package/src/scripts/hook_chunks.mjs +7 -0
  136. package/src/scripts/hook_manifest.json +1 -1
  137. package/src/scripts/hook_manifest.yaml +23 -0
  138. package/src/scripts/hooks/dispatch_hook.ts +4 -4
  139. package/src/scripts/hooks/instructions_observer_hook.ts +25 -6
  140. package/src/scripts/hooks/lazy_concern.ts +4 -0
  141. package/src/scripts/hooks/payload_stub.ts +27 -0
  142. package/src/scripts/hooks/spawn_guard_shadow_hook.ts +29 -70
  143. package/src/scripts/hooks/subagent_ledger_hook.ts +80 -0
  144. package/src/scripts/install-hooks.sh +5 -0
  145. package/src/scripts/install.ts +2 -2
  146. package/src/scripts/lint_agents_layout.ts +2 -0
  147. package/src/scripts/lint_decision_classes.ts +34 -0
  148. package/src/scripts/lint_hook_manifest.ts +25 -0
  149. package/src/scripts/lint_roadmap_blockers.ts +52 -3
  150. package/src/scripts/mcp_server/consumer_tool_catalog.json +53 -6
  151. package/src/scripts/mcp_server/graph_tools.ts +73 -1
  152. package/src/scripts/mcp_server/tools.ts +19 -36
  153. package/src/scripts/release.ts +5 -9
  154. package/src/scripts/release_drill.ts +11 -165
  155. package/src/scripts/release_publication.ts +0 -9
  156. package/src/scripts/report_production_reach.ts +35 -0
  157. package/src/scripts/worker_reuse_shadow_report.ts +399 -1
  158. package/src/server/schemas/settings.ts +1 -1
  159. package/src/vendor/grammars/README.md +10 -5
  160. package/src/vendor/grammars/tree-sitter-go.wasm +0 -0
  161. package/src/vendor/grammars/tree-sitter-java.wasm +0 -0
  162. package/src/vendor/grammars/tree-sitter-python.wasm +0 -0
  163. package/dist/cli-delegate/chunk-A4QJ6ENT.js +0 -50
  164. /package/dist/cli-delegate/{chunk-E6XNBVE4.js → chunk-KR5ZIFPO.js} +0 -0
package/README.md CHANGED
@@ -2,11 +2,12 @@
2
2
 
3
3
  # Agent Config — every claim machine-checked, including the artifact counts in these badges
4
4
 
5
- [![Smoke](https://github.com/event4u-app/agent-config/actions/workflows/smoke.yml/badge.svg)](https://github.com/event4u-app/agent-config/actions/workflows/smoke.yml) [![Public install smoke (3 OS × 2 Node)](https://github.com/event4u-app/agent-config/actions/workflows/smoke-public-install.yml/badge.svg)](https://github.com/event4u-app/agent-config/actions/workflows/smoke-public-install.yml) [![npm](https://img.shields.io/npm/v/@event4u/agent-config?style=flat-square&label=npm&color=orange)](https://www.npmjs.com/package/@event4u/agent-config) [![agent-config MCP server](https://glama.ai/mcp/servers/event4u-app/agent-config/badges/score.svg)](https://glama.ai/mcp/servers/event4u-app/agent-config) [![MCP Toplist](https://mcptoplist.com/badge/glama%2Fevent4u-app%2Fagent-config.svg)](https://mcptoplist.com/server/glama%2Fevent4u-app%2Fagent-config) [![downloads](https://img.shields.io/npm/dm/@event4u/agent-config?style=flat-square&label=downloads%2Fmonth&color=orange)](https://www.npmjs.com/package/@event4u/agent-config)
5
+ [![Smoke](https://github.com/event4u-app/agent-config/actions/workflows/smoke.yml/badge.svg)](https://github.com/event4u-app/agent-config/actions/workflows/smoke.yml) [![Public install smoke (3 OS × 2 Node)](https://github.com/event4u-app/agent-config/actions/workflows/smoke-public-install.yml/badge.svg)](https://github.com/event4u-app/agent-config/actions/workflows/smoke-public-install.yml) [![npm](https://img.shields.io/npm/v/@event4u/agent-config?style=flat-square&label=npm&color=orange)](https://www.npmjs.com/package/@event4u/agent-config) [![agent-config MCP server](https://glama.ai/mcp/servers/event4u-app/agent-config/badges/score.svg)](https://glama.ai/mcp/servers/event4u-app/agent-config) [![MCP Toplist](https://mcptoplist.com/badge/glama%2Fevent4u-app%2Fagent-config.svg)](https://mcptoplist.com/server/glama%2Fevent4u-app%2Fagent-config)
6
6
 
7
- [![Skills](https://img.shields.io/badge/Skills-301-orange?style=flat-square)](dist/agent-src/skills/) [![Rules](https://img.shields.io/badge/Rules-122-orange?style=flat-square)](dist/agent-src/rules/) [![Commands](https://img.shields.io/badge/Commands-209-orange?style=flat-square)](dist/agent-src/commands/) [![Guidelines](https://img.shields.io/badge/Guidelines-121-orange?style=flat-square)](docs/guidelines/) [![Personas](https://img.shields.io/badge/Personas-29-orange?style=flat-square)](dist/agent-src/personas/) [![Advisors](https://img.shields.io/badge/Advisors-5-orange?style=flat-square)](dist/agent-src/personas/advisors/) <!-- claim:published-artifact-counts -->
7
+ [![Skills](https://img.shields.io/badge/Skills-301-orange?style=flat-square)](dist/agent-src/skills/) [![Rules](https://img.shields.io/badge/Rules-122-orange?style=flat-square)](dist/agent-src/rules/) [![Commands](https://img.shields.io/badge/Commands-210-orange?style=flat-square)](dist/agent-src/commands/) [![Guidelines](https://img.shields.io/badge/Guidelines-121-orange?style=flat-square)](docs/guidelines/) [![Personas](https://img.shields.io/badge/Personas-29-orange?style=flat-square)](dist/agent-src/personas/) [![Advisors](https://img.shields.io/badge/Advisors-5-orange?style=flat-square)](dist/agent-src/personas/advisors/) <!-- claim:published-artifact-counts -->
8
+ [![downloads](https://img.shields.io/npm/dm/@event4u/agent-config?style=flat-square&label=downloads%2Fmonth&color=orange)](https://www.npmjs.com/package/@event4u/agent-config)
8
9
 
9
- <sub>**How these are counted** — one canonical counter, `agent-config` → `update_counts --check`, re-derives all six from the tree and fails CI when one is off by one. Two bases are not what the linked directory shows, so they are stated here rather than left to be inferred: **Commands 209** counts every command file **recursively** (the linked directory holds 62 at its top level) and **Rules 122** counts the **source** rules while the linked projection holds 121 — one rule is dormant and is not projected. **Personas 29** excludes the directory README. Counts are of files and directories: none of them measures quality, activation or adoption. <!-- claim:published-artifact-counts --></sub>
10
+ <sub>**How these are counted** — one canonical counter, `agent-config` → `update_counts --check`, re-derives all six from the tree and fails CI when one is off by one. Two bases are not what the linked directory shows, so they are stated here rather than left to be inferred: **Commands 210** counts every command file **recursively** (the linked directory holds 62 at its top level) and **Rules 122** counts the **source** rules while the linked projection holds 121 — one rule is dormant and is not projected. **Personas 29** excludes the directory README. Counts are of files and directories: none of them measures quality, activation or adoption. <!-- claim:published-artifact-counts --></sub>
10
11
 
11
12
  **Try it in 30 seconds** — drop one read-only subagent into any repo and watch it check "done": `@production-validator check this branch is actually done`. No wizard, no lock-in, nothing else installed — the [30-second wedge ↓](#quickstart) is the whole first step. Start at the proof, not the catalog: **[event4u-app.github.io/agent-config/proof/](https://event4u-app.github.io/agent-config/proof/)**.
12
13
 
@@ -31,7 +31,9 @@ Gather in parallel:
31
31
  - Check for: `artisan`, `Makefile`, `Taskfile.yml`, `docker-compose.yml`
32
32
  - Check for: `phpstan.neon`, `ecs.php`, `rector.php`, `config-dev/`
33
33
  - Check for: `.github/workflows/`, `CODEOWNERS`, `.editorconfig`
34
- - Check for module roots (resolve via `scripts/_lib/agent_settings.ts::enumerate_modules()`; Laravel shape: `app/Modules/`), multi-tenant indicators (`customer_database`)
34
+ - Check for module roots (resolve via `scripts/_lib/agent_settings.ts::enumerate_modules()`; Laravel shape: `app/Modules/`)
35
+ - Probe the database connections the project declares (`config/database.php` `connections`, `DB_CONNECTION*` keys in `.env.example`, `$connection` properties on models); more than one connection used by models is a multi-database / multi-tenant indicator — name the connections as found, never assume their names
36
+ - Build or refresh the code map: `agent-config code-graph map`. Structure (areas, hubs, cycles, request paths) is read from `agents/map/MAP.md` below, not re-derived by hand <!-- ref-ignore -->
35
37
  - Read: `AGENTS.md`, `.github/copilot-instructions.md` (if exist)
36
38
 
37
39
  **Assurance dimensions — do NOT hand-detect these.** Run
@@ -137,6 +139,7 @@ full pass, so "continue" is dominant and asking is a routine confirmation.
137
139
  Analyze the directory structure:
138
140
 
139
141
  - Map top-level directories with file counts
142
+ - Read `agents/map/MAP.md` first: its areas, infrastructure files, cycles and request paths ARE the structure section — link to it rather than restating it <!-- ref-ignore -->
140
143
  - Identify pattern: MVC, modules, services, repositories, DTOs
141
144
  - Count: Models, Controllers, Services, Jobs, Commands, Events, Policies
142
145
  - Detect: multi-tenancy, API versioning, queue usage
@@ -150,13 +153,14 @@ ARCHITECTURE:
150
153
 
151
154
  Pattern: {Modular MVC + Service Layer + Repository}
152
155
  API: {Versioned REST (/api/v1/, /api/v2/)}
153
- Multi-Tenant: {✅ Dual-DB (api_database + customer_database) | ❌}
156
+ Databases: {connections the models use, as probed — e.g. "2 (mysql, tenant)" | 1}
157
+ Map: agents/map/MAP.md ({n} areas · largest {name})
154
158
 
155
159
  ───────────────────────────────────────────────
156
160
  CODE INVENTORY:
157
161
  ───────────────────────────────────────────────
158
162
 
159
- Models: {count} (api_database: {n}, customer_database: {n})
163
+ Models: {count} (per connection, when more than one is in use)
160
164
  Controllers: {count}
161
165
  Services: {count}
162
166
  Repositories: {count}
@@ -288,9 +292,8 @@ Write all remaining analysis files that haven't been created yet:
288
292
  - `agents/evidence/analysis/architecture/database.md` — schema, connections, multi-tenancy
289
293
  - `agents/evidence/analysis/architecture/api.md` — versioning, routes, middleware, auth
290
294
  - `agents/evidence/analysis/architecture/infrastructure.md` — Docker, CI/CD, deployment
291
- - `agents/evidence/analysis/architecture/patterns.md` — design patterns used
292
- - `agents/evidence/analysis/models/api-database.md` — all api_database models
293
- - `agents/evidence/analysis/models/customer-database.md` — all customer_database models
295
+ - `agents/evidence/analysis/architecture/patterns.md` — design patterns used; for module and area structure, link `agents/map/MAP.md` instead of restating it <!-- ref-ignore -->
296
+ - `agents/evidence/analysis/models/<connection>.md` — one per database connection the probe found, all models on it
294
297
  - `agents/evidence/analysis/modules/{module}.md` — one per module
295
298
  - `agents/evidence/analysis/testing/test-map.md` — test suites, coverage, strategy
296
299
 
@@ -0,0 +1,58 @@
1
+ ---
2
+ model_tier: lite
3
+ name: project-map
4
+ pack: engineering-base
5
+ visibility: internal
6
+ sub: map
7
+ cluster: project
8
+ skills: [code-intelligence]
9
+ description: Draw the code map into agents/map/
10
+ suggestion:
11
+ eligible: false
12
+ rationale: "Cluster sub-command — reached via its cluster head's routing or its explicit /cluster:sub name; not independently suggested (surface-consolidation)."
13
+ workspaces:
14
+ - agent-config-maintainer
15
+ packs:
16
+ - meta
17
+ ---
18
+
19
+ # project-map
20
+
21
+ ## Instructions
22
+
23
+ Build the map of the project the user is standing in, then point at it. The map is
24
+ deterministic and local: no model call, no network, nothing committed by default
25
+ (`agents/map/` is gitignored, ADR-297). <!-- ref-ignore -->
26
+
27
+ ### 1. Build
28
+
29
+ ```bash
30
+ agent-config code-graph map
31
+ ```
32
+
33
+ It builds or updates the code graph first when it is absent or behind, then writes
34
+ `agents/map/index.html`, `agents/map/MAP.md`, one `agents/map/areas/<id>.md` per <!-- ref-ignore -->
35
+ area of five or more files, and `agents/map/map.json`. With Playwright present it <!-- ref-ignore -->
36
+ also renders the page at three widths and reports the render check; without it the
37
+ check reads `unverified`, which is not a pass.
38
+
39
+ `--root <path>` maps another checkout; `--open` opens the page; `--no-verify`
40
+ skips the render check.
41
+
42
+ ### 2. Report
43
+
44
+ Quote the command's summary lines (files, areas, largest area, graph state, render
45
+ check) and give the path to `agents/map/index.html`. Do not paraphrase the counts. <!-- ref-ignore -->
46
+
47
+ ### 3. Answer from the map, when the user asked a question
48
+
49
+ Read `agents/map/MAP.md` first, then the page of the area in question. Say which <!-- ref-ignore -->
50
+ answers came from the map and which from a follow-up `code-graph query` or a file
51
+ read — the map groups files by coupling, it does not explain what they do.
52
+
53
+ ## Rules
54
+
55
+ - **Never edit `agents/map/` by hand.** Every file there is regenerated. <!-- ref-ignore -->
56
+ - **A `behind:N` or `edited` graph state is part of the answer.** Say it.
57
+ - **"Isolated" is not "dead".** A file reached by reflection, a string path or a
58
+ framework convention the engine does not read is isolated too.
@@ -2,10 +2,10 @@
2
2
  model_tier: medium
3
3
  name: project
4
4
  disable-model-invocation: true
5
- argument-hint: "[analyze|health]"
5
+ argument-hint: "[analyze|health|map]"
6
6
  pack: engineering-base
7
- intent: "Project dispatcher — full analysis or quick health check"
8
- routes_to: [project-analyze, project-health]
7
+ intent: "Project dispatcher — full analysis, quick health check, or the project map"
8
+ routes_to: [project-analyze, project-health, project-map]
9
9
  replaces: []
10
10
  visibility: internal
11
11
  description: Project orchestrator — routes to analyze (full audit) and health (read-only status check)
@@ -35,6 +35,7 @@ Top-level orchestrator for the `/project` family — project-wide inspection.
35
35
  |---|---|---|
36
36
  | `/project analyze` | `commands/project/analyze.md` | Full project analysis — detect stack, inventory modules, audit docs, create missing contexts |
37
37
  | `/project health` | `commands/project/health.md` | Quick health check — status of docs, modules, contexts, roadmaps; creates nothing |
38
+ | `/project map` | `commands/project/map.md` | Draw the code map into `agents/map/` — areas, files, links; offline, gitignored |
38
39
 
39
40
  Sub-command names match the locked contract in
40
41
  [`docs/contracts/command-clusters.md`](../docs/contracts/command-clusters.md).
@@ -50,6 +51,7 @@ Sub-command names match the locked contract in
50
51
 
51
52
  > 1. analyze — full project audit (writes missing contexts)
52
53
  > 2. health — read-only status check (creates nothing)
54
+ > 3. map — draw the code map into `agents/map/`
53
55
 
54
56
  ## Rules
55
57
 
@@ -37,7 +37,9 @@ host, never a configuration decision (always-on orchestration, Phase 1):
37
37
  2. A live environment probe merged on top: `agent_teams` resolves `true`
38
38
  when `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS` is set in the process
39
39
  environment — the one host fact this repo can observe about Claude
40
- Code's experimental multi-instance Agent Teams feature.
40
+ Code's experimental multi-instance Agent Teams feature. On `claude` the
41
+ same flag also sets `worker_resume`: the by-name re-task was observed
42
+ there only under it (cold-start 1.4, 2.1.306; council 2026-10-22).
41
43
  3. `SAFE_DEFAULT` (all `false`) for an unrecognized host or an unset probe.
42
44
 
43
45
  `resolveHostCapabilities(hostId, override?)` still accepts an optional
@@ -92,7 +94,7 @@ SAFE DEGRADATION, NOT BECAUSE ANYTHING WAS MEASURED.
92
94
 
93
95
  The seven booleans look alike and are not. `HOST_CAPABILITY_REGISTRY` holds
94
96
  exactly one row today, so on every other host **all six** fields are the safe
95
- default — and `agent_teams` is the only field any live check touches. The name
97
+ default — and `agent_teams` (plus `worker_resume` on `claude`) is the only field any live check touches. The name
96
98
  `probeHostCapabilities` says "detected" about values that were mostly asserted
97
99
  at authoring time.
98
100
 
@@ -136,7 +138,7 @@ A prober that decides for itself what counts as evidence is the inference
136
138
  | `status_polling` | a **completed poll returning a state the parent did not already hold**. A poll that returns what the parent knew is not an observation of polling |
137
139
  | `separate_quota_pool` | a **child leg continuing across a parent quota condition** — the parent constrained, the child proceeding |
138
140
  | `worker_respawn` | **one task continuing across a killed and re-spawned worker, same task id.** Never inferred from the fact that spawning and killing both exist separately |
139
- | `worker_resume` | **a finished leg re-tasked by name whose next `SubagentStart` carries the same ledger `ref`, and whose first call in that run reads more than half its input from cache.** Never inferred from the host documenting a resume or message tool |
141
+ | `worker_resume` | **a finished leg re-tasked by name whose next `SubagentStart` carries the same ledger `ref`, and whose first call in that run reads more than half its input from cache.** Never inferred from the host documenting a resume or message tool. On `claude` the agent-teams flag stands in for this row: the by-name re-task was observed only under it (2.1.306), so the live probe sets the field from the flag (council 2026-10-22) |
140
142
  | `fork_shares_cache` | **a fork whose first call reads more than half its input from cache and whose tool list equals the parent's.** Never inferred from documentation |
141
143
  | `structured_ask` | **the host's delivered tool surface, in a real session, carrying (or not carrying) a question-picker tool.** The delivered surface is the observation — never the vendor's documentation, and never another host by analogy. A `false` written from an observed surface is "checked, absent"; omitting the field is "never looked" |
142
144
  | `reads_project_mcp_config` | **a server named only in the project-scope config appearing in that host's own tool list.** Never inferred from the host documenting MCP support, which does not say which scope it reads |
@@ -38,6 +38,10 @@ code-graph and no appetite to build one — plain `grep`/read is right there.
38
38
 
39
39
  ## Procedure
40
40
 
41
+ 0. **Orient first, when the question is "where does X live" rather than "who
42
+ calls X".** If `agents/map/MAP.md` exists, read it: it names the project's <!-- ref-ignore -->
43
+ areas (files grouped by coupling), its infrastructure files and its cycles.
44
+ No map yet? `agent-config code-graph map` builds one in seconds. Then query.
41
45
  1. **Detect the source.** `agent-config code-graph detect` — is a fresh consumer
42
46
  `graph.json` or native cache present? (A consumer-shipped fresh index wins;
43
47
  the native engine covers stale-or-absent — ADR-124 § 2.)
@@ -246,6 +250,8 @@ paragraph claiming "the same five answers" directly after a list of four:
246
250
  | `graph_query` | `query` |
247
251
  | `graph_path` | `path` |
248
252
  | `graph_node` | no CLI verb — one symbol's location, in-edges, out-edges and degree |
253
+ | `graph_map_status` | whether `agents/map/` exists, its counts and freshness | <!-- ref-ignore -->
254
+ | `graph_area` | one area of the map: files, hubs, what it depends on |
249
255
 
250
256
  `untested` has no tool of its own; it is a flag on `graph_impact`, because the
251
257
  two answer the same question over the same diff and a separate tool would have
@@ -39,7 +39,7 @@ schema_version: 1
39
39
  # CI guard: a release bump of `package.json` must update this value
40
40
  # in lockstep — see scripts/check_template_pin_drift.ts (road-to-
41
41
  # portable-runtime-and-update-check P3.3).
42
- agent_config_version: "17.0.0"
42
+ agent_config_version: "17.2.0"
43
43
 
44
44
  # --- Project identity ---
45
45
  project:
@@ -731,7 +731,11 @@ that was never a judgement call.
731
731
  (ADR-268 § 10). `resolved by` is one of `evidence`, `agent`,
732
732
  `independent:<session or model>`, `council:<record>`, `team:<record>`,
733
733
  `owner` — the prefixed forms carry their record, because a resolver
734
- with no record is a claim rather than a resolution.
734
+ with no record is a claim rather than a resolution. The three locked
735
+ classes (`product-owned`, `business-owned`, `destructive-owned`) take
736
+ `owner` only; a council answer on one is recorded with `owner` and a
737
+ decision cell starting `proposed`. A resolved human-only blocker needs
738
+ a dated `owner` row naming it.
735
739
 
736
740
  `revisit if` names the condition that reopens the row. Execution reads
737
741
  the table **before any step**, and a closed decision is re-asked only
@@ -77,7 +77,7 @@ export const REGISTRY = [
77
77
  { name: 'affected', disposition: 'delegate', synopsis: 'Artefacts related to <artefact> via the discovery relation-graph (BFS).' },
78
78
  { name: 'graph-explain', disposition: 'delegate', synopsis: 'Seed on a <concept>, expand 2 hops over the discovery relation-graph with a node budget.' },
79
79
  { name: 'benchmark', disposition: 'delegate', synopsis: 'Report context-token reduction vs the full always-loaded projection.' },
80
- { name: 'code-graph', disposition: 'delegate', synopsis: 'Deterministic code-graph engine: build|detect|refresh|validate|query|affected|path|explain, plus the gate verbs impact|tests-for|untested|dead.' },
80
+ { name: 'code-graph', disposition: 'delegate', synopsis: 'Deterministic code-graph engine: build|detect|refresh|validate|query|affected|path|explain|map, plus the gate verbs impact|tests-for|untested|dead.' },
81
81
  { name: 'hooks:install', disposition: 'delegate', synopsis: 'Install the combined pre-commit hook.' },
82
82
  { name: 'hooks:status', disposition: 'delegate', synopsis: 'Print the runtime hook matrix (per-platform install + bindings).' },
83
83
  { name: 'hooks:doctor', disposition: 'delegate', synopsis: 'Diagnose hook health: concerns, posture, missing trampolines (read-only).' },
@@ -1,4 +1,7 @@
1
1
  import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
2
+ import {
3
+ getLogPath
4
+ } from "./chunk-W5JXMF2T.js";
2
5
  import {
3
6
  classifyOwnership
4
7
  } from "./chunk-J2SJZICO.js";
@@ -7,14 +10,21 @@ import {
7
10
  var SIDECAR_SUFFIX = ".agent-config.new";
8
11
  var EXIT_COMPLETED_WITH_CONFLICTS = 3;
9
12
  function isLedgerMigration(inputs) {
10
- if (!inputs.exists || inputs.force) return false;
13
+ if (!inputs.exists || inputs.force || inputs.loggedWrite === true) return false;
11
14
  if (inputs.ledgerListed !== true) return false;
12
15
  if (inputs.onDiskSha256 === null) return false;
13
16
  return classifyOwnership(inputs.recordedSha256, inputs.onDiskSha256) === "unknown";
14
17
  }
18
+ function logProofApplies(inputs) {
19
+ if (!inputs.exists || inputs.force || inputs.onDiskSha256 === null) return false;
20
+ const ownership = classifyOwnership(inputs.recordedSha256, inputs.onDiskSha256);
21
+ if (ownership === "recorded-modified") return inputs.recordedIsStaged === true;
22
+ return ownership === "unknown" && isLedgerMigration({ ...inputs, loggedWrite: false });
23
+ }
15
24
  function decideDeployWrite(inputs) {
16
25
  if (!inputs.exists) return "write";
17
26
  if (inputs.force) return "write";
27
+ if (inputs.loggedWrite === true && logProofApplies(inputs)) return "write";
18
28
  const ownership = classifyOwnership(inputs.recordedSha256, inputs.onDiskSha256);
19
29
  if (ownership === "recorded-modified") return "preserve";
20
30
  if (ownership === "unknown" && isLedgerMigration(inputs)) {
@@ -39,13 +49,97 @@ function foreignSidecarMessage(target, sidecar) {
39
49
  return `Refusing to replace ${sidecar}: it already exists with different content and this install cannot show it wrote it. Package content for ${target} was NOT staged. Move or delete that file and re-run, or rerun with --force to replace the managed file instead.`;
40
50
  }
41
51
 
52
+ // src/install/loggedWrites.ts
53
+ import { gunzipSync } from "node:zlib";
54
+ import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
55
+ import { basename, dirname, join, relative, resolve, sep } from "node:path";
56
+ var WRITE_MARKER = '"kind":"write"';
57
+ var fileCache = /* @__PURE__ */ new Map();
58
+ var dirRealpaths = /* @__PURE__ */ new Map();
59
+ function canonicalPath(p) {
60
+ const abs = resolve(p);
61
+ const dir = dirname(abs);
62
+ let real = dirRealpaths.get(dir);
63
+ if (real === void 0) {
64
+ try {
65
+ real = realpathSync(dir);
66
+ } catch {
67
+ real = dir;
68
+ }
69
+ dirRealpaths.set(dir, real);
70
+ }
71
+ return join(real, basename(abs));
72
+ }
73
+ function logFiles(logPath) {
74
+ const dir = dirname(logPath);
75
+ if (!existsSync(dir)) return [];
76
+ const stem = basename(logPath).replace(/\.jsonl$/, "");
77
+ try {
78
+ return readdirSync(dir).filter((n) => n.startsWith(stem) && (n.endsWith(".jsonl") || n.endsWith(".jsonl.gz"))).map((n) => join(dir, n)).sort();
79
+ } catch {
80
+ return [];
81
+ }
82
+ }
83
+ function writesIn(file) {
84
+ let stamp;
85
+ try {
86
+ const st = statSync(file);
87
+ stamp = `${st.size}:${Math.trunc(st.mtimeMs)}`;
88
+ } catch {
89
+ return [];
90
+ }
91
+ const cached = fileCache.get(file);
92
+ if (cached !== void 0 && cached.stamp === stamp) return cached.entries;
93
+ let text = "";
94
+ try {
95
+ const raw = readFileSync(file);
96
+ text = (file.endsWith(".gz") ? gunzipSync(raw) : raw).toString("utf8");
97
+ } catch {
98
+ text = "";
99
+ }
100
+ const entries = [];
101
+ for (const line of text.split("\n")) {
102
+ if (!line.includes(WRITE_MARKER)) continue;
103
+ let entry;
104
+ try {
105
+ entry = JSON.parse(line);
106
+ } catch {
107
+ continue;
108
+ }
109
+ if (typeof entry.path !== "string" || typeof entry.sha256 !== "string" || entry.sha256 === "") continue;
110
+ entries.push([canonicalPath(entry.path), entry.sha256]);
111
+ }
112
+ fileCache.set(file, { stamp, entries });
113
+ return entries;
114
+ }
115
+ function loggedWriteDigests(logPath = getLogPath()) {
116
+ const writes = /* @__PURE__ */ new Map();
117
+ for (const file of logFiles(logPath)) {
118
+ for (const [target, sha] of writesIn(file)) {
119
+ let set = writes.get(target);
120
+ if (set === void 0) writes.set(target, set = /* @__PURE__ */ new Set());
121
+ set.add(sha);
122
+ }
123
+ }
124
+ return writes;
125
+ }
126
+ function onlyDeployed(anchor, currentFiles, log) {
127
+ return (target) => {
128
+ const rel = relative(resolve(anchor), resolve(target)).split(sep).join("/");
129
+ if (currentFiles.has(rel)) log(target);
130
+ };
131
+ }
132
+ function wroteTheseBytes(target, onDiskSha256, writes) {
133
+ return writes.get(canonicalPath(target))?.has(onDiskSha256) === true;
134
+ }
135
+
42
136
  // src/install/plan.ts
43
137
  import { createHash } from "node:crypto";
44
- import { readFileSync, readdirSync, statSync } from "node:fs";
45
- import { join, relative, resolve } from "node:path";
138
+ import { readFileSync as readFileSync2, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
139
+ import { join as join2, relative as relative2, resolve as resolve2 } from "node:path";
46
140
  function sha256File(path) {
47
141
  try {
48
- const data = readFileSync(path);
142
+ const data = readFileSync2(path);
49
143
  return createHash("sha256").update(data).digest("hex");
50
144
  } catch {
51
145
  return null;
@@ -66,15 +160,15 @@ function walkSourceTree(dir) {
66
160
  function walkInto(dir, out) {
67
161
  let entries;
68
162
  try {
69
- entries = readdirSync(dir).sort();
163
+ entries = readdirSync2(dir).sort();
70
164
  } catch {
71
165
  return;
72
166
  }
73
167
  for (const name of entries) {
74
- const full = join(dir, name);
168
+ const full = join2(dir, name);
75
169
  let isDir;
76
170
  try {
77
- isDir = statSync(full).isDirectory();
171
+ isDir = statSync2(full).isDirectory();
78
172
  } catch {
79
173
  continue;
80
174
  }
@@ -94,8 +188,8 @@ function buildInstallPlan(inputs) {
94
188
  files = files.filter(source.fileFilter);
95
189
  }
96
190
  for (const srcFile of files) {
97
- const rel = relative(source.srcDir, srcFile);
98
- const destPath = resolve(source.destDir, rel);
191
+ const rel = relative2(source.srcDir, srcFile);
192
+ const destPath = resolve2(source.destDir, rel);
99
193
  bucket.push(
100
194
  fileEntry(srcFile, source.kind, {
101
195
  hashContent: source.kind !== "bridge"
@@ -126,12 +220,16 @@ function buildInstallPlan(inputs) {
126
220
  export {
127
221
  EXIT_COMPLETED_WITH_CONFLICTS,
128
222
  isLedgerMigration,
223
+ logProofApplies,
129
224
  decideDeployWrite,
130
225
  sidecarPathFor,
131
226
  preservedFileMessage,
132
227
  migrationPreservedMessage,
133
228
  conflictSummaryMessage,
134
229
  foreignSidecarMessage,
230
+ loggedWriteDigests,
231
+ onlyDeployed,
232
+ wroteTheseBytes,
135
233
  sha256File,
136
234
  buildInstallPlan
137
235
  };
@@ -12,7 +12,7 @@ import {
12
12
  EGRESS_RULES,
13
13
  evaluateMatch,
14
14
  shannonEntropy
15
- } from "./chunk-E6XNBVE4.js";
15
+ } from "./chunk-KR5ZIFPO.js";
16
16
  import {
17
17
  SRC_SKILLS
18
18
  } from "./chunk-YCU3TIXH.js";
@@ -42,7 +42,7 @@ import {
42
42
  readUserPin,
43
43
  residualReport,
44
44
  runtimeRootFor
45
- } from "./chunk-GOMILC3O.js";
45
+ } from "./chunk-QBHEI5N4.js";
46
46
  import {
47
47
  LEGACY_ALL,
48
48
  excludedRuleBasenames,
@@ -89,7 +89,7 @@ import {
89
89
  readCanaryStatusLine,
90
90
  runCensus,
91
91
  summaryLine
92
- } from "./chunk-2SFOOFJJ.js";
92
+ } from "./chunk-BZJ4Y3CI.js";
93
93
  import {
94
94
  EGRESS_RULES,
95
95
  REVIEW_CREDENTIAL_ROWS,
@@ -101,7 +101,7 @@ import {
101
101
  replaceSpans,
102
102
  resolveOverlaps,
103
103
  vendorPrefix
104
- } from "./chunk-E6XNBVE4.js";
104
+ } from "./chunk-KR5ZIFPO.js";
105
105
  import {
106
106
  classifyFileState,
107
107
  classifyOwnership,
@@ -468,7 +468,7 @@ function censusRuleDir(dir) {
468
468
  }
469
469
 
470
470
  // src/scripts/cache_realization_report.ts
471
- import { execFileSync } from "node:child_process";
471
+ import { execFileSync as execFileSync2 } from "node:child_process";
472
472
  import * as fs7 from "node:fs";
473
473
  import { homedir as homedir2 } from "node:os";
474
474
  import * as path12 from "node:path";
@@ -809,6 +809,7 @@ if (process.argv[1] !== void 0) {
809
809
  }
810
810
 
811
811
  // src/scripts/hooks/subagent_ledger_hook.ts
812
+ import { execFileSync } from "node:child_process";
812
813
  import * as crypto from "node:crypto";
813
814
  import * as fs5 from "node:fs";
814
815
  import * as path10 from "node:path";
@@ -1069,6 +1070,41 @@ function appendLedgerLine(root, ts, line) {
1069
1070
  fs5.appendFileSync(file, `${JSON.stringify(line)}
1070
1071
  `, "utf8");
1071
1072
  }
1073
+ var INDEX_SUBDIR = "index";
1074
+ function writeIndexEntry(root, ts, key, value) {
1075
+ if (is_replay_mode()) return;
1076
+ const dir = path10.join(root, LEDGER_DIR, INDEX_SUBDIR, ts.slice(0, 7));
1077
+ fs5.mkdirSync(dir, { recursive: true });
1078
+ const tmp = path10.join(dir, `.${key}.${process.pid}.tmp`);
1079
+ try {
1080
+ fs5.writeFileSync(tmp, value, "utf8");
1081
+ fs5.renameSync(tmp, path10.join(dir, key));
1082
+ } finally {
1083
+ fs5.rmSync(tmp, { force: true });
1084
+ }
1085
+ }
1086
+ function headOf(dir) {
1087
+ try {
1088
+ const env = Object.fromEntries(Object.entries(process.env).filter(([k]) => !k.startsWith("GIT_")));
1089
+ return execFileSync("git", ["-C", dir, "rev-parse", "HEAD"], { encoding: "utf8", timeout: 2e3, stdio: ["ignore", "pipe", "ignore"], env }).trim() || null;
1090
+ } catch {
1091
+ return null;
1092
+ }
1093
+ }
1094
+ function stateStamp(cwd) {
1095
+ let epoch = null;
1096
+ try {
1097
+ epoch = fs5.statSync(fileURLToPath(import.meta.url)).mtimeMs;
1098
+ } catch {
1099
+ }
1100
+ return typeof cwd === "string" && cwd ? { head: headOf(cwd), cwd_key: refFor(cwd), epoch } : { head: null, cwd_key: null, epoch };
1101
+ }
1102
+ function stopIndexKey(sessionId, ref) {
1103
+ return refFor(`subagent-stop\0${sessionId}\0${ref}`);
1104
+ }
1105
+ function recordStopIndex(root, ts, sessionId, ref) {
1106
+ writeIndexEntry(root, ts, stopIndexKey(sessionId, ref), "");
1107
+ }
1072
1108
  var WALL_CLOCK_ARMS_MS = [
1073
1109
  { label: "5m", ms: 5 * 60 * 1e3 },
1074
1110
  { label: "15m", ms: 15 * 60 * 1e3 },
@@ -1236,8 +1272,13 @@ function handleStop(root, payload, nowIso, sessionId) {
1236
1272
  // Beside the count, never instead of it: the count says how wrong the
1237
1273
  // candidate was, this says whether it was aimed at the envelope.
1238
1274
  envelope_field_hits: parse3.field_hits,
1239
- concurrent_open: countOpen(root)
1275
+ concurrent_open: countOpen(root),
1276
+ ...stateStamp(payload["cwd"])
1240
1277
  });
1278
+ try {
1279
+ if (sessionId !== null) recordStopIndex(root, nowIso, sessionId, ref);
1280
+ } catch {
1281
+ }
1241
1282
  }
1242
1283
  function processEnvelope(envelope, consumerRoot) {
1243
1284
  try {
@@ -1827,7 +1868,7 @@ function encodeProjectPath(cwd) {
1827
1868
  }
1828
1869
  function listGitWorktrees(cwd) {
1829
1870
  try {
1830
- const out = execFileSync("git", ["worktree", "list", "--porcelain"], { cwd, encoding: "utf-8", timeout: 1e4 });
1871
+ const out = execFileSync2("git", ["worktree", "list", "--porcelain"], { cwd, encoding: "utf-8", timeout: 1e4 });
1831
1872
  const paths = [];
1832
1873
  for (const line of out.split("\n")) {
1833
1874
  if (line.startsWith("worktree ")) paths.push(line.slice("worktree ".length).trim());
@@ -1900,7 +1941,7 @@ function computeWorktreeFragmentation(projectsRoot, worktreePaths, maxAgeDays) {
1900
1941
  }
1901
1942
  function hostVersion() {
1902
1943
  try {
1903
- const out = execFileSync("claude", ["--version"], { encoding: "utf-8", timeout: 5e3 }).trim();
1944
+ const out = execFileSync2("claude", ["--version"], { encoding: "utf-8", timeout: 5e3 }).trim();
1904
1945
  return out.length > 0 ? out : "unknown";
1905
1946
  } catch {
1906
1947
  return "unknown";
@@ -8060,9 +8101,10 @@ var CliClient = class extends ExternalAIClient {
8060
8101
  _cli_calls_path;
8061
8102
  /** Attribution label for every booking this client makes. */
8062
8103
  _consumer;
8063
- /** The session the last answered call ran in; `resumeNext` continues it once, non-blind only. */
8104
+ /** The session the last answered call ran in; `resumeNext` continues it once, never while `blind`. */
8064
8105
  lastSession = null;
8065
8106
  resumeNext = null;
8107
+ blind = false;
8066
8108
  static _AUTH_FAILURE_PATTERNS = [
8067
8109
  "authentication",
8068
8110
  "unauthorized",
@@ -8435,7 +8477,7 @@ var AnthropicCliClient = class extends CliClient {
8435
8477
  void max_tokens;
8436
8478
  return [
8437
8479
  this.binary,
8438
- ...seatResumeArgs(this.name, this.resumeNext, { blind: false, now_ms: Date.now() }),
8480
+ ...seatResumeArgs(this.name, this.resumeNext, { blind: this.blind, now_ms: Date.now() }),
8439
8481
  "--print",
8440
8482
  // The seat never needs the user environment; only a version the light
8441
8483
  // launch was probed on gets the flag (`LIGHT_LAUNCH_PROBES`).
@@ -8538,7 +8580,7 @@ var OpenAICliClient = class extends CliClient {
8538
8580
  void user_prompt;
8539
8581
  void max_tokens;
8540
8582
  const modelArgs = this.model === OPENAI_CLI_VENDOR_DEFAULT ? [] : ["--model", this.model];
8541
- const resume = seatResumeArgs(this.name, this.resumeNext, { blind: false, now_ms: Date.now() });
8583
+ const resume = seatResumeArgs(this.name, this.resumeNext, { blind: this.blind, now_ms: Date.now() });
8542
8584
  const bound = resume.length === 0 ? boundFor("openai") : resumeBound(boundFor("openai"));
8543
8585
  return [this.binary, "exec", ...resume, "--json", "--skip-git-repo-check", ...bound, ...modelArgs, "-"];
8544
8586
  }
@@ -2,11 +2,15 @@ import { createRequire as __acCreateRequire } from 'node:module'; const require
2
2
  import {
3
3
  buildInstallPlan,
4
4
  isLedgerMigration,
5
- sha256File
6
- } from "./chunk-IROAQ77A.js";
5
+ logProofApplies,
6
+ loggedWriteDigests,
7
+ sha256File,
8
+ sidecarPathFor,
9
+ wroteTheseBytes
10
+ } from "./chunk-3PK3VZHB.js";
7
11
  import {
8
12
  detectToolPresence
9
- } from "./chunk-A4QJ6ENT.js";
13
+ } from "./chunk-W5JXMF2T.js";
10
14
  import {
11
15
  classifyOwnership,
12
16
  ledgerListedPaths,
@@ -23,10 +27,11 @@ import { existsSync } from "node:fs";
23
27
  function isJsonTarget(entry) {
24
28
  return entry.kind === "deployed" && entry.path.endsWith(".json");
25
29
  }
26
- function computeConflicts(plan, recordedOverride) {
30
+ function computeConflicts(plan, recordedOverride, loggedOverride) {
27
31
  if (plan.policy.force) return [];
28
32
  const recorded = recordedOverride ?? recordedHashesWithLedger(plan.root);
29
33
  let ledgerFiles = null;
34
+ let logged;
30
35
  const ledgerLists = (p) => {
31
36
  ledgerFiles ??= ledgerListedPaths();
32
37
  return ledgerFiles.has(p);
@@ -40,6 +45,19 @@ function computeConflicts(plan, recordedOverride) {
40
45
  const onDisk = sha256File(entry.path);
41
46
  const ownership = classifyOwnership(recorded.get(entry.path), onDisk);
42
47
  if (ownership === "recorded-unchanged") continue;
48
+ const recordedSha = recorded.get(entry.path);
49
+ const unproven = {
50
+ exists: true,
51
+ recordedSha256: recordedSha,
52
+ onDiskSha256: onDisk,
53
+ force: false,
54
+ ledgerListed: ledgerLists(entry.path),
55
+ recordedIsStaged: typeof recordedSha === "string" && recordedSha === recorded.get(sidecarPathFor(entry.path))
56
+ };
57
+ if (logProofApplies(unproven)) {
58
+ logged ??= loggedOverride ?? loggedWriteDigests();
59
+ if (wroteTheseBytes(entry.path, onDisk, logged)) continue;
60
+ }
43
61
  if (ownership === "unknown" && plan.policy.knownPaths.has(entry.path)) continue;
44
62
  const migration = ownership === "unknown" && isLedgerMigration({
45
63
  exists: true,