@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
@@ -0,0 +1,460 @@
1
+ /**
2
+ * Project-scope installed-tools manifest at `agents/installed-tools.lock`.
3
+ *
4
+ * Ported from the retired Python `src/scripts/_lib/installed_tools.py` (ADR-200 — Python→TS
5
+ * migration, Phase 2 / Wave 2a). Public API mirrors the Python module exactly
6
+ * (snake_case kept deliberately — fidelity over TS idiom), including the
7
+ * byte-exact YAML wire format the `_render` golden tests pin.
8
+ *
9
+ * Phase 3 of road-to-global-first-install (ADR-008). Committed
10
+ * bill-of-materials for AI tooling a project depends on. Sibling to the
11
+ * global lockfile (`installed_lock.ts`) but architecturally distinct:
12
+ *
13
+ * - `installed_lock` lives in `~/.event4u/agent-config/` and tracks the
14
+ * user-scope environment (a single `agent_config_version` and a flat
15
+ * `tools[]` list).
16
+ * - `installed_tools` lives in `agents/` and tracks **per-project** tooling
17
+ * with richer per-entry metadata (`scope`, `bridge_marker`, `installed_at`).
18
+ *
19
+ * The file is machine-managed: `init` appends / merges; `sync` replays;
20
+ * `validate` drift-checks. Schema is YAML; the Python reference uses `pyyaml`
21
+ * when available and otherwise a constrained manual parser. This port has no
22
+ * YAML library dependency — it always uses the manual parser, which handles
23
+ * the documented schema (no anchors, no flow style, single-level nesting under
24
+ * `tools`).
25
+ *
26
+ * Divergence candidate (flagged, not fixed): the Python reader prefers
27
+ * `pyyaml` and only falls back to the manual parser when it is absent or the
28
+ * YAML is malformed. This port has no YAML dependency, so it always takes the
29
+ * manual-parser path. For the canonical schema both paths agree; for v2
30
+ * manifests with nested `files` / `merged_keys` arrays the manual path
31
+ * silently drops those nested fields (documented degraded read in the Python
32
+ * source). A consumer relying on `pyyaml`-backed full-v2 fidelity would see a
33
+ * behavior change here. See ADR-200 § intentional-divergence.
34
+ */
35
+ import * as fs from "node:fs";
36
+ import * as os from "node:os";
37
+ import * as fsPath from "node:path";
38
+ import { write_atomic } from "./fs_atomic.js";
39
+ export const MANIFEST_ENV = "AGENT_CONFIG_INSTALLED_TOOLS";
40
+ export const DEFAULT_MANIFEST_RELATIVE = fsPath.join("agents", "installed-tools.lock");
41
+ export const SCHEMA_VERSION = 2;
42
+ /**
43
+ * Schema versions older writers may have emitted. Reading any of these
44
+ * succeeds; writing always produces {@link SCHEMA_VERSION}.
45
+ */
46
+ export const SCHEMA_VERSIONS_SUPPORTED = [1, 2];
47
+ const _VALID_SCOPES = ["global", "project"];
48
+ /**
49
+ * Permitted values for `files[].kind` (P1.1, road-to-multi-package-
50
+ * coexistence). `bridge` = team-pointer marker (e.g. `.cursorrules`);
51
+ * `deployed` = bundle content we wrote (e.g. `.augment/rules/*.md`);
52
+ * `marker` = one-off sentinel (e.g. `claude-desktop` install marker).
53
+ */
54
+ export const FILE_KINDS = new Set(["bridge", "deployed", "marker"]);
55
+ /**
56
+ * Stable known deploy roots — directories under which the doctor command
57
+ * surveys for foreign files. Writers may extend the live `deploy_roots` field
58
+ * per project; this constant is the canonical default the installer seeds.
59
+ */
60
+ export const DEFAULT_DEPLOY_ROOTS = [
61
+ ".augment/rules",
62
+ ".augment/skills",
63
+ ".augment/commands",
64
+ ".cursor/rules",
65
+ ".claude/skills",
66
+ ".claude/commands",
67
+ ".clinerules",
68
+ ".windsurf/rules",
69
+ ];
70
+ /** Expand a leading `~` like Python's `Path.expanduser()`. */
71
+ function expanduser(p) {
72
+ if (p === "~") {
73
+ return os.homedir();
74
+ }
75
+ if (p.startsWith("~/") || (process.platform === "win32" && p.startsWith("~\\"))) {
76
+ return fsPath.join(os.homedir(), p.slice(2));
77
+ }
78
+ return p;
79
+ }
80
+ /** Return the active manifest path, honoring the env override. */
81
+ export function manifest_path(project_root, env) {
82
+ const env_map = env ?? process.env;
83
+ const override = env_map[MANIFEST_ENV];
84
+ if (override) {
85
+ return expanduser(override);
86
+ }
87
+ return fsPath.join(project_root, DEFAULT_MANIFEST_RELATIVE);
88
+ }
89
+ // ---------------------------------------------------------------------------
90
+ // Read
91
+ // ---------------------------------------------------------------------------
92
+ const _TOP_KEY_RE = /^([A-Za-z_][A-Za-z0-9_]*)\s*:\s*"?([^"\n]*?)"?\s*$/;
93
+ const _LIST_DASH_RE = /^\s*-\s*(.+?)\s*$/;
94
+ const _INDENT_KEY_RE = /^\s+([A-Za-z_][A-Za-z0-9_]*)\s*:\s*"?([^"\n]*?)"?\s*$/;
95
+ /**
96
+ * Parse the manifest into a dict; return `null` if absent.
97
+ *
98
+ * Tolerates partial / malformed files: missing keys yield missing dict entries
99
+ * rather than throwing, so a corrupted file does not brick `init`. v1 and v2
100
+ * wire formats both return the same shape — v2 optional fields (`deploy_roots`,
101
+ * per-tool `files` / `merged_keys`) default to empty lists when absent, so
102
+ * callers can iterate without `?? []` boilerplate (P1.2).
103
+ */
104
+ export function read_manifest(path) {
105
+ let text;
106
+ try {
107
+ text = require_read_text(path);
108
+ }
109
+ catch {
110
+ return null;
111
+ }
112
+ // This port has no YAML dependency — always use the manual parser. See the
113
+ // module-level divergence note.
114
+ const data = _parse_manual(text);
115
+ return _normalise_v2_shape(data);
116
+ }
117
+ /** Read a file as UTF-8 text, throwing on any error (caller catches). */
118
+ function require_read_text(path) {
119
+ return fs.readFileSync(path, { encoding: "utf-8" });
120
+ }
121
+ /**
122
+ * Backfill v2 optional fields so consumers can iterate uniformly.
123
+ *
124
+ * Idempotent: calling on an already-normalised dict is a no-op. Does not
125
+ * mutate input lists — replaces missing keys with fresh empties.
126
+ */
127
+ export function _normalise_v2_shape(data) {
128
+ if (data["tools"] === undefined || data["tools"] === null) {
129
+ data["tools"] = [];
130
+ }
131
+ if (data["deploy_roots"] === undefined || data["deploy_roots"] === null) {
132
+ data["deploy_roots"] = [];
133
+ }
134
+ const tools = data["tools"];
135
+ for (const tool of tools) {
136
+ if (typeof tool !== "object" || tool === null) {
137
+ continue;
138
+ }
139
+ const t = tool;
140
+ if (t["files"] === undefined || t["files"] === null) {
141
+ t["files"] = [];
142
+ }
143
+ if (t["merged_keys"] === undefined || t["merged_keys"] === null) {
144
+ t["merged_keys"] = [];
145
+ }
146
+ }
147
+ return data;
148
+ }
149
+ /**
150
+ * Strict v1 manual parser; v2 nested fields are skipped, not raised.
151
+ *
152
+ * Handles the canonical v1 wire format (top-level scalars + `tools` array with
153
+ * single-level key:value entries). For v2 manifests it still extracts the
154
+ * top-level scalars and the per-tool scalar fields, but silently drops nested
155
+ * arrays (`files`, `merged_keys`) and top-level `deploy_roots`.
156
+ */
157
+ export function _parse_manual(text) {
158
+ const data = { tools: [] };
159
+ const tools = data["tools"];
160
+ let in_tools = false;
161
+ let current = null;
162
+ // When the current tool entry opened a nested array (`files:` or
163
+ // `merged_keys:`), suppress recognition of the deeper `- key` lines as new
164
+ // tools until the indent climbs back to the per-tool level (4 spaces).
165
+ let skip_until_outdent = false;
166
+ for (const raw of splitlines(text)) {
167
+ const stripped = raw.trim();
168
+ if (!stripped || stripped.startsWith("#")) {
169
+ continue;
170
+ }
171
+ if (stripped === "tools:") {
172
+ in_tools = true;
173
+ current = null;
174
+ skip_until_outdent = false;
175
+ continue;
176
+ }
177
+ if (in_tools) {
178
+ const indent = raw.length - lstrip_spaces(raw).length;
179
+ if (skip_until_outdent && indent > 4) {
180
+ continue;
181
+ }
182
+ skip_until_outdent = false;
183
+ const m = _LIST_DASH_RE.exec(raw);
184
+ if (m && indent === 2) {
185
+ const first = m[1];
186
+ current = {};
187
+ tools.push(current);
188
+ const inline = _TOP_KEY_RE.exec(first);
189
+ if (inline) {
190
+ current[inline[1]] = inline[2];
191
+ }
192
+ continue;
193
+ }
194
+ const mk = _INDENT_KEY_RE.exec(raw);
195
+ if (mk && current !== null && indent === 4) {
196
+ const key = mk[1];
197
+ const val = mk[2];
198
+ if ((key === "files" || key === "merged_keys") && !val) {
199
+ skip_until_outdent = true;
200
+ continue;
201
+ }
202
+ current[key] = val;
203
+ continue;
204
+ }
205
+ }
206
+ const m_top = _TOP_KEY_RE.exec(raw);
207
+ if (m_top) {
208
+ const key = m_top[1];
209
+ const value = m_top[2];
210
+ if (key === "deploy_roots" && !value) {
211
+ // Top-level v2 array — skip until next top-level scalar.
212
+ in_tools = false;
213
+ current = null;
214
+ skip_until_outdent = true;
215
+ continue;
216
+ }
217
+ if (key === "schema_version") {
218
+ const parsed = parse_int_strict(value);
219
+ data[key] = parsed === null ? value : parsed;
220
+ }
221
+ else {
222
+ data[key] = value;
223
+ }
224
+ in_tools = false;
225
+ current = null;
226
+ skip_until_outdent = false;
227
+ }
228
+ }
229
+ return data;
230
+ }
231
+ /** `str.splitlines()` — split on `\n`, dropping a trailing empty segment. */
232
+ function splitlines(text) {
233
+ const parts = text.split("\n");
234
+ if (parts.length > 0 && parts[parts.length - 1] === "") {
235
+ parts.pop();
236
+ }
237
+ return parts;
238
+ }
239
+ /** Left-strip only spaces, mirroring Python's `lstrip(" ")`. */
240
+ function lstrip_spaces(s) {
241
+ let i = 0;
242
+ while (i < s.length && s[i] === " ") {
243
+ i += 1;
244
+ }
245
+ return s.slice(i);
246
+ }
247
+ /** Parse a base-10 int the way Python's `int(value)` does; null on failure. */
248
+ function parse_int_strict(value) {
249
+ if (!/^[+-]?\d+$/.test(value.trim())) {
250
+ return null;
251
+ }
252
+ return Number.parseInt(value, 10);
253
+ }
254
+ // ---------------------------------------------------------------------------
255
+ // Write
256
+ // ---------------------------------------------------------------------------
257
+ function _render(version, tools, options = {}) {
258
+ const deploy_roots = options.deploy_roots ?? null;
259
+ const lines = [
260
+ `schema_version: ${SCHEMA_VERSION}`,
261
+ `agent_config_version: "${version}"`,
262
+ ];
263
+ if (deploy_roots && deploy_roots.length > 0) {
264
+ lines.push("deploy_roots:");
265
+ for (const root of deploy_roots) {
266
+ lines.push(` - ${root}`);
267
+ }
268
+ }
269
+ lines.push("tools:");
270
+ for (const tool of tools) {
271
+ lines.push(` - name: ${String(tool["name"])}`);
272
+ lines.push(` scope: ${String(tool["scope"])}`);
273
+ lines.push(` bridge_marker: ${String(tool["bridge_marker"])}`);
274
+ lines.push(` installed_at: "${String(tool["installed_at"])}"`);
275
+ const status = tool["status"];
276
+ if (status) {
277
+ lines.push(` status: ${String(status)}`);
278
+ }
279
+ let files = tool["files"] ?? [];
280
+ if (files.length > 0) {
281
+ // Sort by path ascending — deterministic output for golden-file tests
282
+ // and stable team diffs (P1.3).
283
+ files = stable_sort(files, (f) => [String(f["path"])]);
284
+ lines.push(" files:");
285
+ for (const entry of files) {
286
+ lines.push(` - path: ${String(entry["path"])}`);
287
+ lines.push(` kind: ${String(entry["kind"])}`);
288
+ const sha = entry["sha256"];
289
+ if (sha === null || sha === undefined) {
290
+ lines.push(" sha256: null");
291
+ }
292
+ else {
293
+ lines.push(` sha256: "${String(sha)}"`);
294
+ }
295
+ }
296
+ }
297
+ let merged = tool["merged_keys"] ?? [];
298
+ if (merged.length > 0) {
299
+ // Sort by (file, json_pointer) ascending — deterministic output
300
+ // regardless of insertion order (P1.3).
301
+ merged = stable_sort(merged, (e) => [
302
+ String(e["file"]),
303
+ String(e["json_pointer"]),
304
+ ]);
305
+ lines.push(" merged_keys:");
306
+ for (const entry of merged) {
307
+ lines.push(` - file: ${String(entry["file"])}`);
308
+ lines.push(` json_pointer: "${String(entry["json_pointer"])}"`);
309
+ const vh = entry["value_hash"];
310
+ if (vh !== null && vh !== undefined) {
311
+ lines.push(` value_hash: "${String(vh)}"`);
312
+ }
313
+ }
314
+ }
315
+ }
316
+ return lines.join("\n") + "\n";
317
+ }
318
+ /**
319
+ * Stable sort by a tuple key, mirroring Python's tuple-comparison `sorted`.
320
+ * Compares each component lexicographically; ties keep input order.
321
+ */
322
+ function stable_sort(items, key) {
323
+ return items
324
+ .map((item, index) => ({ item, index, k: key(item) }))
325
+ .sort((a, b) => {
326
+ const len = Math.max(a.k.length, b.k.length);
327
+ for (let i = 0; i < len; i += 1) {
328
+ const av = a.k[i] ?? "";
329
+ const bv = b.k[i] ?? "";
330
+ if (av < bv)
331
+ return -1;
332
+ if (av > bv)
333
+ return 1;
334
+ }
335
+ return a.index - b.index;
336
+ })
337
+ .map((entry) => entry.item);
338
+ }
339
+ /**
340
+ * Atomically write the manifest; return the path written.
341
+ *
342
+ * Delegates to {@link write_atomic} so the crash-safety guarantees (fsync
343
+ * file, atomic rename, fsync parent dir) are shared with every other v2
344
+ * writer.
345
+ *
346
+ * `deploy_roots` is the optional top-level v2 field listing directories the
347
+ * doctor command surveys for foreign files. When omitted, the field is not
348
+ * emitted (callers may rely on {@link DEFAULT_DEPLOY_ROOTS} for the survey
349
+ * scope).
350
+ */
351
+ export function write_manifest(path, version, tools, options = {}) {
352
+ const rendered = _render(version, tools, { deploy_roots: options.deploy_roots ?? null });
353
+ return write_atomic(path, rendered);
354
+ }
355
+ // ---------------------------------------------------------------------------
356
+ // Mutation helpers
357
+ // ---------------------------------------------------------------------------
358
+ /** Raised when an existing manifest entry conflicts with the new scope. */
359
+ export class ScopeMismatchError extends Error {
360
+ name_;
361
+ recorded_scope;
362
+ new_scope;
363
+ constructor(name, recorded_scope, new_scope) {
364
+ super(`tool '${name}' is committed as scope=${recorded_scope}; ` +
365
+ `refusing to change it to scope=${new_scope} without --force`);
366
+ this.name = "ScopeMismatchError";
367
+ this.name_ = name;
368
+ this.recorded_scope = recorded_scope;
369
+ this.new_scope = new_scope;
370
+ }
371
+ }
372
+ /**
373
+ * Return a new tools list with `name` added or refreshed.
374
+ *
375
+ * Idempotency rules from ADR-008 §Lifecycle:
376
+ * - Same name, same scope → no-op (timestamp preserved).
377
+ * - Same name, different scope → throw {@link ScopeMismatchError} unless
378
+ * `force=true`, in which case the entry is rewritten.
379
+ * - New name → appended in install order (not alphabetised).
380
+ *
381
+ * `files` / `merged_keys` are the v2 per-tool inventories (P1.4). When
382
+ * provided, they replace whatever was previously recorded on the entry — the
383
+ * installer is authoritative for the set of artefacts it just wrote. When
384
+ * `null`/omitted, existing values are preserved on the idempotent path and
385
+ * absent on first-write.
386
+ */
387
+ export function upsert_tool(existing, options) {
388
+ const { name, scope, bridge_marker, installed_at = null, force = false, files = null, merged_keys = null, } = options;
389
+ if (!_VALID_SCOPES.includes(scope)) {
390
+ throw new ValueError(`scope must be one of ${_VALID_SCOPES.join(",")}: '${scope}'`);
391
+ }
392
+ const stamp = installed_at || _today();
393
+ const _build = (prior = null) => {
394
+ const entry = {
395
+ name,
396
+ scope,
397
+ bridge_marker,
398
+ installed_at: stamp,
399
+ };
400
+ if (files !== null) {
401
+ entry["files"] = [...files];
402
+ }
403
+ else if (prior !== null && Array.isArray(prior["files"]) && prior["files"].length > 0) {
404
+ entry["files"] = [...prior["files"]];
405
+ }
406
+ if (merged_keys !== null) {
407
+ entry["merged_keys"] = [...merged_keys];
408
+ }
409
+ else if (prior !== null &&
410
+ Array.isArray(prior["merged_keys"]) &&
411
+ prior["merged_keys"].length > 0) {
412
+ entry["merged_keys"] = [...prior["merged_keys"]];
413
+ }
414
+ return entry;
415
+ };
416
+ const result = [];
417
+ let found = false;
418
+ for (const entry of existing) {
419
+ if (entry["name"] === name) {
420
+ found = true;
421
+ const recorded = String(entry["scope"] ?? "");
422
+ if (recorded === scope) {
423
+ if (files === null && merged_keys === null) {
424
+ // Idempotent no-op — preserve original entry.
425
+ result.push(entry);
426
+ }
427
+ else {
428
+ // Refresh inventories, preserve installed_at.
429
+ const refreshed = _build(entry);
430
+ refreshed["installed_at"] = entry["installed_at"] ?? stamp;
431
+ result.push(refreshed);
432
+ }
433
+ continue;
434
+ }
435
+ if (!force) {
436
+ throw new ScopeMismatchError(name, recorded, scope);
437
+ }
438
+ result.push(_build(entry));
439
+ continue;
440
+ }
441
+ result.push(entry);
442
+ }
443
+ if (!found) {
444
+ result.push(_build());
445
+ }
446
+ return result;
447
+ }
448
+ /** Thrown for invalid-argument conditions, mirroring Python's `ValueError`. */
449
+ export class ValueError extends Error {
450
+ constructor(message) {
451
+ super(message);
452
+ this.name = "ValueError";
453
+ }
454
+ }
455
+ function _today() {
456
+ // UTC date in YYYY-MM-DD — matches Python's
457
+ // `datetime.now(timezone.utc).strftime("%Y-%m-%d")`.
458
+ return new Date().toISOString().slice(0, 10);
459
+ }
460
+ //# sourceMappingURL=installed_tools.js.map
@@ -11,8 +11,9 @@
11
11
  *
