@event4u/agent-config 14.23.0 → 15.0.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 (503) 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 +198 -0
  5. package/README.md +1 -1
  6. package/dist/agent-src/commands/agent-handoff.md +8 -7
  7. package/dist/agent-src/commands/agents/init.md +1 -1
  8. package/dist/agent-src/commands/agents/user/accept.md +4 -4
  9. package/dist/agent-src/commands/agents/user/delete.md +2 -2
  10. package/dist/agent-src/commands/agents/user/init.md +3 -3
  11. package/dist/agent-src/commands/agents/user/review.md +3 -3
  12. package/dist/agent-src/commands/agents/user/show.md +3 -3
  13. package/dist/agent-src/commands/agents/user/update.md +2 -2
  14. package/dist/agent-src/commands/agents/user.md +6 -6
  15. package/dist/agent-src/commands/analytics/prune.md +1 -1
  16. package/dist/agent-src/commands/analytics/show.md +2 -2
  17. package/dist/agent-src/commands/analyze/decision.md +2 -2
  18. package/dist/agent-src/commands/analyze/inbox.md +5 -5
  19. package/dist/agent-src/commands/analyze/incident.md +3 -3
  20. package/dist/agent-src/commands/analyze/near-miss.md +3 -3
  21. package/dist/agent-src/commands/analyze/postmortem.md +4 -4
  22. package/dist/agent-src/commands/analyze/premortem.md +2 -2
  23. package/dist/agent-src/commands/analyze.md +2 -2
  24. package/dist/agent-src/commands/bug/fix.md +1 -1
  25. package/dist/agent-src/commands/bug/investigate.md +2 -2
  26. package/dist/agent-src/commands/cost/profile.md +3 -3
  27. package/dist/agent-src/commands/design.md +1 -1
  28. package/dist/agent-src/commands/feature/explore.md +1 -1
  29. package/dist/agent-src/commands/feature/plan.md +2 -2
  30. package/dist/agent-src/commands/feature/roadmap.md +2 -2
  31. package/dist/agent-src/commands/ghostwriter/fetch.md +4 -4
  32. package/dist/agent-src/commands/ghostwriter/list.md +1 -1
  33. package/dist/agent-src/commands/ghostwriter/show.md +1 -1
  34. package/dist/agent-src/commands/ghostwriter/write.md +9 -9
  35. package/dist/agent-src/commands/humanize.md +6 -6
  36. package/dist/agent-src/commands/jira-ticket.md +1 -1
  37. package/dist/agent-src/commands/judge/solo.md +1 -1
  38. package/dist/agent-src/commands/judge.md +1 -1
  39. package/dist/agent-src/commands/knowledge/cross-repo.md +1 -1
  40. package/dist/agent-src/commands/knowledge/forget.md +1 -1
  41. package/dist/agent-src/commands/knowledge/ingest.md +2 -2
  42. package/dist/agent-src/commands/knowledge/list.md +1 -1
  43. package/dist/agent-src/commands/memory/add.md +3 -3
  44. package/dist/agent-src/commands/memory/load.md +4 -4
  45. package/dist/agent-src/commands/memory/mine-session.md +1 -1
  46. package/dist/agent-src/commands/memory/promote.md +4 -4
  47. package/dist/agent-src/commands/memory/propose.md +2 -2
  48. package/dist/agent-src/commands/mode.md +5 -5
  49. package/dist/agent-src/commands/post-as/ghostwriter.md +3 -3
  50. package/dist/agent-src/commands/post-as/me.md +7 -7
  51. package/dist/agent-src/commands/pr/create.md +1 -1
  52. package/dist/agent-src/commands/pr/merge.md +1 -1
  53. package/dist/agent-src/commands/profile/activate.md +1 -1
  54. package/dist/agent-src/commands/profile/show.md +1 -1
  55. package/dist/agent-src/commands/research/deep.md +2 -2
  56. package/dist/agent-src/commands/research/report.md +2 -2
  57. package/dist/agent-src/commands/review/changes.md +1 -1
  58. package/dist/agent-src/commands/review/routing.md +1 -1
  59. package/dist/agent-src/commands/roadmap/create.md +1 -1
  60. package/dist/agent-src/commands/roadmap/materialize.md +2 -2
  61. package/dist/agent-src/commands/roadmap/next.md +1 -1
  62. package/dist/agent-src/commands/roadmap/process-full.md +2 -2
  63. package/dist/agent-src/commands/security-audit-config.md +3 -3
  64. package/dist/agent-src/commands/skill/preview.md +1 -1
  65. package/dist/agent-src/commands/skills/discover.md +1 -1
  66. package/dist/agent-src/commands/sync/agent-settings.md +2 -2
  67. package/dist/agent-src/commands/sync/gitignore/fix.md +1 -1
  68. package/dist/agent-src/commands/tests/create.md +1 -1
  69. package/dist/agent-src/commands/tests/e2e-heal.md +1 -1
  70. package/dist/agent-src/commands/tests/e2e-plan.md +1 -1
  71. package/dist/agent-src/contexts/authority/kernel-rule-edits.md +6 -6
  72. package/dist/agent-src/contexts/communication/rules-auto/slash-command-routing-policy-mechanics.md +1 -2
  73. package/dist/agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +2 -2
  74. package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +88 -1
  75. package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +1 -1
  76. package/dist/agent-src/contexts/contracts/frugality-charter.md +1 -1
  77. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +1 -1
  78. package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +1 -1
  79. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +1 -1
  80. package/dist/agent-src/contexts/execution/cheap-question-mechanics.md +1 -1
  81. package/dist/agent-src/contexts/execution/contract-decision-sheet.md +3 -3
  82. package/dist/agent-src/contexts/execution/mandated-lines.md +1 -1
  83. package/dist/agent-src/contexts/execution/non-interactive-contract.md +1 -1
  84. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +1 -1
  85. package/dist/agent-src/contexts/execution/plan-confidence-gate.md +5 -5
  86. package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +1 -1
  87. package/dist/agent-src/contexts/execution/subagent-modes-detail.md +1 -1
  88. package/dist/agent-src/contexts/execution/subagent-routing.md +2 -2
  89. package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +1 -1
  90. package/dist/agent-src/contexts/execution/user-memory-channels.md +3 -3
  91. package/dist/agent-src/contexts/execution/verification-mechanics.md +1 -1
  92. package/dist/agent-src/contexts/judges/persona-voice-rubric.md +2 -2
  93. package/dist/agent-src/guidelines/abstraction-thresholds.md +88 -0
  94. package/dist/agent-src/guidelines/agent-infra/5w2h-analysis.md +260 -0
  95. package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +99 -0
  96. package/dist/agent-src/guidelines/agent-infra/agent-interaction-and-decision-quality.md +325 -0
  97. package/dist/agent-src/guidelines/agent-infra/api-cost-levers.md +177 -0
  98. package/dist/agent-src/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +222 -0
  99. package/dist/agent-src/guidelines/agent-infra/ask-when-uncertain-demos.md +134 -0
  100. package/dist/agent-src/guidelines/agent-infra/asking-and-brevity-examples.md +198 -0
  101. package/dist/agent-src/guidelines/agent-infra/break-glass-usage.md +113 -0
  102. package/dist/agent-src/guidelines/agent-infra/carve-out-predicates.md +17 -0
  103. package/dist/agent-src/guidelines/agent-infra/comparison-matrix.md +179 -0
  104. package/dist/agent-src/guidelines/agent-infra/context-hygiene-mechanics.md +165 -0
  105. package/dist/agent-src/guidelines/agent-infra/corpus-grounding-authoring.md +91 -0
  106. package/dist/agent-src/guidelines/agent-infra/critical-thinking.md +156 -0
  107. package/dist/agent-src/guidelines/agent-infra/cross-source-consistency-mechanics.md +109 -0
  108. package/dist/agent-src/guidelines/agent-infra/developer-judgment.md +82 -0
  109. package/dist/agent-src/guidelines/agent-infra/direct-answers-demos.md +142 -0
  110. package/dist/agent-src/guidelines/agent-infra/domain-adoption-gates.md +112 -0
  111. package/dist/agent-src/guidelines/agent-infra/domain-eval-anti-pattern.md +53 -0
  112. package/dist/agent-src/guidelines/agent-infra/domain-pack-architecture.md +56 -0
  113. package/dist/agent-src/guidelines/agent-infra/downstream-changes-mechanics.md +91 -0
  114. package/dist/agent-src/guidelines/agent-infra/emphasis-budget.md +45 -0
  115. package/dist/agent-src/guidelines/agent-infra/engineering-memory-data-format.md +210 -0
  116. package/dist/agent-src/guidelines/agent-infra/evaluator-independence-mechanics.md +92 -0
  117. package/dist/agent-src/guidelines/agent-infra/existence-question-verdicts.md +80 -0
  118. package/dist/agent-src/guidelines/agent-infra/failure-signatures.md +70 -0
  119. package/dist/agent-src/guidelines/agent-infra/false-green.md +233 -0
  120. package/dist/agent-src/guidelines/agent-infra/first-principles.md +192 -0
  121. package/dist/agent-src/guidelines/agent-infra/framework-neutrality-patterns.md +66 -0
  122. package/dist/agent-src/guidelines/agent-infra/frontier-reasoning-operating-profile.md +169 -0
  123. package/dist/agent-src/guidelines/agent-infra/gate-authoring.md +177 -0
  124. package/dist/agent-src/guidelines/agent-infra/guarded-baseline.md +39 -0
  125. package/dist/agent-src/guidelines/agent-infra/installed-tools-manifest.md +142 -0
  126. package/dist/agent-src/guidelines/agent-infra/inversion-thinking.md +388 -0
  127. package/dist/agent-src/guidelines/agent-infra/ios-simulator-guide.md +378 -0
  128. package/dist/agent-src/guidelines/agent-infra/language-and-tone-examples.md +141 -0
  129. package/dist/agent-src/guidelines/agent-infra/layered-settings.md +317 -0
  130. package/dist/agent-src/guidelines/agent-infra/linked-projects-onboarding-gate.md +80 -0
  131. package/dist/agent-src/guidelines/agent-infra/mcp-request-signing.md +197 -0
  132. package/dist/agent-src/guidelines/agent-infra/memory-access.md +149 -0
  133. package/dist/agent-src/guidelines/agent-infra/mental-models.md +314 -0
  134. package/dist/agent-src/guidelines/agent-infra/minimal-safe-diff-mechanics.md +241 -0
  135. package/dist/agent-src/guidelines/agent-infra/missing-tool-handling.md +66 -0
  136. package/dist/agent-src/guidelines/agent-infra/model-recommendation.md +101 -0
  137. package/dist/agent-src/guidelines/agent-infra/naming.md +69 -0
  138. package/dist/agent-src/guidelines/agent-infra/notes-horizon-mechanics.md +90 -0
  139. package/dist/agent-src/guidelines/agent-infra/output-patterns.md +117 -0
  140. package/dist/agent-src/guidelines/agent-infra/recurring-criticism-mechanics.md +96 -0
  141. package/dist/agent-src/guidelines/agent-infra/reuse-verdict-mechanics.md +71 -0
  142. package/dist/agent-src/guidelines/agent-infra/review-routing-data-format.md +143 -0
  143. package/dist/agent-src/guidelines/agent-infra/roadmap-deferred-resolution-provenance.md +54 -0
  144. package/dist/agent-src/guidelines/agent-infra/roadmap-progress-mechanics.md +274 -0
  145. package/dist/agent-src/guidelines/agent-infra/role-contracts.md +209 -0
  146. package/dist/agent-src/guidelines/agent-infra/role-mode-router.md +89 -0
  147. package/dist/agent-src/guidelines/agent-infra/rule-body-migration-inventory.md +241 -0
  148. package/dist/agent-src/guidelines/agent-infra/rule-type-governance.md +73 -0
  149. package/dist/agent-src/guidelines/agent-infra/runtime-layer.md +89 -0
  150. package/dist/agent-src/guidelines/agent-infra/scqa-framework.md +526 -0
  151. package/dist/agent-src/guidelines/agent-infra/security-lint-containment.md +121 -0
  152. package/dist/agent-src/guidelines/agent-infra/self-improvement-pipeline.md +134 -0
  153. package/dist/agent-src/guidelines/agent-infra/simplicity-and-goal-demos.md +211 -0
  154. package/dist/agent-src/guidelines/agent-infra/six-hats.md +353 -0
  155. package/dist/agent-src/guidelines/agent-infra/size-and-scope.md +229 -0
  156. package/dist/agent-src/guidelines/agent-infra/skill-quality-checklist.md +157 -0
  157. package/dist/agent-src/guidelines/agent-infra/source-confidentiality-mechanics.md +109 -0
  158. package/dist/agent-src/guidelines/agent-infra/symptom-driven-harvest-loop.md +104 -0
  159. package/dist/agent-src/guidelines/agent-infra/systems-thinking.md +220 -0
  160. package/dist/agent-src/guidelines/agent-infra/tool-description-as-policy.md +35 -0
  161. package/dist/agent-src/guidelines/agent-infra/tool-integration.md +73 -0
  162. package/dist/agent-src/guidelines/agent-infra/traceability-field-mechanics.md +50 -0
  163. package/dist/agent-src/guidelines/agent-infra/untrusted-input-spotlighting.md +166 -0
  164. package/dist/agent-src/guidelines/agent-infra/verify-before-complete-demos.md +128 -0
  165. package/dist/agent-src/guidelines/augment-portability-patterns.md +70 -0
  166. package/dist/agent-src/guidelines/code-clarity.md +350 -0
  167. package/dist/agent-src/guidelines/component-oriented-and-oop-development.md +189 -0
  168. package/dist/agent-src/guidelines/cross-role-handoff.md +127 -0
  169. package/dist/agent-src/guidelines/design-antipatterns-triggers.json +18 -0
  170. package/dist/agent-src/guidelines/design-antipatterns.md +327 -0
  171. package/dist/agent-src/guidelines/design-asset-discipline.md +38 -0
  172. package/dist/agent-src/guidelines/design-canon.md +63 -0
  173. package/dist/agent-src/guidelines/design-fidelity-mechanics.md +293 -0
  174. package/dist/agent-src/guidelines/design-fidelity-routing.md +78 -0
  175. package/dist/agent-src/guidelines/design-handover-extraction.md +117 -0
  176. package/dist/agent-src/guidelines/design-modes.md +143 -0
  177. package/dist/agent-src/guidelines/design-reconciliation.md +172 -0
  178. package/dist/agent-src/guidelines/docs/readme-size-and-splitting.md +209 -0
  179. package/dist/agent-src/guidelines/e2e/playwright.md +363 -0
  180. package/dist/agent-src/guidelines/gtm-handoff.md +114 -0
  181. package/dist/agent-src/guidelines/monorepo-antipatterns.md +141 -0
  182. package/dist/agent-src/guidelines/php/api-design.md +115 -0
  183. package/dist/agent-src/guidelines/php/artisan-commands.md +81 -0
  184. package/dist/agent-src/guidelines/php/blade-ui.md +78 -0
  185. package/dist/agent-src/guidelines/php/controllers.md +90 -0
  186. package/dist/agent-src/guidelines/php/database.md +194 -0
  187. package/dist/agent-src/guidelines/php/eloquent.md +208 -0
  188. package/dist/agent-src/guidelines/php/flux.md +80 -0
  189. package/dist/agent-src/guidelines/php/general.md +207 -0
  190. package/dist/agent-src/guidelines/php/git.md +164 -0
  191. package/dist/agent-src/guidelines/php/jobs.md +111 -0
  192. package/dist/agent-src/guidelines/php/livewire.md +71 -0
  193. package/dist/agent-src/guidelines/php/logging.md +79 -0
  194. package/dist/agent-src/guidelines/php/naming.md +89 -0
  195. package/dist/agent-src/guidelines/php/patterns/dependency-injection.md +57 -0
  196. package/dist/agent-src/guidelines/php/patterns/dtos.md +199 -0
  197. package/dist/agent-src/guidelines/php/patterns/events.md +67 -0
  198. package/dist/agent-src/guidelines/php/patterns/factory.md +53 -0
  199. package/dist/agent-src/guidelines/php/patterns/pipelines.md +66 -0
  200. package/dist/agent-src/guidelines/php/patterns/policies.md +66 -0
  201. package/dist/agent-src/guidelines/php/patterns/repositories.md +122 -0
  202. package/dist/agent-src/guidelines/php/patterns/service-layer.md +64 -0
  203. package/dist/agent-src/guidelines/php/patterns/strategy.md +247 -0
  204. package/dist/agent-src/guidelines/php/patterns.md +28 -0
  205. package/dist/agent-src/guidelines/php/performance.md +92 -0
  206. package/dist/agent-src/guidelines/php/php-coding-patterns.md +80 -0
  207. package/dist/agent-src/guidelines/php/resources.md +100 -0
  208. package/dist/agent-src/guidelines/php/security.md +110 -0
  209. package/dist/agent-src/guidelines/php/sql.md +97 -0
  210. package/dist/agent-src/guidelines/php/validations.md +119 -0
  211. package/dist/agent-src/guidelines/php/websocket.md +100 -0
  212. package/dist/agent-src/guidelines/prompt-templates.md +189 -0
  213. package/dist/agent-src/guidelines/redundancy-taxonomy.md +208 -0
  214. package/dist/agent-src/guidelines/wing4-handoff.md +127 -0
  215. package/dist/agent-src/personas/README.md +1 -1
  216. package/dist/agent-src/personas/_template-specialist/persona.md +1 -1
  217. package/dist/agent-src/personas/product-owner.md +1 -1
  218. package/dist/agent-src/rules/active-remediation.md +1 -1
  219. package/dist/agent-src/rules/architecture.md +2 -2
  220. package/dist/agent-src/rules/artifact-drafting-protocol.md +1 -1
  221. package/dist/agent-src/rules/ask-when-uncertain.md +2 -2
  222. package/dist/agent-src/rules/brand-source-of-truth.md +9 -7
  223. package/dist/agent-src/rules/code-comment-discipline.md +2 -2
  224. package/dist/agent-src/rules/code-provenance.md +1 -1
  225. package/dist/agent-src/rules/content-quoting-floor.md +1 -1
  226. package/dist/agent-src/rules/context-hygiene.md +5 -5
  227. package/dist/agent-src/rules/cross-source-consistency.md +1 -1
  228. package/dist/agent-src/rules/design-fidelity.md +36 -46
  229. package/dist/agent-src/rules/direct-answers.md +2 -2
  230. package/dist/agent-src/rules/domain-adoption-policy.md +1 -1
  231. package/dist/agent-src/rules/evaluator-independence.md +1 -1
  232. package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +1 -1
  233. package/dist/agent-src/rules/icon-consistency.md +5 -1
  234. package/dist/agent-src/rules/improve-before-implement.md +1 -1
  235. package/dist/agent-src/rules/language-and-tone.md +3 -3
  236. package/dist/agent-src/rules/minimal-safe-diff.md +1 -1
  237. package/dist/agent-src/rules/notes-first-reasoning.md +2 -2
  238. package/dist/agent-src/rules/recurring-criticism.md +1 -1
  239. package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
  240. package/dist/agent-src/rules/role-mode-adherence.md +2 -2
  241. package/dist/agent-src/rules/security-sensitive-stop.md +1 -1
  242. package/dist/agent-src/rules/size-enforcement.md +1 -1
  243. package/dist/agent-src/rules/source-confidentiality.md +2 -2
  244. package/dist/agent-src/rules/think-before-action.md +1 -1
  245. package/dist/agent-src/rules/token-efficiency.md +7 -5
  246. package/dist/agent-src/rules/ui-audit-gate.md +3 -2
  247. package/dist/agent-src/rules/untrusted-input-defense.md +3 -3
  248. package/dist/agent-src/skills/activation-design/SKILL.md +2 -2
  249. package/dist/agent-src/skills/adr-create/SKILL.md +5 -5
  250. package/dist/agent-src/skills/ai-council/SKILL.md +1 -1
  251. package/dist/agent-src/skills/ai-council/references/advanced-modes.md +1 -1
  252. package/dist/agent-src/skills/blade-ui/SKILL.md +1 -1
  253. package/dist/agent-src/skills/blast-radius-analyzer/SKILL.md +1 -1
  254. package/dist/agent-src/skills/brand-to-tokens/SKILL.md +24 -3
  255. package/dist/agent-src/skills/bug-analyzer/SKILL.md +1 -1
  256. package/dist/agent-src/skills/build-buy-partner/SKILL.md +1 -1
  257. package/dist/agent-src/skills/churn-prevention/SKILL.md +2 -2
  258. package/dist/agent-src/skills/code-refactoring/SKILL.md +3 -3
  259. package/dist/agent-src/skills/code-review/SKILL.md +3 -3
  260. package/dist/agent-src/skills/command-writing/SKILL.md +3 -3
  261. package/dist/agent-src/skills/comp-banding/SKILL.md +1 -1
  262. package/dist/agent-src/skills/competitive-moat-analysis/SKILL.md +1 -1
  263. package/dist/agent-src/skills/competitive-positioning/SKILL.md +2 -2
  264. package/dist/agent-src/skills/complexity-first-planning/SKILL.md +1 -1
  265. package/dist/agent-src/skills/condense-memory/SKILL.md +1 -1
  266. package/dist/agent-src/skills/content-funnel-design/SKILL.md +2 -2
  267. package/dist/agent-src/skills/contracts-cognition/SKILL.md +1 -1
  268. package/dist/agent-src/skills/copilot-agents-optimization/SKILL.md +2 -2
  269. package/dist/agent-src/skills/corpus-grounding/SKILL.md +2 -2
  270. package/dist/agent-src/skills/customer-research/SKILL.md +2 -2
  271. package/dist/agent-src/skills/dashboard-design/SKILL.md +1 -1
  272. package/dist/agent-src/skills/data-handling-judgment/SKILL.md +1 -1
  273. package/dist/agent-src/skills/deal-qualification-meddic/SKILL.md +2 -2
  274. package/dist/agent-src/skills/decision-record/SKILL.md +3 -3
  275. package/dist/agent-src/skills/decision-review/SKILL.md +8 -8
  276. package/dist/agent-src/skills/deep-reading-analyst/SKILL.md +22 -22
  277. package/dist/agent-src/skills/design-intelligence/SKILL.md +4 -4
  278. package/dist/agent-src/skills/design-intelligence/references/context-and-registers.md +2 -2
  279. package/dist/agent-src/skills/design-review/SKILL.md +4 -4
  280. package/dist/agent-src/skills/design-system-capture/references/design-system-json.md +20 -1
  281. package/dist/agent-src/skills/design-tokens/SKILL.md +17 -2
  282. package/dist/agent-src/skills/design-variations/SKILL.md +2 -2
  283. package/dist/agent-src/skills/developer-like-execution/SKILL.md +2 -2
  284. package/dist/agent-src/skills/discovery-interview/SKILL.md +2 -2
  285. package/dist/agent-src/skills/doc-coauthoring/SKILL.md +3 -3
  286. package/dist/agent-src/skills/editorial-calendar/SKILL.md +2 -2
  287. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +44 -1
  288. package/dist/agent-src/skills/existing-ui-audit/references/anti-slop-cross-reference.md +2 -2
  289. package/dist/agent-src/skills/expansion-playbook/SKILL.md +2 -2
  290. package/dist/agent-src/skills/experiment-loop/SKILL.md +1 -1
  291. package/dist/agent-src/skills/experiment-loop/references/protocol.md +1 -1
  292. package/dist/agent-src/skills/fe-design/SKILL.md +3 -3
  293. package/dist/agent-src/skills/fe-design/references/design-patterns.md +1 -1
  294. package/dist/agent-src/skills/fe-design/references/design-read-and-memory.md +3 -3
  295. package/dist/agent-src/skills/fe-design/references/source-led-port.md +1 -1
  296. package/dist/agent-src/skills/feature-planning/SKILL.md +1 -1
  297. package/dist/agent-src/skills/flux/SKILL.md +1 -1
  298. package/dist/agent-src/skills/forecast-accuracy/SKILL.md +2 -2
  299. package/dist/agent-src/skills/forecasting/SKILL.md +2 -2
  300. package/dist/agent-src/skills/fundraising-narrative/SKILL.md +2 -2
  301. package/dist/agent-src/skills/funnel-analysis/SKILL.md +2 -2
  302. package/dist/agent-src/skills/gtm-launch/SKILL.md +2 -2
  303. package/dist/agent-src/skills/hiring-loop-design/SKILL.md +1 -1
  304. package/dist/agent-src/skills/html-deck/SKILL.md +5 -5
  305. package/dist/agent-src/skills/humanizer/SKILL.md +1 -1
  306. package/dist/agent-src/skills/humanizer/data/patterns.md +1 -1
  307. package/dist/agent-src/skills/iconography/SKILL.md +3 -3
  308. package/dist/agent-src/skills/judge-synthesis/SKILL.md +1 -1
  309. package/dist/agent-src/skills/laravel-api-endpoint/SKILL.md +2 -2
  310. package/dist/agent-src/skills/launch-readiness/SKILL.md +3 -3
  311. package/dist/agent-src/skills/learning-to-rule-or-skill/SKILL.md +6 -6
  312. package/dist/agent-src/skills/livewire/SKILL.md +1 -1
  313. package/dist/agent-src/skills/market-entry-analysis/SKILL.md +1 -1
  314. package/dist/agent-src/skills/mcp-builder/SKILL.md +1 -1
  315. package/dist/agent-src/skills/memory-consolidation/SKILL.md +3 -3
  316. package/dist/agent-src/skills/messaging-architecture/SKILL.md +2 -2
  317. package/dist/agent-src/skills/monorepo-workspace/SKILL.md +1 -1
  318. package/dist/agent-src/skills/onboarding-design/SKILL.md +2 -2
  319. package/dist/agent-src/skills/onboarding-program/SKILL.md +1 -1
  320. package/dist/agent-src/skills/one-on-one-cadence/SKILL.md +1 -1
  321. package/dist/agent-src/skills/org-design/SKILL.md +1 -1
  322. package/dist/agent-src/skills/overbuild-review-lens/SKILL.md +3 -3
  323. package/dist/agent-src/skills/override-management/SKILL.md +1 -1
  324. package/dist/agent-src/skills/perf-feedback-craft/SKILL.md +1 -1
  325. package/dist/agent-src/skills/persona-writing/SKILL.md +1 -1
  326. package/dist/agent-src/skills/php-coder/SKILL.md +1 -1
  327. package/dist/agent-src/skills/pipeline-strategy/SKILL.md +2 -2
  328. package/dist/agent-src/skills/playwright-testing/SKILL.md +3 -3
  329. package/dist/agent-src/skills/po-discovery/SKILL.md +1 -1
  330. package/dist/agent-src/skills/positioning-strategy/SKILL.md +2 -2
  331. package/dist/agent-src/skills/privacy-review/SKILL.md +1 -1
  332. package/dist/agent-src/skills/prompt-optimizer/SKILL.md +3 -3
  333. package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
  334. package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +2 -2
  335. package/dist/agent-src/skills/receiving-code-review/SKILL.md +1 -1
  336. package/dist/agent-src/skills/refine-ticket/SKILL.md +2 -2
  337. package/dist/agent-src/skills/release-comms/SKILL.md +2 -2
  338. package/dist/agent-src/skills/retention-loops/SKILL.md +2 -2
  339. package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
  340. package/dist/agent-src/skills/roadmap-management/SKILL.md +1 -1
  341. package/dist/agent-src/skills/roadmap-management/references/archival.md +1 -1
  342. package/dist/agent-src/skills/roadmap-writing/SKILL.md +5 -5
  343. package/dist/agent-src/skills/rule-writing/SKILL.md +6 -6
  344. package/dist/agent-src/skills/runway-cognition/SKILL.md +2 -2
  345. package/dist/agent-src/skills/scenario-modeling/SKILL.md +2 -2
  346. package/dist/agent-src/skills/skill-writing/SKILL.md +2 -2
  347. package/dist/agent-src/skills/skill-writing/references/procedure.md +1 -1
  348. package/dist/agent-src/skills/skill-writing/references/section-patterns.md +3 -3
  349. package/dist/agent-src/skills/spreadsheet-authoring/SKILL.md +1 -1
  350. package/dist/agent-src/skills/stakeholder-tradeoff/SKILL.md +3 -3
  351. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +1 -1
  352. package/dist/agent-src/skills/supply-chain-intake/SKILL.md +1 -1
  353. package/dist/agent-src/skills/systematic-debugging/SKILL.md +2 -2
  354. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +11 -5
  355. package/dist/agent-src/skills/throughput-vs-morale-tradeoff/SKILL.md +1 -1
  356. package/dist/agent-src/skills/token-optimizer/SKILL.md +1 -1
  357. package/dist/agent-src/skills/typography-system/SKILL.md +2 -2
  358. package/dist/agent-src/skills/ui-component-architect/SKILL.md +28 -8
  359. package/dist/agent-src/skills/unit-economics-modeling/SKILL.md +2 -2
  360. package/dist/agent-src/skills/upstream-contribute/SKILL.md +1 -1
  361. package/dist/agent-src/skills/validate-feature-fit/SKILL.md +2 -2
  362. package/dist/agent-src/skills/verify-repair-loop/SKILL.md +2 -2
  363. package/dist/agent-src/skills/vision-articulation/SKILL.md +1 -1
  364. package/dist/agent-src/skills/voc-extract/SKILL.md +2 -2
  365. package/dist/agent-src/skills/voice-and-tone-design/SKILL.md +2 -2
  366. package/dist/agent-src/skills/wireframe/SKILL.md +1 -1
  367. package/dist/agent-src/skills/workspace-link/SKILL.md +1 -1
  368. package/dist/agent-src/templates/agent-settings.md +6 -6
  369. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  370. package/dist/agent-src/templates/contexts/knowledge-pages.md +2 -3
  371. package/dist/agent-src/templates/copilot-instructions.md +1 -1
  372. package/dist/agent-src/templates/roadmaps.md +1 -1
  373. package/dist/agent-src/templates/scripts/README.md +2 -2
  374. package/dist/agent-src/user-types/_template/user-type.md +1 -1
  375. package/dist/cli-delegate/check_update_banner.js +12 -1
  376. package/dist/cli-delegate/{chunk-NLP54U3J.js → chunk-2XY7ILBT.js} +13 -1
  377. package/dist/cli-delegate/{chunk-IAI3XL43.js → chunk-3FKR64R5.js} +22 -6
  378. package/dist/cli-delegate/{chunk-J3RFNKG4.js → chunk-55ACLDUE.js} +2 -2
  379. package/dist/cli-delegate/{chunk-PKOBQADP.js → chunk-57PN4GYJ.js} +1 -1
  380. package/dist/cli-delegate/{chunk-KCNTUSPI.js → chunk-AK6HODNQ.js} +4 -4
  381. package/dist/cli-delegate/{chunk-2FTMENJL.js → chunk-GYC3VRYN.js} +1 -1
  382. package/dist/cli-delegate/{chunk-LWTYTX6F.js → chunk-HZ52BRGG.js} +24 -18
  383. package/dist/cli-delegate/{chunk-6RJA2F5A.js → chunk-IVBPRCQO.js} +1 -1
  384. package/dist/cli-delegate/{chunk-I6RHFQEG.js → chunk-YUM5KURG.js} +17 -1
  385. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  386. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  387. package/dist/cli-delegate/cmd_conformance.js +3 -3
  388. package/dist/cli-delegate/cmd_doctor.js +3 -3
  389. package/dist/cli-delegate/cmd_explain.js +2 -2
  390. package/dist/cli-delegate/cmd_export.js +4 -4
  391. package/dist/cli-delegate/cmd_handoff.js +4 -4
  392. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  393. package/dist/cli-delegate/cmd_memory_get.js +1 -1
  394. package/dist/cli-delegate/cmd_migrate.js +2 -2
  395. package/dist/cli-delegate/cmd_packs_active.js +2 -2
  396. package/dist/cli-delegate/cmd_prune.js +4 -4
  397. package/dist/cli-delegate/cmd_route_audit.js +3 -3
  398. package/dist/cli-delegate/cmd_route_explain.js +1 -1
  399. package/dist/cli-delegate/cmd_session_recycle.js +1 -1
  400. package/dist/cli-delegate/cmd_settings_get.js +15 -9
  401. package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
  402. package/dist/cli-delegate/cmd_settings_set.js +41 -11
  403. package/dist/cli-delegate/cmd_sync.js +4 -4
  404. package/dist/cli-delegate/cmd_uninstall.js +4 -4
  405. package/dist/cli-delegate/cmd_update.js +1 -1
  406. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  407. package/dist/cli-delegate/cmd_validate.js +4 -4
  408. package/dist/cli-delegate/cmd_versions.js +1 -1
  409. package/dist/discovery/deprecation-report.md +1 -1
  410. package/dist/discovery/discovery-manifest.json +22 -75
  411. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  412. package/dist/discovery/discovery-manifest.summary.md +3 -3
  413. package/dist/discovery/dormancy-report.md +1 -1
  414. package/dist/discovery/orphan-report.md +1 -1
  415. package/dist/discovery/packs.json +5 -7
  416. package/dist/discovery/trust-report.md +2 -2
  417. package/dist/discovery/workspaces.json +4 -6
  418. package/dist/hooks/dispatch.js +8607 -8930
  419. package/dist/install/install.mjs +35 -8
  420. package/dist/mcp/registry-manifest.json +3 -3
  421. package/dist/mcp/server.json +2 -2
  422. package/dist/mcp/server.mjs +1433 -1342
  423. package/dist/router.json +1 -1
  424. package/dist/scripts/_lib/agent_settings.js +73 -0
  425. package/dist/scripts/_lib/agent_src.js +20 -0
  426. package/dist/scripts/_lib/guidelines_lane.js +158 -0
  427. package/dist/scripts/condense.js +2 -5
  428. package/dist/server/io/yamlIO.js +125 -17
  429. package/dist/server/schemas/settings.js +18 -4
  430. package/dist/shared/settingsSurface.js +6 -1
  431. package/docs/contracts/settings-classes.md +18 -9
  432. package/docs/guidelines/abstraction-thresholds.md +35 -0
  433. package/docs/guidelines/agent-infra/active-remediation-mechanics.md +3 -3
  434. package/docs/guidelines/agent-infra/false-green.md +2 -2
  435. package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
  436. package/docs/guidelines/agent-infra/skill-quality-checklist.md +1 -1
  437. package/docs/guidelines/design-fidelity-mechanics.md +4 -0
  438. package/docs/guidelines/design-fidelity-routing.md +78 -0
  439. package/docs/guidelines/design-reconciliation.md +172 -0
  440. package/docs/guidelines/gtm-handoff.md +2 -2
  441. package/docs/guidelines/wing4-handoff.md +2 -2
  442. package/package.json +7 -3
  443. package/src/config/agent-settings.template.yml +55 -8
  444. package/src/config/ci-local-parity.yml +34 -0
  445. package/src/config/continuity-surface.json +4 -32
  446. package/src/config/gate-coverage.yml +31 -8
  447. package/src/config/gate-reachability-exemptions.json +1 -0
  448. package/src/config/gate-violation-baselines.json +3 -3
  449. package/src/config/hook-token-budget.json +1 -1
  450. package/src/config/host-injection-effect.json +2 -2
  451. package/src/config/pack-size-budget.json +55 -4
  452. package/src/config/platform-anchor.json +47 -0
  453. package/src/config/preamble-payload-budget.json +2 -3
  454. package/src/config/publish-surface.json +4 -2
  455. package/src/config/ratification-policy.json +7 -0
  456. package/src/config/rule-stub-ceilings.json +16 -9
  457. package/src/scripts/_cli/cmd_settings_get.ts +22 -7
  458. package/src/scripts/_lib/agent_settings.ts +85 -0
  459. package/src/scripts/_lib/agent_src.ts +21 -0
  460. package/src/scripts/_lib/artifact_maturity.ts +242 -0
  461. package/src/scripts/_lib/conformance_report.ts +403 -0
  462. package/src/scripts/_lib/continuity_writer.ts +72 -13
  463. package/src/scripts/_lib/design_system_import.ts +20 -0
  464. package/src/scripts/_lib/design_tolerance.ts +281 -0
  465. package/src/scripts/_lib/guidelines_lane.ts +164 -0
  466. package/src/scripts/_lib/obligation_frequency.ts +2 -3
  467. package/src/scripts/_lib/platform_anchor.ts +1053 -0
  468. package/src/scripts/_lib/ratification_artifact.ts +278 -0
  469. package/src/scripts/_lib/security_lint.ts +23 -2
  470. package/src/scripts/_lib/self_repair.ts +21 -0
  471. package/src/scripts/_lib/session_index_trust.ts +42 -1
  472. package/src/scripts/_lib/standing_bound_ratchet.ts +103 -37
  473. package/src/scripts/_lib/tolerance_shadow.ts +177 -0
  474. package/src/scripts/ai_council/redact_low_impact_entry.ts +8 -0
  475. package/src/scripts/autonomy_friction_traffic.ts +379 -0
  476. package/src/scripts/check_kernel_edit_ratified.ts +462 -0
  477. package/src/scripts/check_loss_class_declared.ts +793 -46
  478. package/src/scripts/check_platform_anchor.ts +390 -0
  479. package/src/scripts/check_preamble_payload_budget.ts +292 -4
  480. package/src/scripts/check_projected_rule_routes.ts +395 -0
  481. package/src/scripts/code_graph/cli.ts +6 -0
  482. package/src/scripts/condense.ts +2 -6
  483. package/src/scripts/generate_host_cost_table.ts +354 -0
  484. package/src/scripts/hook_manifest.json +1 -1
  485. package/src/scripts/hook_manifest.yaml +35 -27
  486. package/src/scripts/hooks/category_a.ts +1 -1
  487. package/src/scripts/hooks/chain_nudge_hook.ts +311 -25
  488. package/src/scripts/hooks/session_eol_hook.ts +68 -154
  489. package/src/scripts/hooks/source_first_gate_hook.ts +12 -0
  490. package/src/scripts/hooks/ui_route_nudge_hook.ts +52 -15
  491. package/src/scripts/hot_context_hook.ts +41 -401
  492. package/src/scripts/lint_evidence_artifacts.ts +1 -0
  493. package/src/scripts/lint_knowledge_scale.ts +0 -26
  494. package/src/scripts/memory_lookup.ts +9 -9
  495. package/src/scripts/report_standing_payload_by_host.ts +7 -7
  496. package/src/scripts/session_memory_index.ts +5 -0
  497. package/src/scripts/sync_agent_settings.ts +270 -10
  498. package/src/server/io/yamlIO.ts +127 -17
  499. package/src/server/schemas/settings.ts +25 -5
  500. package/src/shared/settingsSurface.ts +13 -2
  501. package/src/templates/compact-instructions.md +8 -3
  502. package/dist/agent-src/commands/chat-history/import.md +0 -170
  503. package/dist/agent-src/commands/chat-history.md +0 -63
