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,315 @@
1
+ "use strict";
2
+
3
+ // ── Session Timing Evidence v1 ────────────────────────────────────────────────
4
+ //
5
+ // Records and derives timing events for Avorelo sessions.
6
+ // Timing is EVIDENCE — not savings. Duration does not automatically become
7
+ // "time saved". Activation timing is readiness timing, not savings timing.
8
+ // No network. No secrets.
9
+
10
+ const fs = require("fs");
11
+ const path = require("path");
12
+ const crypto = require("crypto");
13
+
14
+ const SCHEMA_VERSION = 1;
15
+ const CONTRACT = "avorelo.sessionTiming.v1";
16
+ const SESSION_TIMING_DIR_REL = ".claude/cco/orchestration/session-timing";
17
+ const SESSION_TIMING_LATEST_REL = ".claude/cco/orchestration/session-timing/latest-session-timing.json";
18
+ const SESSION_TIMING_EVENTS_REL = ".claude/cco/events/session-timing.jsonl";
19
+
20
+ // Receipt paths to read timestamps from
21
+ const ACTIVATION_STATE_REL = ".claude/cco/state/activation.json";
22
+ const SESSION_CONTEXT_REL = ".claude/cco/orchestration/session-context/latest-context.json";
23
+ const OPERATING_VALUE_REL = ".claude/cco/orchestration/operating-value/latest-value.json";
24
+ const PROOF_REL = ".claude/cco/receipts/latest-proof.json";
25
+ const WORK_CONTROL_REL = ".claude/cco/orchestration/work-control/latest-receipt.json";
26
+
27
+ function shortId() {
28
+ return crypto.randomBytes(4).toString("hex");
29
+ }
30
+
31
+ function nowIso() {
32
+ return new Date().toISOString();
33
+ }
34
+
35
+ function ensureDirFor(filePath) {
36
+ const dir = path.dirname(filePath);
37
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
38
+ }
39
+
40
+ function safeReadJson(filePath) {
41
+ try {
42
+ if (!fs.existsSync(filePath)) return null;
43
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ function fileModTimeIso(filePath) {
50
+ try {
51
+ if (!fs.existsSync(filePath)) return null;
52
+ return new Date(fs.statSync(filePath).mtimeMs).toISOString();
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+
58
+ function parseIso(s) {
59
+ if (!s) return null;
60
+ const d = new Date(s);
61
+ return isNaN(d.getTime()) ? null : d.getTime();
62
+ }
63
+
64
+ function diffMs(startIso, endIso) {
65
+ const s = parseIso(startIso);
66
+ const e = parseIso(endIso);
67
+ if (s === null || e === null) return null;
68
+ const diff = e - s;
69
+ return diff >= 0 ? diff : null;
70
+ }
71
+
72
+ // ── Read timing events from existing orchestration artifacts ──────────────────
73
+
74
+ function gatherTimingEvents(cwd) {
75
+ const events = [];
76
+
77
+ // Activation event — from activation state file or its mtime
78
+ const activationPath = path.resolve(cwd, ACTIVATION_STATE_REL);
79
+ const activationData = safeReadJson(activationPath);
80
+ const activationTs = (activationData && (activationData.createdAt || activationData.activatedAt))
81
+ || fileModTimeIso(activationPath);
82
+ if (activationTs) {
83
+ events.push({
84
+ event: "activation_completed",
85
+ timestamp: activationTs,
86
+ source: activationData ? "activation_state_json" : "file_mtime",
87
+ sessionId: activationData && activationData.activationId || null,
88
+ evidencePath: ACTIVATION_STATE_REL,
89
+ });
90
+ }
91
+
92
+ // Session context handoff event
93
+ const scPath = path.resolve(cwd, SESSION_CONTEXT_REL);
94
+ const scData = safeReadJson(scPath);
95
+ const scTs = (scData && scData.createdAt) || fileModTimeIso(scPath);
96
+ if (scTs) {
97
+ events.push({
98
+ event: "session_context_generated",
99
+ timestamp: scTs,
100
+ source: scData ? "session_context_json" : "file_mtime",
101
+ sessionId: scData && scData.contextId || null,
102
+ evidencePath: SESSION_CONTEXT_REL,
103
+ });
104
+ }
105
+
106
+ // Work control receipt event
107
+ const wcPath = path.resolve(cwd, WORK_CONTROL_REL);
108
+ const wcData = safeReadJson(wcPath);
109
+ const wcTs = (wcData && wcData.createdAt) || fileModTimeIso(wcPath);
110
+ if (wcTs) {
111
+ events.push({
112
+ event: "work_control_receipt_written",
113
+ timestamp: wcTs,
114
+ source: wcData ? "work_control_json" : "file_mtime",
115
+ sessionId: wcData && wcData.receiptId || null,
116
+ evidencePath: WORK_CONTROL_REL,
117
+ });
118
+ }
119
+
120
+ // Proof event
121
+ const proofPath = path.resolve(cwd, PROOF_REL);
122
+ const proofData = safeReadJson(proofPath);
123
+ const proofTs = (proofData && proofData.createdAt) || fileModTimeIso(proofPath);
124
+ if (proofTs) {
125
+ events.push({
126
+ event: "proof_written",
127
+ timestamp: proofTs,
128
+ source: proofData ? "proof_json" : "file_mtime",
129
+ sessionId: proofData && proofData.proofId || null,
130
+ evidencePath: PROOF_REL,
131
+ });
132
+ }
133
+
134
+ // Operating value event
135
+ const ovPath = path.resolve(cwd, OPERATING_VALUE_REL);
136
+ const ovData = safeReadJson(ovPath);
137
+ const ovTs = (ovData && ovData.createdAt) || fileModTimeIso(ovPath);
138
+ if (ovTs) {
139
+ events.push({
140
+ event: "operating_value_generated",
141
+ timestamp: ovTs,
142
+ source: ovData ? "operating_value_json" : "file_mtime",
143
+ sessionId: ovData && ovData.valueId || null,
144
+ evidencePath: OPERATING_VALUE_REL,
145
+ });
146
+ }
147
+
148
+ // Sort chronologically
149
+ events.sort((a, b) => {
150
+ const ta = parseIso(a.timestamp) || 0;
151
+ const tb = parseIso(b.timestamp) || 0;
152
+ return ta - tb;
153
+ });
154
+
155
+ return events;
156
+ }
157
+
158
+ // ── Derive timing summary ─────────────────────────────────────────────────────
159
+
160
+ function deriveTimingSummary(events) {
161
+ const byEvent = {};
162
+ events.forEach((e) => { byEvent[e.event] = e.timestamp; });
163
+
164
+ const activationTs = byEvent["activation_completed"];
165
+ const handoffTs = byEvent["session_context_generated"];
166
+ const proofTs = byEvent["proof_written"];
167
+ const ovTs = byEvent["operating_value_generated"];
168
+
169
+ // Session window: earliest to latest event timestamp
170
+ const timestamps = events.map((e) => parseIso(e.timestamp)).filter(Boolean);
171
+ const sessionStart = timestamps.length ? new Date(Math.min(...timestamps)).toISOString() : null;
172
+ const sessionEnd = timestamps.length ? new Date(Math.max(...timestamps)).toISOString() : null;
173
+
174
+ return {
175
+ activationDurationMs: null, // cannot measure activation duration without start+end within activation
176
+ timeToHandoffMs: diffMs(activationTs, handoffTs),
177
+ timeToDashboardReadyMs: diffMs(activationTs, ovTs),
178
+ timeToProofMs: diffMs(activationTs, proofTs),
179
+ sessionDurationMs: diffMs(sessionStart, sessionEnd),
180
+ timeToFirstUsefulActionMs: null, // requires explicit first-useful-action marker
181
+ sessionStart,
182
+ sessionEnd,
183
+ };
184
+ }
185
+
186
+ // ── Record a timing event ─────────────────────────────────────────────────────
187
+
188
+ function recordTimingEvent(cwd, eventName, meta = {}) {
189
+ const eventsPath = path.resolve(cwd, SESSION_TIMING_EVENTS_REL);
190
+ ensureDirFor(eventsPath);
191
+ const entry = {
192
+ event: eventName,
193
+ timestamp: nowIso(),
194
+ source: "explicit_mark",
195
+ ...meta,
196
+ };
197
+ fs.appendFileSync(eventsPath, JSON.stringify(entry) + "\n", "utf8");
198
+ return entry;
199
+ }
200
+
201
+ // ── Build session timing summary ──────────────────────────────────────────────
202
+
203
+ function buildSessionTimingSummary(cwd, options = {}) {
204
+ const timingId = `st-${Date.now()}-${shortId()}`;
205
+ const createdAt = nowIso();
206
+
207
+ const artifactEvents = gatherTimingEvents(cwd);
208
+
209
+ // Also read explicit events from session-timing.jsonl
210
+ const explicitEventsPath = path.resolve(cwd, SESSION_TIMING_EVENTS_REL);
211
+ const explicitEvents = [];
212
+ if (fs.existsSync(explicitEventsPath)) {
213
+ try {
214
+ const lines = fs.readFileSync(explicitEventsPath, "utf8").split("\n").filter(Boolean);
215
+ lines.forEach((line) => {
216
+ try { explicitEvents.push(JSON.parse(line)); } catch {}
217
+ });
218
+ } catch {}
219
+ }
220
+
221
+ // Merge and deduplicate by event name (prefer explicit over artifact)
222
+ const explicitByEvent = {};
223
+ explicitEvents.forEach((e) => { explicitByEvent[e.event] = e; });
224
+
225
+ const allEvents = [...artifactEvents];
226
+ explicitEvents.forEach((ee) => {
227
+ if (!allEvents.find((ae) => ae.event === ee.event && ae.timestamp === ee.timestamp)) {
228
+ allEvents.push(ee);
229
+ }
230
+ });
231
+ allEvents.sort((a, b) => {
232
+ const ta = parseIso(a.timestamp) || 0;
233
+ const tb = parseIso(b.timestamp) || 0;
234
+ return ta - tb;
235
+ });
236
+
237
+ // First-useful-action from explicit events
238
+ const fuaEvent = explicitByEvent["first_useful_action"];
239
+ const activationEvent = allEvents.find((e) => e.event === "activation_completed");
240
+ const timeToFirstUsefulActionMs = fuaEvent && activationEvent
241
+ ? diffMs(activationEvent.timestamp, fuaEvent.timestamp)
242
+ : null;
243
+
244
+ const derived = deriveTimingSummary(allEvents);
245
+ derived.timeToFirstUsefulActionMs = timeToFirstUsefulActionMs;
246
+
247
+ const caveats = [
248
+ "Timing is evidence, not savings. Duration does not automatically mean time saved.",
249
+ "Activation timing measures readiness readiness, not time saved versus a baseline.",
250
+ "Timestamps from file mtimes may be imprecise for files updated outside Avorelo.",
251
+ ];
252
+
253
+ return {
254
+ schemaVersion: SCHEMA_VERSION,
255
+ contract: CONTRACT,
256
+ timingId,
257
+ createdAt,
258
+ cwd,
259
+ events: allEvents,
260
+ derived,
261
+ confidence: allEvents.length >= 3 ? "medium" : "low",
262
+ caveats,
263
+ redacted: true,
264
+ };
265
+ }
266
+
267
+ // ── Write / read ───────────────────────────────────────────────────────────────
268
+
269
+ function writeSessionTiming(cwd, timing) {
270
+ const latestPath = path.resolve(cwd, SESSION_TIMING_LATEST_REL);
271
+ ensureDirFor(latestPath);
272
+ fs.writeFileSync(latestPath, JSON.stringify(timing, null, 2), "utf8");
273
+ }
274
+
275
+ function readSessionTiming(cwd) {
276
+ const latestPath = path.resolve(cwd, SESSION_TIMING_LATEST_REL);
277
+ return safeReadJson(latestPath);
278
+ }
279
+
280
+ // ── Text format ────────────────────────────────────────────────────────────────
281
+
282
+ function formatSessionTimingText(timing) {
283
+ if (!timing) return "Session timing unavailable.\n";
284
+ const d = timing.derived;
285
+ const fmtMs = (ms) => ms === null ? "unavailable" : ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`;
286
+ const lines = [
287
+ "Avorelo Session Timing",
288
+ "",
289
+ ` Events recorded: ${timing.events.length}`,
290
+ ` Confidence: ${timing.confidence}`,
291
+ "",
292
+ ` Time to handoff: ${fmtMs(d.timeToHandoffMs)}`,
293
+ ` Time to dashboard: ${fmtMs(d.timeToDashboardReadyMs)}`,
294
+ ` Time to proof: ${fmtMs(d.timeToProofMs)}`,
295
+ ` Session duration: ${fmtMs(d.sessionDurationMs)}`,
296
+ d.timeToFirstUsefulActionMs !== null
297
+ ? ` First useful action: ${fmtMs(d.timeToFirstUsefulActionMs)}`
298
+ : null,
299
+ "",
300
+ " Note: Timing is evidence — not savings. No time-saved claim is made from duration alone.",
301
+ "",
302
+ ].filter((l) => l !== null);
303
+ return lines.join("\n");
304
+ }
305
+
306
+ module.exports = {
307
+ CONTRACT,
308
+ SESSION_TIMING_LATEST_REL,
309
+ SESSION_TIMING_EVENTS_REL,
310
+ buildSessionTimingSummary,
311
+ writeSessionTiming,
312
+ readSessionTiming,
313
+ recordTimingEvent,
314
+ formatSessionTimingText,
315
+ };