12
12
  * So each side supplies the raw string from its own reader and this module
13
13
  * decides what it means. Anything unrecognised — absent key, typo, `null`,
14
- * a non-string — is `eager-all`, today's shipped behaviour: a mode nobody can
15
- * spell must never silently thin the standing corpus.
14
+ * a non-string — is `eager-all`: the parser fallback, which is NOT the value
15
+ * the template ships (see the type below), because a mode nobody can spell
16
+ * must never silently thin the standing corpus.
16
17
  */
17
18
  export const DEFAULT_LEAN_PROJECTION_MODE = 'eager-all';
18
19
  /** Map a raw settings value onto a mode. Unrecognised → `eager-all`. */
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Settings keys this package has RENAMED, old path -> new path.
3
+ *
4
+ * Distinct from `REMOVED_KEYS` in the one way that matters to a user: a
5
+ * removed key's value is DISCARDED, and a renamed key's value is CARRIED.
6
+ * Putting a rename in the removed map would silently return an installed
7
+ * `false` to the shipped default, which is the opposite of accepting the old
8
+ * key — so the two maps stay separate even though both warn once.
9
+ *
10
+ * An entry is dropped one minor after it lands. The new key always wins when
11
+ * one layer carries both: a reader who has already migrated is not overridden
12
+ * by a stale line they forgot to delete.
13
+ *
14
+ * Its own module rather than more lines in `agent_settings.ts`, which sits
15
+ * three lines under the 1500-line source ceiling — a sixty-line addition there
16
+ * is sixty units of ratchet, and here it is none.
17
+ */
18
+ export const RENAMED_KEYS = new Map([
19
+ ['planning.challenge_on_create', 'planning.closure_pass'],
20
+ ]);
21
+ /** Keys already warned about in THIS process — the "once per run" dedupe. */
22
+ const _warned = new Set();
23
+ /** Read a dotted path out of a settings tree. `undefined` when absent. */
24
+ function _read(tree, dotted) {
25
+ let cur = tree;
26
+ for (const part of dotted.split('.')) {
27
+ if (cur === null || typeof cur !== 'object' || Array.isArray(cur))
28
+ return undefined;
29
+ cur = cur[part];
30
+ }
31
+ return cur;
32
+ }
33
+ /** Write a dotted path into a settings tree, creating the intermediate maps. */
34
+ function _write(tree, dotted, value) {
35
+ const parts = dotted.split('.');
36
+ let cur = tree;
37
+ for (const part of parts.slice(0, -1)) {
38
+ const next = cur[part];
39
+ if (next === null || typeof next !== 'object' || Array.isArray(next))
40
+ cur[part] = {};
41
+ cur = cur[part];
42
+ }
43
+ cur[parts[parts.length - 1]] = value;
44
+ }
45
+ /**
46
+ * Carry every renamed key's value onto its new path within ONE layer, and
47
+ * return that layer.
48
+ *
49
+ * Per LAYER, before the merge, and that ordering is the whole contract: the
50
+ * shipped template already carries the new key, so a rename applied to the
51
+ * MERGED tree would always find the target "already set" and drop the user's
52
+ * value on the floor. Applied per layer, the old key becomes the new key
53
+ * inside the layer that wrote it, and ordinary precedence does the rest.
54
+ *
55
+ * Mutates and returns `layer` so it can wrap an existing merge call without
56
+ * adding a statement. Never throws: a layer that cannot be walked is a layer
57
+ * with nothing to migrate.
58
+ */
59
+ export function applyRenamedKeys(layer) {
60
+ for (const [oldKey, newKey] of RENAMED_KEYS) {
61
+ const oldValue = _read(layer, oldKey);
62
+ if (oldValue === undefined)
63
+ continue;
64
+ if (_read(layer, newKey) === undefined)
65
+ _write(layer, newKey, oldValue);
66
+ if (_warned.has(oldKey))
67
+ continue;
68
+ _warned.add(oldKey);
69
+ process.stderr.write(`⚠️ settings: \`${oldKey}\` was renamed to \`${newKey}\`. The old key still ` +
70
+ `works for one minor; rename it in your .agent-settings.yml.\n`);
71
+ }
72
+ return layer;
73
+ }
74
+ /** Test seam — the once-per-process warning dedupe is process state. */
75
+ export function _resetRenameWarnings() {
76
+ _warned.clear();
77
+ }
78
+ //# sourceMappingURL=settings_renamed_keys.js.map
@@ -77,12 +77,20 @@ const FileEntrySchema = z.object({
77
77
  kind: FileKindSchema,
78
78
  sha256: z.string().nullable(),
79
79
  });
