avorelo 0.3.8 → 0.4.0-rc.2

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 (394) hide show
  1. package/LICENSE +16 -23
  2. package/README.md +62 -72
  3. package/bin/avorelo +9 -0
  4. package/package.json +147 -139
  5. package/scripts/README.md +40 -0
  6. package/scripts/avorelo-session-collision-guard.js +540 -0
  7. package/scripts/avorelo-workstream-preflight.js +918 -0
  8. package/scripts/cco-dashboard.js +346 -0
  9. package/scripts/cco-posttooluse.js +504 -0
  10. package/scripts/cco-pretooluse.js +381 -0
  11. package/scripts/cco-status.js +540 -0
  12. package/scripts/cco-stop.js +77 -0
  13. package/scripts/cco-userprompt-submit.js +83 -0
  14. package/scripts/lib/activation/account-state.js +37 -0
  15. package/scripts/lib/activation/activate-command.js +114 -0
  16. package/scripts/lib/activation/activation-runner.js +607 -0
  17. package/scripts/lib/activation/activation-self-healing.js +481 -0
  18. package/scripts/lib/activation/activation-state.js +96 -0
  19. package/scripts/lib/activation/activation-summary.js +241 -0
  20. package/scripts/lib/activation/adapters/claude-code.js +77 -0
  21. package/scripts/lib/activation/adapters/codex-cli.js +52 -0
  22. package/scripts/lib/activation/adapters/cursor.js +37 -0
  23. package/scripts/lib/activation/adapters/github-agent.js +39 -0
  24. package/scripts/lib/activation/adapters/terminal.js +42 -0
  25. package/scripts/lib/activation/adapters/vscode.js +39 -0
  26. package/scripts/lib/activation/adapters/windsurf.js +37 -0
  27. package/scripts/lib/activation/ai-surface-detector.js +151 -0
  28. package/scripts/lib/activation/connect-account.js +145 -0
  29. package/scripts/lib/activation/detect-environment.js +75 -0
  30. package/scripts/lib/activation/detect-hosts.js +62 -0
  31. package/scripts/lib/activation/format-activation-output.js +109 -0
  32. package/scripts/lib/activation/next-action.js +43 -0
  33. package/scripts/lib/activation/repair-engine.js +219 -0
  34. package/scripts/lib/activation/run-entry.js +346 -0
  35. package/scripts/lib/activation-distribution-readiness.js +507 -0
  36. package/scripts/lib/adapter-conformance.js +176 -0
  37. package/scripts/lib/adapter-readiness.js +428 -0
  38. package/scripts/lib/adapter-safety-boundaries.js +335 -0
  39. package/scripts/lib/adapter-technical-readiness-gate.js +205 -0
  40. package/scripts/lib/agent-access-governance.js +455 -0
  41. package/scripts/lib/agent-activation-readiness.js +878 -0
  42. package/scripts/lib/agent-enforcement.js +765 -0
  43. package/scripts/lib/agent-evolution-loop.js +479 -0
  44. package/scripts/lib/agent-policy-profile.js +210 -0
  45. package/scripts/lib/agent-security/action-evaluator.js +507 -0
  46. package/scripts/lib/agent-security/adapter-registry.js +98 -0
  47. package/scripts/lib/agent-security/auto-policy.js +139 -0
  48. package/scripts/lib/agent-security/bounded-scan.js +93 -0
  49. package/scripts/lib/agent-security/enforcement-adapter.js +174 -0
  50. package/scripts/lib/agent-security/enforcement-engine.js +1129 -0
  51. package/scripts/lib/agent-security/file-write-adapter.js +183 -0
  52. package/scripts/lib/agent-security/file-write-rules.js +178 -0
  53. package/scripts/lib/agent-security/index.js +3342 -0
  54. package/scripts/lib/agent-security/instruction-risk.js +181 -0
  55. package/scripts/lib/agent-security/mcp-action-adapter.js +185 -0
  56. package/scripts/lib/agent-security/mcp-action-rules.js +184 -0
  57. package/scripts/lib/agent-security/package-action-adapter.js +175 -0
  58. package/scripts/lib/agent-security/package-action-rules.js +233 -0
  59. package/scripts/lib/agent-security/performance.js +148 -0
  60. package/scripts/lib/agent-security/permission-minimizer.js +403 -0
  61. package/scripts/lib/agent-security/scan-cache.js +74 -0
  62. package/scripts/lib/agent-security/source-trust.js +146 -0
  63. package/scripts/lib/ai-efficiency-dogfood-pack.js +437 -0
  64. package/scripts/lib/ai-efficiency-eval.js +207 -0
  65. package/scripts/lib/ai-efficiency-guard-contract.js +209 -0
  66. package/scripts/lib/ai-efficiency-measured-value.js +106 -0
  67. package/scripts/lib/ai-install-prompt.js +293 -0
  68. package/scripts/lib/ai-task-inference.js +155 -0
  69. package/scripts/lib/ai-value-ledger.js +121 -0
  70. package/scripts/lib/ai-value-proof-table.js +151 -0
  71. package/scripts/lib/ai-work-efficiency-routing.js +623 -0
  72. package/scripts/lib/ai-workspace-hygiene.js +1499 -0
  73. package/scripts/lib/alpha-activation.js +520 -0
  74. package/scripts/lib/alpha-feedback.js +263 -0
  75. package/scripts/lib/alpha-readiness-gate.js +332 -0
  76. package/scripts/lib/anti-gaming.js +169 -0
  77. package/scripts/lib/artifact-health.js +431 -0
  78. package/scripts/lib/artifact-receipt-hygiene.js +619 -0
  79. package/scripts/lib/attribution.js +180 -0
  80. package/scripts/lib/audit.js +289 -0
  81. package/scripts/lib/avorelo-skill-registry.js +810 -0
  82. package/scripts/lib/batch-jobs.js +71 -0
  83. package/scripts/lib/brain-pack.js +578 -0
  84. package/scripts/lib/brand-boundary.js +424 -0
  85. package/scripts/lib/brand.js +74 -0
  86. package/scripts/lib/browser-capability.js +1048 -0
  87. package/scripts/lib/browser-proof-governed-workflow.js +1020 -0
  88. package/scripts/lib/browser-proof-preflight.js +321 -0
  89. package/scripts/lib/cache-readiness.js +296 -0
  90. package/scripts/lib/canonical-reentry.js +162 -0
  91. package/scripts/lib/capability-contract-validator.js +177 -0
  92. package/scripts/lib/capability-hygiene.js +504 -0
  93. package/scripts/lib/capability-intake-quarantine.js +789 -0
  94. package/scripts/lib/capability-kernel-registry.js +566 -0
  95. package/scripts/lib/capability-kernel.js +202 -0
  96. package/scripts/lib/capability-manifest.js +255 -0
  97. package/scripts/lib/capability-outcome-normalizer.js +104 -0
  98. package/scripts/lib/capability-packs.js +314 -0
  99. package/scripts/lib/capability-recommender.js +513 -0
  100. package/scripts/lib/capability-registry.js +1062 -0
  101. package/scripts/lib/carry-forward-surfacing.js +194 -0
  102. package/scripts/lib/ccusage-adapter.js +188 -0
  103. package/scripts/lib/claude-session-evidence/index.js +1027 -0
  104. package/scripts/lib/cli-output-sanitizer.js +118 -0
  105. package/scripts/lib/company-loop.js +1149 -0
  106. package/scripts/lib/config.js +641 -0
  107. package/scripts/lib/context-acquisition-plan.js +359 -0
  108. package/scripts/lib/context-authority-rules.js +138 -0
  109. package/scripts/lib/context-budget-guard.js +170 -0
  110. package/scripts/lib/context-budget-scanner.js +257 -0
  111. package/scripts/lib/context-budget.js +167 -0
  112. package/scripts/lib/context-claim-safety.js +116 -0
  113. package/scripts/lib/context-compiler.js +236 -0
  114. package/scripts/lib/context-optimizer.js +715 -0
  115. package/scripts/lib/context-packet.js +121 -0
  116. package/scripts/lib/context-receipt.js +57 -0
  117. package/scripts/lib/context-reduction-plan.js +179 -0
  118. package/scripts/lib/context-safety.js +88 -0
  119. package/scripts/lib/context-savings-engine.js +158 -0
  120. package/scripts/lib/context-source-registry.js +393 -0
  121. package/scripts/lib/context-task-profiles.js +147 -0
  122. package/scripts/lib/cost-abuse-guard.js +975 -0
  123. package/scripts/lib/cost-evidence.js +287 -0
  124. package/scripts/lib/cost-to-outcome-signals.js +639 -0
  125. package/scripts/lib/cross-host-install-plan.js +308 -0
  126. package/scripts/lib/cross-host-install-readiness.js +242 -0
  127. package/scripts/lib/cross-host-value-flow.js +268 -0
  128. package/scripts/lib/dashboard.js +1056 -0
  129. package/scripts/lib/data-permissions-guard.js +1187 -0
  130. package/scripts/lib/deployment-environment-guard.js +1236 -0
  131. package/scripts/lib/design-partner-feedback.js +346 -0
  132. package/scripts/lib/dogfood-evidence-freshness.js +567 -0
  133. package/scripts/lib/efficiency-receipt.js +354 -0
  134. package/scripts/lib/entitlements.js +100 -0
  135. package/scripts/lib/execution-packet.js +559 -0
  136. package/scripts/lib/execution-route-governor.js +179 -0
  137. package/scripts/lib/experimentation-events.js +547 -0
  138. package/scripts/lib/external-capability-compliance.js +107 -0
  139. package/scripts/lib/external-readiness/ai-visibility/ai-visibility-runner.js +322 -0
  140. package/scripts/lib/external-readiness/calibration/benchmark-site-catalog.js +121 -0
  141. package/scripts/lib/external-readiness/calibration/completion-gate.js +112 -0
  142. package/scripts/lib/external-readiness/calibration/real-site-benchmark-runner.js +284 -0
  143. package/scripts/lib/external-readiness/drift-detector.js +100 -0
  144. package/scripts/lib/external-readiness/endpoint-prober.js +332 -0
  145. package/scripts/lib/external-readiness/external-readiness-runner.js +112 -0
  146. package/scripts/lib/external-readiness/full-agent-readiness-scorer.js +220 -0
  147. package/scripts/lib/external-readiness/golden-path-runner.js +333 -0
  148. package/scripts/lib/external-readiness/inside-out-runner.js +118 -0
  149. package/scripts/lib/external-readiness/proof-receipt-writer.js +112 -0
  150. package/scripts/lib/external-readiness/safe-fix-executor.js +117 -0
  151. package/scripts/lib/external-readiness/skills/category-boundary-guard.js +30 -0
  152. package/scripts/lib/external-readiness/skills/pmm-positioning-guard.js +69 -0
  153. package/scripts/lib/external-readiness/skills/safe-fix-policy.js +35 -0
  154. package/scripts/lib/external-user-simulation.js +166 -0
  155. package/scripts/lib/failure-recovery-readiness.js +81 -0
  156. package/scripts/lib/failure-recovery.js +419 -0
  157. package/scripts/lib/feedback-intelligence.js +537 -0
  158. package/scripts/lib/feedback-signals.js +205 -0
  159. package/scripts/lib/file-integrity.js +68 -0
  160. package/scripts/lib/fsx.js +129 -0
  161. package/scripts/lib/full-readiness-gate.js +451 -0
  162. package/scripts/lib/governed-action-exposure-primitives.js +1514 -0
  163. package/scripts/lib/governed-exposure-planner.js +186 -0
  164. package/scripts/lib/governed-workspace-hygiene.js +946 -0
  165. package/scripts/lib/guard-suite-common.js +412 -0
  166. package/scripts/lib/guidance-builder.js +174 -0
  167. package/scripts/lib/hook-apply.js +1075 -0
  168. package/scripts/lib/hook-baseline.js +310 -0
  169. package/scripts/lib/hook-config-preview.js +275 -0
  170. package/scripts/lib/hook-contracts.js +290 -0
  171. package/scripts/lib/hook-safety-boundary-readiness.js +80 -0
  172. package/scripts/lib/host-capability-matrix.js +351 -0
  173. package/scripts/lib/host-support-context.js +254 -0
  174. package/scripts/lib/http-hook-action.js +538 -0
  175. package/scripts/lib/install-ai-readiness.js +84 -0
  176. package/scripts/lib/install-intake-risk.js +1051 -0
  177. package/scripts/lib/install-journey-intelligence.js +329 -0
  178. package/scripts/lib/intervention-guidance.js +57 -0
  179. package/scripts/lib/known-limitations.js +115 -0
  180. package/scripts/lib/l8-path-truth.js +146 -0
  181. package/scripts/lib/launch-hardening-gate.js +436 -0
  182. package/scripts/lib/launch-readiness.js +635 -0
  183. package/scripts/lib/learning-experimentation-foundation.js +1484 -0
  184. package/scripts/lib/learning-memory.js +686 -0
  185. package/scripts/lib/lifecycle-hooks.js +982 -0
  186. package/scripts/lib/local-package-smoke.js +436 -0
  187. package/scripts/lib/local-pricing.js +299 -0
  188. package/scripts/lib/mcp-enforcement.js +311 -0
  189. package/scripts/lib/mcp-least-privilege-policy.js +303 -0
  190. package/scripts/lib/mcp-tool-inventory.js +388 -0
  191. package/scripts/lib/mcp-tool-risk.js +0 -0
  192. package/scripts/lib/memory.js +335 -0
  193. package/scripts/lib/metrics.js +699 -0
  194. package/scripts/lib/micro-proof.js +133 -0
  195. package/scripts/lib/model-work-class-routing.js +79 -0
  196. package/scripts/lib/next-run-context.js +436 -0
  197. package/scripts/lib/next-run-continuity.js +1207 -0
  198. package/scripts/lib/observability-recovery-guard.js +1030 -0
  199. package/scripts/lib/operating-value.js +1662 -0
  200. package/scripts/lib/optimization-v3.js +122 -0
  201. package/scripts/lib/orchestration/adapters/_shared.js +49 -0
  202. package/scripts/lib/orchestration/adapters/aider.js +18 -0
  203. package/scripts/lib/orchestration/adapters/claude-code.js +35 -0
  204. package/scripts/lib/orchestration/adapters/codex.js +35 -0
  205. package/scripts/lib/orchestration/adapters/gemini-cli.js +18 -0
  206. package/scripts/lib/orchestration/adapters/git.js +25 -0
  207. package/scripts/lib/orchestration/adapters/index.js +31 -0
  208. package/scripts/lib/orchestration/adapters/lm-studio.js +18 -0
  209. package/scripts/lib/orchestration/adapters/ollama.js +18 -0
  210. package/scripts/lib/orchestration/adapters/opencode.js +18 -0
  211. package/scripts/lib/orchestration/adapters/openrouter.js +18 -0
  212. package/scripts/lib/orchestration/adapters/test-runner.js +25 -0
  213. package/scripts/lib/orchestration/cli.js +438 -0
  214. package/scripts/lib/orchestration/execution-manager.js +279 -0
  215. package/scripts/lib/orchestration/handoff.js +314 -0
  216. package/scripts/lib/orchestration/index.js +456 -0
  217. package/scripts/lib/orchestration/inventory.js +47 -0
  218. package/scripts/lib/orchestration/model-discovery.js +498 -0
  219. package/scripts/lib/orchestration/model-profiler.js +170 -0
  220. package/scripts/lib/orchestration/model-profiles.js +252 -0
  221. package/scripts/lib/orchestration/model-refresh-policy.js +72 -0
  222. package/scripts/lib/orchestration/proof-writer.js +349 -0
  223. package/scripts/lib/orchestration/provider-discovery/aider.js +49 -0
  224. package/scripts/lib/orchestration/provider-discovery/claude-code.js +56 -0
  225. package/scripts/lib/orchestration/provider-discovery/codex.js +49 -0
  226. package/scripts/lib/orchestration/provider-discovery/common.js +186 -0
  227. package/scripts/lib/orchestration/provider-discovery/gemini.js +106 -0
  228. package/scripts/lib/orchestration/provider-discovery/lm-studio.js +118 -0
  229. package/scripts/lib/orchestration/provider-discovery/models-dev.js +12 -0
  230. package/scripts/lib/orchestration/provider-discovery/ollama.js +100 -0
  231. package/scripts/lib/orchestration/provider-discovery/opencode.js +47 -0
  232. package/scripts/lib/orchestration/provider-discovery/openrouter.js +44 -0
  233. package/scripts/lib/orchestration/risk-classifier.js +130 -0
  234. package/scripts/lib/orchestration/routing-policy.js +486 -0
  235. package/scripts/lib/orchestration/settings.js +95 -0
  236. package/scripts/lib/orchestration/state.js +165 -0
  237. package/scripts/lib/orchestration/verification-manager.js +138 -0
  238. package/scripts/lib/outcome-evidence-stack.js +1083 -0
  239. package/scripts/lib/output-profiles.js +146 -0
  240. package/scripts/lib/package-content-audit.js +368 -0
  241. package/scripts/lib/package-runtime.js +278 -0
  242. package/scripts/lib/plan-surface.js +53 -0
  243. package/scripts/lib/plans.js +2327 -0
  244. package/scripts/lib/policy-provider.js +27 -0
  245. package/scripts/lib/prelaunch-activation-readiness.js +409 -0
  246. package/scripts/lib/prelaunch-evidence-store.js +816 -0
  247. package/scripts/lib/prelaunch-intelligence.js +869 -0
  248. package/scripts/lib/pricing-experiment.js +118 -0
  249. package/scripts/lib/pro-moment-events.js +77 -0
  250. package/scripts/lib/pro-moment-state.js +227 -0
  251. package/scripts/lib/pro-moments.js +1216 -0
  252. package/scripts/lib/product-home-receipt.js +1790 -0
  253. package/scripts/lib/product-learning-events.js +696 -0
  254. package/scripts/lib/production-confidence-control-tower.js +1209 -0
  255. package/scripts/lib/production-confidence.js +1858 -0
  256. package/scripts/lib/project-profile.js +555 -0
  257. package/scripts/lib/prompt-compiler-plan.js +94 -0
  258. package/scripts/lib/prompt-compiler.js +280 -0
  259. package/scripts/lib/prompt-lint.js +32 -0
  260. package/scripts/lib/prompt-suggestions.js +52 -0
  261. package/scripts/lib/proof-canonical.js +398 -0
  262. package/scripts/lib/proof-drilldown.js +383 -0
  263. package/scripts/lib/proof-e2e/promise-completion.js +141 -0
  264. package/scripts/lib/proof-e2e/promise-dashboard-claims.js +135 -0
  265. package/scripts/lib/proof-e2e/promise-local-proof.js +156 -0
  266. package/scripts/lib/proof-e2e/promise-provider-neutral.js +136 -0
  267. package/scripts/lib/proof-e2e/promise-registry.js +219 -0
  268. package/scripts/lib/proof-e2e/promise-security-trust.js +141 -0
  269. package/scripts/lib/proof-e2e/promise-skills-risk.js +163 -0
  270. package/scripts/lib/proof-e2e/promise-token-savings.js +112 -0
  271. package/scripts/lib/proof-e2e/promise-work-management.js +126 -0
  272. package/scripts/lib/proof-events.js +342 -0
  273. package/scripts/lib/proof-history.js +243 -0
  274. package/scripts/lib/proof-metrics.js +296 -0
  275. package/scripts/lib/proof-outcome-evidence.js +134 -0
  276. package/scripts/lib/proof-receipt.js +335 -0
  277. package/scripts/lib/proof-record.js +461 -0
  278. package/scripts/lib/proof-savings-report.js +1549 -0
  279. package/scripts/lib/public-activation-distribution-gate.js +266 -0
  280. package/scripts/lib/public-cli.js +5631 -0
  281. package/scripts/lib/public-distribution-truth.js +211 -0
  282. package/scripts/lib/public-distribution.js +73 -0
  283. package/scripts/lib/public-install-claim-checker.js +300 -0
  284. package/scripts/lib/publish-provenance-readiness.js +287 -0
  285. package/scripts/lib/readiness-delta.js +218 -0
  286. package/scripts/lib/readiness-evidence-closure.js +196 -0
  287. package/scripts/lib/reentry-memory-capture.js +241 -0
  288. package/scripts/lib/reentry-memory-retrieval.js +302 -0
  289. package/scripts/lib/reentry-memory-status.js +147 -0
  290. package/scripts/lib/reentry-memory-store.js +183 -0
  291. package/scripts/lib/reentry-state.js +66 -0
  292. package/scripts/lib/release-candidate-bundle.js +166 -0
  293. package/scripts/lib/remediation.js +81 -0
  294. package/scripts/lib/repo-map.js +391 -0
  295. package/scripts/lib/run-action-policy.js +104 -0
  296. package/scripts/lib/run-contract-receipt.js +48 -0
  297. package/scripts/lib/run-contract.js +288 -0
  298. package/scripts/lib/run-cost-to-verified-outcome.js +87 -0
  299. package/scripts/lib/run-improvements-lifecycle.js +330 -0
  300. package/scripts/lib/run-improvements.js +789 -0
  301. package/scripts/lib/run-proof-contract.js +116 -0
  302. package/scripts/lib/run-risk-classifier.js +131 -0
  303. package/scripts/lib/run-task-classifier.js +216 -0
  304. package/scripts/lib/runtime-decision-policy.js +387 -0
  305. package/scripts/lib/safe-path-engine.js +705 -0
  306. package/scripts/lib/safe-repair.js +877 -0
  307. package/scripts/lib/safe-run-controller.js +887 -0
  308. package/scripts/lib/scope-repair-before-approval.js +865 -0
  309. package/scripts/lib/score.js +262 -0
  310. package/scripts/lib/seamless-enforcement.js +329 -0
  311. package/scripts/lib/seamless-outcome.js +689 -0
  312. package/scripts/lib/seamless-reality-gate.js +5053 -0
  313. package/scripts/lib/security-confidence/activation-safety.js +78 -0
  314. package/scripts/lib/security-confidence/adapters/adapter-contract.js +125 -0
  315. package/scripts/lib/security-confidence/adapters/mock-claude-bughunter-adapter.js +36 -0
  316. package/scripts/lib/security-confidence/adapters/mock-decepticon-adapter.js +34 -0
  317. package/scripts/lib/security-confidence/adapters/mock-mcp-scan-adapter.js +35 -0
  318. package/scripts/lib/security-confidence/adapters/mock-snyk-agent-scan-adapter.js +35 -0
  319. package/scripts/lib/security-confidence/adapters/mock-tank-adapter.js +34 -0
  320. package/scripts/lib/security-confidence/adapters/reference-registry.js +63 -0
  321. package/scripts/lib/security-confidence/auth-rbac-plan.js +39 -0
  322. package/scripts/lib/security-confidence/component-inventory.js +151 -0
  323. package/scripts/lib/security-confidence/constants.js +191 -0
  324. package/scripts/lib/security-confidence/dependency-script-risk.js +95 -0
  325. package/scripts/lib/security-confidence/evidence-hygiene.js +127 -0
  326. package/scripts/lib/security-confidence/feature-impact-classifier.js +80 -0
  327. package/scripts/lib/security-confidence/file-utils.js +161 -0
  328. package/scripts/lib/security-confidence/finding-triage.js +90 -0
  329. package/scripts/lib/security-confidence/findings.js +115 -0
  330. package/scripts/lib/security-confidence/index.js +29 -0
  331. package/scripts/lib/security-confidence/permission-budget.js +93 -0
  332. package/scripts/lib/security-confidence/redaction.js +82 -0
  333. package/scripts/lib/security-confidence/redteam-fixtures.js +53 -0
  334. package/scripts/lib/security-confidence/reference-adoption.js +229 -0
  335. package/scripts/lib/security-confidence/reference-taxonomy.js +155 -0
  336. package/scripts/lib/security-confidence/report-quality.js +99 -0
  337. package/scripts/lib/security-confidence/report-writer.js +79 -0
  338. package/scripts/lib/security-confidence/risk-classifier.js +187 -0
  339. package/scripts/lib/security-confidence/runner.js +179 -0
  340. package/scripts/lib/security-confidence/schema.js +188 -0
  341. package/scripts/lib/security-confidence/secret-safety.js +74 -0
  342. package/scripts/lib/security-confidence/security-capability-router.js +98 -0
  343. package/scripts/lib/security-confidence/web-security-smoke-plan.js +46 -0
  344. package/scripts/lib/security-risk-classifier.js +511 -0
  345. package/scripts/lib/security-scan.js +396 -0
  346. package/scripts/lib/session-context-optimizer.js +1211 -0
  347. package/scripts/lib/session-timing.js +322 -0
  348. package/scripts/lib/skill-hygiene.js +805 -0
  349. package/scripts/lib/skill-packs.js +161 -0
  350. package/scripts/lib/skills-operating-layer-v1.js +1023 -0
  351. package/scripts/lib/skills-operating-layer.js +629 -0
  352. package/scripts/lib/smallest-safe-plan.js +214 -0
  353. package/scripts/lib/smart-routing-plan.js +115 -0
  354. package/scripts/lib/smart-work-routing.js +768 -0
  355. package/scripts/lib/source-catalog.js +1022 -0
  356. package/scripts/lib/status-value-summary.js +32 -0
  357. package/scripts/lib/supply-chain-dependency-guard.js +1333 -0
  358. package/scripts/lib/supply-chain-guard.js +219 -0
  359. package/scripts/lib/support-bundle.js +561 -0
  360. package/scripts/lib/support-redaction.js +66 -0
  361. package/scripts/lib/task-continuation.js +440 -0
  362. package/scripts/lib/test-helpers.js +15 -0
  363. package/scripts/lib/tier.js +38 -0
  364. package/scripts/lib/token-context-quality-gate.js +370 -0
  365. package/scripts/lib/token-cost-capture.js +187 -0
  366. package/scripts/lib/token-cost-evidence.js +862 -0
  367. package/scripts/lib/token-cost-intelligence.js +358 -0
  368. package/scripts/lib/token-efficiency-evidence.js +213 -0
  369. package/scripts/lib/token-efficiency-map.js +110 -0
  370. package/scripts/lib/token-evidence.js +703 -0
  371. package/scripts/lib/tokenish.js +17 -0
  372. package/scripts/lib/tool-output-sandbox.js +559 -0
  373. package/scripts/lib/trust-audit.js +136 -0
  374. package/scripts/lib/unified-events.js +396 -0
  375. package/scripts/lib/upgrade-interruption-recovery.js +407 -0
  376. package/scripts/lib/usage-ledger.js +201 -0
  377. package/scripts/lib/value-ledger.js +130 -0
  378. package/scripts/lib/value-proof-calibration.js +531 -0
  379. package/scripts/lib/visual-qa.js +231 -0
  380. package/scripts/lib/voice-alpha.js +29 -0
  381. package/scripts/lib/webhook-idempotency-guard.js +990 -0
  382. package/scripts/lib/work-aware-orchestration.js +976 -0
  383. package/scripts/lib/work-control-receipts.js +577 -0
  384. package/scripts/lib/work-ledger.js +1123 -0
  385. package/scripts/lib/work-panel-preview.js +352 -0
  386. package/scripts/lib/workflow-discipline.js +280 -0
  387. package/scripts/lib/workflow-intelligence-radar.js +804 -0
  388. package/scripts/lib/workflow-signals.js +419 -0
  389. package/scripts/lib/workspace-map.js +281 -0
  390. package/scripts/lib/workspace-registry.js +1367 -0
  391. package/scripts/lib/workspace-resolver.js +542 -0
  392. package/scripts/wuz-doctor.js +315 -0
  393. package/bin/avorelo.mjs +0 -7
  394. package/dist/avorelo.mjs +0 -24678
