@event4u/agent-config 16.0.0 → 16.1.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 (246) 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 +297 -137
  5. package/CONTRIBUTING.md +9 -0
  6. package/README.md +2 -2
  7. package/dist/agent-src/commands/analyze/inbox.md +26 -0
  8. package/dist/agent-src/commands/analyze/roadmap-repos.md +27 -0
  9. package/dist/agent-src/commands/challenge-me/closure.md +178 -0
  10. package/dist/agent-src/commands/challenge-me.md +9 -2
  11. package/dist/agent-src/commands/council/default.md +25 -5
  12. package/dist/agent-src/commands/feature/plan.md +28 -1
  13. package/dist/agent-src/commands/feature/roadmap.md +28 -1
  14. package/dist/agent-src/commands/implement-ticket.md +27 -0
  15. package/dist/agent-src/commands/jira-ticket.md +27 -0
  16. package/dist/agent-src/commands/pr/create.md +29 -0
  17. package/dist/agent-src/commands/pr/merge.md +60 -9
  18. package/dist/agent-src/commands/roadmap/ai-council.md +7 -1
  19. package/dist/agent-src/commands/roadmap/create.md +74 -27
  20. package/dist/agent-src/commands/roadmap/materialize.md +27 -0
  21. package/dist/agent-src/commands/roadmap/process-full.md +123 -18
  22. package/dist/agent-src/commands/team.md +22 -0
  23. package/dist/agent-src/contexts/authority/scope-growth-ownership.md +80 -0
  24. package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +47 -0
  25. package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +44 -3
  26. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +15 -15
  27. package/dist/agent-src/contexts/execution/decision-closure-execution.md +93 -0
  28. package/dist/agent-src/contexts/execution/fix-loop-ladder.md +108 -0
  29. package/dist/agent-src/contexts/execution/plan-confidence-gate.md +4 -2
  30. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +75 -6
  31. package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
  32. package/dist/agent-src/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
  33. package/dist/agent-src/guidelines/design-fidelity-routing.md +29 -0
  34. package/dist/agent-src/rules/autonomous-execution.md +15 -8
  35. package/dist/agent-src/rules/delegation-policy.md +23 -45
  36. package/dist/agent-src/rules/design-fidelity.md +32 -37
  37. package/dist/agent-src/rules/evaluator-independence.md +43 -49
  38. package/dist/agent-src/rules/test-first.md +41 -0
  39. package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
  40. package/dist/agent-src/skills/ai-council/SKILL.md +11 -3
  41. package/dist/agent-src/skills/api-design/data/manifest.json +3 -3
  42. package/dist/agent-src/skills/database/data/manifest.json +4 -4
  43. package/dist/agent-src/skills/database/data/query-tuning.csv +3 -3
  44. package/dist/agent-src/skills/design-review/SKILL.md +50 -0
  45. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +14 -4
  46. package/dist/agent-src/skills/experiment-loop/SKILL.md +31 -1
  47. package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
  48. package/dist/agent-src/skills/storybook-workshop/SKILL.md +42 -3
  49. package/dist/agent-src/skills/threat-modeling/SKILL.md +9 -4
  50. package/dist/agent-src/skills/threat-modeling/data/manifest.json +4 -4
  51. package/dist/agent-src/skills/verify-repair-loop/SKILL.md +37 -3
  52. package/dist/agent-src/templates/agent-settings.md +1 -1
  53. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  54. package/dist/agent-src/templates/roadmaps.md +188 -0
  55. package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
  56. package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
  57. package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
  58. package/dist/cli-delegate/check_update_banner.js +40 -2
  59. package/dist/cli-delegate/chunk-4E2W63DA.js +335 -0
  60. package/dist/cli-delegate/chunk-5R7HOLAF.js +99 -0
  61. package/dist/cli-delegate/{chunk-IVBPRCQO.js → chunk-6KFEC3G7.js} +1 -1
  62. package/dist/cli-delegate/{chunk-2TAQ2K4V.js → chunk-6KM6ORRY.js} +55 -386
  63. package/dist/cli-delegate/{chunk-2XY7ILBT.js → chunk-FC75PGJE.js} +42 -2
  64. package/dist/cli-delegate/{chunk-GYC3VRYN.js → chunk-FXYUYU4N.js} +57 -13
  65. package/dist/cli-delegate/{chunk-55ACLDUE.js → chunk-GJCBB2UG.js} +168 -10
  66. package/dist/cli-delegate/{chunk-AK6HODNQ.js → chunk-HDVVGPQW.js} +873 -539
  67. package/dist/cli-delegate/{chunk-H32DGUJH.js → chunk-P6ZHP5W2.js} +1 -1
  68. package/dist/cli-delegate/{chunk-57PN4GYJ.js → chunk-RGNIWTEA.js} +1 -1
  69. package/dist/cli-delegate/chunk-ROYYTEZU.js +39 -0
  70. package/dist/cli-delegate/{chunk-3FKR64R5.js → chunk-UNCRJBT3.js} +31 -7
  71. package/dist/cli-delegate/{chunk-2DK2VJWG.js → chunk-WGXTVKCQ.js} +20 -6
  72. package/dist/cli-delegate/chunk-YDPILCOL.js +152 -0
  73. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  74. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  75. package/dist/cli-delegate/cmd_conformance.js +90 -56
  76. package/dist/cli-delegate/cmd_doctor.js +7 -5
  77. package/dist/cli-delegate/cmd_explain.js +2 -2
  78. package/dist/cli-delegate/cmd_export.js +9 -7
  79. package/dist/cli-delegate/cmd_fleet.js +4 -2
  80. package/dist/cli-delegate/cmd_handoff.js +4 -4
  81. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  82. package/dist/cli-delegate/cmd_migrate.js +2 -2
  83. package/dist/cli-delegate/cmd_packs_active.js +2 -2
  84. package/dist/cli-delegate/cmd_preflight.js +4 -2
  85. package/dist/cli-delegate/cmd_prune.js +10 -8
  86. package/dist/cli-delegate/cmd_route_audit.js +2 -2
  87. package/dist/cli-delegate/cmd_route_explain.js +1 -1
  88. package/dist/cli-delegate/cmd_session_recycle.js +2 -2
  89. package/dist/cli-delegate/cmd_settings_get.js +28 -2
  90. package/dist/cli-delegate/cmd_settings_migrate.js +9 -7
  91. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  92. package/dist/cli-delegate/cmd_sync.js +10 -8
  93. package/dist/cli-delegate/cmd_uninstall.js +16 -13
  94. package/dist/cli-delegate/cmd_update.js +1 -1
  95. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  96. package/dist/cli-delegate/cmd_validate.js +12 -10
  97. package/dist/cli-delegate/cmd_versions.js +1 -1
  98. package/dist/discovery/deprecation-report.md +1 -1
  99. package/dist/discovery/discovery-manifest.json +85 -38
  100. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  101. package/dist/discovery/discovery-manifest.summary.md +4 -4
  102. package/dist/discovery/dormancy-report.md +1 -1
  103. package/dist/discovery/orphan-report.md +1 -1
  104. package/dist/discovery/packs.json +10 -8
  105. package/dist/discovery/trust-report.md +3 -3
  106. package/dist/discovery/workspaces.json +8 -6
  107. package/dist/hooks/dispatch.js +1832 -1007
  108. package/dist/install/claudeRuleRewrite.js +14 -3
  109. package/dist/install/conflict.js +60 -10
  110. package/dist/install/conflictTracker.js +218 -0
  111. package/dist/install/fsPrimitives.js +54 -0
  112. package/dist/install/install.mjs +2007 -1481
  113. package/dist/install/manifestFiles.js +259 -0
  114. package/dist/install/preflight.js +14 -2
  115. package/dist/install/preserve.js +157 -0
  116. package/dist/install/recordedOwnership.js +132 -0
  117. package/dist/install/txlog.js +11 -2
  118. package/dist/mcp/registry-manifest.json +3 -3
  119. package/dist/mcp/server.json +2 -2
  120. package/dist/mcp/server.mjs +398 -39
  121. package/dist/router.json +1 -1
  122. package/dist/scripts/_lib/agent_settings.js +3 -2
  123. package/dist/scripts/_lib/fs_atomic.js +167 -0
  124. package/dist/scripts/_lib/installed_tools.js +460 -0
  125. package/dist/scripts/_lib/lean_projection_mode.js +3 -2
  126. package/dist/scripts/_lib/settings_renamed_keys.js +78 -0
  127. package/dist/server/routes/install.js +8 -0
  128. package/dist/server/routes/wizard.js +9 -3
  129. package/dist/server/schemas/settings.js +17 -5
  130. package/dist/shared/missionExecution.js +97 -0
  131. package/dist/ui/assets/{index-D0jaCRPM.js → index-DH1xc62v.js} +17 -17
  132. package/dist/ui/index.html +1 -1
  133. package/docs/MIGRATION.md +103 -0
  134. package/docs/contracts/settings-classes.md +26 -7
  135. package/docs/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
  136. package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
  137. package/docs/guidelines/design-fidelity-routing.md +29 -0
  138. package/package.json +1 -1
  139. package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
  140. package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
  141. package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
  142. package/src/config/agent-settings.template.yml +139 -27
  143. package/src/config/auto-rule-budget.json +16 -0
  144. package/src/config/ci-local-parity.yml +18 -5
  145. package/src/config/continuity-surface.json +21 -0
  146. package/src/config/gate-coverage.yml +146 -13
  147. package/src/config/gate-violation-baselines.json +6 -6
  148. package/src/config/hook-token-budget.json +2 -0
  149. package/src/config/loop-surfaces.yaml +215 -0
  150. package/src/config/platform-anchor.json +3 -2
  151. package/src/config/release-gate-locality.yml +11 -0
  152. package/src/config/routing-coverage-seed.json +18 -4
  153. package/src/config/rule-enforcement-baseline.json +0 -1
  154. package/src/install/claudeRuleRewrite.ts +16 -2
  155. package/src/install/conflict.ts +65 -9
  156. package/src/install/conflictTracker.ts +299 -0
  157. package/src/install/fsPrimitives.ts +54 -0
  158. package/src/install/manifestFiles.ts +312 -0
  159. package/src/install/preflight.ts +16 -2
  160. package/src/install/preserve.ts +205 -0
  161. package/src/install/recordedOwnership.ts +149 -0
  162. package/src/install/txlog.ts +11 -2
  163. package/src/install/types.ts +19 -4
  164. package/src/scripts/_cli/cmd_conformance.ts +88 -15
  165. package/src/scripts/_cli/cmd_doctor.ts +4 -3
  166. package/src/scripts/_cli/cmd_route_explain.ts +9 -2
  167. package/src/scripts/_cli/cmd_settings_get.ts +44 -0
  168. package/src/scripts/_cli/doctor_execution.ts +90 -0
  169. package/src/scripts/_lib/activation_payload.ts +247 -0
  170. package/src/scripts/_lib/agent_settings.ts +4 -2
  171. package/src/scripts/_lib/authority_path.ts +102 -0
  172. package/src/scripts/_lib/cascade_base.ts +94 -0
  173. package/src/scripts/_lib/changelog_eras.ts +7 -2
  174. package/src/scripts/_lib/continuation_ladder.ts +149 -2
  175. package/src/scripts/_lib/council_transport.ts +129 -0
  176. package/src/scripts/_lib/delivery_ready.ts +112 -0
  177. package/src/scripts/_lib/forge_protection.ts +267 -0
  178. package/src/scripts/_lib/hook_platform_keys.ts +27 -0
  179. package/src/scripts/_lib/json_python_parity.ts +127 -0
  180. package/src/scripts/_lib/lean_projection_mode.ts +14 -3
  181. package/src/scripts/_lib/loop_surfaces.ts +382 -0
  182. package/src/scripts/_lib/mission_record.ts +190 -0
  183. package/src/scripts/_lib/obligation_frequency.ts +217 -0
  184. package/src/scripts/_lib/obligations.ts +402 -0
  185. package/src/scripts/_lib/orchestration_record.ts +11 -20
  186. package/src/scripts/_lib/release_highlights.ts +199 -10
  187. package/src/scripts/_lib/release_holds.ts +493 -0
  188. package/src/scripts/_lib/release_migration_gate.ts +184 -0
  189. package/src/scripts/_lib/rides_along.ts +102 -0
  190. package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
  191. package/src/scripts/_lib/test_delta.ts +166 -0
  192. package/src/scripts/_lib/test_provenance.ts +171 -0
  193. package/src/scripts/_lib/typed_op_grant.ts +246 -0
  194. package/src/scripts/_lib/typed_op_watch.ts +185 -0
  195. package/src/scripts/ai_council/config.ts +8 -11
  196. package/src/scripts/ai_council/fallback_config.ts +5 -3
  197. package/src/scripts/ai_council/ownership_classes.ts +118 -0
  198. package/src/scripts/ask_block_census.ts +285 -15
  199. package/src/scripts/check_enforcement_matrix.ts +624 -0
  200. package/src/scripts/check_gate_reachability.ts +173 -10
  201. package/src/scripts/check_generator_sync.ts +954 -0
  202. package/src/scripts/check_preamble_payload_budget.ts +75 -2
  203. package/src/scripts/check_release_highlights.ts +33 -1
  204. package/src/scripts/check_release_holds.ts +567 -0
  205. package/src/scripts/check_rule_projection_integrity.ts +22 -12
  206. package/src/scripts/check_test_delta.ts +294 -0
  207. package/src/scripts/check_test_weakening.ts +299 -0
  208. package/src/scripts/closure_scan.ts +333 -0
  209. package/src/scripts/compile_hook_manifest.ts +43 -3
  210. package/src/scripts/council_record_shape.ts +209 -0
  211. package/src/scripts/hook_manifest.json +1 -1
  212. package/src/scripts/hook_manifest.yaml +88 -9
  213. package/src/scripts/hooks/concern_registry.ts +4 -0
  214. package/src/scripts/hooks/design_pass_hook.ts +128 -3
  215. package/src/scripts/hooks/evidence_independence.ts +96 -0
  216. package/src/scripts/hooks/memory_recall_hook.ts +540 -0
  217. package/src/scripts/hooks/obligation_settle_hook.ts +182 -0
  218. package/src/scripts/hooks/rule_inject_hook.ts +49 -1
  219. package/src/scripts/hooks/run_continuation_hook.ts +17 -20
  220. package/src/scripts/hooks_doctor.ts +85 -0
  221. package/src/scripts/hooks_status.ts +19 -0
  222. package/src/scripts/install.ts +106 -206
  223. package/src/scripts/janitor.ts +7 -0
  224. package/src/scripts/lint_decision_classes.ts +464 -0
  225. package/src/scripts/lint_hook_manifest.ts +11 -0
  226. package/src/scripts/lint_major_migration_sections.ts +450 -0
  227. package/src/scripts/lint_plan_risk_register.ts +47 -3
  228. package/src/scripts/lint_roadmap_blockers.ts +61 -0
  229. package/src/scripts/lint_roadmap_producers.ts +202 -0
  230. package/src/scripts/measure_rule_budget.ts +101 -5
  231. package/src/scripts/new_roadmap.ts +37 -0
  232. package/src/scripts/orchestration_record.ts +0 -5
  233. package/src/scripts/release.ts +14 -98
  234. package/src/scripts/release_drill.ts +267 -1
  235. package/src/scripts/release_publication.ts +51 -0
  236. package/src/scripts/report_enforcement_drift.ts +566 -0
  237. package/src/scripts/report_host_injection_effect.ts +3 -11
  238. package/src/scripts/report_standing_payload_by_host.ts +26 -152
  239. package/src/scripts/schemas/agent-settings.schema.json +23 -0
  240. package/src/scripts/schemas/command.schema.json +4 -0
  241. package/src/scripts/scope_growth.ts +219 -0
  242. package/src/scripts/trigger_eval_grandfather.json +0 -1
  243. package/src/scripts/ui_conformance_probe.ts +622 -0
  244. package/src/server/schemas/settings.ts +31 -7
  245. package/src/shared/missionExecution.ts +126 -0
  246. package/dist/cli-delegate/chunk-523HCFYO.js +0 -90