@@ -441,11 +441,201 @@ function loadTemplate(p: string, profileValues: Record<string, string>): string
441
441
  return renderTemplate(fs.readFileSync(p, 'utf-8'), profileValues);
442
442
  }
443
443
 
444
- function loadUser(p: string): Record<string, unknown> {
445
- if (!isFile(p)) {
446
- return {};
444
+ export interface DuplicateRepair {
445
+ /** The document with every safely-collapsible duplicate run reduced to one. */
446
+ text: string;
447
+ /** Keys that were collapsed, in first-seen order. */
448
+ collapsed: string[];
449
+ /** Duplicated keys this pass refuses to touch, with the reason. */
450
+ unsafe: string[];
451
+ }
452
+
453
+ /**
454
+ * Collapse duplicate TOP-LEVEL inline-scalar keys, keeping the last value.
455
+ *
456
+ * A strict YAML parser rejects a duplicate mapping key outright, so one such
457
+ * pair takes the whole file — and with it `task sync`, `task release-prepare`
458
+ * and every release — out of service with "Map keys must be unique". The
459
+ * duplicates in the wild were written by `mergeIntoTemplate`, whose flat
460
+ * `a.b: value` append was not idempotent before this change: each wizard save
461
+ * re-appended the entire "Wizard-added keys" block. That writer is fixed, but
462
+ * every file it already corrupted stays unreadable until something repairs
463
+ * it, and a release is the worst moment to discover that.
464
+ *
465
+ * Last-wins is value-neutral, not a guess: a lenient reader already resolved
466
+ * such a run that way, so collapsing changes what the file PARSES as in no
467
+ * way — it only changes whether it parses at all.
468
+ *
469
+ * Deliberately narrow, and the narrowness is what makes last-wins safe. A key
470
+ * is collapsible only when EVERY occurrence in the run is ONE-LINE-ONLY — its
471
+ * value sits on the key's own line and it owns no continuation lines at all.
472
+ * Anything else is reported in `unsafe` and left alone, because dropping the
473
+ * loser of a multi-line pair does not drop its body: the body survives, stops
474
+ * being that key's value, and attaches to whatever line precedes it. That
475
+ * turns a parse error into silent data corruption, which is strictly worse.
476
+ *
477
+ * Refused for that reason: a block key with indented children, a block scalar
478
+ * (`|`, `>`), a flow collection opened on one line and closed on another, and
479
+ * an anchor / alias / tag / merge value, whose meaning depends on which
480
+ * occurrence a reader binds rather than only on the value.
481
+ *
482
+ * A multi-document stream (`---` / `...`) is not repaired either. The reader
483
+ * takes a single document and rejects such a file whatever this pass does, so
484
+ * collapsing across a boundary could only merge two documents' keys into one.
485
+ */
486
+ export function collapseDuplicateFlatKeys(text: string): DuplicateRepair {
487
+ // Line endings are preserved: splitting on `\n` alone leaves a trailing
488
+ // `\r` on every CRLF line, which the key pattern then never matches — so a
489
+ // CRLF file reported zero duplicates and fell straight into the parse error
490
+ // this pass exists to prevent.
491
+ //
492
+ // The MAJORITY terminator, not "any CRLF present". A mostly-LF file with one
493
+ // stray CRLF line would otherwise be rewritten to CRLF throughout — a whole-
494
+ // file change nobody asked for, on a pass whose entire promise is that it
495
+ // touches only what it must. Matches `sync_yaml_rt.detectEol`.
496
+ const crlfCount = (text.match(/\r\n/g) ?? []).length;
497
+ const lfCount = (text.match(/(^|[^\r])\n/g) ?? []).length;
498
+ const eol = crlfCount > lfCount ? '\r\n' : '\n';
499
+ const lines = text.split(/\r?\n/);
500
+
501
+ // A document separator anywhere means more than one document may be in play.
502
+ if (lines.some((l) => /^(---|\.\.\.)\s*$/.test(l))) {
503
+ return { text, collapsed: [], unsafe: [] };
504
+ }
505
+
506
+ // A mapping key ends at a colon FOLLOWED BY SPACE OR END OF LINE — YAML's
507
+ // own plain-scalar rule. Stopping at the first colon instead reads `a:b: 1`
508
+ // as the key `a`, so a file carrying both `a:b:` and `a:` saw two `a` heads
509
+ // and collapsed them: the `a:b` entry vanished and the result parsed
510
+ // cleanly, so nothing downstream could notice. `sync_yaml_rt`'s own
511
+ // tokeniser already had this right; the two disagreeing was the defect.
512
+ //
513
+ // The value is captured with `[\s\S]` rather than `.`, which does not match
514
+ // a lone `\r`. A line holding one (a value like `"x\ry"`) was not recognised
515
+ // as a head at all, so a duplicate of it stayed invisible and the parse
516
+ // error this pass exists to remove survived untouched.
517
+ const HEAD_RE = /^([A-Za-z_][A-Za-z0-9_.-]*)[ \t]*:(?=[ \t]|$)([\s\S]*)$/;
518
+
519
+ /** Top-level key lines, in order. */
520
+ const heads: { key: string; at: number; rest: string }[] = [];
521
+ for (let i = 0; i < lines.length; i++) {
522
+ const line = lines[i];
523
+ if (line === undefined) continue;
524
+ if (line.trim() === '' || line.trimStart().startsWith('#')) continue;
525
+ // Top level only — an indented `a.b:` is somebody else's child key.
526
+ if (line.length !== line.trimStart().length) continue;
527
+ const m = HEAD_RE.exec(line);
528
+ if (m === null) continue;
529
+ heads.push({ key: m[1] as string, at: i, rest: (m[2] as string).trim() });
530
+ }
531
+
532
+ /**
533
+ * Why this key may NOT be collapsed, or null when it may.
534
+ *
535
+ * Collapsing is safe only for a key that occupies exactly its own line: its
536
+ * value is a plain inline scalar and nothing between here and the next
537
+ * top-level key belongs to it. Everything else keeps its real reason, so the
538
+ * refusal message names what actually blocked it rather than asserting
539
+ * "different children" over a null, an anchor or a block scalar.
540
+ */
541
+ const blocker = (h: { at: number; rest: string }, nextAt: number): string | null => {
542
+ const v = h.rest;
543
+ if (v === '' || v.startsWith('#')) return 'a block key or a null value';
544
+ if (/^[|>]/.test(v)) return 'a block scalar';
545
+ // An anchor, alias or tag ANYWHERE in the value, not merely at its start:
546
+ // `a: [&x 1]` fits on one line, and dropping it deletes an anchor that an
547
+ // alias on some other line binds to — turning a duplicate-key error into
548
+ // an unresolved-alias error. Token position only, so `a&b` in a plain
549
+ // scalar is left alone.
550
+ if (/(^|[\s[{,])[&*!]/.test(v)) return 'an anchor, alias or tag';
551
+ // A flow collection must open and close on this line.
552
+ const opens = (v.match(/[[{]/g) ?? []).length;
553
+ const closes = (v.match(/[\]}]/g) ?? []).length;
554
+ if (opens !== closes) return 'a flow collection spanning lines';
555
+ for (let i = h.at + 1; i < nextAt; i++) {
556
+ const line = lines[i];
557
+ if (line === undefined) continue;
558
+ if (line.trim() === '') continue;
559
+ if (line.trimStart().startsWith('#')) continue;
560
+ return 'a value spanning more than its own line';
561
+ }
562
+ return null;
563
+ };
564
+
565
+ const solo = new Set<number>();
566
+ const why = new Map<number, string>();
567
+ for (let n = 0; n < heads.length; n++) {
568
+ const h = heads[n] as { key: string; at: number; rest: string };
569
+ const nextAt = n + 1 < heads.length ? (heads[n + 1] as { at: number }).at : lines.length;
570
+ const reason = blocker(h, nextAt);
571
+ if (reason === null) solo.add(h.at);
572
+ else why.set(h.at, reason);
447
573
  }
448
- const data = parseYaml(fs.readFileSync(p, 'utf-8'), { version: '1.1' });
574
+
575
+ const seen = new Map<string, number[]>();
576
+ for (const h of heads) {
577
+ const at = seen.get(h.key);
578
+ if (at === undefined) seen.set(h.key, [h.at]);
579
+ else at.push(h.at);
580
+ }
581
+
582
+ const collapsed: string[] = [];
583
+ const unsafe: string[] = [];
584
+ const drop = new Set<number>();
585
+ for (const [key, hits] of seen) {
586
+ if (hits.length < 2) continue;
587
+ if (!hits.every((i) => solo.has(i))) {
588
+ const reason = hits.map((i) => why.get(i)).find((r) => r !== undefined) ?? 'not a plain one-line value';
589
+ unsafe.push(`${key} — ${reason}`);
590
+ continue;
591
+ }
592
+ collapsed.push(key);
593
+ // Keep the LAST occurrence's value at the FIRST occurrence's position, so
594
+ // the surrounding comments stay attached to the line they document.
595
+ lines[hits[0] as number] = lines[hits[hits.length - 1] as number] as string;
596
+ for (let i = 1; i < hits.length; i++) drop.add(hits[i] as number);
597
+ }
598
+
599
+ // Duplicates this pass cannot even SEE, reported rather than skipped.
600
+ //
601
+ // `HEAD_RE` recognises the key shapes the repair understands. A key starting
602
+ // with a digit (`2fa.on`) or a quoted key (`"a:b"`) matches nothing, so a
603
+ // file duplicating one used to come back with zero collapses AND zero
604
+ // refusals — and then died on the strict parse with the same opaque "Map keys
605
+ // must be unique" the whole repair exists to remove, giving the operator no
606
+ // hint that their key was the part being skipped. Shipping a repair pass
607
+ // creates the expectation that it either fixes the file or says why it
608
+ // cannot; silence is neither.
609
+ //
610
+ // Detection only. These keys are never collapsed — the shapes are exactly the
611
+ // ones whose tokenisation this pass does not model, which is why it declines
612
+ // to rewrite them.
613
+ const loose = new Map<string, number>();
614
+ for (let i = 0; i < lines.length; i++) {
615
+ const line = lines[i];
616
+ if (line === undefined) continue;
617
+ if (line.trim() === '' || line.trimStart().startsWith('#')) continue;
618
+ if (line.length !== line.trimStart().length) continue;
619
+ if (HEAD_RE.test(line)) continue;
620
+ const m = /^("[^"]*"|'[^']*'|[^\s:#][^:]*?)[ \t]*:(?=[ \t]|$)/.exec(line);
621
+ if (m === null) continue;
622
+ const key = m[1] as string;
623
+ loose.set(key, (loose.get(key) ?? 0) + 1);
624
+ }
625
+ for (const [key, count] of loose) {
626
+ if (count < 2) continue;
627
+ unsafe.push(`${key} — a key shape this repair does not model (quoted, or not starting with a letter)`);
628
+ }
629
+
630
+ if (collapsed.length === 0) {
631
+ return { text, collapsed, unsafe };
632
+ }
633
+ const kept = lines.filter((_, i) => !drop.has(i));
634
+ return { text: kept.join(eol), collapsed, unsafe };
635
+ }
636
+
637
+ function loadUserText(raw: string): Record<string, unknown> {
638
+ const data = parseYaml(raw, { version: '1.1' });
449
639
  if (data === null || data === undefined) {
450
640
  return {};
451
641
  }
@@ -655,10 +845,61 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
655
845
  const templatePath = args.template;
656
846
  const profileDir = args.profile_dir;
657
847
 
848
+ // Repair BEFORE the first parse. A duplicate mapping key makes the whole
849
+ // file unreadable, so without this the run dies here and takes the release
850
+ // with it — see `collapseDuplicateFlatKeys` for why last-wins is safe.
851
+ const rawText = isFile(target) ? fs.readFileSync(target, 'utf-8') : '';
852
+ const repair = collapseDuplicateFlatKeys(rawText);
853
+ if (repair.unsafe.length > 0) {
854
+ // Each entry carries the reason that key was refused. A single blanket
855
+ // sentence used to claim "different children" for every refusal — over a
856
+ // null value, a block scalar or an anchor that has none — which sends the
857
+ // reader looking for children to merge that are not there.
858
+ process.stderr.write(
859
+ `error: ${target} has duplicate mapping keys this tool will not collapse:\n` +
860
+ repair.unsafe.map((u) => ` - ${u}\n`).join('') +
861
+ `Keeping one occurrence would change what the file means. Merge them by hand, then re-run.\n`,
862
+ );
863
+ return 2;
864
+ }
865
+ if (repair.collapsed.length > 0) {
866
+ // NOT gated on --quiet, and not on stdout. Both callers that matter run
867
+ // quiet — the release's `sync-agent-settings` step and the pre-release
868
+ // probe — so gating this would let a release rewrite the operator's own
869
+ // settings file with no trace. `--quiet` suppresses routine success
870
+ // chatter; "I modified your file" is not that.
871
+ //
872
+ // The tense follows the mode. `--check` and `--dry-run` never write, so
873
+ // announcing a completed collapse there describes a write that did not
874
+ // happen — and the pre-release probe runs `--dry-run`, which made that the
875
+ // FIRST thing an operator saw on every release with a corrupted file.
876
+ const writes = !args.check && !args.dry_run;
877
+ const verb = writes
878
+ ? `collapsed ${repair.collapsed.length} duplicate key(s), last value kept`
879
+ : `would collapse ${repair.collapsed.length} duplicate key(s), keeping the last value (no write in this mode)`;
880
+ process.stderr.write(`🔧 ${target}: ${verb} — ${repair.collapsed.join(', ')}\n`);
881
+
882
+ // The collapse promises to change only WHETHER the file parses, never what
883
+ // it parses as — and that promise is only meaningful over an input that had
884
+ // a reading to preserve. If the original was unparseable for some further
885
+ // reason (an unterminated quote, say), the repaired file may parse to
886
+ // structure no human wrote, and the next `git diff` shows keys nobody
887
+ // added. Cheap to say, and the operator cannot recover it from the output.
888
+ try {
889
+ parseYaml(rawText, { version: '1.1', uniqueKeys: false });
890
+ } catch {
891
+ process.stderr.write(
892
+ `⚠️ ${target}: the original had a syntax error beyond the duplicate keys, so the ` +
893
+ `result may differ in structure, not only in duplicates — read the diff before trusting it.\n`,
894
+ );
895
+ }
896
+ }
897
+ const sourceText = repair.text;
898
+
658
899
  let profile: string;
659
900
  let templateBody: string;
660
901
  try {
661
- const userData = loadUser(target);
902
+ const userData = loadUserText(sourceText);
662
903
  const personalRaw = userData['personal'];
663
904
  const personal =
664
905
  personalRaw !== null && typeof personalRaw === 'object' && !Array.isArray(personalRaw)
@@ -706,7 +947,12 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
706
947
  throw err;
707
948
  }
708
949
 
709
- const existingText = isFile(target) ? fs.readFileSync(target, 'utf-8') : '';
950
+ // The merge reads the REPAIRED text; every "did anything change" decision
951
+ // below compares against `rawText`, what is actually on disk. Comparing
952
+ // against the repaired text instead would report "already in sync" on a
953
+ // file whose collapse never reached disk — the repair would run on every
954
+ // invocation and fix nothing.
955
+ const existingText = sourceText;
710
956
 
711
957
  let newText: string;
712
958
  if (existingText) {
@@ -724,7 +970,7 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
724
970
  newText = templateBody;
725
971
  }
726
972
 
727
- if (newText === existingText) {
973
+ if (newText === rawText) {
728
974
  if (!args.quiet) {
729
975
  process.stdout.write(`✅ ${target}: already in sync (profile=${profile})\n`);
730
976
  }
@@ -732,14 +978,28 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
732
978
  }
733
979
 
734
980
  if (args.check) {
735
- const diff = renderDiff(existingText, newText, String(target));
981
+ const diff = renderDiff(rawText, newText, String(target));
736
982
  process.stdout.write(diff);
737
- process.stderr.write(`\n❌ ${target}: drift detected (profile=${profile})\n`);
983
+ // Name WHICH problem, because the two have different remedies and a CI job
984
+ // reading only "drift detected" cannot tell them apart. A repair-only diff
985
+ // means the file is corrupt, not out of step with the template; the
986
+ // discriminator is whether the merge changed anything beyond the collapse.
987
+ //
988
+ // Still exit 2 in both cases: the file needs a write either way, and a
989
+ // `--check` that returned 0 over a file the reader cannot parse would hide
990
+ // exactly the breakage this whole change exists to surface.
991
+ const repairOnly = repair.collapsed.length > 0 && newText === sourceText;
992
+ process.stderr.write(
993
+ repairOnly
994
+ ? `\n❌ ${target}: duplicate keys need collapsing — no template drift (profile=${profile})\n` +
995
+ ` Run \`sync_agent_settings\` without --check to apply the repair.\n`
996
+ : `\n❌ ${target}: drift detected (profile=${profile})\n`,
997
+ );
738
998
  return 2;
739
999
  }
740
1000
 
741
1001
  if (args.dry_run) {
742
- const diff = renderDiff(existingText, newText, String(target));
1002
+ const diff = renderDiff(rawText, newText, String(target));
743
1003
  process.stdout.write(diff);
744
1004
  if (!args.quiet) {
745
1005
  process.stderr.write(`\n(dry-run) would update ${target} (profile=${profile})\n`);
@@ -40,6 +40,25 @@ function formatScalar(value: unknown): string {
40
40
  return yamlDump(value, { lineWidth: -1, flowLevel: 0 }).replace(/\n$/, '').trim();
41
41
  }
42
42
 
43
+ /**
44
+ * A NESTED mapping key, as both the presence probe and the insertion walk read
45
+ * one. Shared so the writer and its own existence-probe cannot drift apart —
46
+ * that drift is the shape this file has now hit four times.
47
+ *
48
+ * `-` is in the class because a dashed key is ordinary YAML (`first-name`,
49
+ * `dry-run`, `retry-count`) and `detectIndentWidth` already accepted one.
50
+ * While this pattern did not, `findScalarLine` could never find such a key:
51
+ * `mergeIntoTemplate` then wrote a bogus top-level `x.first-principles:` line
52
+ * beside the real nested one and the intended value was never written — a save
53
+ * that silently did nothing — and `upsertScalar` appended another child line on
54
+ * EVERY call, growing without bound until the file stopped parsing.
55
+ *
56
+ * `.` stays OUT, deliberately. A dotted key is the FLAT form, which
57
+ * `replaceFlatDottedKey` owns; letting this pattern match `a.b:` would make the
58
+ * nested probe claim a line that is one key literally named "a.b".
59
+ */
60
+ const NESTED_KEY_RE = /^([A-Za-z_][A-Za-z0-9_-]*)\s*:/;
61
+
43
62
  interface FlatEntry {
44
63
  path: string[];
45
64
  value: unknown;
@@ -68,6 +87,25 @@ export function replaceScalar(template: string, dottedPath: string[], value: unk
68
87
  const key = dottedPath[dottedPath.length - 1];
69
88
 
70
89
  const lines = template.split('\n');
90
+ const at = findScalarLine(lines, sections, key);
91
+ if (at === -1) return template;
92
+ const width = detectIndentWidth(lines);
93
+ lines[at] = `${' '.repeat(width * sections.length)}${key}: ${formatScalar(value)}`;
94
+ return lines.join('\n');
95
+ }
96
+
97
+ /**
98
+ * Index of the line holding the scalar at `sections`/`key`, or -1.
99
+ *
100
+ * Split out of `replaceScalar` so that "is this path present?" is answerable
101
+ * WITHOUT writing. `mergeIntoTemplate` used to infer presence from
102
+ * `replaceScalar` returning a changed string, which conflates "absent" with
103
+ * "present and already equal to the value being written" — under that test a
104
+ * no-op write reads as a miss and the caller appends a duplicate mapping key.
105
+ * A presence question answered by a mutation's side effect is the shape to
106
+ * look for here; the file already carries two earlier rounds of it.
107
+ */
108
+ function findScalarLine(lines: readonly string[], sections: readonly string[], key: string | undefined): number {
71
109
  // The document's own width, the same read `upsertScalar` performs.
72
110
  //
73
111
  // R2 round 5, finding 1, and it is the second half of round 4's finding 7.
@@ -84,9 +122,7 @@ export function replaceScalar(template: string, dottedPath: string[], value: unk
84
122
  // A writer and its own existence-probe disagreeing about the format is the
85
123
  // shape to look for whenever one of a pair is taught something new.
86
124
  const width = detectIndentWidth(lines);
87
- const targetIndent = ' '.repeat(width * sections.length);
88
125
  const currentPath: (string | null)[] = new Array<string | null>(sections.length).fill(null);
89
- const formatted = formatScalar(value);
90
126
 
91
127
  for (let i = 0; i < lines.length; i++) {
92
128
  const line = lines[i];
@@ -98,7 +134,7 @@ export function replaceScalar(template: string, dottedPath: string[], value: unk
98
134
  const level = indentLen / width;
99
135
  if (level > sections.length) continue;
100
136
 
101
- const m = /^([A-Za-z_][A-Za-z0-9_]*)\s*:/.exec(stripped);
137
+ const m = NESTED_KEY_RE.exec(stripped);
102
138
  if (!m) continue;
103
139
  const lineKey = m[1];
104
140
  if (lineKey === undefined) continue;
@@ -115,10 +151,9 @@ export function replaceScalar(template: string, dottedPath: string[], value: unk
115
151
  const parentsMatch = sections.every((s, idx) => currentPath[idx] === s);
116
152
  if (!parentsMatch) continue;
117
153
  if (lineKey !== key) continue;
118
- lines[i] = `${targetIndent}${key}: ${formatted}`;
119
- return lines.join('\n');
154
+ return i;
120
155
  }
121
- return template;
156
+ return -1;
122
157
  }
123
158
 
124
159
  /**
@@ -182,11 +217,21 @@ export function detectIndentWidth(lines: readonly string[]): number {
182
217
  */
183
218
  export function upsertScalar(template: string, dottedPath: string[], value: unknown): string {
184
219
  if (dottedPath.length === 0) return template;
185
- const replaced = replaceScalar(template, dottedPath, value);
186
- if (replaced !== template) return replaced;
187
-
188
220
  const sections = dottedPath.slice(0, -1);
189
221
  const key = dottedPath[dottedPath.length - 1] as string;
222
+
223
+ // Presence is asked, never inferred from the write changing the text. The
224
+ // string compare that used to stand here read "key already holds this
225
+ // value" as "key absent" and fell through to the append below — so saving
226
+ // the wizard's council page twice WITHOUT changing a toggle wrote a second
227
+ // `api_on_quota:` into the same block, over two `{ok: true}` responses, and
228
+ // left `.ai-council.yml` rejected by a strict parser. Third instance of one
229
+ // shape in this file; `findScalarLine` exists to end it, and a fix that
230
+ // taught only `mergeIntoTemplate` would have left the live path here.
231
+ if (findScalarLine(template.split('\n'), sections, key) !== -1) {
232
+ return replaceScalar(template, dottedPath, value);
233
+ }
234
+
190
235
  const formatted = formatScalar(value);
191
236
  const lines = template.split('\n');
192
237
  // The document's OWN indent width, not an assumed two.
@@ -228,7 +273,7 @@ export function upsertScalar(template: string, dottedPath: string[], value: unkn
228
273
  if (line.trim() === '' || line.trim().startsWith('#')) continue;
229
274
  const indentLen = line.length - line.trimStart().length;
230
275
  if (indentLen !== indent.length) continue;
231
- const m = /^([A-Za-z_][A-Za-z0-9_]*)\s*:/.exec(line.trim());
276
+ const m = NESTED_KEY_RE.exec(line.trim());
232
277
  if (m !== null && m[1] === want) {
233
278
  found = i;
234
279
  break;
@@ -269,23 +314,88 @@ export function upsertScalar(template: string, dottedPath: string[], value: unkn
269
314
  return lines.join('\n');
270
315
  }
271
316
 
317
+ /**
318
+ * Rewrite an existing top-level `a.b.c: value` line — the FLAT form
319
+ * `mergeIntoTemplate` itself appends when a path has no template entry.
320
+ * Returns the template unchanged when no such line exists.
321
+ *
322
+ * `replaceScalar` cannot see this line: its key pattern is
323
+ * `[A-Za-z_][A-Za-z0-9_]*`, with no dot, so a path this module appended on
324
+ * one pass is invisible to the probe on the next and `mergeIntoTemplate`
325
+ * appends a SECOND copy. Two wizard saves therefore produce a file carrying
326
+ * duplicate mapping keys — a strict parser rejects it outright, which is what
327
+ * wedged `task release`: its `sync` step exits 2 with "Map keys must be
328
+ * unique" before the release does any git work.
329
+ *
330
+ * A pre-existing duplicate run is COLLAPSED, not merely updated: the first
331
+ * occurrence keeps its position and takes the new value, every later one is
332
+ * dropped. Collapsing is value-neutral — a lenient reader already resolved
333
+ * such a run last-wins — and it is the only way a file corrupted by the old
334
+ * behaviour becomes parseable again through the ordinary write path.
335
+ *
336
+ * Deliberately NOT folded into `replaceScalar`: `upsertScalar` calls that
337
+ * probe and documents the flat form as the WRONG shape for a nested key (a
338
+ * reader sees one key literally named "a.b"). Teaching the shared probe to
339
+ * match it would make `upsertScalar` write into that broken line instead of
340
+ * creating the nesting. The flat form is legitimate only here, where this
341
+ * function is the one that wrote it.
342
+ */
343
+ function replaceFlatDottedKey(
344
+ template: string,
345
+ dottedPath: string[],
346
+ value: unknown,
347
+ ): { found: boolean; body: string } {
348
+ if (dottedPath.length < 2) return { found: false, body: template };
349
+ const flat = dottedPath.join('.');
350
+ const lines = template.split('\n');
351
+ const hits: number[] = [];
352
+ for (let i = 0; i < lines.length; i++) {
353
+ const line = lines[i];
354
+ if (line === undefined) continue;
355
+ // Top level only — an indented `a.b:` is somebody else's child key.
356
+ if (line.length !== line.trimStart().length) continue;
357
+ const m = /^([A-Za-z_][A-Za-z0-9_.-]*)\s*:/.exec(line);
358
+ if (m === null || m[1] !== flat) continue;
359
+ hits.push(i);
360
+ }
361
+ if (hits.length === 0) return { found: false, body: template };
362
+ lines[hits[0] as number] = `${flat}: ${formatScalar(value)}`;
363
+ // Drop the later copies back-to-front so the earlier indices stay valid.
364
+ for (let i = hits.length - 1; i >= 1; i--) lines.splice(hits[i] as number, 1);
365
+ return { found: true, body: lines.join('\n') };
366
+ }
367
+
272
368
  /**
273
369
  * Apply every leaf change from `newValues` to `templateBody`. Paths that
274
370
  * are not present in the template are appended at the end. Comments are
275
371
  * preserved for every key that already exists in the template.
372
+ *
373
+ * Idempotent: applying the same values twice yields the same document, so a
374
+ * second wizard save re-writes the flat block instead of duplicating it.
276
375
  */
277
376
  export function mergeIntoTemplate(templateBody: string, newValues: Record<string, unknown>): string {
278
377
  let body = templateBody;
279
378
  const appended: string[] = [];
280
379
  for (const entry of flatten(newValues)) {
281
- const before = body;
282
- body = replaceScalar(body, entry.path, entry.value);
283
- if (body === before) {
284
- // Path is not in the template — append a flat key=value at EOF.
285
- // Form coverage is asserted by parity test, so this branch only
286
- // fires on hand-edited templates.
287
- appended.push(`${entry.path.join('.')}: ${formatScalar(entry.value)}`);
380
+ const sections = entry.path.slice(0, -1);
381
+ const leaf = entry.path[entry.path.length - 1];
382
+ // Presence is asked, never inferred from the write changing the text —
383
+ // see `findScalarLine`. Writing the value it already holds is a hit.
384
+ if (findScalarLine(body.split('\n'), sections, leaf) !== -1) {
385
+ body = replaceScalar(body, entry.path, entry.value);
386
+ continue;
387
+ }
388
+ // Not in the template's nested form. It may still be here as a flat
389
+ // dotted line this function appended on an earlier pass.
390
+ const flat = replaceFlatDottedKey(body, entry.path, entry.value);
391
+ if (flat.found) {
392
+ body = flat.body;
393
+ continue;
288
394
  }
395
+ // Genuinely absent — append a flat key=value at EOF. Form coverage is
396
+ // asserted by parity test, so this branch only fires on hand-edited
397
+ // templates.
398
+ appended.push(`${entry.path.join('.')}: ${formatScalar(entry.value)}`);
289
399
  }
290
400
  if (appended.length > 0) {
291
401
  if (!body.endsWith('\n')) body += '\n';
@@ -173,7 +173,7 @@ export const settingsSchema = z.object({
173
173
  }),
174
174
  chat_history: z.object({
175
175
  enabled: z.boolean().default(true).describe(
176
- 'Persist a structured log of every chat turn to .agent-config/chat-history/ so /chat-history:show, :import, and :learn can replay sessions. Turn off if you never want chat transcripts on disk.',
176
+ 'Persist a structured log of every chat turn to agents/runtime/.agent-chat-history (JSONL). Recording only — no command in this package replays a session from the file; it is a record on disk that a human or a later tool can read. Turn off if you never want chat transcripts on disk.',
177
177
  ),
178
178
  frequency: chatFreq.default('per_turn').describe(
179
179
  'How often the chat-history writer flushes to disk. per_turn = after every user / agent exchange (default, lowest data loss on crash). per_phase = at phase boundaries (cheaper I/O). per_tool = after every tool call (highest fidelity, noisiest log).',
@@ -247,7 +247,27 @@ export const settingsSchema = z.object({
247
247
  fidelity_mode: fidelityMode.default('strict').describe(
248
248
  'How strictly the agent must follow a user-provided prototype / mockup / design system (consumed by the design-fidelity rule). strict = build 1:1, every visible deviation needs confirmation; structural = structure locked, silent gaps fillable with a stated assumption; hard-floor = any deviation is never autonomous.',
249
249
  ),
250
- }).default({ fidelity_mode: 'strict' }),
250
+ approximation: z
251
+ .object({
252
+ enabled: z.boolean().default(false).describe(
253
+ 'Whether a value INSIDE the configured tolerance may be replaced by the project\'s own token without a per-value confirmation. Ships false. The distance from the artifact\'s value to the nearest project token is computed and REPORTED on every value row regardless of this switch — what it decides is whether the agent may act on it without asking. Turning it on for a whole install is a consumer-facing default flip and is owner-reserved (agents/roadmaps/stubs/road-to-frontend-power-default-flip.md); design.fidelity_mode: hard-floor overrides it to off.',
254
+ ),
255
+ tolerance: z
256
+ .object({
257
+ color: z.number().nullable().default(null).describe(
258
+ 'Colour distance below which a value may be reconciled, in OKLab ΔEOK. Ships null, and null means "preserve and report" rather than "reconcile freely". No number is set because none is supported: zero of seven design-to-code benchmarks in the evidence set score token conformance, and a number in a config file — even flagged unmeasured — shapes behavior and acquires the authority of a default. Not an RGB channel delta: RGB distance is not perceptually uniform.',
259
+ ),
260
+ length: z.number().nullable().default(null).describe(
261
+ 'Length distance below which a value may be reconciled, in absolute CSS pixels. Ships null, same reasoning as color. GitHub Primer\'s ±1px spacing widening is recorded as an externally observed candidate in src/scripts/_lib/design_tolerance.ts and is deliberately not the shipped value; max(1px, 2%) was rejected for growing more permissive at large dimensions with no evidence that this is wanted.',
262
+ ),
263
+ })
264
+ .default({ color: null, length: null }),
265
+ })
266
+ .default({ enabled: false, tolerance: { color: null, length: null } }),
267
+ }).default({
268
+ fidelity_mode: 'strict',
269
+ approximation: { enabled: false, tolerance: { color: null, length: null } },
270
+ }),
251
271
  consistency: z.object({
252
272
  cross_source: crossSourceMode.default('on').describe(
253
273
  'Consumed by the cross-source-consistency rule. When the agent works from multiple sources (ticket text, an attached image/mockup, the spec, the codebase) it checks them against each other and asks before proceeding on a discrepancy — instead of silently guessing. on (default) = surface every real cross-source contradiction / silent-scope-expansion as one question; auto = surface only high-confidence contradictions, state low-confidence as an assumption; off = no cross-source checking.',
@@ -450,11 +470,11 @@ export const settingsSchema = z.object({
450
470
  }),
451
471
  }),
452
472
  continuity: z.object({
453
- auto_record: z.enum(['on', 'off']).default('off').describe(
454
- 'Deterministic continuity-record writer at session end (road-to-continuity-writer-activation Phase 1). on = the session-eol concern writes the continuity_record capsule variant on Stop for a substantive session that has claimed a roadmap; every field is computed from on-disk state, with no model spend and no subprocess. off (default) = no automatic record — while session:recycle is still the normal path, a second producer on it before the parity evidence is in would be unverified.',
473
+ auto_record: z.enum(['on', 'off']).default('on').describe(
474
+ 'Deterministic continuity-record writer at session end (road-to-continuity-writer-activation Phase 1). on (default since 2026-09-10) = the session-eol concern writes the continuity_record capsule variant on Stop for a substantive session that has claimed a roadmap; every field is computed from on-disk state, with no model spend and no subprocess. off = no automatic record, and only the explicit agent-config session:recycle writes one. The default flipped once the parity evidence landed, so the normal path carries an automatic writer rather than a remembered command; session:recycle is retained because the automatic writer skips a session that claimed no roadmap and omits the git-status anchors.',
455
475
  ),
456
476
  run_checkpoints: z.enum(['on', 'off']).default('on').describe(
457
- 'Deterministic run-checkpoint production at session end (road-to-continuity-writer-activation Phase 1.4). on (default) = a session above the recycle threshold and inside a running roadmap contract leaves agents/runtime/state/checkpoints/<run>.json, so a killed run resumes from a derived checkpoint rather than from bookkeeping. off = no checkpoint; continuity writing, the recycle advisory and the context-fill surface are unaffected. Default ON because it is the behaviour the tree already had — the switch exists to make the three session-end handlers independently disableable, not to change what ships.',
477
+ 'Deterministic run-checkpoint production at session end (road-to-continuity-writer-activation Phase 1.4). on (default) = a session above the recycle threshold and inside a running roadmap contract leaves agents/runtime/state/checkpoints/<run>.json, so a killed run resumes from a derived checkpoint rather than from bookkeeping. off = no checkpoint; continuity writing, the recycle advisory and the context-fill surface are unaffected. Default ON because it is the behavior the tree already had — the switch exists to make the three session-end handlers independently disableable, not to change what ships.',
458
478
  ),
459
479
  }),
460
480
  memory: z.object({
@@ -23,7 +23,13 @@ export type JsonLike =
23
23
  | { [key: string]: JsonLike };
24
24
 
25
25
  export interface SurfaceEntry {
26
- type: string;
26
+ /**
27
+ * JSON-schema type. A STRING for an ordinary key, and an ARRAY for a
28
+ * nullable one (`["number", "null"]`) — which is what the generator has
29
+ * always produced for a `z.number().nullable()`, though this field was
30
+ * declared `string` until the first such key shipped.
31
+ */
32
+ type: string | string[];
27
33
  default?: JsonLike;
28
34
  enum?: Array<string | number>;
29
35
  description?: string;
@@ -129,7 +135,12 @@ export function computeSurfaceDelta(oldS: SettingsSurface, newS: SettingsSurface
129
135
  continue;
130
136
  }
131
137
  const o = oldS.entries[key] as SurfaceEntry;
132
- if (o.type !== n.type) {
138
+ // `sameJson`, not `!==`: `type` is an array for a nullable key, and two
139
+ // structurally equal arrays are never `===`. Left as a strict compare
140
+ // it reported `type_changed` on EVERY version bump for every nullable
141
+ // key, forever — caught by the identical-surface test the moment the
142
+ // first one shipped (`design.approximation.tolerance.*`).
143
+ if (!sameJson(o.type, n.type)) {
133
144
  changes.push({ key, kind: 'type_changed', old: o, new: n });
134
145
  }
135
146
  if (!sameJson(o.default, n.default)) {
@@ -1,9 +1,14 @@
1
1
  # Compact-instructions template — lose less when you compact anyway
2
2
 
3
3
  Fallback lane of `road-to-token-economy-recycling` Phase 3.3. The recycle
4
- envelope (`agent-config session:recycle` + `/clear`) is the primary path —
5
- validated state, no summarizer. This template is for the user who compacts
6
- instead: Claude Code honours a `# Compact instructions` section in the
4
+ envelope is the primary path — validated state, no summarizer — and since
5
+ 2026-09-10 it is written for you: the `session-eol` concern publishes the
6
+ continuity record at session end by default (`continuity.auto_record`), so
7
+ `/clear` resumes from it with no command to remember.
8
+ `agent-config session:recycle` still exists as an explicit affordance for the
9
+ cases the automatic writer does not cover (a session that claimed no roadmap, or
10
+ a record that should carry the `git status` anchors). This template is for the
11
+ user who compacts instead: Claude Code honours a `# Compact instructions` section in the
7
12
  project's `CLAUDE.md` when it summarizes (see the "Manage context
8
13
  proactively" section of the host's cost docs), and `/compact <instructions>`
9
14
  accepts the same guidance ad hoc.