@event4u/agent-config 9.9.0 → 9.10.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 (384) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +153 -194
  3. package/README.md +1 -1
  4. package/dist/agent-src/commands/agent-status.md +12 -11
  5. package/dist/agent-src/commands/agents/user/accept.md +115 -30
  6. package/dist/agent-src/commands/agents/user/delete.md +147 -0
  7. package/dist/agent-src/commands/agents/user/review.md +65 -28
  8. package/dist/agent-src/commands/agents/user/show.md +82 -29
  9. package/dist/agent-src/commands/agents/user/update.md +2 -2
  10. package/dist/agent-src/commands/agents/user.md +16 -3
  11. package/dist/agent-src/commands/check-current-md.md +0 -10
  12. package/dist/agent-src/commands/condense.md +28 -97
  13. package/dist/agent-src/commands/cost/report.md +7 -0
  14. package/dist/agent-src/commands/fix/portability.md +0 -8
  15. package/dist/agent-src/commands/fix/refs.md +0 -8
  16. package/dist/agent-src/commands/humanize.md +2 -2
  17. package/dist/agent-src/commands/jira-ticket.md +1 -1
  18. package/dist/agent-src/commands/memory/load.md +11 -0
  19. package/dist/agent-src/commands/optimize/skills.md +1 -1
  20. package/dist/agent-src/commands/pr/create.md +4 -4
  21. package/dist/agent-src/commands/profile/show.md +3 -3
  22. package/dist/agent-src/commands/rule-compliance-audit.md +5 -4
  23. package/dist/agent-src/commands/security-audit-config.md +1 -1
  24. package/dist/agent-src/commands/sync/gitignore/fix.md +1 -1
  25. package/dist/agent-src/commands/tdd.md +21 -0
  26. package/dist/agent-src/commands/team/delegate.md +2 -2
  27. package/dist/agent-src/commands/team-knowledge/bootstrap.md +9 -8
  28. package/dist/agent-src/commands/team-knowledge/consolidate.md +8 -7
  29. package/dist/agent-src/commands/team.md +4 -1
  30. package/dist/agent-src/commands/upstream-contribute.md +0 -1
  31. package/dist/agent-src/contexts/authority/commit-mechanics.md +7 -0
  32. package/dist/agent-src/contexts/authority/destructive-mechanics.md +16 -0
  33. package/dist/agent-src/contexts/authority/scope-mechanics.md +61 -0
  34. package/dist/agent-src/contexts/communication/rules-auto/reply-close-mechanics.md +20 -18
  35. package/dist/agent-src/contexts/communication/rules-auto/skill-quality-mechanics.md +1 -1
  36. package/dist/agent-src/contexts/communication/rules-auto/slash-command-routing-policy-mechanics.md +1 -1
  37. package/dist/agent-src/contexts/communication/rules-auto/source-of-truth-mechanics.md +9 -10
  38. package/dist/agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +1 -1
  39. package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +11 -0
  40. package/dist/agent-src/contexts/contracts/artifact-engagement-flow.md +26 -15
  41. package/dist/agent-src/contexts/contracts/command-suggestion-flow.md +3 -0
  42. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +67 -37
  43. package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +1 -1
  44. package/dist/agent-src/contexts/execution/autonomy-examples.md +1 -1
  45. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +7 -6
  46. package/dist/agent-src/contexts/execution/evidence-discipline.md +92 -77
  47. package/dist/agent-src/contexts/execution/orchestration-benchmark-gate.md +22 -21
  48. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +24 -0
  49. package/dist/agent-src/contexts/execution/project-intelligence.md +122 -103
  50. package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +23 -21
  51. package/dist/agent-src/contexts/execution/subagent-response-contract.md +5 -5
  52. package/dist/agent-src/contexts/execution/subagent-routing.md +11 -0
  53. package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +13 -13
  54. package/dist/agent-src/contexts/execution/subagent-steering.md +36 -34
  55. package/dist/agent-src/contexts/execution/toolchain-resolver.md +1 -1
  56. package/dist/agent-src/contexts/execution/user-memory-channels.md +189 -0
  57. package/dist/agent-src/contexts/execution/verification-mechanics.md +9 -0
  58. package/dist/agent-src/contexts/model-recommendations.md +3 -2
  59. package/dist/agent-src/contexts/subagent-configuration.md +3 -3
  60. package/dist/agent-src/ghostwriter/README.md +2 -2
  61. package/dist/agent-src/packs/README.md +1 -1
  62. package/dist/agent-src/personas/README.md +5 -5
  63. package/dist/agent-src/personas/_template-specialist/persona.md +3 -3
  64. package/dist/agent-src/personas/backend-architect.md +24 -23
  65. package/dist/agent-src/personas/eloquent-tamer.md +30 -30
  66. package/dist/agent-src/personas/frontend-engineer.md +30 -30
  67. package/dist/agent-src/personas/product-owner.md +41 -38
  68. package/dist/agent-src/personas/qa.md +13 -13
  69. package/dist/agent-src/personas/security-engineer.md +22 -22
  70. package/dist/agent-src/presets/README.md +3 -3
  71. package/dist/agent-src/profiles/README.md +3 -3
  72. package/dist/agent-src/rules/active-remediation.md +2 -2
  73. package/dist/agent-src/rules/artifact-engagement-recording.md +4 -4
  74. package/dist/agent-src/rules/ask-when-uncertain.md +0 -4
  75. package/dist/agent-src/rules/augment-edit-discipline.md +1 -1
  76. package/dist/agent-src/rules/broken-access-control.md +4 -4
  77. package/dist/agent-src/rules/code-comment-discipline.md +1 -1
  78. package/dist/agent-src/rules/code-provenance.md +2 -2
  79. package/dist/agent-src/rules/commit-policy.md +4 -9
  80. package/dist/agent-src/rules/communication-through-line.md +5 -5
  81. package/dist/agent-src/rules/decision-revisit-gate.md +1 -1
  82. package/dist/agent-src/rules/doc-screenshot-hygiene.md +1 -1
  83. package/dist/agent-src/rules/domain-adoption-policy.md +1 -1
  84. package/dist/agent-src/rules/domain-safety-pii.md +1 -1
  85. package/dist/agent-src/rules/external-code-graph-interop.md +1 -1
  86. package/dist/agent-src/rules/fast-path-marker-visibility.md +43 -7
  87. package/dist/agent-src/rules/finance-safety-floor.md +1 -1
  88. package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +3 -3
  89. package/dist/agent-src/rules/history-discipline.md +1 -1
  90. package/dist/agent-src/rules/legal-safety-floor.md +1 -1
  91. package/dist/agent-src/rules/lethal-trifecta-guard.md +5 -5
  92. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +10 -8
  93. package/dist/agent-src/rules/media-governance-routing.md +13 -13
  94. package/dist/agent-src/rules/media-sync-ground-truth.md +29 -11
  95. package/dist/agent-src/rules/no-cheap-questions.md +7 -7
  96. package/dist/agent-src/rules/no-decorative-emojis-in-git-surfaces.md +29 -18
  97. package/dist/agent-src/rules/non-destructive-by-default.md +5 -23
  98. package/dist/agent-src/rules/onboarding-gate.md +14 -4
  99. package/dist/agent-src/rules/persona-governance.md +2 -2
  100. package/dist/agent-src/rules/prefer-enums-over-literals.md +9 -9
  101. package/dist/agent-src/rules/preservation-guard.md +10 -2
  102. package/dist/agent-src/rules/question-not-instruction.md +1 -1
  103. package/dist/agent-src/rules/roadmap-progress-sync.md +6 -6
  104. package/dist/agent-src/rules/rule-type-governance.md +1 -1
  105. package/dist/agent-src/rules/scale-discipline.md +1 -1
  106. package/dist/agent-src/rules/scope-control.md +1 -1
  107. package/dist/agent-src/rules/secret-vcs-guard.md +1 -1
  108. package/dist/agent-src/rules/security-sensitive-stop.md +17 -11
  109. package/dist/agent-src/rules/senior-engineering-discipline.md +17 -17
  110. package/dist/agent-src/rules/session-canary.md +1 -1
  111. package/dist/agent-src/rules/skill-quality.md +1 -1
  112. package/dist/agent-src/rules/source-confidentiality.md +21 -19
  113. package/dist/agent-src/rules/source-of-truth.md +6 -5
  114. package/dist/agent-src/rules/spreadsheet-source-quality.md +2 -2
  115. package/dist/agent-src/rules/strategy-safety-floor.md +1 -1
  116. package/dist/agent-src/rules/think-before-action.md +5 -5
  117. package/dist/agent-src/rules/token-budget-discipline.md +5 -3
  118. package/dist/agent-src/rules/token-efficiency.md +2 -2
  119. package/dist/agent-src/rules/untrusted-input-defense.md +4 -4
  120. package/dist/agent-src/rules/user-interaction.md +12 -0
  121. package/dist/agent-src/rules/verify-before-complete.md +4 -24
  122. package/dist/agent-src/skills/adversarial-review/SKILL.md +2 -2
  123. package/dist/agent-src/skills/agent-security-review/SKILL.md +52 -45
  124. package/dist/agent-src/skills/agents-md-thin-root/SKILL.md +2 -2
  125. package/dist/agent-src/skills/ai-code-blindspots/SKILL.md +20 -20
  126. package/dist/agent-src/skills/ai-council/SKILL.md +185 -128
  127. package/dist/agent-src/skills/analysis-skill-router/SKILL.md +3 -3
  128. package/dist/agent-src/skills/api-endpoint/SKILL.md +2 -2
  129. package/dist/agent-src/skills/artisan-commands/SKILL.md +2 -2
  130. package/dist/agent-src/skills/async-python-patterns/SKILL.md +8 -7
  131. package/dist/agent-src/skills/authz-review/SKILL.md +16 -15
  132. package/dist/agent-src/skills/aws-infrastructure/SKILL.md +5 -5
  133. package/dist/agent-src/skills/blade-ui/SKILL.md +1 -1
  134. package/dist/agent-src/skills/bug-analyzer/SKILL.md +30 -12
  135. package/dist/agent-src/skills/code-intelligence/SKILL.md +0 -1
  136. package/dist/agent-src/skills/code-intelligence/evals/triggers.json +52 -0
  137. package/dist/agent-src/skills/code-review/SKILL.md +6 -6
  138. package/dist/agent-src/skills/command-routing/SKILL.md +5 -4
  139. package/dist/agent-src/skills/composer-packages/SKILL.md +2 -2
  140. package/dist/agent-src/skills/condense-memory/SKILL.md +43 -41
  141. package/dist/agent-src/skills/context-document/SKILL.md +23 -21
  142. package/dist/agent-src/skills/copilot-config/SKILL.md +6 -1
  143. package/dist/agent-src/skills/decision-record/SKILL.md +20 -18
  144. package/dist/agent-src/skills/design-intelligence/SKILL.md +4 -4
  145. package/dist/agent-src/skills/design-review/SKILL.md +15 -14
  146. package/dist/agent-src/skills/design-system-capture/reference/design-system-json.md +28 -25
  147. package/dist/agent-src/skills/devcontainer/SKILL.md +2 -2
  148. package/dist/agent-src/skills/docker/SKILL.md +1 -1
  149. package/dist/agent-src/skills/docx-authoring/evals/triggers.json +52 -0
  150. package/dist/agent-src/skills/emit-tickets/SKILL.md +2 -2
  151. package/dist/agent-src/skills/error-handling-patterns/SKILL.md +1 -1
  152. package/dist/agent-src/skills/estimate-ticket/SKILL.md +2 -2
  153. package/dist/agent-src/skills/evaluate-llm-feature/SKILL.md +0 -1
  154. package/dist/agent-src/skills/evaluate-llm-feature/evals/triggers.json +52 -0
  155. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +6 -5
  156. package/dist/agent-src/skills/feature-planning/SKILL.md +2 -2
  157. package/dist/agent-src/skills/finishing-a-development-branch/SKILL.md +3 -3
  158. package/dist/agent-src/skills/flux/SKILL.md +1 -1
  159. package/dist/agent-src/skills/frontend-render-security/SKILL.md +13 -13
  160. package/dist/agent-src/skills/git-workflow/SKILL.md +33 -33
  161. package/dist/agent-src/skills/grafana/SKILL.md +5 -0
  162. package/dist/agent-src/skills/humanizer/SKILL.md +83 -66
  163. package/dist/agent-src/skills/humanizer/data/patterns.md +14 -14
  164. package/dist/agent-src/skills/humanizer/references/anti-aiisms.md +4 -4
  165. package/dist/agent-src/skills/judge-artifact-completeness/SKILL.md +82 -37
  166. package/dist/agent-src/skills/judge-bug-hunter/SKILL.md +17 -5
  167. package/dist/agent-src/skills/judge-injection-defense/SKILL.md +63 -25
  168. package/dist/agent-src/skills/judge-security-auditor/SKILL.md +2 -0
  169. package/dist/agent-src/skills/judge-synthesis/SKILL.md +72 -28
  170. package/dist/agent-src/skills/laravel-horizon/SKILL.md +5 -0
  171. package/dist/agent-src/skills/laravel-mail/SKILL.md +5 -4
  172. package/dist/agent-src/skills/laravel-middleware/SKILL.md +5 -4
  173. package/dist/agent-src/skills/laravel-notifications/SKILL.md +10 -9
  174. package/dist/agent-src/skills/laravel-pennant/SKILL.md +1 -1
  175. package/dist/agent-src/skills/laravel-pulse/SKILL.md +9 -8
  176. package/dist/agent-src/skills/laravel-reverb/SKILL.md +6 -5
  177. package/dist/agent-src/skills/laravel-scheduling/SKILL.md +6 -1
  178. package/dist/agent-src/skills/laravel-validation/SKILL.md +12 -0
  179. package/dist/agent-src/skills/launch-readiness/SKILL.md +1 -1
  180. package/dist/agent-src/skills/learning-to-rule-or-skill/SKILL.md +7 -7
  181. package/dist/agent-src/skills/learning-tutor/SKILL.md +44 -43
  182. package/dist/agent-src/skills/learning-tutor/evals/triggers.json +52 -0
  183. package/dist/agent-src/skills/license-compliance-audit/SKILL.md +7 -7
  184. package/dist/agent-src/skills/license-compliance-borrow-check/SKILL.md +3 -3
  185. package/dist/agent-src/skills/license-compliance-credits/SKILL.md +4 -4
  186. package/dist/agent-src/skills/llm-provider-knowledge/SKILL.md +0 -1
  187. package/dist/agent-src/skills/memory-consolidation/SKILL.md +69 -64
  188. package/dist/agent-src/skills/motion-choreographer/SKILL.md +6 -6
  189. package/dist/agent-src/skills/pdf-tools/evals/triggers.json +52 -0
  190. package/dist/agent-src/skills/performance-analysis/SKILL.md +7 -3
  191. package/dist/agent-src/skills/persona-improvement/SKILL.md +0 -2
  192. package/dist/agent-src/skills/persona-improvement/evals/triggers.json +52 -0
  193. package/dist/agent-src/skills/php-coder/SKILL.md +10 -0
  194. package/dist/agent-src/skills/prediction-pool-optimizer/SKILL.md +129 -120
  195. package/dist/agent-src/skills/prediction-pool-optimizer/reference/ev-fixtures.md +36 -35
  196. package/dist/agent-src/skills/prediction-pool-optimizer/reference/odds-and-bonus.md +45 -41
  197. package/dist/agent-src/skills/project-analysis-core/SKILL.md +4 -4
  198. package/dist/agent-src/skills/project-analysis-hypothesis-driven/SKILL.md +3 -3
  199. package/dist/agent-src/skills/project-analysis-react/SKILL.md +1 -1
  200. package/dist/agent-src/skills/project-analysis-symfony/SKILL.md +1 -1
  201. package/dist/agent-src/skills/project-analysis-zend-laminas/SKILL.md +2 -2
  202. package/dist/agent-src/skills/prompt-engineering-patterns/SKILL.md +1 -1
  203. package/dist/agent-src/skills/prompt-validator/SKILL.md +39 -39
  204. package/dist/agent-src/skills/quality-tools/SKILL.md +15 -15
  205. package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +55 -19
  206. package/dist/agent-src/skills/react-shadcn-ui/reference/registry.md +29 -27
  207. package/dist/agent-src/skills/readme-writing-package/SKILL.md +2 -2
  208. package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +25 -25
  209. package/dist/agent-src/skills/recursive-verification/SKILL.md +6 -5
  210. package/dist/agent-src/skills/refine-prompt/SKILL.md +1 -1
  211. package/dist/agent-src/skills/refine-ticket/SKILL.md +20 -2
  212. package/dist/agent-src/skills/repomix-packer/SKILL.md +1 -1
  213. package/dist/agent-src/skills/requesting-code-review/SKILL.md +2 -2
  214. package/dist/agent-src/skills/roadmap-management/SKILL.md +80 -66
  215. package/dist/agent-src/skills/roadmap-writing/SKILL.md +84 -77
  216. package/dist/agent-src/skills/rule-refactor/SKILL.md +1 -1
  217. package/dist/agent-src/skills/screenshot-hygiene/evals/triggers.json +52 -0
  218. package/dist/agent-src/skills/security-audit/SKILL.md +26 -9
  219. package/dist/agent-src/skills/security-maturity-assessment/SKILL.md +38 -16
  220. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +14 -11
  221. package/dist/agent-src/skills/skill-writing/SKILL.md +9 -8
  222. package/dist/agent-src/skills/song-to-script/SKILL.md +93 -85
  223. package/dist/agent-src/skills/sql-writing/SKILL.md +2 -2
  224. package/dist/agent-src/skills/stakeholder-tradeoff/SKILL.md +4 -3
  225. package/dist/agent-src/skills/standards-from-config/SKILL.md +61 -43
  226. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +22 -8
  227. package/dist/agent-src/skills/subagent-orchestration/prompts/README.md +28 -2
  228. package/dist/agent-src/skills/supply-chain-intake/SKILL.md +13 -13
  229. package/dist/agent-src/skills/systematic-debugging/SKILL.md +22 -21
  230. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
  231. package/dist/agent-src/skills/technical-specification/SKILL.md +5 -0
  232. package/dist/agent-src/skills/terraform/SKILL.md +2 -2
  233. package/dist/agent-src/skills/terragrunt/SKILL.md +8 -8
  234. package/dist/agent-src/skills/testing-anti-patterns/process-anti-patterns.md +1 -1
  235. package/dist/agent-src/skills/threat-modeling/SKILL.md +19 -13
  236. package/dist/agent-src/skills/universal-project-analysis/SKILL.md +1 -1
  237. package/dist/agent-src/skills/upstream-contribute/SKILL.md +0 -1
  238. package/dist/agent-src/skills/using-git-worktrees/SKILL.md +9 -9
  239. package/dist/agent-src/skills/validate-feature-fit/SKILL.md +3 -1
  240. package/dist/agent-src/skills/verify-completion-evidence/SKILL.md +4 -6
  241. package/dist/agent-src/skills/verify-repair-loop/SKILL.md +67 -61
  242. package/dist/agent-src/skills/video-director/SKILL.md +7 -7
  243. package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +3 -3
  244. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  245. package/dist/agent-src/templates/contexts/knowledge-pages.md +47 -45
  246. package/dist/agent-src/templates/contexts/lesson-card.md +2 -2
  247. package/dist/agent-src/templates/contexts/tenant-boundaries.md +2 -2
  248. package/dist/agent-src/templates/contexts.md +1 -1
  249. package/dist/agent-src/templates/features.md +1 -1
  250. package/dist/agent-src/templates/roadmaps.md +125 -116
  251. package/dist/agent-src/templates/rule.md +2 -1
  252. package/dist/agent-src/templates/scripts/pr_risk_review.ts +0 -0
  253. package/dist/agent-src/templates/scripts/telemetry/aggregator.ts +0 -0
  254. package/dist/agent-src/templates/scripts/tier_usage_report.ts +2 -2
  255. package/dist/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +2 -2
  256. package/dist/agent-src/templates/scripts/work_engine/directives/ui/review.ts +1 -1
  257. package/dist/agent-src/user-types/README.md +8 -8
  258. package/dist/agent-src/user-types/galabau-field-crew.md +34 -33
  259. package/dist/agent-src/user-types/metalworking-shop.md +35 -34
  260. package/dist/agent-src/user-types/truck-driver.md +22 -20
  261. package/dist/cli/python/workspace_roles.js +2 -2
  262. package/dist/cli/python/workspace_roles.js.map +1 -1
  263. package/dist/discovery/deprecation-report.md +1 -1
  264. package/dist/discovery/discovery-manifest.json +88 -62
  265. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  266. package/dist/discovery/discovery-manifest.summary.md +3 -3
  267. package/dist/discovery/orphan-report.md +1 -1
  268. package/dist/discovery/packs.json +6 -5
  269. package/dist/discovery/trust-report.md +2 -2
  270. package/dist/discovery/workspaces.json +5 -4
  271. package/dist/hooks/dispatch.js +186 -12
  272. package/dist/install/install.mjs +8 -0
  273. package/dist/mcp/registry-manifest.json +3 -3
  274. package/dist/mcp/server.json +2 -2
  275. package/dist/mcp/server.mjs +1199 -857
  276. package/dist/router.json +1 -1
  277. package/dist/scripts/_lib/compile_time_toggles.js +48 -0
  278. package/dist/scripts/_lib/compile_time_toggles.js.map +1 -0
  279. package/dist/scripts/condense.js +234 -217
  280. package/dist/scripts/condense.js.map +1 -1
  281. package/dist/server/schemas/settings.js +4 -0
  282. package/dist/server/schemas/settings.js.map +1 -1
  283. package/dist/shared/userMd/utils.js +30 -0
  284. package/dist/shared/userMd/utils.js.map +1 -1
  285. package/dist/ui/assets/index-BpSQt5vm.js.map +1 -1
  286. package/docs/guidelines/agent-infra/api-cost-levers.md +56 -5
  287. package/docs/guidelines/agent-infra/simplicity-and-goal-demos.md +93 -33
  288. package/package.json +1 -1
  289. package/src/agent-src/templates/scripts/pr_risk_review.ts +0 -0
  290. package/src/agent-src/templates/scripts/telemetry/aggregator.ts +0 -0
  291. package/src/agent-src/templates/scripts/tier_usage_report.ts +2 -2
  292. package/src/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +2 -2
  293. package/src/agent-src/templates/scripts/work_engine/directives/ui/review.ts +1 -1
  294. package/src/config/agent-settings.template.yml +32 -2
  295. package/src/config/ci-local-parity.yml +142 -0
  296. package/src/config/gate-coverage.yml +127 -0
  297. package/src/config/preamble-payload-budget.json +44 -0
  298. package/src/scripts/_cli/cmd_doctor.ts +78 -2
  299. package/src/scripts/_lib/agent_user_profile.ts +381 -0
  300. package/src/scripts/_lib/agents_overlay.ts +9 -0
  301. package/src/scripts/_lib/anchor_eval.ts +311 -0
  302. package/src/scripts/_lib/cc_transcript.ts +316 -0
  303. package/src/scripts/_lib/compile_time_toggles.ts +79 -0
  304. package/src/scripts/_lib/confusables.ts +101 -0
  305. package/src/scripts/_lib/duplicate_scope_census.ts +77 -0
  306. package/src/scripts/_lib/managed_agents_folder.ts +181 -0
  307. package/src/scripts/_lib/payload_hash_drift.ts +126 -0
  308. package/src/scripts/_lib/preservation_migration.ts +291 -0
  309. package/src/scripts/_lib/retrieval_sanitize.ts +166 -2
  310. package/src/scripts/_lib/scan_scope.ts +115 -0
  311. package/src/scripts/_lib/user_global_memory_audit.ts +187 -0
  312. package/src/scripts/_lib/user_global_observations.ts +830 -0
  313. package/src/scripts/_lib/user_global_revocations.ts +110 -0
  314. package/src/scripts/_lib/user_memory_gate_counters.ts +291 -0
  315. package/src/scripts/ai_council/blind_review.ts +0 -0
  316. package/src/scripts/ai_council/budget_guard.ts +90 -11
  317. package/src/scripts/ai_council/bundler.ts +2 -2
  318. package/src/scripts/ai_council/clients.ts +59 -3
  319. package/src/scripts/ai_council/config.ts +38 -0
  320. package/src/scripts/ai_council/orchestrator.ts +54 -9
  321. package/src/scripts/ai_team/team_dispatch.ts +17 -6
  322. package/src/scripts/anchor_eval_falsify.ts +198 -0
  323. package/src/scripts/anchor_scoring_run.ts +239 -0
  324. package/src/scripts/annotate_discovery.ts +1 -65
  325. package/src/scripts/bench_quality_run.ts +168 -4
  326. package/src/scripts/cache_realization_report.ts +782 -0
  327. package/src/scripts/check_artefact_count_messaging.ts +5 -1
  328. package/src/scripts/check_augment_description_cap.ts +15 -4
  329. package/src/scripts/check_ci_local_parity.ts +317 -0
  330. package/src/scripts/check_condensation.ts +96 -191
  331. package/src/scripts/check_context_paths.ts +38 -9
  332. package/src/scripts/check_gate_coverage.ts +299 -0
  333. package/src/scripts/check_iron_law_prominence.ts +22 -1
  334. package/src/scripts/check_kernel_prefix_stability.ts +0 -0
  335. package/src/scripts/check_no_new_legacy_path.ts +16 -6
  336. package/src/scripts/check_no_roadmap_refs.ts +22 -9
  337. package/src/scripts/check_originality_freshness.ts +167 -0
  338. package/src/scripts/check_preamble_payload_budget.ts +130 -0
  339. package/src/scripts/check_proposal.ts +0 -0
  340. package/src/scripts/check_references.ts +0 -1
  341. package/src/scripts/check_safety_floor_untouched.ts +171 -24
  342. package/src/scripts/check_site_links.ts +62 -3
  343. package/src/scripts/ci_status.ts +0 -0
  344. package/src/scripts/cluster_near_miss_patterns.ts +0 -0
  345. package/src/scripts/code_graph/build.ts +0 -0
  346. package/src/scripts/command_suggester/types.ts +0 -0
  347. package/src/scripts/compile_router.ts +10 -14
  348. package/src/scripts/condense.sh +3 -4
  349. package/src/scripts/condense.ts +247 -228
  350. package/src/scripts/cost/track.mjs +168 -33
  351. package/src/scripts/cost_summary.ts +33 -1
  352. package/src/scripts/council_cli.ts +45 -3
  353. package/src/scripts/discovery_graph.ts +0 -0
  354. package/src/scripts/encoding_corpus.ts +500 -0
  355. package/src/scripts/encoding_corpus_report.ts +216 -0
  356. package/src/scripts/generate_index.ts +19 -2
  357. package/src/scripts/install-hooks.sh +2 -2
  358. package/src/scripts/learning_sidecar.ts +0 -0
  359. package/src/scripts/lint_confusables.ts +10 -72
  360. package/src/scripts/lint_featured_skills.ts +0 -0
  361. package/src/scripts/lint_hidden_unicode.ts +208 -0
  362. package/src/scripts/lint_load_context.ts +17 -5
  363. package/src/scripts/lint_new_skill_gate.ts +9 -3
  364. package/src/scripts/lint_no_python_twin_rationale.ts +17 -2
  365. package/src/scripts/lint_originality.ts +1 -1
  366. package/src/scripts/lint_skill_originality.ts +1 -1
  367. package/src/scripts/lint_ticket_buildable.ts +1 -1
  368. package/src/scripts/mcp_server/tools.ts +4 -4
  369. package/src/scripts/measure_scope_dedup.ts +224 -0
  370. package/src/scripts/memory_lookup.ts +0 -0
  371. package/src/scripts/mine_session.ts +98 -0
  372. package/src/scripts/orchestration_payload_hash_drift.ts +145 -0
  373. package/src/scripts/preamble_byte_census.ts +574 -0
  374. package/src/scripts/probe_skill_registration.ts +2 -2
  375. package/src/scripts/redact_hook_capture.ts +13 -0
  376. package/src/scripts/release.ts +65 -10
  377. package/src/scripts/router_telemetry.ts +1 -1
  378. package/src/scripts/second_brain_retrieval.ts +14 -4
  379. package/src/scripts/skill_linter.ts +5 -0
  380. package/src/scripts/surface-tiers.yml +2 -0
  381. package/src/scripts/validate_pack_yaml.ts +0 -0
  382. package/src/server/schemas/settings.ts +8 -0
  383. package/src/shared/userMd/utils.ts +31 -0
  384. package/dist/agent-src/rules/telegraph-speak.md +0 -87