@@ -20,13 +20,17 @@
20
20
  * Usage:
21
21
  * tsx src/scripts/install.ts # defaults: rule_loading_tier=balanced
22
22
  * tsx src/scripts/install.ts --profile=minimal # set rule_loading_tier=minimal (kernel only)
23
- * tsx src/scripts/install.ts --force # accepted (no-op): installs always overwrite
23
+ * tsx src/scripts/install.ts --force # replace managed files the user has edited
24
24
  * tsx src/scripts/install.ts --skip-bridges # only create .agent-settings.yml
25
25
  * tsx src/scripts/install.ts --project <dir> # override project root
26
26
  *
27
- * Idempotent — safe to run multiple times. A run always refreshes every
28
- * deployed file with the current package content; user configuration
29
- * (.agent-settings.yml) is merged by the settings layer, never clobbered.
27
+ * Idempotent — safe to run multiple times. A run refreshes every deployed file
28
+ * with the current package content, EXCEPT a managed file whose bytes diverge
29
+ * from the digest the install manifest recorded: that one is preserved, package
30
+ * content is staged beside it as `<path>.agent-config.new`, and the run exits 3
31
+ * rather than 0 so the staleness is stated, not silent; `--force` replaces it
32
+ * instead. Contract + state machine: `install/preserve.ts`, `conflictTracker.ts`.
33
+ * User config (.agent-settings.yml) is settings-layer merged, never clobbered.
30
34
  *