80
+ // Input type is `unknown` rather than `ConflictEntry`: `ownership` carries a
81
+ // `.default()`, so the parsed OUTPUT always has the field while the accepted
82
+ // INPUT may omit it (older clients on an apply round-trip).
80
83
  const ConflictEntrySchema = z.object({
81
84
  path: z.string().min(1),
82
85
  kind: FileKindSchema,
83
86
  plannedSha256: z.string().nullable(),
84
87
  existingSha256: z.string().nullable(),
85
88
  mergeable: z.boolean(),
89
+ // Additive (Phase 5.1). `default` keeps apply round-trips from older
90
+ // clients parsing — they omit the field and get the pre-hash answer.
91
+ ownership: z
92
+ .enum(['recorded-unchanged', 'recorded-modified', 'unknown'])
93
+ .default('unknown'),
86
94
  });
87
95
  /**
88
96
  * Wire-shape of {@link InstallPlan} — Sets serialise as arrays over JSON,
@@ -17,6 +17,7 @@ import { detectRtk, rtkInstallCommands, RTK_UPSTREAM_REPO } from '../../install/
17
17
  import { readSelectedTools, readSelectedPacks, writeSelectedTools } from '../../install/selectedTools.js';
18
18
  import { detectAgentSwitch, AGENT_SWITCH_INSTALL_COMMAND, AGENT_SWITCH_REPO } from '../../install/agentSwitchDetection.js';
19
19
  import { readDismissedRecommendations, dismissRecommendation } from '../../install/wizardDismissals.js';
20
+ import { installCompleted, installDoneSummary } from '../../install/preserve.js';
20
21
  import { apiOnQuotaView } from '../../scripts/ai_council/transport_resolver.js';
21
22
  const STATE_REL = join('state', 'wizard-state.json');
22
23
  const SETTINGS_REL = join('settings', '.agent-settings.yml');
@@ -1067,6 +1068,7 @@ export function wizardRoute(opts) {
1067
1068
  let written = 0;
1068
1069
  let total = 0;
1069
1070
  let sawTerminal = false;
1071
+ let conflictCount = 0;
1070
1072
  try {
1071
1073
  await fs.writeFile(tmpPath, JSON.stringify(payload), { mode: 0o600 });
1072
1074
  const result = await streamInstaller(scriptPath, ['--apply-payload', tmpPath], (obj) => {
@@ -1082,9 +1084,12 @@ export function wizardRoute(opts) {
1082
1084
  total,
1083
1085
  });
1084
1086
  }
1087
+ else if (t === 'conflicts') {
1088
+ conflictCount = typeof obj.count === 'number' ? obj.count : conflictCount;
1089
+ }
1085
1090
  else if (t === 'done') {
1086
1091
  sawTerminal = true;
1087
- writeFrame(reply, { type: 'done', summary: { written, total } });
1092
+ writeFrame(reply, { type: 'done', summary: installDoneSummary(obj, written, total) });
1088
1093
  }
1089
1094
  else if (t === 'error') {
1090
1095
  sawTerminal = true;
@@ -1099,8 +1104,9 @@ export function wizardRoute(opts) {
1099
1104
  // install.py exited without a terminal frame (crash / kill): emit
1100
1105
  // one so the UI never hangs waiting for `done`/`error`.
1101
1106
  if (!sawTerminal) {
1102
- if (result.exitCode === 0) {
1103
- writeFrame(reply, { type: 'done', summary: { written, total } });
1107
+ if (installCompleted(result.exitCode)) {
1108
+ const c = { conflicts: result.exitCode === 0 ? 0 : conflictCount };
1109
+ writeFrame(reply, { type: 'done', summary: installDoneSummary(c, written, total) });
1104
1110
  }
1105
1111
  else {
1106
1112
  writeFrame(reply, {