avorelo 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +56 -0
  3. package/bin/avorelo +9 -0
  4. package/package.json +135 -0
  5. package/scripts/README.md +40 -0
  6. package/scripts/cco-dashboard.js +252 -0
  7. package/scripts/cco-status.js +430 -0
  8. package/scripts/lib/activation/account-state.js +37 -0
  9. package/scripts/lib/activation/activation-runner.js +546 -0
  10. package/scripts/lib/activation/activation-self-healing.js +480 -0
  11. package/scripts/lib/activation/activation-state.js +83 -0
  12. package/scripts/lib/activation/activation-summary.js +191 -0
  13. package/scripts/lib/activation/adapters/claude-code.js +77 -0
  14. package/scripts/lib/activation/adapters/codex-cli.js +52 -0
  15. package/scripts/lib/activation/adapters/cursor.js +37 -0
  16. package/scripts/lib/activation/adapters/github-agent.js +39 -0
  17. package/scripts/lib/activation/adapters/terminal.js +42 -0
  18. package/scripts/lib/activation/adapters/vscode.js +39 -0
  19. package/scripts/lib/activation/adapters/windsurf.js +37 -0
  20. package/scripts/lib/activation/ai-surface-detector.js +151 -0
  21. package/scripts/lib/activation/connect-account.js +145 -0
  22. package/scripts/lib/activation/detect-environment.js +75 -0
  23. package/scripts/lib/activation/detect-hosts.js +62 -0
  24. package/scripts/lib/activation/format-activation-output.js +109 -0
  25. package/scripts/lib/activation/next-action.js +43 -0
  26. package/scripts/lib/activation/repair-engine.js +219 -0
  27. package/scripts/lib/activation-distribution-readiness.js +507 -0
  28. package/scripts/lib/adapter-conformance.js +176 -0
  29. package/scripts/lib/adapter-readiness.js +417 -0
  30. package/scripts/lib/adapter-safety-boundaries.js +335 -0
  31. package/scripts/lib/adapter-technical-readiness-gate.js +205 -0
  32. package/scripts/lib/agent-access-governance.js +455 -0
  33. package/scripts/lib/agent-enforcement.js +765 -0
  34. package/scripts/lib/agent-policy-profile.js +210 -0
  35. package/scripts/lib/agent-security/action-evaluator.js +507 -0
  36. package/scripts/lib/agent-security/adapter-registry.js +98 -0
  37. package/scripts/lib/agent-security/auto-policy.js +139 -0
  38. package/scripts/lib/agent-security/bounded-scan.js +93 -0
  39. package/scripts/lib/agent-security/enforcement-adapter.js +174 -0
  40. package/scripts/lib/agent-security/enforcement-engine.js +1129 -0
  41. package/scripts/lib/agent-security/file-write-adapter.js +183 -0
  42. package/scripts/lib/agent-security/file-write-rules.js +178 -0
  43. package/scripts/lib/agent-security/index.js +3342 -0
  44. package/scripts/lib/agent-security/instruction-risk.js +181 -0
  45. package/scripts/lib/agent-security/mcp-action-adapter.js +185 -0
  46. package/scripts/lib/agent-security/mcp-action-rules.js +184 -0
  47. package/scripts/lib/agent-security/package-action-adapter.js +175 -0
  48. package/scripts/lib/agent-security/package-action-rules.js +233 -0
  49. package/scripts/lib/agent-security/performance.js +148 -0
  50. package/scripts/lib/agent-security/permission-minimizer.js +403 -0
  51. package/scripts/lib/agent-security/scan-cache.js +74 -0
  52. package/scripts/lib/agent-security/source-trust.js +146 -0
  53. package/scripts/lib/ai-install-prompt.js +288 -0
  54. package/scripts/lib/ai-workspace-hygiene.js +1499 -0
  55. package/scripts/lib/alpha-activation.js +520 -0
  56. package/scripts/lib/alpha-feedback.js +263 -0
  57. package/scripts/lib/alpha-readiness-gate.js +332 -0
  58. package/scripts/lib/anti-gaming.js +169 -0
  59. package/scripts/lib/artifact-health.js +431 -0
  60. package/scripts/lib/attribution.js +180 -0
  61. package/scripts/lib/audit.js +289 -0
  62. package/scripts/lib/avorelo-skill-registry.js +810 -0
  63. package/scripts/lib/batch-jobs.js +71 -0
  64. package/scripts/lib/brain-pack.js +578 -0
  65. package/scripts/lib/brand-boundary.js +424 -0
  66. package/scripts/lib/brand.js +74 -0
  67. package/scripts/lib/browser-capability.js +1048 -0
  68. package/scripts/lib/browser-proof-preflight.js +321 -0
  69. package/scripts/lib/cache-readiness.js +187 -0
  70. package/scripts/lib/canonical-reentry.js +162 -0
  71. package/scripts/lib/capability-packs.js +314 -0
  72. package/scripts/lib/capability-recommender.js +512 -0
  73. package/scripts/lib/capability-registry.js +1059 -0
  74. package/scripts/lib/carry-forward-surfacing.js +194 -0
  75. package/scripts/lib/ccusage-adapter.js +188 -0
  76. package/scripts/lib/company-loop.js +1149 -0
  77. package/scripts/lib/config.js +637 -0
  78. package/scripts/lib/context-acquisition-plan.js +287 -0
  79. package/scripts/lib/context-budget-guard.js +170 -0
  80. package/scripts/lib/context-budget-scanner.js +257 -0
  81. package/scripts/lib/context-optimizer.js +715 -0
  82. package/scripts/lib/context-reduction-plan.js +178 -0
  83. package/scripts/lib/context-safety.js +88 -0
  84. package/scripts/lib/context-savings-engine.js +158 -0
  85. package/scripts/lib/cost-evidence.js +254 -0
  86. package/scripts/lib/cross-host-install-plan.js +308 -0
  87. package/scripts/lib/cross-host-install-readiness.js +237 -0
  88. package/scripts/lib/cross-host-value-flow.js +268 -0
  89. package/scripts/lib/dashboard.js +900 -0
  90. package/scripts/lib/design-partner-feedback.js +346 -0
  91. package/scripts/lib/entitlements.js +100 -0
  92. package/scripts/lib/execution-packet.js +559 -0
  93. package/scripts/lib/experimentation-events.js +547 -0
  94. package/scripts/lib/external-capability-compliance.js +107 -0
  95. package/scripts/lib/external-user-simulation.js +166 -0
  96. package/scripts/lib/failure-recovery-readiness.js +81 -0
  97. package/scripts/lib/failure-recovery.js +419 -0
  98. package/scripts/lib/feedback-intelligence.js +537 -0
  99. package/scripts/lib/feedback-signals.js +205 -0
  100. package/scripts/lib/file-integrity.js +68 -0
  101. package/scripts/lib/fsx.js +127 -0
  102. package/scripts/lib/full-readiness-gate.js +451 -0
  103. package/scripts/lib/guidance-builder.js +174 -0
  104. package/scripts/lib/hook-apply.js +1019 -0
  105. package/scripts/lib/hook-baseline.js +310 -0
  106. package/scripts/lib/hook-config-preview.js +275 -0
  107. package/scripts/lib/hook-contracts.js +290 -0
  108. package/scripts/lib/hook-safety-boundary-readiness.js +80 -0
  109. package/scripts/lib/host-capability-matrix.js +351 -0
  110. package/scripts/lib/host-support-context.js +254 -0
  111. package/scripts/lib/http-hook-action.js +538 -0
  112. package/scripts/lib/install-ai-readiness.js +84 -0
  113. package/scripts/lib/install-intake-risk.js +1037 -0
  114. package/scripts/lib/install-journey-intelligence.js +329 -0
  115. package/scripts/lib/intervention-guidance.js +57 -0
  116. package/scripts/lib/known-limitations.js +115 -0
  117. package/scripts/lib/l8-path-truth.js +146 -0
  118. package/scripts/lib/launch-hardening-gate.js +436 -0
  119. package/scripts/lib/launch-readiness.js +628 -0
  120. package/scripts/lib/learning-memory.js +686 -0
  121. package/scripts/lib/lifecycle-hooks.js +802 -0
  122. package/scripts/lib/local-package-smoke.js +423 -0
  123. package/scripts/lib/local-pricing.js +299 -0
  124. package/scripts/lib/mcp-enforcement.js +311 -0
  125. package/scripts/lib/mcp-least-privilege-policy.js +303 -0
  126. package/scripts/lib/mcp-tool-inventory.js +388 -0
  127. package/scripts/lib/mcp-tool-risk.js +0 -0
  128. package/scripts/lib/memory.js +335 -0
  129. package/scripts/lib/metrics.js +699 -0
  130. package/scripts/lib/micro-proof.js +133 -0
  131. package/scripts/lib/next-run-context.js +436 -0
  132. package/scripts/lib/operating-value.js +1648 -0
  133. package/scripts/lib/optimization-v3.js +122 -0
  134. package/scripts/lib/orchestration/adapters/_shared.js +49 -0
  135. package/scripts/lib/orchestration/adapters/aider.js +18 -0
  136. package/scripts/lib/orchestration/adapters/claude-code.js +35 -0
  137. package/scripts/lib/orchestration/adapters/codex.js +35 -0
  138. package/scripts/lib/orchestration/adapters/gemini-cli.js +18 -0
  139. package/scripts/lib/orchestration/adapters/git.js +25 -0
  140. package/scripts/lib/orchestration/adapters/index.js +31 -0
  141. package/scripts/lib/orchestration/adapters/lm-studio.js +18 -0
  142. package/scripts/lib/orchestration/adapters/ollama.js +18 -0
  143. package/scripts/lib/orchestration/adapters/opencode.js +18 -0
  144. package/scripts/lib/orchestration/adapters/openrouter.js +18 -0
  145. package/scripts/lib/orchestration/adapters/test-runner.js +25 -0
  146. package/scripts/lib/orchestration/cli.js +438 -0
  147. package/scripts/lib/orchestration/execution-manager.js +279 -0
  148. package/scripts/lib/orchestration/handoff.js +314 -0
  149. package/scripts/lib/orchestration/index.js +456 -0
  150. package/scripts/lib/orchestration/inventory.js +47 -0
  151. package/scripts/lib/orchestration/model-discovery.js +498 -0
  152. package/scripts/lib/orchestration/model-profiler.js +170 -0
  153. package/scripts/lib/orchestration/model-profiles.js +252 -0
  154. package/scripts/lib/orchestration/model-refresh-policy.js +72 -0
  155. package/scripts/lib/orchestration/proof-writer.js +349 -0
  156. package/scripts/lib/orchestration/provider-discovery/aider.js +49 -0
  157. package/scripts/lib/orchestration/provider-discovery/claude-code.js +56 -0
  158. package/scripts/lib/orchestration/provider-discovery/codex.js +49 -0
  159. package/scripts/lib/orchestration/provider-discovery/common.js +186 -0
  160. package/scripts/lib/orchestration/provider-discovery/gemini.js +106 -0
  161. package/scripts/lib/orchestration/provider-discovery/lm-studio.js +118 -0
  162. package/scripts/lib/orchestration/provider-discovery/models-dev.js +12 -0
  163. package/scripts/lib/orchestration/provider-discovery/ollama.js +100 -0
  164. package/scripts/lib/orchestration/provider-discovery/opencode.js +47 -0
  165. package/scripts/lib/orchestration/provider-discovery/openrouter.js +44 -0
  166. package/scripts/lib/orchestration/risk-classifier.js +130 -0
  167. package/scripts/lib/orchestration/routing-policy.js +486 -0
  168. package/scripts/lib/orchestration/settings.js +112 -0
  169. package/scripts/lib/orchestration/state.js +165 -0
  170. package/scripts/lib/orchestration/verification-manager.js +138 -0
  171. package/scripts/lib/output-profiles.js +146 -0
  172. package/scripts/lib/package-content-audit.js +368 -0
  173. package/scripts/lib/package-runtime.js +278 -0
  174. package/scripts/lib/plan-surface.js +53 -0
  175. package/scripts/lib/plans.js +2318 -0
  176. package/scripts/lib/policy-provider.js +27 -0
  177. package/scripts/lib/prelaunch-activation-readiness.js +409 -0
  178. package/scripts/lib/prelaunch-evidence-store.js +816 -0
  179. package/scripts/lib/prelaunch-intelligence.js +869 -0
  180. package/scripts/lib/pricing-experiment.js +118 -0
  181. package/scripts/lib/pro-moment-events.js +77 -0
  182. package/scripts/lib/pro-moment-state.js +227 -0
  183. package/scripts/lib/pro-moments.js +1216 -0
  184. package/scripts/lib/product-learning-events.js +629 -0
  185. package/scripts/lib/project-profile.js +555 -0
  186. package/scripts/lib/prompt-compiler.js +280 -0
  187. package/scripts/lib/prompt-lint.js +32 -0
  188. package/scripts/lib/prompt-suggestions.js +52 -0
  189. package/scripts/lib/proof-canonical.js +398 -0
  190. package/scripts/lib/proof-drilldown.js +383 -0
  191. package/scripts/lib/proof-events.js +342 -0
  192. package/scripts/lib/proof-history.js +243 -0
  193. package/scripts/lib/proof-metrics.js +296 -0
  194. package/scripts/lib/proof-outcome-evidence.js +134 -0
  195. package/scripts/lib/proof-receipt.js +335 -0
  196. package/scripts/lib/proof-record.js +461 -0
  197. package/scripts/lib/public-activation-distribution-gate.js +258 -0
  198. package/scripts/lib/public-cli.js +3891 -0
  199. package/scripts/lib/public-distribution-truth.js +211 -0
  200. package/scripts/lib/public-install-claim-checker.js +294 -0
  201. package/scripts/lib/publish-provenance-readiness.js +283 -0
  202. package/scripts/lib/readiness-delta.js +218 -0
  203. package/scripts/lib/readiness-evidence-closure.js +196 -0
  204. package/scripts/lib/reentry-memory-capture.js +241 -0
  205. package/scripts/lib/reentry-memory-retrieval.js +302 -0
  206. package/scripts/lib/reentry-memory-status.js +146 -0
  207. package/scripts/lib/reentry-memory-store.js +178 -0
  208. package/scripts/lib/reentry-state.js +66 -0
  209. package/scripts/lib/release-candidate-bundle.js +166 -0
  210. package/scripts/lib/remediation.js +81 -0
  211. package/scripts/lib/repo-map.js +391 -0
  212. package/scripts/lib/run-improvements-lifecycle.js +330 -0
  213. package/scripts/lib/run-improvements.js +789 -0
  214. package/scripts/lib/runtime-decision-policy.js +387 -0
  215. package/scripts/lib/safe-path-engine.js +705 -0
  216. package/scripts/lib/safe-run-controller.js +887 -0
  217. package/scripts/lib/score.js +262 -0
  218. package/scripts/lib/seamless-enforcement.js +329 -0
  219. package/scripts/lib/seamless-outcome.js +689 -0
  220. package/scripts/lib/seamless-reality-gate.js +5043 -0
  221. package/scripts/lib/security-risk-classifier.js +511 -0
  222. package/scripts/lib/security-scan.js +384 -0
  223. package/scripts/lib/session-context-optimizer.js +1211 -0
  224. package/scripts/lib/session-timing.js +315 -0
  225. package/scripts/lib/skill-hygiene.js +805 -0
  226. package/scripts/lib/skill-packs.js +161 -0
  227. package/scripts/lib/skills-operating-layer.js +580 -0
  228. package/scripts/lib/smart-work-routing.js +768 -0
  229. package/scripts/lib/source-catalog.js +700 -0
  230. package/scripts/lib/status-value-summary.js +32 -0
  231. package/scripts/lib/support-bundle.js +578 -0
  232. package/scripts/lib/task-continuation.js +440 -0
  233. package/scripts/lib/test-helpers.js +15 -0
  234. package/scripts/lib/tier.js +38 -0
  235. package/scripts/lib/token-context-quality-gate.js +370 -0
  236. package/scripts/lib/token-cost-capture.js +187 -0
  237. package/scripts/lib/token-cost-intelligence.js +358 -0
  238. package/scripts/lib/token-efficiency-evidence.js +213 -0
  239. package/scripts/lib/token-evidence.js +699 -0
  240. package/scripts/lib/tokenish.js +17 -0
  241. package/scripts/lib/tool-output-sandbox.js +304 -0
  242. package/scripts/lib/trust-audit.js +136 -0
  243. package/scripts/lib/unified-events.js +396 -0
  244. package/scripts/lib/upgrade-interruption-recovery.js +407 -0
  245. package/scripts/lib/usage-ledger.js +201 -0
  246. package/scripts/lib/value-ledger.js +130 -0
  247. package/scripts/lib/value-proof-calibration.js +531 -0
  248. package/scripts/lib/visual-qa.js +231 -0
  249. package/scripts/lib/voice-alpha.js +29 -0
  250. package/scripts/lib/work-aware-orchestration.js +976 -0
  251. package/scripts/lib/work-control-receipts.js +577 -0
  252. package/scripts/lib/work-ledger.js +1123 -0
  253. package/scripts/lib/work-panel-preview.js +352 -0
  254. package/scripts/lib/workflow-discipline.js +280 -0
  255. package/scripts/lib/workflow-signals.js +419 -0
  256. package/scripts/lib/workspace-map.js +281 -0
  257. package/scripts/lib/workspace-registry.js +1367 -0
  258. package/scripts/lib/workspace-resolver.js +480 -0