31
35
  * --- Parity notes (ADR-200) ---
32
36
  *
@@ -72,11 +76,16 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
72
76
  import type * as YamlModule from 'yaml';
73
77
 
74
78
  import { build_merge_entries } from './_lib/json_pointers.js';
79
+ import { jsonDumpsCompact, jsonDumpsIndent } from './_lib/json_python_parity.js';
75
80
  import { is_claude_builtin_name } from './_lib/claude_builtin_names.js';
76
81
  import * as installed_lock from './_lib/installed_lock.js';
77
82
  import * as mcp_bridge from './_lib/mcp_bridge.js';
78
83
  import * as mcp_consent from './_lib/mcp_consent_residual.js';
79
84
  import * as scoped_projection from './_lib/scoped_projection.js';
85
+ import { createConflictTracker } from '../install/conflictTracker.js';
86
+ import { mkdirp, pathExists, resolvePath, sha256OfFile } from '../install/fsPrimitives.js';
87
+ import { filesByToolFromBridges as _files_by_tool_from_bridges, filesByToolFromDeploy as _files_by_tool_from_deploy, hydrateRecordedInventories, preservedIndex, readRecordedByTool, reconcileToolFiles, type ManifestFileEntry } from '../install/manifestFiles.js';
88
+ import { EXIT_COMPLETED_WITH_CONFLICTS } from '../install/preserve.js';
80
89
  import * as surface_tiers from './_lib/surface_tiers.js';