@@ -0,0 +1,830 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * The global observation buffer — the write path of ADR-138's sibling phase
4
+ * (road-to-global-user-memory Phase 2, "the learning channel"). Mirrors the
5
+ * project-local `.agent-user.observations.jsonl` contract
6
+ * (`docs/contracts/agent-user-schema.md § Observation buffer`) exactly, one
7
+ * level up: `~/.event4u/agent-config/user/observations.jsonl`.
8
+ *
9
+ * This module is the ONLY writer of that file. It is never read by the
10
+ * profile loader (`agent_user_profile.ts`) directly — only
11
+ * `/agents:user review` / `/agents:user accept` (and, through them,
12
+ * `applyObservationToGlobalProfile` in `agent_user_profile.ts`) may turn a
13
+ * buffered line into a change to `profile.md`. Append-only; nothing here
14
+ * ever rewrites `profile.md`.
15
+ *
16
+ * Capture-time guards (council cut, 2026-07-29 — see
17
+ * `agents/settings/contexts/global-user-memory-cut.md`): every candidate is
18
+ * checked BEFORE it is written, not filtered later at review. Four
19
+ * independently-testable classes, in the order they run:
20
+ *
21
+ * 1. `standing_command` — a verbatim standing directive ("always fetch
22
+ * <url> on every message") stored as memory becomes a durable
23
+ * injection that re-fires forever — and now in every project, not
24
+ * just one.
25
+ * 2. `self_harmful_preference` — a preference that would disable honest
26
+ * feedback ("never criticize me", "always agree with me") is
27
+ * surfaced, never stored, per `direct-answers`.
28
+ * 3. `exclusion_list` — the `.agent-user.md` explicit-exclusions list
29
+ * (credentials, third-party names/birthdays, financial figures,
30
+ * health/legal/therapy status, demographics, external-source
31
+ * identifiers) refused at CAPTURE, not at review. Rejecting the same
32
+ * class fifty times at review is the noise problem this avoids.
33
+ * 4. `hidden_unicode` — invisible-character identifier smuggling
34
+ * (the ADR-103 zero-width-smuggling class). Same gate as (3) — both
35
+ * come from one call to `knowledge_global_redaction.redaction_scan`
36
+ * — but reported as its own category so a test can pin it
37
+ * independently.
38
+ *
39
+ * The third persist-time guard the miner already documents — the
40
+ * derivability check ("if git/config answers it, store the surprising
41
+ * part") — is a judgment call the agent makes before a candidate ever
42
+ * reaches this module; it is not a deterministic function here (see
43
+ * `memory-consolidation` SKILL.md § Global user-scoped channel).
44
+ *
45
+ * The ≤5-normalised-facts-per-cycle gate applies GLOBALLY across the
46
+ * project-scoped intake channel (`mine_session.ts`) and this one — see
47
+ * `applySharedFactCap`. Enforcing it per-channel would let the second
48
+ * channel double the write volume the cap was meant to bound.
49
+ *
50
+ * Pure except for `appendGlobalObservation` and `readGlobalObservations`,
51
+ * which are the only functions that touch disk. Every disk path is
52
+ * resolved via `user_global_paths.ts`, honouring `$EVENT4U_CONFIG_HOME`
53
+ * exactly like every other artefact under the global root — tests MUST
54
+ * inject that variable rather than touch the real `~/.event4u/`.
55
+ *
56
+ * Phase 3 (project attribution, below the read path) adds `routeProjectObservation`
57
+ * — the Phase 0 managed/unmanaged/not-a-project predicate wired as a router
58
+ * for a project-scoped fact that has no managed `agents/` folder to live in.
59
+ * It writes through the SAME `appendGlobalObservation` guard pipeline above,
60
+ * plus a `context` object (`project_path`/`project_name`/`first_seen`) and a
61
+ * `seen_count`/`seen_in[]` recurrence tally — the only generalisation path
62
+ * into the durable profile (`findPromotionCandidates`,
63
+ * `promotionValueFor`). No project-indexed directory is ever created; see
64
+ * `tests/lib/user_global_observations_project_attribution.test.ts`.
65
+ *
66
+ * Phase 4 (delete, revoke, audit) adds `deleteGlobalObservation` and
67
+ * `purgeProjectContext` — the delete counterpart of `appendGlobalObservation`
68
+ * / `routeProjectObservation` above. Both write an append-only tombstone to
69
+ * `user_global_revocations.ts`'s ledger BEFORE the buffer is rewritten
70
+ * without the removed line(s), reusing ADR-121's tombstone-before-deletion
71
+ * discipline (see that module's docstring for the one necessary adaptation).
72
+ * `observationId` supplies the stable identifier neither the JSONL schema
73
+ * nor a knowledge card's `card_id_from` slug has an analogue for.
74
+ */
75
+ import * as crypto from 'node:crypto';
76
+ import * as fs from 'node:fs';
77
+ import * as path from 'node:path';
78
+
79
+ import { redaction_scan } from './knowledge_global_redaction.js';
80
+ import { detect_managed_agents_folder } from './managed_agents_folder.js';
81
+ import type { ManagedAgentsFolderStatus } from './managed_agents_folder.js';
82
+ import { classifySimilarity, jaccardSimilarity } from './text_similarity.js';
83
+ import * as revocations from './user_global_revocations.js';
84
+ import type { RevocationEntry } from './user_global_revocations.js';
85
+ import * as user_global_paths from './user_global_paths.js';
86
+ import { recordObservationProposed } from './user_memory_gate_counters.js';
87
+
88
+ /** Relative-to-root path of the global observation buffer. */
89
+ export const GLOBAL_OBSERVATIONS_RELATIVE = path.join('user', 'observations.jsonl');
90
+
91
+ /**
92
+ * Shared cap across BOTH channels (the project-scoped miner and this one).
93
+ * Duplicated from `mine_session.MAX_FACTS` deliberately rather than
94
+ * imported — `_lib/` modules do not import from `src/scripts/*.ts` CLI
95
+ * entrypoints, keeping the dependency direction one-way. Keep the two
96
+ * constants in sync; a test in `tests/lib/user_global_observations.test.ts`
97
+ * would need updating (not silently pass) if they diverge.
98
+ */
99
+ export const SHARED_FACT_CAP = 5;
100
+
101
+ /** Allowed `field` values — mirrors `docs/contracts/agent-user-schema.md § Observation buffer`. */
102
+ export const ALLOWED_OBSERVATION_FIELDS = [
103
+ 'identity.name',
104
+ 'language',
105
+ 'role',
106
+ 'style.pace',
107
+ 'voice_sample',
108
+ 'notes',
109
+ ] as const;
110
+
111
+ export type ObservationField = (typeof ALLOWED_OBSERVATION_FIELDS)[number];
112
+
113
+ const _ALLOWED_FIELD_SET: ReadonlySet<string> = new Set(ALLOWED_OBSERVATION_FIELDS);
114
+
115
+ /**
116
+ * Project-attribution footer (road-to-global-user-memory Phase 3). Present
117
+ * only on an observation routed here because its project has no managed
118
+ * `agents/` folder (see `routeProjectObservation`, below) — a pure
119
+ * user-attribute observation (Phase 2's `mineUserObservationCandidates`
120
+ * channel) never carries one. Mirrors the semantics of the global knowledge
121
+ * card's `seen_in` provenance footer (`knowledge_global.ts`) rather than
122
+ * inventing a parallel primitive — this is a field on a user observation,
123
+ * never a project-shaped artefact of its own (the council's round-2
124
+ * namespace refusal; see `agents/settings/contexts/global-user-memory-cut.md`).
125
+ */
126
+ export interface ObservationContext {
127
+ readonly project_path: string;
128
+ readonly project_name: string;
129
+ /** ISO timestamp of the FIRST sighting — never updated on recurrence. */
130
+ readonly first_seen: string;
131
+ }
132
+
133
+ /**
134
+ * One candidate/buffered observation. Shape mirrors the project-local
135
+ * buffer's JSONL line, plus the optional Phase 3 project-attribution
136
+ * fields. `context`/`seen_count`/`seen_in` are present together or not at
137
+ * all — set only via `routeProjectObservation`, never by
138
+ * `appendGlobalObservation` callers directly (Phase 2's pure user-attribute
139
+ * path has no project to attribute to).
140
+ */
141
+ export interface ObservationCandidate {
142
+ readonly ts: string;
143
+ readonly field: string;
144
+ readonly suggest: string;
145
+ readonly source: string;
146
+ readonly evidence: string;
147
+ readonly context?: ObservationContext | undefined;
148
+ /** Human-confirmed-recurrence tally — see `findPromotionCandidates`. Counting evidence, never a detector: it only ever grows via `routeProjectObservation` observing another project's write, never a cross-project batch scan. */
149
+ readonly seen_count?: number | undefined;
150
+ /** Project names the observation has recurred in, oldest first. Pruned to nothing at promotion — see `promotionValueFor`. */
151
+ readonly seen_in?: readonly string[] | undefined;
152
+ }
153
+
154
+ export type CaptureGuardCategory =
155
+ | 'standing_command'
156
+ | 'self_harmful_preference'
157
+ | 'exclusion_list'
158
+ | 'hidden_unicode';
159
+
160
+ export interface CaptureGuardResult {
161
+ readonly allowed: boolean;
162
+ readonly category?: CaptureGuardCategory | undefined;
163
+ readonly reason?: string | undefined;
164
+ }
165
+
166
+ export interface AppendResult {
167
+ readonly written: boolean;
168
+ readonly reason?: string | undefined;
169
+ readonly category?: CaptureGuardCategory | 'invalid_field' | undefined;
170
+ }
171
+
172
+ export interface ReadObservationsResult {
173
+ readonly entries: readonly ObservationCandidate[];
174
+ /** Lines that were not valid JSON objects at all. */
175
+ readonly droppedMalformed: number;
176
+ /** Lines whose `field` fell outside {@link ALLOWED_OBSERVATION_FIELDS}. */
177
+ readonly droppedUnknownField: number;
178
+ }
179
+
180
+ // ---------------------------------------------------------------------------
181
+ // Path resolution — mirrors agent_user_profile.ts's global-layer functions.
182
+ // ---------------------------------------------------------------------------
183
+
184
+ /** Canonical write target for the global observation buffer. */
185
+ export function globalObservationsWriteTarget(
186
+ env?: user_global_paths.EnvMap | null,
187
+ ): string {
188
+ return user_global_paths.write_target(GLOBAL_OBSERVATIONS_RELATIVE, { env: env ?? null });
189
+ }
190
+
191
+ /** Resolve the buffer's on-disk path — new namespace first, legacy fallback, `null` if neither exists. */
192
+ export function resolveGlobalObservationsPath(
193
+ env?: user_global_paths.EnvMap | null,
194
+ ): string | null {
195
+ return user_global_paths.resolve_with_fallback(GLOBAL_OBSERVATIONS_RELATIVE, { env: env ?? null });
196
+ }
197
+
198
+ // ---------------------------------------------------------------------------
199
+ // Capture-time guards
200
+ // ---------------------------------------------------------------------------
201
+
202
+ // Matches an imperative paired with a recurrence marker, in either order —
203
+ // "always fetch <url> on every message" / "run <cmd> at the start of each
204
+ // session". Deliberately broad: a false positive here costs one refused
205
+ // candidate the human never sees; a false negative persists a durable
206
+ // injection forever, in every project.
207
+ const _STANDING_COMMAND_RE =
208
+ /\b(?:always|every\s+(?:session|message|time|turn))\b[\s\S]{0,80}?\b(?:fetch|run|curl|execute|call|check)\b|\b(?:fetch|run|curl|execute|call|check)\b[\s\S]{0,80}?\b(?:at\s+the\s+start\s+of\s+(?:each|every)\s+(?:session|message)|on\s+every\s+(?:message|turn|session)|always)\b/i;
209
+
210
+ // "never criticize me", "always agree with me", "never say I'm wrong",
211
+ // "don't ever disagree with me" — a user weaponizing their own memory to
212
+ // enforce sycophancy (direct-answers Iron Law 1).
213
+ const _SELF_HARMFUL_PREFERENCE_RE =
214
+ /\bnever\s+(?:criticize|disagree\s+with|push\s+back\s+on|question|correct)\s+me\b|\balways\s+agree\s+with\s+me\b|\bnever\s+say\s+i(?:'|’)?m\s+wrong\b|\bdon'?t\s+ever\s+(?:disagree|criticize)\b/i;
215
+
216
+ /** `true` when `text` reads as a verbatim standing command. */
217
+ export function detectStandingCommand(text: string): boolean {
218
+ return _STANDING_COMMAND_RE.test(text);
219
+ }
220
+
221
+ /** `true` when `text` reads as a self-harmful standing preference. */
222
+ export function detectSelfHarmfulPreference(text: string): boolean {
223
+ return _SELF_HARMFUL_PREFERENCE_RE.test(text);
224
+ }
225
+
226
+ /**
227
+ * Run all four capture-time guards against a candidate, in order. Returns
228
+ * the FIRST violation found (a candidate tripping two guards at once is
229
+ * refused for the earlier one; the categories are independently testable,
230
+ * not mutually exclusive in the input space).
231
+ */
232
+ export function evaluateCaptureGuards(
233
+ candidate: ObservationCandidate,
234
+ options: { repo_root?: string | null } = {},
235
+ ): CaptureGuardResult {
236
+ const text = `${candidate.suggest}\n${candidate.evidence}`;
237
+
238
+ if (detectStandingCommand(text)) {
239
+ return {
240
+ allowed: false,
241
+ category: 'standing_command',
242
+ reason:
243
+ 'reads as a verbatim standing command — a stored directive becomes a durable ' +
244
+ 'injection that re-fires forever, and at global scope that means every project, ' +
245
+ 'not just this one. Refused at capture.',
246
+ };
247
+ }
248
+ if (detectSelfHarmfulPreference(text)) {
249
+ return {
250
+ allowed: false,
251
+ category: 'self_harmful_preference',
252
+ reason:
253
+ 'would disable honest feedback across every project — surfaced to the user, ' +
254
+ 'never stored.',
255
+ };
256
+ }
257
+
258
+ const violations = redaction_scan(text, { repo_root: options.repo_root ?? null });
259
+ const hiddenUnicode = violations.find((v) => v.category === 'hidden_unicode');
260
+ if (hiddenUnicode) {
261
+ return {
262
+ allowed: false,
263
+ category: 'hidden_unicode',
264
+ reason: `invisible characters detected — possible identifier smuggling: ${hiddenUnicode.snippet}`,
265
+ };
266
+ }
267
+ if (violations.length > 0) {
268
+ return {
269
+ allowed: false,
270
+ category: 'exclusion_list',
271
+ reason:
272
+ 'capture-time exclusion-list match (credentials / third-party PII / financial ' +
273
+ `figures / health-legal-therapy / demographics / external-source identifiers): ${violations
274
+ .map((v) => v.category)
275
+ .join(', ')}`,
276
+ };
277
+ }
278
+ return { allowed: true };
279
+ }
280
+
281
+ // ---------------------------------------------------------------------------
282
+ // Write path
283
+ // ---------------------------------------------------------------------------
284
+
285
+ /**
286
+ * Append one candidate to the global observation buffer, after running
287
+ * every capture-time guard. Never rewrites — a refused candidate is
288
+ * refused outright, not silently redacted-then-stored (the low-impact
289
+ * corpus's own "halt-and-surface, never silent-rewrite" discipline).
290
+ */
291
+ export function appendGlobalObservation(
292
+ candidate: ObservationCandidate,
293
+ options: { env?: user_global_paths.EnvMap | null; repo_root?: string | null } = {},
294
+ ): AppendResult {
295
+ if (!_ALLOWED_FIELD_SET.has(candidate.field)) {
296
+ return {
297
+ written: false,
298
+ category: 'invalid_field',
299
+ reason: `field '${candidate.field}' is outside the allowed schema enum`,
300
+ };
301
+ }
302
+
303
+ const guard = evaluateCaptureGuards(candidate, { repo_root: options.repo_root ?? null });
304
+ if (!guard.allowed) {
305
+ return { written: false, category: guard.category, reason: guard.reason };
306
+ }
307
+
308
+ const target = globalObservationsWriteTarget(options.env ?? null);
309
+ fs.mkdirSync(path.dirname(target), { recursive: true });
310
+ fs.appendFileSync(target, JSON.stringify(candidate) + '\n', 'utf-8');
311
+ // Phase 5 gate: count the proposal where it actually lands, so the
312
+ // review→accept rate cannot be skewed by a caller that bypasses the
313
+ // command layer. A refused candidate is deliberately NOT counted — it was
314
+ // never proposed to the human. Counter failure never fails the append.
315
+ try {
316
+ recordObservationProposed({ env: options.env ?? null });
317
+ } catch {
318
+ /* a counter is telemetry, never a gate on the buffer write */
319
+ }
320
+ return { written: true };
321
+ }
322
+
323
+ /**
324
+ * Apply the shared ≤5-facts-per-cycle cap across both channels. `existingCount`
325
+ * is however many facts the project-scoped channel already produced THIS
326
+ * cycle; only the remaining headroom is available to the candidates here.
327
+ * Never negative — a project-scoped channel that already hit the cap leaves
328
+ * zero room for the second channel this cycle.
329
+ */
330
+ export function applySharedFactCap<T>(
331
+ existingCount: number,
332
+ candidates: readonly T[],
333
+ cap: number = SHARED_FACT_CAP,
334
+ ): T[] {
335
+ const remaining = Math.max(0, cap - existingCount);
336
+ return candidates.slice(0, remaining);
337
+ }
338
+
339
+ // ---------------------------------------------------------------------------
340
+ // Read path — feeds `/agents:user review` / `/agents:user accept`.
341
+ // ---------------------------------------------------------------------------
342
+
343
+ /**
344
+ * Read + parse the global observation buffer. Tolerant: a malformed JSON
345
+ * line or a line whose `field` falls outside the schema enum is dropped
346
+ * silently, with a count so the caller can surface "N observations
347
+ * skipped" — mirroring "Anything outside that set is dropped on read" in
348
+ * the project-local contract.
349
+ */
350
+ /** Tolerant parse of a JSONL line's `context` field — malformed shapes read back as absent, never thrown. */
351
+ function _parseContext(raw: unknown): ObservationContext | undefined {
352
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
353
+ return undefined;
354
+ }
355
+ const rec = raw as Record<string, unknown>;
356
+ const project_path = typeof rec.project_path === 'string' ? rec.project_path : undefined;
357
+ const project_name = typeof rec.project_name === 'string' ? rec.project_name : undefined;
358
+ const first_seen = typeof rec.first_seen === 'string' ? rec.first_seen : undefined;
359
+ if (project_path === undefined || project_name === undefined || first_seen === undefined) {
360
+ return undefined;
361
+ }
362
+ return { project_path, project_name, first_seen };
363
+ }
364
+
365
+ /** Tolerant parse of a JSONL line's `seen_in` field — drops non-string entries rather than failing the whole line. */
366
+ function _parseSeenIn(raw: unknown): readonly string[] | undefined {
367
+ if (!Array.isArray(raw)) {
368
+ return undefined;
369
+ }
370
+ const out = raw.filter((v): v is string => typeof v === 'string');
371
+ return out.length > 0 ? out : undefined;
372
+ }
373
+
374
+ export function readGlobalObservations(
375
+ options: { env?: user_global_paths.EnvMap | null } = {},
376
+ ): ReadObservationsResult {
377
+ const target = resolveGlobalObservationsPath(options.env ?? null);
378
+ if (target === null) {
379
+ return { entries: [], droppedMalformed: 0, droppedUnknownField: 0 };
380
+ }
381
+ let raw: string;
382
+ try {
383
+ raw = fs.readFileSync(target, 'utf-8');
384
+ } catch {
385
+ return { entries: [], droppedMalformed: 0, droppedUnknownField: 0 };
386
+ }
387
+
388
+ let droppedMalformed = 0;
389
+ let droppedUnknownField = 0;
390
+ const entries: ObservationCandidate[] = [];
391
+
392
+ for (const line of raw.split(/\r?\n/)) {
393
+ const trimmed = line.trim();
394
+ if (trimmed === '') {
395
+ continue;
396
+ }
397
+ let obj: unknown;
398
+ try {
399
+ obj = JSON.parse(trimmed);
400
+ } catch {
401
+ droppedMalformed += 1;
402
+ continue;
403
+ }
404
+ if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) {
405
+ droppedMalformed += 1;
406
+ continue;
407
+ }
408
+ const rec = obj as Record<string, unknown>;
409
+ const field = typeof rec.field === 'string' ? rec.field : '';
410
+ if (!_ALLOWED_FIELD_SET.has(field)) {
411
+ droppedUnknownField += 1;
412
+ continue;
413
+ }
414
+ entries.push({
415
+ ts: typeof rec.ts === 'string' ? rec.ts : '',
416
+ field,
417
+ suggest: typeof rec.suggest === 'string' ? rec.suggest : '',
418
+ source: typeof rec.source === 'string' ? rec.source : '',
419
+ evidence: typeof rec.evidence === 'string' ? rec.evidence : '',
420
+ context: _parseContext(rec.context),
421
+ seen_count: typeof rec.seen_count === 'number' ? rec.seen_count : undefined,
422
+ seen_in: _parseSeenIn(rec.seen_in),
423
+ });
424
+ }
425
+
426
+ return { entries, droppedMalformed, droppedUnknownField };
427
+ }
428
+
429
+ // ---------------------------------------------------------------------------
430
+ // Phase 3 — project attribution and the generalisation promotion.
431
+ //
432
+ // The operator's third ask ("P — project facts with no managed folder"),
433
+ // implemented as the council's round-2 reading: attribution, not isolation.
434
+ // No global project registry, no project-indexed directory tree, no fourth
435
+ // sensitivity class — see `agents/settings/contexts/global-user-memory-cut.md`.
436
+ //
437
+ // `routeProjectObservation` is the Phase 0 predicate wired as a router: a
438
+ // project WITH a managed `agents/` folder keeps its facts local exactly as
439
+ // today (this module never touches them); a project withOUT one persists the
440
+ // fact here, tagged with `context` so it survives that project's deletion.
441
+ // ---------------------------------------------------------------------------
442
+
443
+ /** Where a project-scoped observation is routed, per the Phase 0 predicate. */
444
+ export type ProjectObservationRoute = 'local' | 'global';
445
+
446
+ /** `managed` keeps facts in the project's own `agents/memory/`; anything else has nowhere local to land. */
447
+ export function routeForManagedStatus(status: ManagedAgentsFolderStatus): ProjectObservationRoute {
448
+ return status === 'managed' ? 'local' : 'global';
449
+ }
450
+
451
+ /** Build the attribution footer for a project-scoped observation. `project_name` is the directory basename — the same human-readable unit the global knowledge card's `seen_in` footer already uses. */
452
+ export function buildObservationContext(projectRoot: string, firstSeen: string): ObservationContext {
453
+ return {
454
+ project_path: projectRoot,
455
+ project_name: path.basename(projectRoot) || projectRoot,
456
+ first_seen: firstSeen,
457
+ };
458
+ }
459
+
460
+ /**
461
+ * Capture-time guards for the two context strings ADDED by Phase 3, run
462
+ * before the context is ever attached to a candidate. `project_path` is
463
+ * *expected* to look like an absolute path — `redaction_scan`'s generic
464
+ * `project_path` category exists to catch a path LEAKED into free-form
465
+ * text, and would refuse every real value here by design, so only the
466
+ * `hidden_unicode` class (identifier smuggling, ADR-103) applies to it.
467
+ * `project_name` is free-form and gets the full scan, since a hostile or
468
+ * careless directory name could otherwise smuggle a secret or a hidden
469
+ * character past capture. Reuses the same hardened gate Phase 2 already
470
+ * calls for `suggest`/`evidence` — never a second detector.
471
+ */
472
+ export function evaluateContextCaptureGuards(context: ObservationContext): CaptureGuardResult {
473
+ const pathHiddenUnicode = redaction_scan(context.project_path).find(
474
+ (v) => v.category === 'hidden_unicode',
475
+ );
476
+ if (pathHiddenUnicode) {
477
+ return {
478
+ allowed: false,
479
+ category: 'hidden_unicode',
480
+ reason:
481
+ 'invisible characters detected in project_path — possible identifier smuggling: ' +
482
+ pathHiddenUnicode.snippet,
483
+ };
484
+ }
485
+
486
+ const nameViolations = redaction_scan(context.project_name);
487
+ const nameHiddenUnicode = nameViolations.find((v) => v.category === 'hidden_unicode');
488
+ if (nameHiddenUnicode) {
489
+ return {
490
+ allowed: false,
491
+ category: 'hidden_unicode',
492
+ reason:
493
+ 'invisible characters detected in project_name — possible identifier smuggling: ' +
494
+ nameHiddenUnicode.snippet,
495
+ };
496
+ }
497
+ if (nameViolations.length > 0) {
498
+ return {
499
+ allowed: false,
500
+ category: 'exclusion_list',
501
+ reason:
502
+ 'project_name matched a capture-time exclusion pattern: ' +
503
+ nameViolations.map((v) => v.category).join(', '),
504
+ };
505
+ }
506
+ return { allowed: true };
507
+ }
508
+
509
+ /** Current `seen_count` for an already-buffered context-bearing entry — 1 when the field is absent (its first sighting was never re-written with an explicit count). */
510
+ function _existingSeenCount(entry: ObservationCandidate): number {
511
+ return entry.seen_count ?? 1;
512
+ }
513
+
514
+ /** Current `seen_in` for an already-buffered context-bearing entry, falling back to its own context's project when the field is absent. */
515
+ function _existingSeenIn(entry: ObservationCandidate): readonly string[] {
516
+ if (entry.seen_in !== undefined) {
517
+ return entry.seen_in;
518
+ }
519
+ return entry.context !== undefined ? [entry.context.project_name] : [];
520
+ }
521
+
522
+ /** Result of matching a new project-scoped candidate against the existing context-bearing buffer. */
523
+ export interface ObservationRecurrence {
524
+ readonly seen_count: number;
525
+ readonly seen_in: readonly string[];
526
+ }
527
+
528
+ /**
529
+ * Decide whether `candidateText` is the SAME observation as one already
530
+ * buffered from a different project, using the identical dedup primitive
531
+ * (`_lib/text_similarity.ts`'s `MERGE_THRESHOLD`, via `classifySimilarity`)
532
+ * the curated-memory dedup path uses — "same observation" means the same
533
+ * thing in both places, per the roadmap. Only entries carrying a `context`
534
+ * are eligible matches; a pure user-attribute observation (no project to
535
+ * attribute to) never participates in cross-project recurrence counting.
536
+ *
537
+ * The buffer is append-only, so a recurring observation accumulates
538
+ * multiple lines over time, each carrying that append's cumulative
539
+ * `seen_count`/`seen_in`. Deliberately scans linearly (not
540
+ * `findMostSimilar`, which ties to the FIRST equal-score match) and keeps
541
+ * the LAST line at/above `MERGE_THRESHOLD` — otherwise a run of
542
+ * byte-identical `suggest` values would always tie back to the stale first
543
+ * sighting instead of the most recently accumulated state.
544
+ *
545
+ * Recurrence in the SAME project again does not bump the counter — the
546
+ * roadmap ties `seen_count` to "recurs in a DIFFERENT project" specifically,
547
+ * so the growth stays a cross-project confirmation tally, never a
548
+ * same-project noise counter.
549
+ */
550
+ export function computeRecurrence(
551
+ candidateContext: ObservationContext,
552
+ candidateText: string,
553
+ existingEntries: readonly ObservationCandidate[],
554
+ ): ObservationRecurrence {
555
+ const contextBearing = existingEntries.filter((e) => e.context !== undefined);
556
+
557
+ let matched: ObservationCandidate | undefined;
558
+ for (const entry of contextBearing) {
559
+ const score = jaccardSimilarity(candidateText, entry.suggest);
560
+ if (classifySimilarity(score) === 'merge') {
561
+ matched = entry;
562
+ }
563
+ }
564
+ if (matched === undefined) {
565
+ return { seen_count: 1, seen_in: [candidateContext.project_name] };
566
+ }
567
+ const priorSeenIn = _existingSeenIn(matched);
568
+ if (priorSeenIn.includes(candidateContext.project_name)) {
569
+ // Same project recurring — evidence already counted; hold steady.
570
+ return { seen_count: _existingSeenCount(matched), seen_in: priorSeenIn };
571
+ }
572
+ return {
573
+ seen_count: _existingSeenCount(matched) + 1,
574
+ seen_in: [...priorSeenIn, candidateContext.project_name],
575
+ };
576
+ }
577
+
578
+ /** A project-scoped candidate awaiting routing — no `context`/`seen_count`/`seen_in` yet; `routeProjectObservation` attaches them. */
579
+ export type ProjectObservationInput = Pick<ObservationCandidate, 'ts' | 'suggest' | 'source' | 'evidence'> & {
580
+ readonly field?: string;
581
+ };
582
+
583
+ export interface RouteProjectObservationOptions {
584
+ readonly env?: user_global_paths.EnvMap | null;
585
+ readonly repo_root?: string | null;
586
+ }
587
+
588
+ export interface ProjectObservationRouteResult {
589
+ readonly route: ProjectObservationRoute;
590
+ readonly written: boolean;
591
+ readonly reason?: string | undefined;
592
+ readonly category?: CaptureGuardCategory | 'invalid_field' | undefined;
593
+ readonly context?: ObservationContext | undefined;
594
+ readonly seen_count?: number | undefined;
595
+ readonly seen_in?: readonly string[] | undefined;
596
+ }
597
+
598
+ /**
599
+ * The Phase 0 predicate wired as the router. `managed` → returns `route:
600
+ * 'local'` and writes NOTHING here — the fact stays in that project's
601
+ * `agents/memory/` exactly as today, unchanged by this module. `unmanaged` /
602
+ * `not-a-project` → the observation is tagged with `context`, checked for
603
+ * cross-project recurrence, and appended to the global buffer via the SAME
604
+ * `appendGlobalObservation` guard pipeline Phase 2 established (never a
605
+ * second write path).
606
+ *
607
+ * `field` defaults to `'notes'` — the schema's free-form catch-all — since a
608
+ * project-scoped fact (a convention, an invariant, a recurring gotcha) has
609
+ * no natural mapping onto the closed identity/style enum; `context` is what
610
+ * distinguishes it from a plain user preference note, not a new field value.
611
+ */
612
+ export function routeProjectObservation(
613
+ input: ProjectObservationInput,
614
+ projectRoot: string,
615
+ options: RouteProjectObservationOptions = {},
616
+ ): ProjectObservationRouteResult {
617
+ const status = detect_managed_agents_folder(projectRoot);
618
+ const route = routeForManagedStatus(status);
619
+ if (route === 'local') {
620
+ return {
621
+ route: 'local',
622
+ written: false,
623
+ reason:
624
+ "managed agents/ folder detected — the fact stays in this project's agents/memory/ " +
625
+ 'exactly as today; never routed to the global buffer.',
626
+ };
627
+ }
628
+
629
+ const context = buildObservationContext(projectRoot, input.ts);
630
+ const contextGuard = evaluateContextCaptureGuards(context);
631
+ if (!contextGuard.allowed) {
632
+ return {
633
+ route: 'global',
634
+ written: false,
635
+ category: contextGuard.category,
636
+ reason: contextGuard.reason,
637
+ };
638
+ }
639
+
640
+ const existing = readGlobalObservations({ env: options.env ?? null }).entries;
641
+ const recurrence = computeRecurrence(context, input.suggest, existing);
642
+
643
+ const candidate: ObservationCandidate = {
644
+ ts: input.ts,
645
+ field: input.field ?? 'notes',
646
+ suggest: input.suggest,
647
+ source: input.source,
648
+ evidence: input.evidence,
649
+ context,
650
+ seen_count: recurrence.seen_count,
651
+ seen_in: recurrence.seen_in,
652
+ };
653
+
654
+ const result = appendGlobalObservation(candidate, {
655
+ env: options.env ?? null,
656
+ repo_root: options.repo_root ?? null,
657
+ });
658
+ return {
659
+ route: 'global',
660
+ written: result.written,
661
+ reason: result.reason,
662
+ category: result.category,
663
+ context,
664
+ seen_count: recurrence.seen_count,
665
+ seen_in: recurrence.seen_in,
666
+ };
667
+ }
668
+
669
+ // ---------------------------------------------------------------------------
670
+ // Promotion — the ONLY generalisation path (see agent-user-schema.md).
671
+ // ---------------------------------------------------------------------------
672
+
673
+ /** Human-confirmed-recurrence threshold for promotion candidacy. Never auto-promotes past this — it only makes the observation eligible for `/agents:user review` to surface, with a mandatory `promotion_reason` as human input before `/agents:user accept` writes anything. */
674
+ export const PROMOTION_SEEN_COUNT_THRESHOLD = 3;
675
+
676
+ export interface PromotionCandidate {
677
+ readonly observation: ObservationCandidate;
678
+ readonly seenCount: number;
679
+ readonly projects: readonly string[];
680
+ }
681
+
682
+ /**
683
+ * Observations that have crossed the promotion-candidacy threshold. This is
684
+ * the ONLY generalisation path in the whole layer — the agent never infers a
685
+ * cross-project pattern itself (a non-goal in the roadmap's Goal section);
686
+ * `seen_count` only ever grows one write at a time via `routeProjectObservation`
687
+ * observing a genuinely new project, never a batch scan across the store.
688
+ * Crossing the threshold surfaces candidacy; it never writes `profile.md` —
689
+ * that still requires the human-confirmed `/agents:user accept` step, with a
690
+ * `promotion_reason` the review flow must collect (mirroring ADR-121's rule
691
+ * that there is no auto-`shareable` path for a knowledge card).
692
+ */
693
+ export function findPromotionCandidates(
694
+ entries: readonly ObservationCandidate[],
695
+ ): PromotionCandidate[] {
696
+ const out: PromotionCandidate[] = [];
697
+ for (const entry of entries) {
698
+ if (entry.context === undefined) {
699
+ continue;
700
+ }
701
+ const seenCount = _existingSeenCount(entry);
702
+ if (seenCount >= PROMOTION_SEEN_COUNT_THRESHOLD) {
703
+ out.push({ observation: entry, seenCount, projects: _existingSeenIn(entry) });
704
+ }
705
+ }
706
+ return out;
707
+ }
708
+
709
+ /**
710
+ * The value that may ever reach `profile.md` for a promoted observation —
711
+ * the fact text ONLY, never `context`, never `seen_in`. This is what keeps
712
+ * the long-lived artefact converging to zero project references: the
713
+ * counter is evidence that lived on the buffer entry, and only the
714
+ * short-lived buffer entry needed it (see agent-user-schema.md § Project
715
+ * attribution — "`seen_in[]` is a narrower metadata surface, not a null
716
+ * one").
717
+ */
718
+ export function promotionValueFor(candidate: ObservationCandidate): string {
719
+ return candidate.suggest;
720
+ }
721
+
722
+ // ---------------------------------------------------------------------------
723
+ // Phase 4 — delete, revoke, audit (road-to-global-user-memory).
724
+ //
725
+ // `deleteGlobalObservation` and `purgeProjectContext` are the delete
726
+ // counterpart of `appendGlobalObservation` / `routeProjectObservation`
727
+ // above — this module remains the ONLY writer (now also the only deleter)
728
+ // of the observation buffer, mirroring the single-writer discipline the
729
+ // module docstring already states for the append path.
730
+ // ---------------------------------------------------------------------------
731
+
732
+ /**
733
+ * Deterministic content-derived identifier for one buffered observation —
734
+ * this layer's analogue of a knowledge card's `card_id_from` slug. The
735
+ * JSONL schema (Phase 2/3) never added a stored `id` field, so identity is
736
+ * derived from the fields that make one capture event unique: `ts`
737
+ * (capture timestamp) plus `field` + `suggest` (what was proposed).
738
+ * `source` / `evidence` / `context` are deliberately excluded — two
739
+ * identical suggestions logged at the same instant from a different source
740
+ * string, or with different evidence phrasing, are still "the same
741
+ * observation" to a human choosing to delete it.
742
+ */
743
+ export function observationId(entry: Pick<ObservationCandidate, 'ts' | 'field' | 'suggest'>): string {
744
+ const canonical = `${entry.ts}\u0000${entry.field}\u0000${entry.suggest}`;
745
+ return crypto.createHash('sha256').update(canonical, 'utf-8').digest('hex').slice(0, 16);
746
+ }
747
+
748
+ /** Rewrite the buffer file with exactly `entries` — the shared primitive both delete paths use. */
749
+ function _writeBufferEntries(
750
+ entries: readonly ObservationCandidate[],
751
+ env: user_global_paths.EnvMap | null,
752
+ ): void {
753
+ const target = globalObservationsWriteTarget(env);
754
+ fs.mkdirSync(path.dirname(target), { recursive: true });
755
+ const body = entries.map((e) => JSON.stringify(e)).join('\n');
756
+ fs.writeFileSync(target, entries.length > 0 ? body + '\n' : '', 'utf-8');
757
+ }
758
+
759
+ export interface DeleteObservationResult {
760
+ readonly deleted: boolean;
761
+ readonly tombstone?: RevocationEntry | undefined;
762
+ }
763
+
764
+ /**
765
+ * Delete ONE buffered observation by its content-derived id. Writes an
766
+ * append-only tombstone to `user_global_revocations.ts`'s ledger BEFORE the
767
+ * buffer is rewritten without it — never the reverse — mirroring
768
+ * `knowledge_global_cli.ts`'s `_forget_one` (tombstone THEN delete).
769
+ * `deleted: false` (no tombstone written) when no entry matches `observation_id`.
770
+ */
771
+ export function deleteGlobalObservation(
772
+ observation_id: string,
773
+ reason: string,
774
+ options: { env?: user_global_paths.EnvMap | null; today?: string } = {},
775
+ ): DeleteObservationResult {
776
+ const env = options.env ?? null;
777
+ const { entries } = readGlobalObservations({ env });
778
+ const hasMatch = entries.some((e) => observationId(e) === observation_id);
779
+ if (!hasMatch) {
780
+ return { deleted: false };
781
+ }
782
+ const tombstone = revocations.appendTombstone(observation_id, reason, {
783
+ today: options.today,
784
+ env,
785
+ });
786
+ _writeBufferEntries(
787
+ entries.filter((e) => observationId(e) !== observation_id),
788
+ env,
789
+ );
790
+ return { deleted: true, tombstone };
791
+ }
792
+
793
+ export interface PurgeProjectContextResult {
794
+ readonly purgedCount: number;
795
+ readonly tombstones: readonly RevocationEntry[];
796
+ }
797
+
798
+ /**
799
+ * Whole-project-context purge — removes EVERY buffered observation
800
+ * attributed (via `context.project_path`) to `projectPath`, so a deleted
801
+ * project's facts do not linger in the global buffer forever. Tombstones
802
+ * each removed observation individually BEFORE the buffer is rewritten,
803
+ * mirroring `knowledge_global_cli.ts`'s `cmd_purge` (one tombstone per card,
804
+ * written before that card is wiped). Observations from OTHER projects and
805
+ * observations carrying no `context` at all (pure user-attribute facts, not
806
+ * project-attributed) are left untouched. `purgedCount: 0` when no
807
+ * observation matches `projectPath` — the buffer is not rewritten in that
808
+ * case, so an unrelated purge call never touches the file's mtime.
809
+ */
810
+ export function purgeProjectContext(
811
+ projectPath: string,
812
+ reason: string,
813
+ options: { env?: user_global_paths.EnvMap | null; today?: string } = {},
814
+ ): PurgeProjectContextResult {
815
+ const env = options.env ?? null;
816
+ const { entries } = readGlobalObservations({ env });
817
+ const toRemove = entries.filter((e) => e.context?.project_path === projectPath);
818
+ if (toRemove.length === 0) {
819
+ return { purgedCount: 0, tombstones: [] };
820
+ }
821
+ const tombstones = toRemove.map((e) =>
822
+ revocations.appendTombstone(observationId(e), reason, { today: options.today, env }),
823
+ );
824
+ const removedIds = new Set(toRemove.map((e) => observationId(e)));
825
+ _writeBufferEntries(
826
+ entries.filter((e) => !removedIds.has(observationId(e))),
827
+ env,
828
+ );
829
+ return { purgedCount: toRemove.length, tombstones };
830
+ }