@@ -0,0 +1,802 @@
1
+ "use strict";
2
+
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+
6
+ const CONTRACT = "avorelo.lifecycleHookHandler.v1";
7
+ const SCHEMA_VERSION = 1;
8
+
9
+ const HOOKS_DIR_REL = ".claude/cco/orchestration/lifecycle-hooks";
10
+
11
+ const DESTRUCTIVE_KEYWORDS = Object.freeze([
12
+ "deploy",
13
+ "publish",
14
+ "delete database",
15
+ "drop table",
16
+ "rm -rf",
17
+ "git clean",
18
+ "force push",
19
+ "reset --hard",
20
+ "secret",
21
+ "password",
22
+ "api key",
23
+ "prod",
24
+ "production",
25
+ "staging push",
26
+ ]);
27
+
28
+ function ensureDir(dir) {
29
+ fs.mkdirSync(dir, { recursive: true });
30
+ }
31
+
32
+ function nowIso() {
33
+ return new Date().toISOString();
34
+ }
35
+
36
+ function makeHandlerId() {
37
+ return `lh-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
38
+ }
39
+
40
+ function safeWriteJson(filePath, data) {
41
+ ensureDir(path.dirname(filePath));
42
+ fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
43
+ }
44
+
45
+ function safeReadJson(filePath) {
46
+ try {
47
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
52
+
53
+ function containsDestructiveIntent(text) {
54
+ if (!text) return false;
55
+ const lower = text.toLowerCase();
56
+ return DESTRUCTIVE_KEYWORDS.some((kw) => lower.includes(kw));
57
+ }
58
+
59
+ function writeReceipt(cwd, event, result) {
60
+ const eventSlug = event.toLowerCase().replace(/[^a-z0-9]/g, "-");
61
+ const relPath = `${HOOKS_DIR_REL}/latest-${eventSlug}.json`;
62
+ const absPath = path.join(cwd, relPath);
63
+ safeWriteJson(absPath, result);
64
+ return relPath;
65
+ }
66
+
67
+ function writeLedgerEntry(cwd, event, result) {
68
+ try {
69
+ const { appendProductLearningEvent } = require("./product-learning-events");
70
+ appendProductLearningEvent(cwd, {
71
+ eventName: "lifecycle_hook_handler_invoked",
72
+ payload: {
73
+ event,
74
+ handlerId: result.handlerId,
75
+ status: result.status,
76
+ blocking: result.blocking,
77
+ reasonCodes: result.reasonCodes || [],
78
+ },
79
+ });
80
+ } catch {
81
+ // product-learning not available — non-fatal
82
+ }
83
+ }
84
+
85
+ // ── SessionStart ─────────────────────────────────────────────────────────────
86
+
87
+ function handleSessionStart(cwd, input = {}, options = {}) {
88
+ const handlerId = makeHandlerId();
89
+ const result = {
90
+ contract: CONTRACT,
91
+ schemaVersion: SCHEMA_VERSION,
92
+ handlerId,
93
+ event: "SessionStart",
94
+ blocking: false,
95
+ status: "ok",
96
+ createdAt: nowIso(),
97
+ actions: [],
98
+ summary: null,
99
+ redacted: true,
100
+ };
101
+
102
+ // Load project profile (cheap, local)
103
+ try {
104
+ const profilePath = path.join(cwd, ".claude/cco/orchestration/project-profile/latest-profile.json");
105
+ const profile = safeReadJson(profilePath);
106
+ if (profile) {
107
+ result.actions.push({ action: "project_profile_loaded", status: "ok" });
108
+ result.projectProfile = {
109
+ framework: profile.framework || null,
110
+ packageManager: profile.packageManager || null,
111
+ allowedActions: profile.allowedActions ? profile.allowedActions.slice(0, 5) : [],
112
+ redacted: true,
113
+ };
114
+ } else {
115
+ result.actions.push({ action: "project_profile_loaded", status: "not_found" });
116
+ }
117
+ } catch {
118
+ result.actions.push({ action: "project_profile_loaded", status: "error" });
119
+ }
120
+
121
+ // Workspace hygiene summary (status only, no heavy scan)
122
+ try {
123
+ const hygienePath = path.join(cwd, ".claude/cco/orchestration/ai-workspace-hygiene/latest-report.json");
124
+ const hygiene = safeReadJson(hygienePath);
125
+ if (hygiene) {
126
+ result.actions.push({ action: "workspace_hygiene_checked", status: "ok" });
127
+ result.workspaceStatus = {
128
+ status: hygiene.status || "unknown",
129
+ redacted: true,
130
+ };
131
+ } else {
132
+ result.actions.push({ action: "workspace_hygiene_checked", status: "not_found" });
133
+ }
134
+ } catch {
135
+ result.actions.push({ action: "workspace_hygiene_checked", status: "skipped" });
136
+ }
137
+
138
+ // Auto-inject next-run context if available and not expired
139
+ try {
140
+ const { loadNextRunContext, buildCompactContextText, updateNextRunContextStatus, emitNextRunContextEvent } = require("./next-run-context");
141
+ const nrc = loadNextRunContext(cwd);
142
+ if (nrc && nrc.status !== "not_available" && nrc.status !== "expired" && nrc.status !== "insufficient_evidence" && nrc.status !== "error") {
143
+ const compactText = buildCompactContextText(nrc);
144
+ result.actions.push({ action: "next_run_context_loaded", status: "ok" });
145
+ result.nextRunContext = {
146
+ status: nrc.status,
147
+ defaultsCount: (nrc.appliedDefaults || []).length,
148
+ decisionsCount: nrc.activeDecisionCount || 0,
149
+ risksCount: nrc.activeRiskCount || 0,
150
+ sourceRunFingerprint: nrc.sourceRunFingerprint,
151
+ compactText: compactText || null,
152
+ artifactPath: ".claude/cco/state/next-run-context.json",
153
+ redacted: true,
154
+ };
155
+ // Mark as injected if it was applied (session-start is the real injection point)
156
+ if (nrc.status === "applied") {
157
+ const now = nowIso();
158
+ updateNextRunContextStatus(cwd, "injected", { injectedAt: now });
159
+ emitNextRunContextEvent(cwd, { ...nrc, status: "injected", injectedSurface: "session_start" }, "next_run_context.injected");
160
+ result.nextRunContext.status = "injected";
161
+ }
162
+ } else {
163
+ result.actions.push({ action: "next_run_context_loaded", status: nrc ? nrc.status : "not_found" });
164
+ }
165
+ } catch {
166
+ result.actions.push({ action: "next_run_context_loaded", status: "skipped" });
167
+ }
168
+
169
+ result.summary = "Session state prepared. Project profile, workspace status, and next-run context loaded.";
170
+ result.safeNextAction = "Continue session. Avorelo has prepared project/workspace state.";
171
+ result.reasonCodes = ["SESSION_STATE_PREPARED", "NON_BLOCKING"];
172
+
173
+ const receiptPath = writeReceipt(cwd, "SessionStart", result);
174
+ result.receiptPath = receiptPath;
175
+ writeLedgerEntry(cwd, "SessionStart", result);
176
+
177
+ return result;
178
+ }
179
+
180
+ // ── UserPromptSubmit ──────────────────────────────────────────────────────────
181
+
182
+ function handleUserPromptSubmit(cwd, input = {}, options = {}) {
183
+ const handlerId = makeHandlerId();
184
+ const taskText = input.prompt || input.taskText || input.task || "";
185
+
186
+ const result = {
187
+ contract: CONTRACT,
188
+ schemaVersion: SCHEMA_VERSION,
189
+ handlerId,
190
+ event: "UserPromptSubmit",
191
+ blocking: false,
192
+ status: "ok",
193
+ createdAt: nowIso(),
194
+ actions: [],
195
+ decision: "allow",
196
+ summary: null,
197
+ redacted: true,
198
+ };
199
+
200
+ // Check for prohibited intent
201
+ if (containsDestructiveIntent(taskText)) {
202
+ result.blocking = true;
203
+ result.decision = "block";
204
+ result.status = "blocked";
205
+ result.summary = "Prompt contains prohibited intent (destructive/deploy/secrets/prod). Blocked before routing.";
206
+ result.safeNextAction = "Rephrase the task to avoid destructive, deployment, or secrets-related actions.";
207
+ result.reasonCodes = ["BLOCKING_PROHIBITED_INTENT", "DESTRUCTIVE_KEYWORDS_DETECTED"];
208
+ result.actions.push({ action: "prompt_classified", status: "blocked", reason: "prohibited_intent" });
209
+ const receiptPath = writeReceipt(cwd, "UserPromptSubmit", result);
210
+ result.receiptPath = receiptPath;
211
+ writeLedgerEntry(cwd, "UserPromptSubmit", result);
212
+ try {
213
+ const { appendProductLearningEvent } = require("./product-learning-events");
214
+ appendProductLearningEvent(cwd, {
215
+ eventName: "lifecycle_pre_tool_use_blocked",
216
+ payload: { event: "UserPromptSubmit", reason: "prohibited_intent", redacted: true },
217
+ });
218
+ } catch {}
219
+ return result;
220
+ }
221
+
222
+ result.actions.push({ action: "prompt_classified", status: "ok", reason: "safe" });
223
+
224
+ // Build safe-run plan (prepare-only, no execution)
225
+ if (taskText) {
226
+ try {
227
+ const { buildSafeRunPlan } = require("./safe-run-controller");
228
+ const plan = buildSafeRunPlan(cwd, taskText, { prepareOnly: true });
229
+ result.actions.push({ action: "safe_run_plan_built", status: "ok" });
230
+ result.safeRunPlan = {
231
+ runId: plan.runId || null,
232
+ taskType: plan.taskType || null,
233
+ riskLevel: plan.riskLevel || null,
234
+ steps: plan.steps ? plan.steps.length : 0,
235
+ redacted: true,
236
+ };
237
+ } catch {
238
+ result.actions.push({ action: "safe_run_plan_built", status: "skipped" });
239
+ }
240
+
241
+ // Smart-route
242
+ try {
243
+ const { classifyTaskForRouting, decideExecutionPath } = require("./smart-work-routing");
244
+ const classification = classifyTaskForRouting(taskText);
245
+ result.actions.push({ action: "smart_route_classified", status: "ok" });
246
+ result.routeClassification = {
247
+ taskType: classification.taskType || null,
248
+ riskLevel: classification.riskLevel || null,
249
+ redacted: true,
250
+ };
251
+ } catch {
252
+ result.actions.push({ action: "smart_route_classified", status: "skipped" });
253
+ }
254
+
255
+ // Compile execution packet (prepare only)
256
+ try {
257
+ const { compileExecutionPacket } = require("./execution-packet");
258
+ if (typeof compileExecutionPacket === "function") {
259
+ const packet = compileExecutionPacket(cwd, { task: taskText, prepareOnly: true });
260
+ result.actions.push({ action: "execution_packet_compiled", status: "ok" });
261
+ result.executionPacket = {
262
+ packetId: packet.packetId || null,
263
+ mode: packet.mode || null,
264
+ riskLevel: packet.riskLevel || null,
265
+ redacted: true,
266
+ };
267
+ }
268
+ } catch {
269
+ result.actions.push({ action: "execution_packet_compiled", status: "skipped" });
270
+ }
271
+ }
272
+
273
+ result.summary = "Task classified and safe run plan prepared. No code modified.";
274
+ result.safeNextAction = "Continue with the prepared execution plan.";
275
+ result.reasonCodes = ["NON_BLOCKING", "TASK_CLASSIFIED", "SAFE_RUN_PLAN_PREPARED"];
276
+
277
+ const receiptPath = writeReceipt(cwd, "UserPromptSubmit", result);
278
+ result.receiptPath = receiptPath;
279
+ writeLedgerEntry(cwd, "UserPromptSubmit", result);
280
+
281
+ return result;
282
+ }
283
+
284
+ // ── PreToolUse ────────────────────────────────────────────────────────────────
285
+
286
+ const BLOCKED_TOOL_PATTERNS = Object.freeze([
287
+ /deploy/i,
288
+ /publish/i,
289
+ /drop[_\s]table/i,
290
+ /delete[_\s]database/i,
291
+ /rm\s+-rf/i,
292
+ /git\s+clean/i,
293
+ /force[_\s]push/i,
294
+ /reset\s+--hard/i,
295
+ ]);
296
+
297
+ const BLOCKED_INPUT_PATTERNS = Object.freeze([
298
+ { key: "command", patterns: [/deploy/i, /publish/i, /rm\s+-rf/i, /git\s+clean/i, /reset\s+--hard/i, /force.*push/i] },
299
+ { key: "file_path", patterns: [/\.env$/, /\.pem$/, /\.key$/, /secrets?\./i] },
300
+ { key: "path", patterns: [/\.env$/, /\.pem$/, /\.key$/, /secrets?\./i] },
301
+ ]);
302
+
303
+ function evaluateToolSafety(toolName, toolInput) {
304
+ const toolStr = String(toolName || "").toLowerCase();
305
+
306
+ // Check tool name for blocked patterns
307
+ for (const pat of BLOCKED_TOOL_PATTERNS) {
308
+ if (pat.test(toolStr)) {
309
+ return {
310
+ decision: "block",
311
+ reason: `Tool name matches blocked pattern: ${pat}`,
312
+ reasonCode: "TOOL_NAME_BLOCKED",
313
+ };
314
+ }
315
+ }
316
+
317
+ // Check input fields
318
+ const inputObj = typeof toolInput === "object" && toolInput !== null ? toolInput : {};
319
+ for (const { key, patterns } of BLOCKED_INPUT_PATTERNS) {
320
+ const val = String(inputObj[key] || "");
321
+ if (!val) continue;
322
+ for (const pat of patterns) {
323
+ if (pat.test(val)) {
324
+ return {
325
+ decision: "block",
326
+ reason: `Input field '${key}' matches blocked pattern`,
327
+ reasonCode: "INPUT_FIELD_BLOCKED",
328
+ };
329
+ }
330
+ }
331
+ }
332
+
333
+ // Check for secrets in input values
334
+ const inputStr = JSON.stringify(inputObj).toLowerCase();
335
+ if (/password|api_key|apikey|secret|token/.test(inputStr)) {
336
+ return {
337
+ decision: "warn",
338
+ reason: "Input may reference sensitive fields (secrets/tokens). Review before proceeding.",
339
+ reasonCode: "SENSITIVE_INPUT_DETECTED",
340
+ };
341
+ }
342
+
343
+ return { decision: "allow", reason: "Tool and input appear safe.", reasonCode: "SAFE_TOOL_INPUT" };
344
+ }
345
+
346
+ function handlePreToolUse(cwd, input = {}, options = {}) {
347
+ const handlerId = makeHandlerId();
348
+ const toolName = input.tool_name || input.toolName || input.tool || "";
349
+ const toolInput = input.tool_input || input.toolInput || input.input || {};
350
+
351
+ const result = {
352
+ contract: CONTRACT,
353
+ schemaVersion: SCHEMA_VERSION,
354
+ handlerId,
355
+ event: "PreToolUse",
356
+ blocking: true,
357
+ status: "ok",
358
+ createdAt: nowIso(),
359
+ actions: [],
360
+ decision: "allow",
361
+ toolName,
362
+ summary: null,
363
+ redacted: true,
364
+ };
365
+
366
+ const safety = evaluateToolSafety(toolName, toolInput);
367
+ result.actions.push({ action: "tool_safety_evaluated", status: safety.decision, reason: safety.reason });
368
+ result.decision = safety.decision;
369
+ result.reasonCode = safety.reasonCode;
370
+
371
+ // MCP governance: apply least-privilege policy if tool signal present
372
+ if (result.decision !== "block") {
373
+ try {
374
+ const { decideMcpPreToolUse, writeMcpPreToolUseDecision } = require("./mcp-enforcement");
375
+ const mcpDecision = decideMcpPreToolUse(cwd, input, options);
376
+ writeMcpPreToolUseDecision(cwd, mcpDecision);
377
+ result.actions.push({ action: "mcp_governance_evaluated", status: mcpDecision.decision });
378
+ if (mcpDecision.decision === "block" || mcpDecision.decision === "approval_required") {
379
+ if (result.decision !== "block") {
380
+ result.decision = mcpDecision.decision === "block" ? "block" : "warn";
381
+ result.reasonCode = "MCP_GOVERNANCE_DECISION";
382
+ result.mcpGovernance = {
383
+ decision: mcpDecision.decision,
384
+ reasonCodes: (mcpDecision.reasonCodes || []).slice(0, 5),
385
+ safeNextAction: mcpDecision.safeNextAction,
386
+ };
387
+ }
388
+ }
389
+ } catch {
390
+ result.actions.push({ action: "mcp_governance_evaluated", status: "skipped" });
391
+ }
392
+ }
393
+
394
+ if (result.decision === "block") {
395
+ result.status = "blocked";
396
+ result.summary = `Tool use blocked: ${safety.reason}`;
397
+ result.safeNextAction = result.mcpGovernance?.safeNextAction || "Use a safe, whitelisted tool. Avoid destructive/deploy/secrets/prod actions.";
398
+ result.reasonCodes = ["BLOCKING_HOOK", "DESTRUCTIVE_OR_SENSITIVE_BLOCKED", safety.reasonCode];
399
+
400
+ try {
401
+ const { appendProductLearningEvent } = require("./product-learning-events");
402
+ appendProductLearningEvent(cwd, {
403
+ eventName: "lifecycle_pre_tool_use_blocked",
404
+ payload: { event: "PreToolUse", toolName, reasonCode: safety.reasonCode, redacted: true },
405
+ });
406
+ } catch {}
407
+ } else if (safety.decision === "warn") {
408
+ result.status = "warn";
409
+ result.summary = `Tool use allowed with warning: ${safety.reason}`;
410
+ result.safeNextAction = "Proceed with caution. Verify input does not contain secrets or sensitive data.";
411
+ result.reasonCodes = ["BLOCKING_HOOK", "ALLOWED_WITH_WARNING", safety.reasonCode];
412
+ } else {
413
+ result.status = "ok";
414
+ result.summary = "Tool use allowed.";
415
+ result.safeNextAction = null;
416
+ result.reasonCodes = ["BLOCKING_HOOK", "ALLOWED", safety.reasonCode];
417
+ }
418
+
419
+ // Enforce packet boundaries if packet exists
420
+ try {
421
+ const packetPath = path.join(cwd, ".claude/cco/orchestration/execution-packet/latest-packet.json");
422
+ const packet = safeReadJson(packetPath);
423
+ if (packet && packet.blockedScope && Array.isArray(packet.blockedScope)) {
424
+ const toolLower = toolName.toLowerCase();
425
+ const blocked = packet.blockedScope.some((s) => toolLower.includes(String(s).toLowerCase()));
426
+ if (blocked && result.decision !== "block") {
427
+ result.decision = "block";
428
+ result.status = "blocked";
429
+ result.summary = `Tool '${toolName}' is in the blocked scope of the current execution packet.`;
430
+ result.safeNextAction = "Review the execution packet blocked scope. This action is not allowed for the current task.";
431
+ result.reasonCodes = ["BLOCKING_HOOK", "PACKET_BOUNDARY_ENFORCED"];
432
+ result.actions.push({ action: "packet_boundary_checked", status: "blocked", reason: "in_blocked_scope" });
433
+ } else {
434
+ result.actions.push({ action: "packet_boundary_checked", status: "ok" });
435
+ }
436
+ } else {
437
+ result.actions.push({ action: "packet_boundary_checked", status: "no_packet" });
438
+ }
439
+ } catch {
440
+ result.actions.push({ action: "packet_boundary_checked", status: "skipped" });
441
+ }
442
+
443
+ const receiptPath = writeReceipt(cwd, "PreToolUse", result);
444
+ result.receiptPath = receiptPath;
445
+ writeLedgerEntry(cwd, "PreToolUse", result);
446
+
447
+ return result;
448
+ }
449
+
450
+ // ── PostToolUse ───────────────────────────────────────────────────────────────
451
+
452
+ function handlePostToolUse(cwd, input = {}, options = {}) {
453
+ const handlerId = makeHandlerId();
454
+ const toolName = input.tool_name || input.toolName || input.tool || "";
455
+
456
+ const result = {
457
+ contract: CONTRACT,
458
+ schemaVersion: SCHEMA_VERSION,
459
+ handlerId,
460
+ event: "PostToolUse",
461
+ blocking: false,
462
+ status: "ok",
463
+ createdAt: nowIso(),
464
+ actions: [],
465
+ summary: null,
466
+ redacted: true,
467
+ };
468
+
469
+ // Record tool result summary (no raw output)
470
+ result.actions.push({
471
+ action: "tool_result_recorded",
472
+ status: "ok",
473
+ toolName,
474
+ note: "Raw tool output not stored. Status and metadata only.",
475
+ });
476
+
477
+ // Write product-learning event
478
+ try {
479
+ const { appendProductLearningEvent } = require("./product-learning-events");
480
+ appendProductLearningEvent(cwd, {
481
+ eventName: "lifecycle_hook_handler_invoked",
482
+ payload: {
483
+ event: "PostToolUse",
484
+ handlerId,
485
+ toolName,
486
+ status: "ok",
487
+ redacted: true,
488
+ },
489
+ });
490
+ result.actions.push({ action: "product_learning_event_written", status: "ok" });
491
+ } catch {
492
+ result.actions.push({ action: "product_learning_event_written", status: "skipped" });
493
+ }
494
+
495
+ result.summary = `Tool '${toolName}' post-use recorded. Ledger and product-learning updated.`;
496
+ result.safeNextAction = null;
497
+ result.reasonCodes = ["NON_BLOCKING", "LEDGER_UPDATED", "PRODUCT_LEARNING_RECORDED"];
498
+
499
+ const receiptPath = writeReceipt(cwd, "PostToolUse", result);
500
+ result.receiptPath = receiptPath;
501
+ writeLedgerEntry(cwd, "PostToolUse", result);
502
+
503
+ return result;
504
+ }
505
+
506
+ // ── Stop ─────────────────────────────────────────────────────────────────────
507
+
508
+ function handleStop(cwd, input = {}, options = {}) {
509
+ const handlerId = makeHandlerId();
510
+
511
+ const result = {
512
+ contract: CONTRACT,
513
+ schemaVersion: SCHEMA_VERSION,
514
+ handlerId,
515
+ event: "Stop",
516
+ blocking: true,
517
+ status: "ok",
518
+ createdAt: nowIso(),
519
+ actions: [],
520
+ decision: "allow",
521
+ proofRequired: false,
522
+ proofFound: false,
523
+ summary: null,
524
+ redacted: true,
525
+ };
526
+
527
+ // Check execution packet for proof requirements
528
+ let proofRequired = false;
529
+ try {
530
+ const packetPath = path.join(cwd, ".claude/cco/orchestration/execution-packet/latest-packet.json");
531
+ const packet = safeReadJson(packetPath);
532
+ if (packet) {
533
+ proofRequired = !!(packet.proofRequired || (packet.requirements && packet.requirements.proofRequired));
534
+ result.actions.push({ action: "execution_packet_checked", status: "ok", proofRequired });
535
+ } else {
536
+ result.actions.push({ action: "execution_packet_checked", status: "not_found" });
537
+ }
538
+ } catch {
539
+ result.actions.push({ action: "execution_packet_checked", status: "skipped" });
540
+ }
541
+
542
+ // Check safe-run result for proof
543
+ let proofFound = false;
544
+ try {
545
+ const runPath = path.join(cwd, ".claude/cco/orchestration/safe-run/latest-run.json");
546
+ const run = safeReadJson(runPath);
547
+ if (run) {
548
+ proofFound = !!(run.proofSummary || run.status === "completed" || run.proofGenerated);
549
+ result.actions.push({ action: "safe_run_checked", status: "ok", proofFound });
550
+ } else {
551
+ result.actions.push({ action: "safe_run_checked", status: "not_found" });
552
+ }
553
+ } catch {
554
+ result.actions.push({ action: "safe_run_checked", status: "skipped" });
555
+ }
556
+
557
+ result.proofRequired = proofRequired;
558
+ result.proofFound = proofFound;
559
+
560
+ if (proofRequired && !proofFound) {
561
+ result.decision = "block";
562
+ result.status = "blocked";
563
+ result.summary = "Proof required by execution packet but not found. Cannot stop until proof is generated.";
564
+ result.safeNextAction = "Run `avorelo run <task>` to generate proof, then stop.";
565
+ result.reasonCodes = ["BLOCKING_HOOK", "PROOF_REQUIRED", "PROOF_MISSING"];
566
+
567
+ try {
568
+ const { appendProductLearningEvent } = require("./product-learning-events");
569
+ appendProductLearningEvent(cwd, {
570
+ eventName: "lifecycle_stop_proof_required",
571
+ payload: { event: "Stop", proofRequired, proofFound, redacted: true },
572
+ });
573
+ } catch {}
574
+ } else {
575
+ result.decision = "allow";
576
+ result.status = "ok";
577
+ if (proofRequired && proofFound) {
578
+ result.summary = "Proof found. Stop allowed.";
579
+ } else {
580
+ result.summary = "Proof not required for this task. Stop allowed.";
581
+ }
582
+ result.safeNextAction = null;
583
+ result.reasonCodes = ["BLOCKING_HOOK", "STOP_ALLOWED"];
584
+ }
585
+
586
+ const receiptPath = writeReceipt(cwd, "Stop", result);
587
+ result.receiptPath = receiptPath;
588
+ writeLedgerEntry(cwd, "Stop", result);
589
+
590
+ return result;
591
+ }
592
+
593
+ // ── SessionEnd ────────────────────────────────────────────────────────────────
594
+
595
+ function handleSessionEnd(cwd, input = {}, options = {}) {
596
+ const handlerId = makeHandlerId();
597
+
598
+ const result = {
599
+ contract: CONTRACT,
600
+ schemaVersion: SCHEMA_VERSION,
601
+ handlerId,
602
+ event: "SessionEnd",
603
+ blocking: false,
604
+ status: "ok",
605
+ createdAt: nowIso(),
606
+ actions: [],
607
+ summary: null,
608
+ redacted: true,
609
+ };
610
+
611
+ // Run outcome summary
612
+ try {
613
+ const outcomePath = path.join(cwd, ".claude/cco/orchestration/seamless-outcome/latest-outcome.json");
614
+ const outcome = safeReadJson(outcomePath);
615
+ if (outcome) {
616
+ result.actions.push({ action: "outcome_summary_read", status: "ok" });
617
+ result.outcomeSummary = {
618
+ status: outcome.status || "unknown",
619
+ redacted: true,
620
+ };
621
+ } else {
622
+ result.actions.push({ action: "outcome_summary_read", status: "not_found" });
623
+ }
624
+ } catch {
625
+ result.actions.push({ action: "outcome_summary_read", status: "skipped" });
626
+ }
627
+
628
+ // Update ledger (via product-learning event)
629
+ try {
630
+ const { appendProductLearningEvent } = require("./product-learning-events");
631
+ appendProductLearningEvent(cwd, {
632
+ eventName: "lifecycle_session_end_summary_written",
633
+ payload: {
634
+ event: "SessionEnd",
635
+ handlerId,
636
+ status: "ok",
637
+ redacted: true,
638
+ },
639
+ });
640
+ result.actions.push({ action: "ledger_updated", status: "ok" });
641
+ } catch {
642
+ result.actions.push({ action: "ledger_updated", status: "skipped" });
643
+ }
644
+
645
+ // Read company-loop status
646
+ try {
647
+ const loopPath = path.join(cwd, ".claude/cco/orchestration/company-loop/latest-report.json");
648
+ const loop = safeReadJson(loopPath);
649
+ if (loop) {
650
+ result.actions.push({ action: "company_loop_read", status: "ok" });
651
+ result.companyLoopStatus = {
652
+ status: loop.status || "unknown",
653
+ nextPrRecommendation: (loop.surface && loop.surface.recommendedNextPr) || null,
654
+ redacted: true,
655
+ };
656
+ } else {
657
+ result.actions.push({ action: "company_loop_read", status: "not_found" });
658
+ }
659
+ } catch {
660
+ result.actions.push({ action: "company_loop_read", status: "skipped" });
661
+ }
662
+
663
+ // Prepare handoff summary
664
+ try {
665
+ const handoffPath = path.join(cwd, ".claude/cco/orchestration/worker-handoff/latest-handoff.json");
666
+ const handoff = safeReadJson(handoffPath);
667
+ if (handoff) {
668
+ result.actions.push({ action: "handoff_summary_prepared", status: "ok" });
669
+ result.handoffStatus = { available: true, redacted: true };
670
+ } else {
671
+ result.actions.push({ action: "handoff_summary_prepared", status: "not_found" });
672
+ result.handoffStatus = { available: false, redacted: true };
673
+ }
674
+ } catch {
675
+ result.actions.push({ action: "handoff_summary_prepared", status: "skipped" });
676
+ result.handoffStatus = { available: false, redacted: true };
677
+ }
678
+
679
+ result.summary = "Session end: outcome, ledger, company-loop, and handoff updated.";
680
+ result.safeNextAction = null;
681
+ result.reasonCodes = ["NON_BLOCKING", "OUTCOME_WRITTEN", "LEDGER_UPDATED", "HANDOFF_PREPARED"];
682
+
683
+ const receiptPath = writeReceipt(cwd, "SessionEnd", result);
684
+ result.receiptPath = receiptPath;
685
+ writeLedgerEntry(cwd, "SessionEnd", result);
686
+
687
+ return result;
688
+ }
689
+
690
+ // ── Dispatcher ────────────────────────────────────────────────────────────────
691
+
692
+ // ── Recursion guard ───────────────────────────────────────────────────────────
693
+ // Prevents Avorelo lifecycle hooks from re-entering when a hook-triggered
694
+ // command itself calls another Avorelo command (e.g. lifecycle-hook → run →
695
+ // lifecycle-hook). The environment variable is set by the shell hook wrapper
696
+ // and checked here before any handler runs.
697
+
698
+ const RECURSION_GUARD_ENV = "AVORELO_HOOK_ACTIVE";
699
+
700
+ function isRecursionGuardActive() {
701
+ return process.env[RECURSION_GUARD_ENV] === "1";
702
+ }
703
+
704
+ function buildRecursionSkippedResult(event) {
705
+ return {
706
+ contract: CONTRACT,
707
+ schemaVersion: SCHEMA_VERSION,
708
+ event,
709
+ status: "ok",
710
+ recursionSkipped: true,
711
+ blocking: false,
712
+ decision: "allow",
713
+ summary: "Recursion guard active. Lifecycle re-entry skipped.",
714
+ reasonCodes: ["RECURSION_GUARD_ACTIVE", "AVORELO_HOOK_ACTIVE_SET"],
715
+ redacted: true,
716
+ };
717
+ }
718
+
719
+ function handleLifecycleHook(cwd, event, input = {}, options = {}) {
720
+ // Recursion guard: if a hook-triggered command calls us again, skip silently
721
+ if (isRecursionGuardActive()) {
722
+ return buildRecursionSkippedResult(event);
723
+ }
724
+
725
+ switch (event) {
726
+ case "SessionStart":
727
+ case "session-start":
728
+ return handleSessionStart(cwd, input, options);
729
+ case "UserPromptSubmit":
730
+ case "user-prompt-submit":
731
+ return handleUserPromptSubmit(cwd, input, options);
732
+ case "PreToolUse":
733
+ case "pre-tool-use":
734
+ return handlePreToolUse(cwd, input, options);
735
+ case "PostToolUse":
736
+ case "post-tool-use":
737
+ return handlePostToolUse(cwd, input, options);
738
+ case "Stop":
739
+ case "stop":
740
+ return handleStop(cwd, input, options);
741
+ case "SessionEnd":
742
+ case "session-end":
743
+ return handleSessionEnd(cwd, input, options);
744
+ default:
745
+ return {
746
+ contract: CONTRACT,
747
+ schemaVersion: SCHEMA_VERSION,
748
+ event,
749
+ status: "error",
750
+ error: `Unknown lifecycle event: ${event}`,
751
+ reasonCodes: ["UNKNOWN_EVENT"],
752
+ redacted: true,
753
+ };
754
+ }
755
+ }
756
+
757
+ function formatLifecycleHookText(result, options = {}) {
758
+ const debug = options.debug || false;
759
+ const lines = [];
760
+
761
+ const statusLabel = result.status === "ok" ? "ok" : result.status === "blocked" ? "BLOCKED" : result.status;
762
+ lines.push(`Lifecycle hook [${result.event}]: ${statusLabel}`);
763
+
764
+ if (result.decision && result.decision !== "allow") {
765
+ lines.push(`Decision: ${result.decision}`);
766
+ }
767
+ if (result.summary) {
768
+ lines.push(`Summary: ${result.summary}`);
769
+ }
770
+ if (result.safeNextAction) {
771
+ lines.push(`Next: ${result.safeNextAction}`);
772
+ }
773
+
774
+ if (debug && result.actions && result.actions.length > 0) {
775
+ lines.push("", "Actions:");
776
+ for (const a of result.actions) {
777
+ lines.push(` - ${a.action}: ${a.status}${a.reason ? ` (${a.reason})` : ""}`);
778
+ }
779
+ if (result.reasonCodes) {
780
+ lines.push("", `Reason codes: ${result.reasonCodes.join(", ")}`);
781
+ }
782
+ }
783
+
784
+ return lines.join("\n");
785
+ }
786
+
787
+ module.exports = {
788
+ CONTRACT,
789
+ SCHEMA_VERSION,
790
+ HOOKS_DIR_REL,
791
+ RECURSION_GUARD_ENV,
792
+ handleLifecycleHook,
793
+ handleSessionStart,
794
+ handleUserPromptSubmit,
795
+ handlePreToolUse,
796
+ handlePostToolUse,
797
+ handleStop,
798
+ handleSessionEnd,
799
+ formatLifecycleHookText,
800
+ isRecursionGuardActive,
801
+ buildRecursionSkippedResult,
802
+ };