81
90
  import * as global_deploy_inventory from './_lib/global_deploy_inventory.js';
82
91
  import * as rule_layer_overlap from './_lib/rule_layer_overlap.js';
@@ -113,6 +122,8 @@ import {
113
122
  } from './_lib/model_tier.js';
114
123
  import { main as cmdMigrateMain } from './_cli/cmd_migrate.js';
115
124
  import { SCOPE_DETECT_AI_DIRS, SCOPE_DETECT_MANIFESTS } from '../install/detect.js';
125
+ import { getLogPath } from '../install/paths.js';
126
+ import { appendTxLog } from '../install/txlog.js';
116
127
 
117
128
  // ---------------------------------------------------------------------------
118
129
  // Python-runtime parity helpers
@@ -143,15 +154,6 @@ function expanduser(p: string): string {
143
154
  return p;
144
155
  }
145
156
 
146
- /** `Path.resolve()` — absolute, symlink-resolved where possible. */
147
- function resolvePath(p: string): string {
148
- try {
149
- return fs.realpathSync(path.resolve(p));
150
- } catch {
151
- return path.resolve(p);
152
- }
153
- }
154
-
155
157
  function isFile(p: string): boolean {
156
158
  try {
157
159
  return fs.statSync(p).isFile();
@@ -168,15 +170,6 @@ function isDir(p: string): boolean {
168
170
  }
169
171
  }
170
172
 
171
- function pathExists(p: string): boolean {
172
- try {
173
- fs.statSync(p);
174
- return true;
175
- } catch {
176
- return false;
177
- }
178
- }
179
-
180
173
  function isSymlink(p: string): boolean {
181
174
  try {
182
175
  return fs.lstatSync(p).isSymbolicLink();
@@ -193,11 +186,6 @@ function writeText(p: string, content: string): void {
193
186
  fs.writeFileSync(p, content, 'utf-8');
194
187
  }
195
188
 
196
- /** `path.mkdir(parents=True, exist_ok=True)`. */
197
- function mkdirp(p: string): void {
198
- fs.mkdirSync(p, { recursive: true });
199
- }
200
-
201
189
  /**
202
190
  * `sorted(Path.glob(pattern))` with PyYAML/pathlib component-wise ordering.
203
191
  * We only need a non-recursive `*.glob("<pat>")`; pathlib sorts the matched
@@ -238,17 +226,6 @@ function countZips(directory: string): number {
238
226
  return n;
239
227
  }
240
228
 
241
- /** `hashlib.sha256(data).hexdigest()` of a file's bytes, or null when unreadable. */
242
- function sha256OfFile(p: string): string | null {
243
- let data: Buffer;
244
- try {
245
- data = fs.readFileSync(p);
246
- } catch {
247
- return null;
248
- }
249
- return crypto.createHash('sha256').update(data).digest('hex');
250
- }
251
-
252
229
  /**
253
230
  * Atomic write mirroring the Python `tempfile.mkstemp(...) → os.fdopen → write
254
231
  * → os.chmod(0o644) → os.replace` pattern in `_write_consumer_bridge_marker`
@@ -299,108 +276,6 @@ function utcStamp(now?: Date): string {
299
276
  );
300
277
  }
301
278
 
302
- // --- JSON byte-parity (ensure_ascii=False; insertion order) ---
303
-
304
- function _jsonStrNoAscii(s: string): string {
305
- // json.dumps(ensure_ascii=False): escape control chars + " + \, keep >=0x20
306
- let out = '"';
307
- for (const ch of s) {
308
- const code = ch.codePointAt(0) as number;
309
- switch (ch) {
310
- case '"':
311
- out += '\\"';
312
- break;
313
- case '\\':
314
- out += '\\\\';
315
- break;
316
- case '\n':
317
- out += '\\n';
318
- break;
319
- case '\r':
320
- out += '\\r';
321
- break;
322
- case '\t':
323
- out += '\\t';
324
- break;
325
- case '\b':
326
- out += '\\b';
327
- break;
328
- case '\f':
329
- out += '\\f';
330
- break;
331
- default:
332
- if (code < 0x20) {
333
- out += '\\u' + code.toString(16).padStart(4, '0');
334
- } else {
335
- out += ch;
336
- }
337
- }
338
- }
339
- return out + '"';
340
- }
341
-
342
- function _jsonScalar(value: unknown): string | null {
343
- if (value === null || value === undefined) return 'null';
344
- if (typeof value === 'boolean') return value ? 'true' : 'false';
345
- if (typeof value === 'number') {
346
- if (!Number.isFinite(value)) {
347
- if (Number.isNaN(value)) return 'NaN';
348
- return value > 0 ? 'Infinity' : '-Infinity';
349
- }
350
- // Our payloads carry only integers; render as-is.
351
- return String(value);
352
- }
353
- if (typeof value === 'string') return _jsonStrNoAscii(value);
354
- return null;
355
- }
356
-
357
- function _dumpIndent(value: unknown, indent: number, depth: number): string {
358
- const scalar = _jsonScalar(value);
359
- if (scalar !== null) return scalar;
360
- const pad = ' '.repeat(indent * (depth + 1));
361
- const closePad = ' '.repeat(indent * depth);
362
- if (Array.isArray(value)) {
363
- if (value.length === 0) return '[]';
364
- const items = value.map((v) => pad + _dumpIndent(v, indent, depth + 1));
365
- return `[\n${items.join(',\n')}\n${closePad}]`;
366
- }
367
- if (typeof value === 'object' && value !== null) {
368
- const obj = value as Record<string, unknown>;
369
- const keys = Object.keys(obj);
370
- if (keys.length === 0) return '{}';
371
- const items = keys.map(
372
- (k) => `${pad}${_jsonStrNoAscii(k)}: ${_dumpIndent(obj[k], indent, depth + 1)}`,
373
- );
374
- return `{\n${items.join(',\n')}\n${closePad}}`;
375
- }
376
- return _jsonStrNoAscii(String(value));
377
- }
378
-
379
- /** `json.dumps(data, indent=N, ensure_ascii=False)` (sort_keys=False). */
380
- function jsonDumpsIndent(value: unknown, indent: number): string {
381
- return _dumpIndent(value, indent, 0);
382
- }
383
-
384
- /** `json.dumps(obj, separators=(",", ":"))` — compact, ensure_ascii=False here. */
385
- function jsonDumpsCompact(value: unknown): string {
386
- const scalar = _jsonScalar(value);
387
- if (scalar !== null) return scalar;
388
- if (Array.isArray(value)) {
389
- return '[' + value.map((v) => jsonDumpsCompact(v)).join(',') + ']';
390
- }
391
- if (typeof value === 'object' && value !== null) {
392
- const obj = value as Record<string, unknown>;
393
- return (
394
- '{' +
395
- Object.keys(obj)
396
- .map((k) => `${_jsonStrNoAscii(k)}:${jsonDumpsCompact(obj[k])}`)
397
- .join(',') +
398
- '}'
399
- );
400
- }
401
- return _jsonStrNoAscii(String(value));
402
- }
403
-
404
279
  /** Lazy YAML safe_load mirroring PyYAML (version 1.1), `{}` on every error. */
405
280
  function yamlSafeLoad(text: string): unknown {
406
281
  let YAML: typeof YamlModule;
@@ -482,6 +357,11 @@ function _emit_progress_terminal(rc: number): void {
482
357
  if (!state.PROGRESS_NDJSON) return;
483
358
  if (rc === 0) {
484
359
  _emit_progress({ type: 'done' });
360
+ } else if (rc === EXIT_COMPLETED_WITH_CONFLICTS) {
361
+ // A conflict is a completion, not a failure, so the count rides on
362
+ // `done`, not an `error` frame. Zero conflicts never reach here, so that
363
+ // frame stays byte-identical to `{"type":"done"}`.
364
+ _emit_progress({ type: 'done', conflicts: tracker.conflictState.preserved.length });
485
365
  } else {
486
366
  _emit_progress({ type: 'error', code: 'E_INSTALL', exitCode: rc });
487
367
  }
@@ -553,10 +433,21 @@ function _is_interactive(): boolean {
553
433
  }
554
434
  }
555
435
 
556
- function _resolve_file_conflict(_target: string, _force_hint: boolean): string {
557
- // del force_hint / del target — deploys always overwrite our own content.
558
- return 'write';
559
- }
436
+ /**
437
+ * Per-run conflict tracking; the state machine, the sidecar staging and the
438
+ * exit-code folding live in `src/install/conflictTracker.ts`, and only the
439
+ * wiring is here. Module scope, like `state` above, because
440
+ * `_copy_dir_dereferencing_symlinks` is reached through six `_deploy_*` frames
441
+ * carrying no project root — `begin()` scopes the tracker to a run instead.
442
+ */
443
+ const tracker = createConflictTracker({
444
+ warn,
445
+ fail,
446
+ logWrite: (target) => _log_tx_entry('write', target),
447
+ emitProgress: (frame) => _emit_progress(frame),
448
+ tagDeployed: (target, source, package_root) =>
449
+ _inject_package_tag(target, source, package_root),
450
+ });
560
451
 
561
452
  // --- File utilities ---
562
453
 
@@ -2444,75 +2335,36 @@ void prompt_collision_choice;
2444
2335
 
2445
2336
  // --- Manifest / inventory helpers (lazy-import twins are eager static here) ---
2446
2337
 
2447
- function _sha256_of_file(p: string): string | null {
2448
- return sha256OfFile(p);
2449
- }
2450
-
2451
- function _file_entry(p: string, kind: string, hash_content: boolean): Record<string, unknown> {
2452
- return {
2453
- path: p,
2454
- kind,
2455
- sha256: hash_content ? _sha256_of_file(p) : null,
2456
- };
2457
- }
2458
-
2459
2338
  type DeployResult = scoped_projection.DeployTuple;
2460
2339
 
2461
- function _files_by_tool_from_deploy(
2462
- deploy_results: Record<string, DeployResult>,
2463
- ): Record<string, Record<string, unknown>[]> {
2464
- const out: Record<string, Record<string, unknown>[]> = {};
2465
- for (const tool_id of Object.keys(deploy_results)) {
2466
- const [, , status, paths] = deploy_results[tool_id] as DeployResult;
2467
- if (status === 'deployed') {
2468
- out[tool_id] = paths.map((p) => _file_entry(p, 'deployed', true));
2469
- } else if (status === 'marker') {
2470
- out[tool_id] = paths.map((p) => _file_entry(p, 'marker', true));
2471
- } else {
2472
- out[tool_id] = [];
2473
- }
2474
- }
2475
- return out;
2476
- }
2477
-
2478
- function _files_by_tool_from_bridges(
2479
- tools: Set<string>,
2480
- project_root: string,
2481
- scope: string,
2482
- ): Record<string, Record<string, unknown>[]> {
2483
- const out: Record<string, Record<string, unknown>[]> = {};
2484
- for (const tool_id of [...tools].sort()) {
2485
- const marker = _bridge_marker(tool_id, scope);
2486
- if (!marker) continue;
2487
- let marker_path = marker;
2488
- if (!path.isAbsolute(marker_path)) {
2489
- marker_path = path.join(project_root, marker_path);
2490
- }
2491
- out[tool_id] = [_file_entry(marker_path, 'bridge', false)];
2492
- }
2493
- return out;
2494
- }
2495
-
2496
2340
  function _update_installed_tools_manifest(
2497
2341
  project_root: string,
2498
2342
  tools: Set<string>,
2499
2343
  scope: string,
2500
2344
  force: boolean,
2501
- files_by_tool: Record<string, Record<string, unknown>[]> | null = null,
2345
+ files_by_tool: Record<string, ManifestFileEntry[] | null> | null = null,
2502
2346
  merged_keys_by_tool: Record<string, Record<string, unknown>[]> | null = null,
2503
2347
  ): number {
2504
2348
  const target = installed_tools.manifest_path(project_root);
2505
2349
  const existing = (installed_tools.read_manifest(target) ?? {}) as Record<string, unknown>;
2506
- let entries = Array.isArray(existing['tools'])
2507
- ? ([...(existing['tools'] as unknown[])] as Record<string, unknown>[])
2508
- : [];
2350
+ // Hydrated because `read_manifest` drops nested `files[]` by design: without
2351
+ // this, rewriting the manifest erases every recorded digest it did not just
2352
+ // rebuild, and an unrecorded managed file is overwritten on the next run.
2353
+ const prior = readRecordedByTool(target, project_root);
2354
+ const preserved = preservedIndex(tracker.conflictState.preserved, tracker.conflictState.staged);
2355
+ let entries = hydrateRecordedInventories(
2356
+ Array.isArray(existing['tools'])
2357
+ ? ([...(existing['tools'] as unknown[])] as Record<string, unknown>[])
2358
+ : [],
2359
+ prior,
2360
+ );
2509
2361
 
2510
2362
  const version = installed_lock.current_package_version();
2511
2363
 
2512
2364
  for (const tool_id of [...tools].sort()) {
2513
2365
  const marker = _bridge_marker(tool_id, scope);
2514
2366
  if (!marker) continue;
2515
- const files = files_by_tool ? (files_by_tool[tool_id] ?? null) : null;
2367
+ const files = reconcileToolFiles(files_by_tool?.[tool_id], prior.get(tool_id), preserved);
2516
2368
  const merged_keys = merged_keys_by_tool ? (merged_keys_by_tool[tool_id] ?? null) : null;
2517
2369
  try {
2518
2370
  entries = installed_tools.upsert_tool(entries, {
@@ -2890,6 +2742,25 @@ function _escapes_package_root(resolved: string, package_root: string | null): b
2890
2742
  return !global_deploy_inventory.is_ancestor(root, resolved);
2891
2743
  }
2892
2744
 
2745
+ /**
2746
+ * Append one `write`/`skip` entry to the transaction log, through the same
2747
+ * `appendTxLog` module the recovery-dismiss route calls — one writer, two
2748
+ * callers, identical entry shape. Failure is swallowed: the log is a recovery
2749
+ * aid, never a condition for the install itself to proceed.
2750
+ */
2751
+ function _log_tx_entry(kind: 'write' | 'skip', target: string): void {
2752
+ try {
2753
+ appendTxLog(getLogPath(), {
2754
+ ts: new Date().toISOString(),
2755
+ kind,
2756
+ path: target,
2757
+ sha256: kind === 'write' ? sha256OfFile(target) : null,
2758
+ });
2759
+ } catch {
2760
+ /* swallow — the log is a recovery aid, not a precondition */
2761
+ }
2762
+ }
2763
+
2893
2764
  /**
2894
2765
  * Copy `src` → `dest`, dereferencing symlinks, returning
2895
2766
  * `[written, skipped, written_paths]`.
@@ -2925,10 +2796,15 @@ function _copy_dir_dereferencing_symlinks(
2925
2796
  return [0, 0, written_paths];
2926
2797
  }
2927
2798
  mkdirp(path.dirname(dest));
2928
- const decision = _resolve_file_conflict(dest, force);
2929
- if (decision === 'skip') return [0, 1, written_paths];
2799
+ const decision = tracker.resolve(dest, force);
2800
+ if (decision === 'preserve') {
2801
+ tracker.preserve(dest, resolved_src, package_root);
2802
+ _log_tx_entry('skip', dest);
2803
+ return [0, 1, written_paths];
2804
+ }
2930
2805
  fs.copyFileSync(src, dest); // follow_symlinks=True is fs.copyFileSync default
2931
2806
  _inject_package_tag(dest, src, package_root);
2807
+ _log_tx_entry('write', dest);
2932
2808
  written_paths.push(dest);
2933
2809
  return [1, 0, written_paths];
2934
2810
  }
@@ -2995,14 +2871,17 @@ function _copy_dir_dereferencing_symlinks(
2995
2871
  if (file_filter !== null && !file_filter(entry)) {
2996
2872
  continue;
2997
2873
  }
2998
- const decision = _resolve_file_conflict(target, force);
2999
- if (decision === 'skip') {
2874
+ const decision = tracker.resolve(target, force);
2875
+ if (decision === 'preserve') {
2876
+ tracker.preserve(target, resolved, package_root);
3000
2877
  skipped += 1;
2878
+ _log_tx_entry('skip', target);
3001
2879
  continue;
3002
2880
  }
3003
2881
  mkdirp(path.dirname(target));
3004
2882
  fs.copyFileSync(resolved, target);
3005
2883
  _inject_package_tag(target, resolved, package_root);
2884
+ _log_tx_entry('write', target);
3006
2885
  written += 1;
3007
2886
  written_paths.push(target);
3008
2887
  }
@@ -3014,7 +2893,7 @@ function _claude_desktop_bundles_dir(): string {
3014
2893
  }
3015
2894
 
3016
2895
  function _write_claude_desktop_marker(
3017
- _force: boolean,
2896
+ force: boolean,
3018
2897
  lockfile_path: string,
3019
2898
  bundles_dir: string,
3020
2899
  bundle_count: number,
@@ -3023,7 +2902,15 @@ function _write_claude_desktop_marker(
3023
2902
  const target = path.join(anchor, 'agent-config.md');
3024
2903
  mkdirp(anchor);
3025
2904
  const body = claudeDesktopMarkerBody(lockfile_path, anchor, bundles_dir, bundle_count);
2905
+ // Generated, but manifest-recorded with a digest — so it is in the class the
2906
+ // preservation ruling covers, and it used to write unconditionally, ignoring
2907
+ // even `--force`, because the tracker was wired only into the copy loop.
2908
+ if (tracker.resolve(target, force) === 'preserve') {
2909
+ tracker.preserveContent(target, body);
2910
+ return [0, 1, []];
2911
+ }
3026
2912
  writeText(target, body);
2913
+ _log_tx_entry('write', target);
3027
2914
  return [1, 0, [target]];
3028
2915
  }
3029
2916
 
@@ -3217,6 +3104,7 @@ function _deploy_global_content(
3217
3104
  info,
3218
3105
  warn,
3219
3106
  res,
3107
+ new Set(tracker.conflictState.preserved.map((p) => path.resolve(p))),
3220
3108
  );
3221
3109
  }
3222
3110
 
@@ -4927,10 +4815,16 @@ function main(argv: string[]): number {
4927
4815
  const rc = _run_migrate_to_global(detect_root);
4928
4816
  if (rc !== 0) return rc;
4929
4817
  }
4930
- const rc = install_global(parsed_tools, opts.force, detect_root, opts.core_only);
4818
+ tracker.begin(detect_root);
4819
+ const rc = tracker.finalizeRc(
4820
+ install_global(parsed_tools, opts.force, detect_root, opts.core_only),
4821
+ );
4931
4822
  _emit_progress_terminal(rc);
4932
- if (rc === 0 && wizard_handoff) {
4933
- return _wizard_spawn(detect_root, false);
4823
+ // A preserved edit does not withhold the wizard, but it survives one: a
4824
+ // zero from the wizard must not erase the conflict signal this run earned.
4825
+ if ((rc === 0 || rc === EXIT_COMPLETED_WITH_CONFLICTS) && wizard_handoff) {
4826
+ const wizard_rc = _wizard_spawn(detect_root, false);
4827
+ return wizard_rc === 0 ? rc : wizard_rc;
4934
4828
  }
4935
4829
  return rc;
4936
4830
  }
@@ -4938,10 +4832,12 @@ function main(argv: string[]): number {
4938
4832
  const project_root =
4939
4833
  custom_path || resolvePath(opts.project || process.env['PROJECT_ROOT'] || process.cwd());
4940
4834
  const is_first_run = !pathExists(path.join(project_root, SETTINGS_FILE));
4941
- const rc = _main_project_install(opts, project_root, parsed_tools, is_first_run);
4835
+ tracker.begin(project_root);
4836
+ let rc = _main_project_install(opts, project_root, parsed_tools, is_first_run);
4942
4837
  if (rc === 0 && opts.interactive) {
4943
4838
  run_interactive_init(project_root, opts.force);
4944
4839
  }
4840
+ rc = tracker.finalizeRc(rc);
4945
4841
  _emit_progress_terminal(rc);
4946
4842
  return rc;
4947
4843
  }
@@ -5185,7 +5081,7 @@ function _main_project_install(
5185
5081
  }
5186
5082
 
5187
5083
  if (!opts.skip_bridges) {
5188
- const files_by_tool = _files_by_tool_from_bridges(parsed_tools, project_root, 'project');
5084
+ const files_by_tool = _files_by_tool_from_bridges(parsed_tools, project_root, 'project', _bridge_marker);
5189
5085
  const rc = _update_installed_tools_manifest(
5190
5086
  project_root,
5191
5087
  parsed_tools,
@@ -5301,6 +5197,7 @@ export {
5301
5197
  _format_global_root_for_marker,
5302
5198
  _files_by_tool_from_bridges,
5303
5199
  _files_by_tool_from_deploy,
5200
+ _update_installed_tools_manifest,
5304
5201
  _verify_deploy_targets,
5305
5202
  _wizard_should_launch,
5306
5203
  _dry_run_summary,
@@ -5363,6 +5260,9 @@ export {
5363
5260
  // road-to-local-ci-trust: exported for the symlink-confinement tests
5364
5261
  // (PR #1076 review-gate finding).
5365
5262
  _copy_dir_dereferencing_symlinks,
5263
+ // road-to-a-conformance-check-that-can-fail Phase 5.1: the wired tracker,
5264
+ // so the preservation path is testable (owner ruling 2026-09-21).
5265
+ tracker,
5366
5266
  _preview_global_reap,
5367
5267
  _resolve_global_rule_scope,
5368
5268
  _rule_filter_for_source,
@@ -87,6 +87,13 @@ const TTL_CONFIG: ReadonlyArray<{
87
87
  owner: 'maintainer',
88
88
  reviewBy: '2026-11-10',
89
89
  },
90
+ {
91
+ dir: 'agents/runtime/state/memory-recall',
92
+ ttlDays: 7,
93
+ description: 'Per-session memory-recall latch (one flag per delivered memory slug)',
94
+ owner: 'maintainer',
95
+ reviewBy: '2026-11-10',
96
+ },
90
97
  {
91
98
  dir: 'agents/runtime/state/end-review-nudge',
92
99
  ttlDays: 7,