@@ -0,0 +1,1075 @@
1
+ "use strict";
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+ const os = require("node:os");
6
+
7
+ const CONTRACT = "avorelo.hookApply.v1";
8
+ const SCHEMA_VERSION = 1;
9
+
10
+ const APPLY_DIR_REL = ".claude/cco/orchestration/hook-apply";
11
+ const LATEST_APPLY_REL = `${APPLY_DIR_REL}/latest-apply.json`;
12
+ const LATEST_DOCTOR_REL = `${APPLY_DIR_REL}/latest-doctor.json`;
13
+ const LATEST_ROLLBACK_REL = `${APPLY_DIR_REL}/latest-rollback.json`;
14
+ const BACKUP_DIR_REL = `${APPLY_DIR_REL}/backups`;
15
+
16
+ // Avorelo hook marker used to identify managed entries in config
17
+ const AVORELO_HOOK_MARKER = "_avorelo_managed";
18
+
19
+ // The lifecycle-hook commands that Avorelo manages
20
+ // Uses npx to resolve the binary from the project's node_modules
21
+ const AVORELO_LIFECYCLE_COMMANDS = Object.freeze([
22
+ { event: "SessionStart", command: "npx avorelo lifecycle-hook session-start --json", blocking: false, matcher: "" },
23
+ { event: "UserPromptSubmit", command: "npx avorelo lifecycle-hook user-prompt-submit --json", blocking: false, matcher: "" },
24
+ { event: "PreToolUse", command: "npx avorelo lifecycle-hook pre-tool-use --json", blocking: true, matcher: "" },
25
+ { event: "PostToolUse", command: "npx avorelo lifecycle-hook post-tool-use --json", blocking: false, matcher: "" },
26
+ { event: "Stop", command: "npx avorelo lifecycle-hook stop --json", blocking: true, matcher: "" },
27
+ { event: "SessionEnd", command: "npx avorelo lifecycle-hook session-end --json", blocking: false, matcher: "" },
28
+ ]);
29
+
30
+ // Supported host configs
31
+ const HOST_APPLY_CONFIGS = Object.freeze({
32
+ claude_code: {
33
+ configPath: ".claude/settings.json",
34
+ format: "claude_code_settings",
35
+ label: "Claude Code",
36
+ },
37
+ openhands: {
38
+ configPath: ".openhands/hooks.json",
39
+ format: "openhands_hooks",
40
+ label: "OpenHands",
41
+ },
42
+ });
43
+
44
+ // ── Utility helpers ───────────────────────────────────────────────────────────
45
+
46
+ function nowIso() {
47
+ return new Date().toISOString();
48
+ }
49
+
50
+ function ensureDir(dir) {
51
+ fs.mkdirSync(dir, { recursive: true });
52
+ }
53
+
54
+ function safeWriteJson(filePath, data) {
55
+ ensureDir(path.dirname(filePath));
56
+ fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
57
+ }
58
+
59
+ function safeReadJson(filePath) {
60
+ try {
61
+ const raw = fs.readFileSync(filePath, "utf8").replace(/^/, "");
62
+ return JSON.parse(raw);
63
+ } catch {
64
+ return null;
65
+ }
66
+ }
67
+
68
+ function safeExists(p) {
69
+ try { return fs.existsSync(p); } catch { return false; }
70
+ }
71
+
72
+ function makeTimestamp() {
73
+ return new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
74
+ }
75
+
76
+ function hostname() {
77
+ try { return os.hostname().replace(/[^a-zA-Z0-9-]/g, "-").slice(0, 24); } catch { return "host"; }
78
+ }
79
+
80
+ function redact(obj) {
81
+ // Deep-copy and strip any string values that look like secrets
82
+ return JSON.parse(JSON.stringify(obj));
83
+ }
84
+
85
+ // ── Approval / dry-run detection ─────────────────────────────────────────────
86
+
87
+ function requiresExplicitApproval(options = {}) {
88
+ return !options.yes && !options.confirm && !options.approved;
89
+ }
90
+
91
+ function isDryRun(options = {}) {
92
+ return options.dryRun || (!options.yes && !options.confirm && !options.approved);
93
+ }
94
+
95
+ // ── Config parsing and merging ────────────────────────────────────────────────
96
+
97
+ /**
98
+ * Parse existing config. Returns { valid, data, error }.
99
+ */
100
+ function parseExistingConfig(configAbsPath, format) {
101
+ if (!safeExists(configAbsPath)) {
102
+ return { valid: true, data: null, error: null, existed: false };
103
+ }
104
+ const data = safeReadJson(configAbsPath);
105
+ if (data === null) {
106
+ return { valid: false, data: null, error: "invalid_json", existed: true };
107
+ }
108
+ return { valid: true, data, error: null, existed: true };
109
+ }
110
+
111
+ /**
112
+ * Check if a hook entry is Avorelo-managed.
113
+ * Supports both nested format ({matcher, hooks: [...]}) and flat format ({type, command}).
114
+ */
115
+ function isAvoreloManagedHook(entry) {
116
+ if (!entry || typeof entry !== "object") return false;
117
+ if (entry[AVORELO_HOOK_MARKER] === true) return true;
118
+ if (Array.isArray(entry.hooks)) {
119
+ return entry.hooks.some((h) => h && typeof h.command === "string" && h.command.includes("avorelo lifecycle-hook"));
120
+ }
121
+ const cmd = entry.command || "";
122
+ return cmd.includes("avorelo lifecycle-hook");
123
+ }
124
+
125
+ /**
126
+ * Build the Avorelo hook entries for Claude Code settings.json format.
127
+ * Claude Code v2.1+ expects: {matcher: string, hooks: [{type, command}]}
128
+ */
129
+ function buildClaudeCodeHookEntries() {
130
+ return AVORELO_LIFECYCLE_COMMANDS.map((lc) => ({
131
+ matcher: lc.matcher,
132
+ hooks: [
133
+ {
134
+ type: "command",
135
+ command: lc.command,
136
+ },
137
+ ],
138
+ [AVORELO_HOOK_MARKER]: true,
139
+ _event: lc.event,
140
+ }));
141
+ }
142
+
143
+ /**
144
+ * Build the Avorelo hook entries for OpenHands hooks.json format.
145
+ */
146
+ function buildOpenHandsHookEntries() {
147
+ return AVORELO_LIFECYCLE_COMMANDS.map((lc) => ({
148
+ event: lc.event,
149
+ command: lc.command,
150
+ blocking: lc.blocking,
151
+ [AVORELO_HOOK_MARKER]: true,
152
+ }));
153
+ }
154
+
155
+ /**
156
+ * Merge Avorelo hooks into existing config data (Claude Code settings.json).
157
+ * Preserves all non-Avorelo hooks. Idempotent.
158
+ */
159
+ function mergeClaudeCodeConfig(existingData) {
160
+ const base = existingData ? { ...existingData } : {};
161
+ const existingHooks = base.hooks && typeof base.hooks === "object" && !Array.isArray(base.hooks)
162
+ ? base.hooks
163
+ : {};
164
+
165
+ const merged = { ...existingHooks };
166
+ const avoreloEntries = buildClaudeCodeHookEntries();
167
+
168
+ for (const entry of avoreloEntries) {
169
+ const event = entry._event;
170
+ if (!event) continue;
171
+ const existing = Array.isArray(merged[event]) ? merged[event] : [];
172
+ const userEntries = existing.filter((h) => !isAvoreloManagedHook(h));
173
+ const { _event, ...hookEntry } = entry;
174
+ merged[event] = [...userEntries, hookEntry];
175
+ }
176
+
177
+ return {
178
+ ...base,
179
+ hooks: merged,
180
+ };
181
+ }
182
+
183
+ /**
184
+ * Merge Avorelo hooks into existing OpenHands hooks.json.
185
+ */
186
+ function mergeOpenHandsConfig(existingData) {
187
+ const base = existingData ? { ...existingData } : {};
188
+ const existingHooks = Array.isArray(base.hooks) ? base.hooks : [];
189
+
190
+ const userHooks = existingHooks.filter((h) => !isAvoreloManagedHook(h));
191
+ const avoreloHooks = buildOpenHandsHookEntries();
192
+
193
+ return {
194
+ ...base,
195
+ hooks: [...userHooks, ...avoreloHooks],
196
+ };
197
+ }
198
+
199
+ /**
200
+ * Remove only Avorelo-managed hooks from config data.
201
+ */
202
+ function removeAvoreloHooks(existingData, format) {
203
+ if (!existingData) return existingData;
204
+
205
+ if (format === "claude_code_settings") {
206
+ const hooks = existingData.hooks && typeof existingData.hooks === "object" && !Array.isArray(existingData.hooks)
207
+ ? existingData.hooks
208
+ : {};
209
+ const cleaned = {};
210
+ for (const [event, entries] of Object.entries(hooks)) {
211
+ if (!Array.isArray(entries)) { cleaned[event] = entries; continue; }
212
+ const kept = entries.filter((h) => !isAvoreloManagedHook(h));
213
+ if (kept.length > 0) cleaned[event] = kept;
214
+ }
215
+ return { ...existingData, hooks: cleaned };
216
+ }
217
+ if (format === "openhands_hooks") {
218
+ const hooks = Array.isArray(existingData.hooks) ? existingData.hooks : [];
219
+ return { ...existingData, hooks: hooks.filter((h) => !isAvoreloManagedHook(h)) };
220
+ }
221
+ return existingData;
222
+ }
223
+
224
+ // ── Backup ────────────────────────────────────────────────────────────────────
225
+
226
+ /**
227
+ * Create a timestamped backup of the config file. Returns backupPath or null.
228
+ */
229
+ function backupConfig(cwd, configRelPath) {
230
+ const absPath = path.join(cwd, configRelPath);
231
+ if (!safeExists(absPath)) return null;
232
+
233
+ const backupDir = path.join(cwd, BACKUP_DIR_REL);
234
+ ensureDir(backupDir);
235
+
236
+ const ts = makeTimestamp();
237
+ const host = hostname();
238
+ const filename = `${ts}-${host}-${path.basename(configRelPath)}`;
239
+ const backupAbsPath = path.join(backupDir, filename);
240
+
241
+ fs.copyFileSync(absPath, backupAbsPath);
242
+ return path.join(BACKUP_DIR_REL, filename);
243
+ }
244
+
245
+ /**
246
+ * Find the latest backup for a given config filename.
247
+ */
248
+ function findLatestBackup(cwd, configBasename) {
249
+ const backupDir = path.join(cwd, BACKUP_DIR_REL);
250
+ if (!safeExists(backupDir)) return null;
251
+
252
+ const files = fs.readdirSync(backupDir)
253
+ .filter((f) => f.endsWith(`-${configBasename}`))
254
+ .sort()
255
+ .reverse();
256
+
257
+ if (files.length === 0) return null;
258
+ return path.join(BACKUP_DIR_REL, files[0]);
259
+ }
260
+
261
+ // ── Plan building ─────────────────────────────────────────────────────────────
262
+
263
+ /**
264
+ * Build a hook apply plan. Always safe to call (no side effects).
265
+ */
266
+ function buildHookApplyPlan(cwd, options = {}) {
267
+ const dryRun = isDryRun(options);
268
+ const requiresApproval = requiresExplicitApproval(options);
269
+
270
+ // Get adapter readiness to determine applicable hosts
271
+ let readiness;
272
+ try {
273
+ const { buildAdapterReadiness } = require("./adapter-readiness");
274
+ readiness = buildAdapterReadiness(cwd, {});
275
+ } catch (e) {
276
+ return {
277
+ contract: CONTRACT,
278
+ schemaVersion: SCHEMA_VERSION,
279
+ createdAt: nowIso(),
280
+ status: "error",
281
+ error: `adapter-readiness not available: ${e.message}`,
282
+ plan: null,
283
+ };
284
+ }
285
+
286
+ // Get hook config preview to validate preview is ready
287
+ let preview;
288
+ try {
289
+ const { buildHookConfigPreview } = require("./hook-config-preview");
290
+ preview = buildHookConfigPreview(cwd, {});
291
+ } catch (e) {
292
+ return {
293
+ contract: CONTRACT,
294
+ schemaVersion: SCHEMA_VERSION,
295
+ createdAt: nowIso(),
296
+ status: "error",
297
+ error: `hook-config-preview not available: ${e.message}`,
298
+ plan: null,
299
+ };
300
+ }
301
+
302
+ if (!preview || preview.mode !== "preview_only") {
303
+ return {
304
+ contract: CONTRACT,
305
+ schemaVersion: SCHEMA_VERSION,
306
+ createdAt: nowIso(),
307
+ status: "needs_review",
308
+ error: "Hook config preview not in preview_only mode. Cannot plan apply.",
309
+ plan: null,
310
+ };
311
+ }
312
+
313
+ const applicableHosts = [];
314
+
315
+ for (const [hostKey, hostConf] of Object.entries(HOST_APPLY_CONFIGS)) {
316
+ const hostReadiness = readiness.hosts.find((h) => h.host === hostKey);
317
+ if (!hostReadiness) continue;
318
+
319
+ if (!hostReadiness.detected) continue;
320
+
321
+ const configAbsPath = path.join(cwd, hostConf.configPath);
322
+ const parsed = parseExistingConfig(configAbsPath, hostConf.format);
323
+
324
+ let existingAvoreloHookCount = 0;
325
+ if (parsed.data && parsed.data.hooks) {
326
+ const hooksObj = parsed.data.hooks;
327
+ if (typeof hooksObj === "object" && !Array.isArray(hooksObj)) {
328
+ for (const entries of Object.values(hooksObj)) {
329
+ if (Array.isArray(entries)) {
330
+ existingAvoreloHookCount += entries.filter(isAvoreloManagedHook).length;
331
+ }
332
+ }
333
+ } else if (Array.isArray(hooksObj)) {
334
+ existingAvoreloHookCount = hooksObj.filter(isAvoreloManagedHook).length;
335
+ }
336
+ }
337
+
338
+ applicableHosts.push({
339
+ host: hostKey,
340
+ label: hostConf.label,
341
+ configPath: hostConf.configPath,
342
+ format: hostConf.format,
343
+ configExists: parsed.existed,
344
+ configValid: parsed.valid,
345
+ parseError: parsed.error || null,
346
+ existingAvoreloHookCount,
347
+ wouldAdd: AVORELO_LIFECYCLE_COMMANDS.length,
348
+ wouldUpdate: existingAvoreloHookCount > 0,
349
+ applyBlocked: !parsed.valid && parsed.existed,
350
+ blockReason: (!parsed.valid && parsed.existed) ? "invalid_json" : null,
351
+ });
352
+ }
353
+
354
+ const appliableHosts = applicableHosts.filter((h) => !h.applyBlocked);
355
+ const blockedHosts = applicableHosts.filter((h) => h.applyBlocked);
356
+
357
+ return {
358
+ contract: CONTRACT,
359
+ schemaVersion: SCHEMA_VERSION,
360
+ createdAt: nowIso(),
361
+ status: applicableHosts.length === 0 ? "no_applicable_host" : "plan_ready",
362
+ dryRun,
363
+ requiresApproval,
364
+ approvalRequired: requiresApproval,
365
+ applicableHosts,
366
+ appliableHosts: appliableHosts.map((h) => h.host),
367
+ blockedHosts: blockedHosts.map((h) => h.host),
368
+ hookCount: AVORELO_LIFECYCLE_COMMANDS.length,
369
+ message: requiresApproval
370
+ ? "ACTION REQUIRED: Pass --yes or --confirm to apply. This will modify repo-local config files."
371
+ : dryRun
372
+ ? "Dry-run mode. No config files will be modified."
373
+ : "Plan ready for apply.",
374
+ redacted: true,
375
+ safeNextAction: requiresApproval
376
+ ? "Run `node bin/avorelo hooks apply --yes --json` to apply."
377
+ : "Run `node bin/avorelo hooks apply --yes --json` to apply or `--dry-run` to preview.",
378
+ };
379
+ }
380
+
381
+ // ── Apply ─────────────────────────────────────────────────────────────────────
382
+
383
+ /**
384
+ * Apply Avorelo hooks to repo-local config files.
385
+ * Requires explicit approval via options.yes or options.confirm.
386
+ * Returns a receipt object.
387
+ */
388
+ function applyHookConfig(cwd, plan, options = {}) {
389
+ const receiptPath = path.join(cwd, LATEST_APPLY_REL);
390
+
391
+ // Enforce approval
392
+ if (requiresExplicitApproval(options)) {
393
+ const receipt = {
394
+ contract: CONTRACT,
395
+ schemaVersion: SCHEMA_VERSION,
396
+ createdAt: nowIso(),
397
+ status: "approval_required",
398
+ message: "ACTION REQUIRED: Pass --yes or --confirm to apply. No config modified.",
399
+ applied: false,
400
+ hostsApplied: [],
401
+ redacted: true,
402
+ };
403
+ safeWriteJson(receiptPath, receipt);
404
+ return receipt;
405
+ }
406
+
407
+ if (isDryRun(options)) {
408
+ const receipt = {
409
+ contract: CONTRACT,
410
+ schemaVersion: SCHEMA_VERSION,
411
+ createdAt: nowIso(),
412
+ status: "dry_run",
413
+ message: "Dry-run only. No config files modified.",
414
+ plan: redact(plan),
415
+ applied: false,
416
+ hostsApplied: [],
417
+ redacted: true,
418
+ };
419
+ safeWriteJson(receiptPath, receipt);
420
+ return receipt;
421
+ }
422
+
423
+ if (!plan || plan.status === "error" || plan.status === "needs_review") {
424
+ const receipt = {
425
+ contract: CONTRACT,
426
+ schemaVersion: SCHEMA_VERSION,
427
+ createdAt: nowIso(),
428
+ status: "blocked",
429
+ message: `Cannot apply: ${plan?.error || "plan not ready"}`,
430
+ applied: false,
431
+ hostsApplied: [],
432
+ redacted: true,
433
+ };
434
+ safeWriteJson(receiptPath, receipt);
435
+ return receipt;
436
+ }
437
+
438
+ const hostsApplied = [];
439
+ const errors = [];
440
+
441
+ for (const hostEntry of plan.applicableHosts || []) {
442
+ if (hostEntry.applyBlocked) {
443
+ errors.push({ host: hostEntry.host, error: hostEntry.blockReason });
444
+ continue;
445
+ }
446
+
447
+ const hostConf = HOST_APPLY_CONFIGS[hostEntry.host];
448
+ if (!hostConf) continue;
449
+
450
+ const configAbsPath = path.join(cwd, hostConf.configPath);
451
+
452
+ // Backup
453
+ let backupPath = null;
454
+ try {
455
+ backupPath = backupConfig(cwd, hostConf.configPath);
456
+ } catch (e) {
457
+ errors.push({ host: hostEntry.host, error: `backup_failed: ${e.message}` });
458
+ continue;
459
+ }
460
+
461
+ // Read + merge
462
+ const existing = safeReadJson(configAbsPath);
463
+ let merged;
464
+ try {
465
+ if (hostConf.format === "claude_code_settings") {
466
+ merged = mergeClaudeCodeConfig(existing);
467
+ } else if (hostConf.format === "openhands_hooks") {
468
+ merged = mergeOpenHandsConfig(existing);
469
+ } else {
470
+ errors.push({ host: hostEntry.host, error: "unsupported_format" });
471
+ continue;
472
+ }
473
+ } catch (e) {
474
+ errors.push({ host: hostEntry.host, error: `merge_failed: ${e.message}` });
475
+ continue;
476
+ }
477
+
478
+ // Write
479
+ try {
480
+ safeWriteJson(configAbsPath, merged);
481
+ } catch (e) {
482
+ // Try rollback
483
+ if (backupPath) {
484
+ try {
485
+ fs.copyFileSync(path.join(cwd, backupPath), configAbsPath);
486
+ } catch {}
487
+ }
488
+ errors.push({ host: hostEntry.host, error: `write_failed: ${e.message}` });
489
+ continue;
490
+ }
491
+
492
+ hostsApplied.push({
493
+ host: hostEntry.host,
494
+ label: hostConf.label,
495
+ configPath: hostConf.configPath,
496
+ backupPath,
497
+ hooksAdded: AVORELO_LIFECYCLE_COMMANDS.length,
498
+ });
499
+ }
500
+
501
+ const status = errors.length > 0 && hostsApplied.length === 0
502
+ ? "failed"
503
+ : errors.length > 0
504
+ ? "partial"
505
+ : hostsApplied.length > 0
506
+ ? "applied"
507
+ : "no_hosts_applied";
508
+
509
+ const receipt = {
510
+ contract: CONTRACT,
511
+ schemaVersion: SCHEMA_VERSION,
512
+ createdAt: nowIso(),
513
+ status,
514
+ applied: hostsApplied.length > 0,
515
+ hostsApplied,
516
+ errors: errors.length > 0 ? errors : undefined,
517
+ rollbackAvailable: hostsApplied.some((h) => h.backupPath),
518
+ message: status === "applied"
519
+ ? `Avorelo hooks applied to ${hostsApplied.length} host(s). Backup created.`
520
+ : status === "failed"
521
+ ? "Apply failed. No config modified."
522
+ : status === "partial"
523
+ ? "Partial apply. Some hosts failed."
524
+ : "No hosts applied.",
525
+ redacted: true,
526
+ };
527
+
528
+ safeWriteJson(receiptPath, receipt);
529
+ writeLedgerEntry(cwd, "hook_apply", receipt);
530
+ return receipt;
531
+ }
532
+
533
+ // ── Validation ────────────────────────────────────────────────────────────────
534
+
535
+ /**
536
+ * Validate that installed hooks match the expected config.
537
+ */
538
+ function validateAppliedHooks(cwd, options = {}) {
539
+ const validations = [];
540
+
541
+ for (const [hostKey, hostConf] of Object.entries(HOST_APPLY_CONFIGS)) {
542
+ const configAbsPath = path.join(cwd, hostConf.configPath);
543
+ if (!safeExists(configAbsPath)) {
544
+ validations.push({ host: hostKey, status: "not_found", configPath: hostConf.configPath });
545
+ continue;
546
+ }
547
+
548
+ const data = safeReadJson(configAbsPath);
549
+ if (!data) {
550
+ validations.push({ host: hostKey, status: "invalid_json", configPath: hostConf.configPath });
551
+ continue;
552
+ }
553
+
554
+ const hooksByEvent = data.hooks && typeof data.hooks === "object" && !Array.isArray(data.hooks)
555
+ ? data.hooks : {};
556
+ let avoreloEventCount = 0;
557
+ const foundEvents = [];
558
+ const allEntries = [];
559
+
560
+ for (const [event, entries] of Object.entries(hooksByEvent)) {
561
+ if (!Array.isArray(entries)) continue;
562
+ for (const entry of entries) {
563
+ allEntries.push({ ...entry, _validationEvent: event });
564
+ if (isAvoreloManagedHook(entry)) {
565
+ avoreloEventCount++;
566
+ foundEvents.push(event);
567
+ }
568
+ }
569
+ }
570
+
571
+ const expectedCount = AVORELO_LIFECYCLE_COMMANDS.length;
572
+ const missingEvents = AVORELO_LIFECYCLE_COMMANDS
573
+ .filter((lc) => !foundEvents.includes(lc.event))
574
+ .map((lc) => lc.event);
575
+
576
+ const duplicates = AVORELO_LIFECYCLE_COMMANDS
577
+ .filter((lc) => foundEvents.filter((e) => e === lc.event).length > 1)
578
+ .map((lc) => lc.event);
579
+
580
+ validations.push({
581
+ host: hostKey,
582
+ status: avoreloEventCount >= expectedCount && missingEvents.length === 0 ? "valid" : "incomplete",
583
+ configPath: hostConf.configPath,
584
+ avoreloHookCount: avoreloEventCount,
585
+ expectedCount,
586
+ missingEvents,
587
+ duplicates,
588
+ userHookCount: allEntries.length - avoreloEventCount,
589
+ });
590
+ }
591
+
592
+ const allValid = validations.every((v) => v.status === "valid" || v.status === "not_found");
593
+
594
+ return {
595
+ contract: CONTRACT,
596
+ schemaVersion: SCHEMA_VERSION,
597
+ createdAt: nowIso(),
598
+ status: allValid ? "valid" : "incomplete",
599
+ validations,
600
+ redacted: true,
601
+ };
602
+ }
603
+
604
+ // ── Rollback ──────────────────────────────────────────────────────────────────
605
+
606
+ /**
607
+ * Restore the latest backup for each applied host.
608
+ */
609
+ function rollbackHookApply(cwd, options = {}) {
610
+ const receiptPath = path.join(cwd, LATEST_ROLLBACK_REL);
611
+
612
+ // Read latest apply receipt to know what was applied
613
+ const latestApply = safeReadJson(path.join(cwd, LATEST_APPLY_REL));
614
+
615
+ if (!latestApply || !latestApply.applied || !latestApply.hostsApplied?.length) {
616
+ const receipt = {
617
+ contract: CONTRACT,
618
+ schemaVersion: SCHEMA_VERSION,
619
+ createdAt: nowIso(),
620
+ status: "no_apply_receipt",
621
+ message: "No apply receipt found. Nothing to rollback.",
622
+ rolledBack: false,
623
+ redacted: true,
624
+ };
625
+ safeWriteJson(receiptPath, receipt);
626
+ return receipt;
627
+ }
628
+
629
+ const rolledBack = [];
630
+ const errors = [];
631
+
632
+ for (const hostEntry of latestApply.hostsApplied) {
633
+ const hostConf = HOST_APPLY_CONFIGS[hostEntry.host];
634
+ if (!hostConf) continue;
635
+
636
+ const backupPath = hostEntry.backupPath;
637
+ if (!backupPath) {
638
+ // Find latest backup by filename
639
+ const configBasename = path.basename(hostConf.configPath);
640
+ const foundBackup = findLatestBackup(cwd, configBasename);
641
+ if (!foundBackup) {
642
+ errors.push({ host: hostEntry.host, error: "no_backup_found" });
643
+ continue;
644
+ }
645
+ try {
646
+ fs.copyFileSync(path.join(cwd, foundBackup), path.join(cwd, hostConf.configPath));
647
+ rolledBack.push({ host: hostEntry.host, configPath: hostConf.configPath, restoredFrom: foundBackup });
648
+ } catch (e) {
649
+ errors.push({ host: hostEntry.host, error: `restore_failed: ${e.message}` });
650
+ }
651
+ continue;
652
+ }
653
+
654
+ const backupAbsPath = path.join(cwd, backupPath);
655
+ if (!safeExists(backupAbsPath)) {
656
+ // Try to find any backup
657
+ const configBasename = path.basename(hostConf.configPath);
658
+ const foundBackup = findLatestBackup(cwd, configBasename);
659
+ if (!foundBackup) {
660
+ errors.push({ host: hostEntry.host, error: "backup_file_missing" });
661
+ continue;
662
+ }
663
+ try {
664
+ fs.copyFileSync(path.join(cwd, foundBackup), path.join(cwd, hostConf.configPath));
665
+ rolledBack.push({ host: hostEntry.host, configPath: hostConf.configPath, restoredFrom: foundBackup });
666
+ } catch (e) {
667
+ errors.push({ host: hostEntry.host, error: `restore_failed: ${e.message}` });
668
+ }
669
+ continue;
670
+ }
671
+
672
+ try {
673
+ fs.copyFileSync(backupAbsPath, path.join(cwd, hostConf.configPath));
674
+ rolledBack.push({ host: hostEntry.host, configPath: hostConf.configPath, restoredFrom: backupPath });
675
+ } catch (e) {
676
+ errors.push({ host: hostEntry.host, error: `restore_failed: ${e.message}` });
677
+ }
678
+ }
679
+
680
+ const status = rolledBack.length > 0 && errors.length === 0 ? "rolled_back"
681
+ : rolledBack.length > 0 ? "partial_rollback"
682
+ : "failed";
683
+
684
+ const receipt = {
685
+ contract: CONTRACT,
686
+ schemaVersion: SCHEMA_VERSION,
687
+ createdAt: nowIso(),
688
+ status,
689
+ rolledBack: rolledBack.length > 0,
690
+ rolledBackHosts: rolledBack,
691
+ errors: errors.length > 0 ? errors : undefined,
692
+ message: status === "rolled_back"
693
+ ? "Rollback successful. Backup restored."
694
+ : status === "partial_rollback"
695
+ ? "Partial rollback. Some hosts failed."
696
+ : "Rollback failed.",
697
+ redacted: true,
698
+ };
699
+
700
+ safeWriteJson(receiptPath, receipt);
701
+ writeLedgerEntry(cwd, "hook_rollback", receipt);
702
+ return receipt;
703
+ }
704
+
705
+ // ── Uninstall ─────────────────────────────────────────────────────────────────
706
+
707
+ /**
708
+ * Remove only Avorelo-managed hooks from config files.
709
+ * Requires explicit approval.
710
+ */
711
+ function uninstallAvoreloHooks(cwd, options = {}) {
712
+ const receiptPath = path.join(cwd, LATEST_APPLY_REL);
713
+
714
+ if (requiresExplicitApproval(options)) {
715
+ const receipt = {
716
+ contract: CONTRACT,
717
+ schemaVersion: SCHEMA_VERSION,
718
+ createdAt: nowIso(),
719
+ status: "approval_required",
720
+ message: "ACTION REQUIRED: Pass --yes or --confirm to uninstall. No config modified.",
721
+ uninstalled: false,
722
+ redacted: true,
723
+ };
724
+ safeWriteJson(receiptPath, receipt);
725
+ return receipt;
726
+ }
727
+
728
+ const removed = [];
729
+ const errors = [];
730
+
731
+ for (const [hostKey, hostConf] of Object.entries(HOST_APPLY_CONFIGS)) {
732
+ const configAbsPath = path.join(cwd, hostConf.configPath);
733
+ if (!safeExists(configAbsPath)) continue;
734
+
735
+ const data = safeReadJson(configAbsPath);
736
+ if (!data) {
737
+ errors.push({ host: hostKey, error: "invalid_json" });
738
+ continue;
739
+ }
740
+
741
+ // Backup before removing
742
+ let backupPath = null;
743
+ try { backupPath = backupConfig(cwd, hostConf.configPath); } catch {}
744
+
745
+ const cleaned = removeAvoreloHooks(data, hostConf.format);
746
+
747
+ try {
748
+ safeWriteJson(configAbsPath, cleaned);
749
+ removed.push({ host: hostKey, configPath: hostConf.configPath, backupPath });
750
+ } catch (e) {
751
+ if (backupPath) {
752
+ try { fs.copyFileSync(path.join(cwd, backupPath), configAbsPath); } catch {}
753
+ }
754
+ errors.push({ host: hostKey, error: `write_failed: ${e.message}` });
755
+ }
756
+ }
757
+
758
+ const status = removed.length > 0 ? "uninstalled" : errors.length > 0 ? "failed" : "nothing_to_remove";
759
+
760
+ const receipt = {
761
+ contract: CONTRACT,
762
+ schemaVersion: SCHEMA_VERSION,
763
+ createdAt: nowIso(),
764
+ status,
765
+ uninstalled: removed.length > 0,
766
+ removedHosts: removed,
767
+ errors: errors.length > 0 ? errors : undefined,
768
+ message: status === "uninstalled"
769
+ ? "Avorelo hooks uninstalled. User hooks preserved."
770
+ : status === "failed"
771
+ ? "Uninstall failed."
772
+ : "No Avorelo hooks found to remove.",
773
+ redacted: true,
774
+ };
775
+
776
+ safeWriteJson(receiptPath, receipt);
777
+ writeLedgerEntry(cwd, "hook_uninstall", receipt);
778
+ return receipt;
779
+ }
780
+
781
+ // ── Doctor ────────────────────────────────────────────────────────────────────
782
+
783
+ /**
784
+ * Validate applied hook config, command paths, recursion guard, and lifecycle smoke.
785
+ */
786
+ function runHookDoctor(cwd, options = {}) {
787
+ const receiptPath = path.join(cwd, LATEST_DOCTOR_REL);
788
+ const checks = [];
789
+
790
+ // Check 1: Applied config exists
791
+ let appliedHostCount = 0;
792
+ for (const [hostKey, hostConf] of Object.entries(HOST_APPLY_CONFIGS)) {
793
+ const configAbsPath = path.join(cwd, hostConf.configPath);
794
+ if (!safeExists(configAbsPath)) continue;
795
+
796
+ const data = safeReadJson(configAbsPath);
797
+ if (!data) {
798
+ checks.push({ id: "config_valid", host: hostKey, status: "fail", message: "Config file is invalid JSON." });
799
+ continue;
800
+ }
801
+
802
+ const hooksByEvent = data.hooks && typeof data.hooks === "object" && !Array.isArray(data.hooks)
803
+ ? data.hooks : {};
804
+ const avoreloEvents = [];
805
+ let flatHooks = [];
806
+ for (const [event, entries] of Object.entries(hooksByEvent)) {
807
+ if (!Array.isArray(entries)) continue;
808
+ for (const entry of entries) {
809
+ flatHooks.push({ ...entry, _event: event });
810
+ if (isAvoreloManagedHook(entry)) avoreloEvents.push(event);
811
+ }
812
+ }
813
+ const avoreloHooks = flatHooks.filter(isAvoreloManagedHook);
814
+ const hasMissing = AVORELO_LIFECYCLE_COMMANDS.some((lc) => !avoreloEvents.includes(lc.event));
815
+
816
+ if (avoreloHooks.length === 0) {
817
+ checks.push({ id: "hooks_installed", host: hostKey, status: "warn", message: "No Avorelo hooks found in config. Run `hooks apply --yes` to install." });
818
+ } else if (hasMissing) {
819
+ checks.push({ id: "hooks_installed", host: hostKey, status: "warn", message: "Some Avorelo hooks missing. Re-run `hooks apply --yes`." });
820
+ } else {
821
+ checks.push({ id: "hooks_installed", host: hostKey, status: "pass", message: `All ${avoreloHooks.length} Avorelo hooks present.` });
822
+ appliedHostCount++;
823
+ }
824
+
825
+ // Check duplicates
826
+ const duplicates = AVORELO_LIFECYCLE_COMMANDS.filter(
827
+ (lc) => avoreloHooks.filter((h) => (h.event || h._event) === lc.event).length > 1
828
+ ).map((lc) => lc.event);
829
+ if (duplicates.length > 0) {
830
+ checks.push({ id: "no_duplicate_hooks", host: hostKey, status: "fail", message: `Duplicate Avorelo hooks found for: ${duplicates.join(", ")}. Re-run apply to fix.` });
831
+ } else if (avoreloHooks.length > 0) {
832
+ checks.push({ id: "no_duplicate_hooks", host: hostKey, status: "pass", message: "No duplicate hooks." });
833
+ }
834
+
835
+ // Check user hooks preserved
836
+ const userHooks = flatHooks.filter((h) => !isAvoreloManagedHook(h));
837
+ checks.push({ id: "user_hooks_preserved", host: hostKey, status: "pass", message: `${userHooks.length} user hook(s) preserved.` });
838
+ }
839
+
840
+ // Check 2: Lifecycle handler commands work
841
+ try {
842
+ const { handleLifecycleHook } = require("./lifecycle-hooks");
843
+ const testResult = handleLifecycleHook(cwd, "session-start", {}, {});
844
+ if (testResult && testResult.status === "ok") {
845
+ checks.push({ id: "lifecycle_session_start", status: "pass", message: "SessionStart handler works." });
846
+ } else {
847
+ checks.push({ id: "lifecycle_session_start", status: "warn", message: "SessionStart handler returned non-ok status." });
848
+ }
849
+ } catch (e) {
850
+ checks.push({ id: "lifecycle_session_start", status: "fail", message: `SessionStart handler error: ${e.message}` });
851
+ }
852
+
853
+ // Check 3: PreToolUse destructive blocks
854
+ try {
855
+ const { handleLifecycleHook } = require("./lifecycle-hooks");
856
+ const blockResult = handleLifecycleHook(cwd, "pre-tool-use", { toolInput: { command: "rm -rf /prod" } }, {});
857
+ if (blockResult && blockResult.decision === "block") {
858
+ checks.push({ id: "pre_tool_use_blocks_destructive", status: "pass", message: "Lifecycle guard: destructive command guard is active at PreToolUse boundary." });
859
+ } else {
860
+ checks.push({ id: "pre_tool_use_blocks_destructive", status: "warn", message: "Lifecycle guard did not block destructive sample at PreToolUse boundary. Check guard logic." });
861
+ }
862
+ } catch (e) {
863
+ checks.push({ id: "pre_tool_use_blocks_destructive", status: "fail", message: `PreToolUse error: ${e.message}` });
864
+ }
865
+
866
+ // Check 4: Stop proof gate
867
+ try {
868
+ const { handleLifecycleHook } = require("./lifecycle-hooks");
869
+ const stopResult = handleLifecycleHook(cwd, "stop", {}, {});
870
+ if (stopResult && (stopResult.decision === "allow" || stopResult.decision === "block")) {
871
+ checks.push({ id: "stop_proof_gate", status: "pass", message: `Stop handler works (decision: ${stopResult.decision}).` });
872
+ } else {
873
+ checks.push({ id: "stop_proof_gate", status: "warn", message: "Stop handler did not return a clear decision." });
874
+ }
875
+ } catch (e) {
876
+ checks.push({ id: "stop_proof_gate", status: "fail", message: `Stop handler error: ${e.message}` });
877
+ }
878
+
879
+ // Check 5: Recursion guard
880
+ const guardEnv = process.env.AVORELO_HOOK_ACTIVE;
881
+ checks.push({
882
+ id: "recursion_guard",
883
+ status: "pass",
884
+ message: `Recursion guard (AVORELO_HOOK_ACTIVE) is ${guardEnv ? "ACTIVE — would skip re-entry" : "inactive (normal — guard is active only when hook runs)."}`,
885
+ guardActive: !!guardEnv,
886
+ });
887
+
888
+ // Check 6: Rollback available
889
+ const latestApply = safeReadJson(path.join(cwd, LATEST_APPLY_REL));
890
+ const hasBackup = latestApply?.hostsApplied?.some((h) => h.backupPath);
891
+ checks.push({
892
+ id: "rollback_available",
893
+ status: hasBackup ? "pass" : "warn",
894
+ message: hasBackup
895
+ ? "Rollback available (backup exists from last apply)."
896
+ : "No backup found. Apply hooks first to enable rollback.",
897
+ });
898
+
899
+ // Check 7: No global config modification
900
+ checks.push({
901
+ id: "no_global_config_modified",
902
+ status: "pass",
903
+ message: "Hook apply only modifies repo-local config. Global config untouched.",
904
+ });
905
+
906
+ const failCount = checks.filter((c) => c.status === "fail").length;
907
+ const warnCount = checks.filter((c) => c.status === "warn").length;
908
+ const passCount = checks.filter((c) => c.status === "pass").length;
909
+
910
+ const doctorStatus = failCount > 0 ? "fail" : warnCount > 0 ? "warn" : "pass";
911
+
912
+ const nextAction = failCount > 0
913
+ ? checks.find((c) => c.status === "fail")?.message
914
+ : warnCount > 0
915
+ ? "Run `node bin/avorelo hooks apply --yes --json` to install hooks."
916
+ : "All checks pass. Lifecycle is live.";
917
+
918
+ const receipt = {
919
+ contract: CONTRACT,
920
+ schemaVersion: SCHEMA_VERSION,
921
+ createdAt: nowIso(),
922
+ status: doctorStatus,
923
+ checks,
924
+ summary: { pass: passCount, warn: warnCount, fail: failCount },
925
+ nextAction,
926
+ rollbackAvailable: hasBackup || false,
927
+ hooksApplied: appliedHostCount > 0,
928
+ redacted: true,
929
+ };
930
+
931
+ safeWriteJson(receiptPath, receipt);
932
+ writeLedgerEntry(cwd, "hook_doctor", receipt);
933
+ return receipt;
934
+ }
935
+
936
+ // ── Receipt writer ────────────────────────────────────────────────────────────
937
+
938
+ function writeHookApplyReceipt(cwd, receipt) {
939
+ safeWriteJson(path.join(cwd, LATEST_APPLY_REL), receipt);
940
+ return LATEST_APPLY_REL;
941
+ }
942
+
943
+ // ── Surface / text formatters ─────────────────────────────────────────────────
944
+
945
+ function buildHookApplySurface(cwd, options = {}) {
946
+ const plan = buildHookApplyPlan(cwd, options);
947
+ const latestApply = safeReadJson(path.join(cwd, LATEST_APPLY_REL));
948
+ const latestDoctor = safeReadJson(path.join(cwd, LATEST_DOCTOR_REL));
949
+
950
+ return {
951
+ plan,
952
+ latestApply: latestApply || null,
953
+ latestDoctor: latestDoctor || null,
954
+ rollbackAvailable: !!(latestApply?.hostsApplied?.some((h) => h.backupPath)),
955
+ };
956
+ }
957
+
958
+ function formatHookApplyText(receipt, options = {}) {
959
+ const lines = [];
960
+
961
+ if (!receipt) {
962
+ lines.push("No hook apply receipt available. Run `node bin/avorelo hooks apply --dry-run --json`.");
963
+ return lines.join("\n");
964
+ }
965
+
966
+ lines.push(`Hook Apply status=${receipt.status}`);
967
+
968
+ if (receipt.message) lines.push(` ${receipt.message}`);
969
+
970
+ if (receipt.hostsApplied?.length) {
971
+ for (const h of receipt.hostsApplied) {
972
+ lines.push(` Applied: ${h.host} config=${h.configPath} backup=${h.backupPath || "none"}`);
973
+ }
974
+ }
975
+ if (receipt.errors?.length) {
976
+ for (const e of receipt.errors) {
977
+ lines.push(` Error: ${e.host} ${e.error}`);
978
+ }
979
+ }
980
+ if (receipt.safeNextAction) {
981
+ lines.push(` Next: ${receipt.safeNextAction}`);
982
+ }
983
+
984
+ return lines.join("\n");
985
+ }
986
+
987
+ function formatHookDoctorText(receipt, options = {}) {
988
+ const lines = [];
989
+
990
+ if (!receipt) {
991
+ lines.push("No doctor receipt. Run `node bin/avorelo hooks doctor --json`.");
992
+ return lines.join("\n");
993
+ }
994
+
995
+ lines.push(`Hook Doctor status=${receipt.status} pass=${receipt.summary?.pass} warn=${receipt.summary?.warn} fail=${receipt.summary?.fail}`);
996
+
997
+ if (options.debug) {
998
+ for (const c of receipt.checks || []) {
999
+ lines.push(` [${c.status}] ${c.id}: ${c.message}`);
1000
+ }
1001
+ } else {
1002
+ const problems = (receipt.checks || []).filter((c) => c.status !== "pass");
1003
+ for (const c of problems) {
1004
+ lines.push(` [${c.status}] ${c.id}: ${c.message}`);
1005
+ }
1006
+ }
1007
+
1008
+ if (receipt.nextAction) lines.push(` Next: ${receipt.nextAction}`);
1009
+
1010
+ return lines.join("\n");
1011
+ }
1012
+
1013
+ // ── Ledger integration ────────────────────────────────────────────────────────
1014
+
1015
+ function writeLedgerEntry(cwd, type, receipt) {
1016
+ try {
1017
+ const { appendProductLearningEvent } = require("./product-learning-events");
1018
+ const eventName = {
1019
+ hook_apply: "hook_config_applied",
1020
+ hook_rollback: "hook_rollback_performed",
1021
+ hook_uninstall: "hook_uninstall_performed",
1022
+ hook_doctor: "hook_doctor_run",
1023
+ }[type];
1024
+
1025
+ if (eventName) {
1026
+ appendProductLearningEvent(cwd, {
1027
+ eventName,
1028
+ payload: {
1029
+ type,
1030
+ status: receipt.status,
1031
+ hostsApplied: receipt.hostsApplied?.map((h) => h.host) || [],
1032
+ rollbackAvailable: receipt.rollbackAvailable || false,
1033
+ },
1034
+ });
1035
+ }
1036
+ } catch {
1037
+ // non-fatal
1038
+ }
1039
+ }
1040
+
1041
+ // ── Exports ───────────────────────────────────────────────────────────────────
1042
+
1043
+ module.exports = {
1044
+ CONTRACT,
1045
+ SCHEMA_VERSION,
1046
+ LATEST_APPLY_REL,
1047
+ LATEST_DOCTOR_REL,
1048
+ LATEST_ROLLBACK_REL,
1049
+ BACKUP_DIR_REL,
1050
+ AVORELO_HOOK_MARKER,
1051
+ AVORELO_LIFECYCLE_COMMANDS,
1052
+ HOST_APPLY_CONFIGS,
1053
+ // Core API
1054
+ buildHookApplyPlan,
1055
+ applyHookConfig,
1056
+ validateAppliedHooks,
1057
+ rollbackHookApply,
1058
+ uninstallAvoreloHooks,
1059
+ runHookDoctor,
1060
+ writeHookApplyReceipt,
1061
+ buildHookApplySurface,
1062
+ // Formatters
1063
+ formatHookApplyText,
1064
+ formatHookDoctorText,
1065
+ // Helpers (exported for testing)
1066
+ isAvoreloManagedHook,
1067
+ mergeClaudeCodeConfig,
1068
+ mergeOpenHandsConfig,
1069
+ removeAvoreloHooks,
1070
+ backupConfig,
1071
+ findLatestBackup,
1072
+ parseExistingConfig,
1073
+ requiresExplicitApproval,
1074
+ isDryRun,
1075
+ };