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,169 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Anti-Gaming Rules for the Effectiveness Proof System.
5
+ *
6
+ * The system must not appear effective by becoming worse.
7
+ * These validation functions check proof records and scorecard data
8
+ * for patterns that suggest gaming or misleading metrics.
9
+ *
10
+ * Each returns { valid: boolean, warning?: string }.
11
+ */
12
+
13
+ /**
14
+ * Rule 1: Efficiency must never outrank truth or outcome quality.
15
+ * Flag if context reduction is high but completion quality is low.
16
+ */
17
+ function validateEfficiencyWithQuality(proof) {
18
+ const largeOutputsManaged = Number(proof?.efficiency?.large_output_bytes_managed || 0);
19
+ const completionQuality = String(proof?.outcome?.completion_quality_state || "unknown");
20
+ const usefulResult = String(proof?.outcome?.useful_result_state || "unknown");
21
+
22
+ if (largeOutputsManaged > 100000 && (completionQuality === "low" || usefulResult === "not_useful")) {
23
+ return {
24
+ valid: false,
25
+ warning: "Many large outputs were managed out-of-band but completion quality is low. Aggressive output handling may have harmed results.",
26
+ };
27
+ }
28
+ return { valid: true };
29
+ }
30
+
31
+ /**
32
+ * Rule 2: Tool skipping is not good if it harms the result.
33
+ * Flag if many tools were skipped but dead-end rate is high.
34
+ */
35
+ function validateToolSkipSafety(proof) {
36
+ const toolsSkipped = Number(proof?.efficiency?.tools_skipped || 0);
37
+ const deadEndCount = Number(proof?.efficiency?.dead_end_count || 0);
38
+
39
+ if (toolsSkipped > 3 && deadEndCount > 0) {
40
+ return {
41
+ valid: false,
42
+ warning: "Tools were skipped but run ended in a dead end. Skipping may have been premature.",
43
+ };
44
+ }
45
+ return { valid: true };
46
+ }
47
+
48
+ /**
49
+ * Rule 3: Approval avoidance is not good if escalation should have happened.
50
+ * Flag if approvals were avoided but risky actions were also high.
51
+ */
52
+ function validateApprovalAvoidance(proof) {
53
+ const avoided = Number(proof?.control?.approval_requests_avoided || 0);
54
+ const riskyAttempts = Number(proof?.control?.risky_action_attempts || 0);
55
+ const riskyBlocked = Number(proof?.control?.risky_actions_blocked || 0);
56
+
57
+ if (avoided > 2 && riskyAttempts > 3 && riskyBlocked < riskyAttempts / 2) {
58
+ return {
59
+ valid: false,
60
+ warning: "Many approvals avoided while risky actions were frequent. Some escalations may have been skipped incorrectly.",
61
+ };
62
+ }
63
+ return { valid: true };
64
+ }
65
+
66
+ /**
67
+ * Rule 4: Honest fallback is not good if overused to avoid work.
68
+ * Flag if honest fallback rate exceeds 30%.
69
+ */
70
+ function validateHonestFallback(proofMetrics) {
71
+ const rate = Number(proofMetrics?.honest_fallback_rate?.value || 0);
72
+
73
+ if (rate > 30) {
74
+ return {
75
+ valid: false,
76
+ warning: `Honest fallback rate is ${rate}%. Above 30% suggests the system may be stopping too early rather than completing work.`,
77
+ };
78
+ }
79
+ return { valid: true };
80
+ }
81
+
82
+ /**
83
+ * Rule 5: Scorecards must always pair efficiency with trust and outcome.
84
+ * Flag if a scorecard shows efficiency without corresponding trust/outcome data.
85
+ */
86
+ function validateScorecardBalance(scorecard) {
87
+ const hasEfficiency = scorecard?.efficiency && Object.values(scorecard.efficiency).some((v) => v?.value > 0);
88
+ const hasOutcome = scorecard?.outcomes && Object.values(scorecard.outcomes).some((v) => v?.value > 0);
89
+ const hasTrust = scorecard?.trust && Object.values(scorecard.trust).some((v) => v?.value > 0);
90
+
91
+ if (hasEfficiency && !hasOutcome && !hasTrust) {
92
+ return {
93
+ valid: false,
94
+ warning: "Scorecard shows efficiency data without outcome or trust context. This is misleading.",
95
+ };
96
+ }
97
+ return { valid: true };
98
+ }
99
+
100
+ /**
101
+ * Rule 6: No metric should create pressure to hide uncertainty.
102
+ * Flag if evidence coverage is claimed as 100% with very few sessions.
103
+ */
104
+ function validateEvidencePlausibility(proofMetrics) {
105
+ const coverage = Number(proofMetrics?.evidence_coverage?.value || 0);
106
+ const sessions = Number(proofMetrics?.evidence_coverage?.denominator || 0);
107
+
108
+ if (coverage === 100 && sessions < 3) {
109
+ return {
110
+ valid: false,
111
+ warning: "100% evidence coverage with fewer than 3 sessions is not statistically meaningful.",
112
+ };
113
+ }
114
+ return { valid: true };
115
+ }
116
+
117
+ /**
118
+ * Run all anti-gaming validations on a proof record.
119
+ * Returns { valid: boolean, warnings: string[] }.
120
+ */
121
+ function validateProofRecord(proof) {
122
+ const checks = [
123
+ validateEfficiencyWithQuality(proof),
124
+ validateToolSkipSafety(proof),
125
+ validateApprovalAvoidance(proof),
126
+ ];
127
+
128
+ const warnings = checks.filter((c) => !c.valid).map((c) => c.warning);
129
+ return { valid: warnings.length === 0, warnings };
130
+ }
131
+
132
+ /**
133
+ * Run all anti-gaming validations on computed proof metrics.
134
+ * Returns { valid: boolean, warnings: string[] }.
135
+ */
136
+ function validateProofMetrics(proofMetrics) {
137
+ const checks = [
138
+ validateHonestFallback(proofMetrics),
139
+ validateEvidencePlausibility(proofMetrics),
140
+ ];
141
+
142
+ const warnings = checks.filter((c) => !c.valid).map((c) => c.warning);
143
+ return { valid: warnings.length === 0, warnings };
144
+ }
145
+
146
+ /**
147
+ * Run all anti-gaming validations on a scorecard.
148
+ * Returns { valid: boolean, warnings: string[] }.
149
+ */
150
+ function validateScorecard(scorecard) {
151
+ const checks = [
152
+ validateScorecardBalance(scorecard),
153
+ ];
154
+
155
+ const warnings = checks.filter((c) => !c.valid).map((c) => c.warning);
156
+ return { valid: warnings.length === 0, warnings };
157
+ }
158
+
159
+ module.exports = {
160
+ validateEfficiencyWithQuality,
161
+ validateToolSkipSafety,
162
+ validateApprovalAvoidance,
163
+ validateHonestFallback,
164
+ validateScorecardBalance,
165
+ validateEvidencePlausibility,
166
+ validateProofRecord,
167
+ validateProofMetrics,
168
+ validateScorecard,
169
+ };
@@ -0,0 +1,431 @@
1
+ "use strict";
2
+
3
+ // ── Artifact Health ───────────────────────────────────────────────────────────
4
+ //
5
+ // Contracts: avorelo.artifactHealth.v1, avorelo.artifactCleanup.v1
6
+ //
7
+ // Scans .claude/cco artifacts for validity, staleness, and size issues.
8
+ // Plans and applies safe cleanup. Never deletes latest-*.json or outside cco.
9
+
10
+ const fs = require("fs");
11
+ const path = require("path");
12
+ const { nowIso } = require("./fsx");
13
+
14
+ const HEALTH_CONTRACT = "avorelo.artifactHealth.v1";
15
+ const CLEANUP_CONTRACT = "avorelo.artifactCleanup.v1";
16
+ const SCHEMA_VERSION = 1;
17
+
18
+ const CCO_DIR_REL = ".claude/cco";
19
+ const HEALTH_DIR_REL = ".claude/cco/orchestration/artifact-health";
20
+ const LATEST_HEALTH_REL = `${HEALTH_DIR_REL}/latest-health.json`;
21
+ const LATEST_CLEANUP_REL = `${HEALTH_DIR_REL}/latest-cleanup.json`;
22
+
23
+ const MAX_FILE_BYTES = 512 * 1024; // 512KB
24
+ const STALE_DAYS = 30;
25
+ const MAX_JSONL_LINES = 5000;
26
+
27
+ // ── Helpers ───────────────────────────────────────────────────────────────────
28
+
29
+ function bestEffortWrite(absPath, obj) {
30
+ try {
31
+ fs.mkdirSync(path.dirname(absPath), { recursive: true });
32
+ fs.writeFileSync(absPath, JSON.stringify(obj, null, 2), "utf8");
33
+ } catch {}
34
+ }
35
+
36
+ function isLatestFile(filename) {
37
+ return /^latest-/.test(filename) || filename === "latest-support-bundle.json" || filename === "latest-ledger.json";
38
+ }
39
+
40
+ function walkJsonFiles(dirAbs, results, ccoRootAbs) {
41
+ try {
42
+ if (!fs.existsSync(dirAbs)) return;
43
+ const entries = fs.readdirSync(dirAbs, { withFileTypes: true });
44
+ for (const entry of entries) {
45
+ const absPath = path.join(dirAbs, entry.name);
46
+ if (entry.isDirectory()) {
47
+ walkJsonFiles(absPath, results, ccoRootAbs);
48
+ } else if (entry.isFile()) {
49
+ const relToCco = path.relative(ccoRootAbs, absPath).replace(/\\/g, "/");
50
+ results.push({ absPath, relToCco, filename: entry.name });
51
+ }
52
+ }
53
+ } catch {}
54
+ }
55
+
56
+ function checkJsonFile(absPath) {
57
+ const issues = [];
58
+ try {
59
+ const stat = fs.statSync(absPath);
60
+ const raw = fs.readFileSync(absPath, "utf8").replace(/^/, "");
61
+
62
+ // Parse check
63
+ let parsed;
64
+ try {
65
+ parsed = JSON.parse(raw);
66
+ } catch {
67
+ issues.push({ issueType: "invalid_json", severity: "error", message: "File is not valid JSON." });
68
+ return { parsed: null, issues };
69
+ }
70
+
71
+ // Contract field
72
+ if (!parsed.contract) {
73
+ issues.push({ issueType: "missing_contract", severity: "warn", message: "File missing 'contract' field." });
74
+ }
75
+
76
+ // SchemaVersion field
77
+ if (parsed.schemaVersion == null) {
78
+ issues.push({ issueType: "missing_schema_version", severity: "warn", message: "File missing 'schemaVersion' field." });
79
+ }
80
+
81
+ // Oversized
82
+ if (stat.size > MAX_FILE_BYTES) {
83
+ issues.push({ issueType: "oversized", severity: "warn", message: `File is ${stat.size} bytes (limit: ${MAX_FILE_BYTES}).` });
84
+ }
85
+
86
+ // Stale
87
+ const ageMs = Date.now() - stat.mtimeMs;
88
+ const ageDays = ageMs / (1000 * 60 * 60 * 24);
89
+ if (ageDays > STALE_DAYS) {
90
+ issues.push({ issueType: "stale", severity: "info", message: `File is ${Math.round(ageDays)} days old (threshold: ${STALE_DAYS}).` });
91
+ }
92
+
93
+ return { parsed, issues, stat };
94
+ } catch (e) {
95
+ issues.push({ issueType: "invalid_json", severity: "error", message: `Could not read file: ${e.message}` });
96
+ return { parsed: null, issues };
97
+ }
98
+ }
99
+
100
+ function checkJsonlFile(absPath, relToCco) {
101
+ const issues = [];
102
+ try {
103
+ const raw = fs.readFileSync(absPath, "utf8");
104
+ const lines = raw.split(/\r?\n/).filter((l) => l.trim());
105
+ let badLines = 0;
106
+ for (const line of lines) {
107
+ try { JSON.parse(line); } catch { badLines++; }
108
+ }
109
+ if (badLines > 0) {
110
+ issues.push({ issueType: "corrupt_jsonl", severity: "warn", message: `${badLines} unparseable line(s) in JSONL file.` });
111
+ }
112
+ if (lines.length > MAX_JSONL_LINES) {
113
+ issues.push({ issueType: "jsonl_too_large", severity: "warn", message: `JSONL has ${lines.length} lines (limit: ${MAX_JSONL_LINES}).` });
114
+ }
115
+ } catch (e) {
116
+ issues.push({ issueType: "corrupt_jsonl", severity: "error", message: `Could not read JSONL: ${e.message}` });
117
+ }
118
+ return issues;
119
+ }
120
+
121
+ // ── classifyArtifactIssue ─────────────────────────────────────────────────────
122
+
123
+ function classifyArtifactIssue(issue, options = {}) {
124
+ return {
125
+ ...issue,
126
+ severity: issue.severity || "warn",
127
+ suggestedAction: suggestAction(issue.issueType),
128
+ };
129
+ }
130
+
131
+ function suggestAction(issueType) {
132
+ switch (issueType) {
133
+ case "invalid_json": return "Delete and regenerate this artifact.";
134
+ case "missing_contract": return "Update the module that writes this artifact to include a contract field.";
135
+ case "missing_schema_version": return "Update the module that writes this artifact to include a schemaVersion field.";
136
+ case "oversized": return "Review the artifact for unnecessary data and trim it.";
137
+ case "stale": return "Re-run the command that generates this artifact to refresh it.";
138
+ case "corrupt_jsonl": return "Truncate or delete the corrupt JSONL file and re-run the generating command.";
139
+ case "jsonl_too_large": return "Run `avorelo artifacts cleanup` to truncate or archive old JSONL lines.";
140
+ case "redaction_failure": return "Fix the generating module to ensure redacted:true is set.";
141
+ default: return "Review and regenerate this artifact.";
142
+ }
143
+ }
144
+
145
+ // ── scanArtifactHealth ────────────────────────────────────────────────────────
146
+
147
+ function scanArtifactHealth(cwd, options = {}) {
148
+ const ccoRootAbs = path.join(cwd, CCO_DIR_REL);
149
+ const allFiles = [];
150
+ walkJsonFiles(ccoRootAbs, allFiles, ccoRootAbs);
151
+
152
+ const allIssues = [];
153
+ let healthyCount = 0;
154
+ let warnCount = 0;
155
+ let errorCount = 0;
156
+
157
+ for (const { absPath, relToCco, filename } of allFiles) {
158
+ const isJsonl = filename.endsWith(".jsonl");
159
+ if (isJsonl) {
160
+ const jsonlIssues = checkJsonlFile(absPath, relToCco);
161
+ for (const issue of jsonlIssues) {
162
+ const classified = classifyArtifactIssue({ ...issue, artifactPath: relToCco });
163
+ allIssues.push(classified);
164
+ if (issue.severity === "error") errorCount++;
165
+ else if (issue.severity === "warn") warnCount++;
166
+ }
167
+ if (jsonlIssues.length === 0) healthyCount++;
168
+ } else if (filename.endsWith(".json")) {
169
+ const { issues } = checkJsonFile(absPath);
170
+ const fileIssues = issues.map((issue) => classifyArtifactIssue({ ...issue, artifactPath: relToCco }));
171
+ if (fileIssues.length === 0) {
172
+ healthyCount++;
173
+ } else {
174
+ for (const issue of fileIssues) {
175
+ allIssues.push(issue);
176
+ if (issue.severity === "error") errorCount++;
177
+ else if (issue.severity === "warn") warnCount++;
178
+ else if (issue.severity === "info") {
179
+ // info issues don't degrade healthyCount
180
+ }
181
+ }
182
+ // If only info/stale, still count as healthy for summary
183
+ const hasRealIssue = fileIssues.some((i) => i.severity === "error" || i.severity === "warn");
184
+ if (!hasRealIssue) healthyCount++;
185
+ }
186
+ }
187
+ }
188
+
189
+ const artifactsScanned = allFiles.length;
190
+
191
+ let status;
192
+ if (errorCount > 0) status = "unhealthy";
193
+ else if (warnCount > 0) status = "warn";
194
+ else status = "healthy";
195
+
196
+ const nextAction = allIssues.length === 0
197
+ ? "All artifacts are healthy."
198
+ : `${allIssues.length} issue(s) found. Run \`avorelo artifacts cleanup --dry-run\` to review cleanup options.`;
199
+
200
+ return {
201
+ contract: HEALTH_CONTRACT,
202
+ schemaVersion: SCHEMA_VERSION,
203
+ status,
204
+ scanRoot: CCO_DIR_REL,
205
+ artifactsScanned,
206
+ issues: allIssues,
207
+ summary: { healthy: healthyCount, warn: warnCount, error: errorCount },
208
+ nextAction,
209
+ createdAt: nowIso(),
210
+ redacted: true,
211
+ };
212
+ }
213
+
214
+ // ── buildArtifactCleanupPlan ──────────────────────────────────────────────────
215
+
216
+ function buildArtifactCleanupPlan(cwd, health, options = {}) {
217
+ const dryRun = options.yes !== true;
218
+ const ccoRootAbs = path.join(cwd, CCO_DIR_REL);
219
+ const actions = [];
220
+
221
+ // Only plan cleanup for issues in .claude/cco
222
+ for (const issue of (health.issues || [])) {
223
+ const absPath = path.join(ccoRootAbs, issue.artifactPath);
224
+
225
+ // Safety: never plan to delete outside .claude/cco
226
+ const relResolved = path.relative(ccoRootAbs, absPath);
227
+ if (relResolved.startsWith("..")) continue;
228
+
229
+ // Never plan to delete latest-* files
230
+ const filename = path.basename(absPath);
231
+ if (isLatestFile(filename)) continue;
232
+
233
+ if (issue.issueType === "stale") {
234
+ actions.push({
235
+ artifactPath: issue.artifactPath,
236
+ absPath,
237
+ action: "delete_stale",
238
+ reason: issue.message,
239
+ severity: issue.severity,
240
+ protected: false,
241
+ });
242
+ } else if (issue.issueType === "jsonl_too_large") {
243
+ actions.push({
244
+ artifactPath: issue.artifactPath,
245
+ absPath,
246
+ action: "truncate_jsonl",
247
+ reason: issue.message,
248
+ severity: issue.severity,
249
+ protected: false,
250
+ });
251
+ }
252
+ // invalid_json, corrupt, oversized, etc. — do not auto-delete; surface for review
253
+ }
254
+
255
+ return {
256
+ contract: CLEANUP_CONTRACT,
257
+ schemaVersion: SCHEMA_VERSION,
258
+ dryRun,
259
+ actions,
260
+ summary: {
261
+ toDelete: actions.filter((a) => a.action === "delete_stale").length,
262
+ toTruncate: actions.filter((a) => a.action === "truncate_jsonl").length,
263
+ },
264
+ note: dryRun
265
+ ? "Dry-run mode. No files will be modified. Pass --yes to apply."
266
+ : "Real cleanup mode. Files will be modified.",
267
+ createdAt: nowIso(),
268
+ redacted: true,
269
+ };
270
+ }
271
+
272
+ // ── applyArtifactCleanup ──────────────────────────────────────────────────────
273
+
274
+ function applyArtifactCleanup(cwd, plan, options = {}) {
275
+ // Default: dry-run only. Requires options.yes === true for real execution.
276
+ const realRun = options.yes === true && options.dryRun !== true;
277
+ const ccoRootAbs = path.join(cwd, CCO_DIR_REL);
278
+ const applied = [];
279
+ const skipped = [];
280
+ const errors = [];
281
+
282
+ for (const action of (plan.actions || [])) {
283
+ const absPath = action.absPath || path.join(ccoRootAbs, action.artifactPath);
284
+
285
+ // Safety: never delete outside .claude/cco
286
+ const relResolved = path.relative(ccoRootAbs, absPath);
287
+ if (relResolved.startsWith("..")) {
288
+ skipped.push({ artifactPath: action.artifactPath, reason: "outside .claude/cco — skipped for safety" });
289
+ continue;
290
+ }
291
+
292
+ // Never delete latest-* files
293
+ const filename = path.basename(absPath);
294
+ if (isLatestFile(filename)) {
295
+ skipped.push({ artifactPath: action.artifactPath, reason: "protected latest-* file" });
296
+ continue;
297
+ }
298
+
299
+ if (!realRun) {
300
+ skipped.push({ artifactPath: action.artifactPath, reason: "dry-run mode" });
301
+ continue;
302
+ }
303
+
304
+ try {
305
+ if (action.action === "delete_stale") {
306
+ fs.unlinkSync(absPath);
307
+ applied.push({ artifactPath: action.artifactPath, action: "deleted" });
308
+ } else if (action.action === "truncate_jsonl") {
309
+ // Keep last 1000 lines
310
+ const raw = fs.readFileSync(absPath, "utf8");
311
+ const lines = raw.split(/\r?\n/).filter((l) => l.trim());
312
+ const kept = lines.slice(-1000);
313
+ fs.writeFileSync(absPath, kept.join("\n") + "\n", "utf8");
314
+ applied.push({ artifactPath: action.artifactPath, action: "truncated", keptLines: kept.length });
315
+ }
316
+ } catch (e) {
317
+ errors.push({ artifactPath: action.artifactPath, error: e.message });
318
+ }
319
+ }
320
+
321
+ return {
322
+ contract: CLEANUP_CONTRACT,
323
+ schemaVersion: SCHEMA_VERSION,
324
+ dryRun: !realRun,
325
+ applied,
326
+ skipped,
327
+ errors,
328
+ createdAt: nowIso(),
329
+ redacted: true,
330
+ };
331
+ }
332
+
333
+ // ── writeArtifactHealthReceipt ────────────────────────────────────────────────
334
+
335
+ function writeArtifactHealthReceipt(cwd, receipt) {
336
+ bestEffortWrite(path.join(cwd, LATEST_HEALTH_REL), receipt);
337
+ }
338
+
339
+ // ── writeArtifactCleanupReceipt ───────────────────────────────────────────────
340
+
341
+ function writeArtifactCleanupReceipt(cwd, receipt) {
342
+ bestEffortWrite(path.join(cwd, LATEST_CLEANUP_REL), receipt);
343
+ }
344
+
345
+ // ── formatArtifactHealthText ──────────────────────────────────────────────────
346
+
347
+ function formatArtifactHealthText(health, options = {}) {
348
+ if (!health) return "No artifact health data available.";
349
+ const lines = [];
350
+ lines.push(`Artifact Health: ${health.status?.toUpperCase() || "UNKNOWN"}`);
351
+ lines.push(`Scanned ${health.artifactsScanned ?? 0} artifact(s) in ${health.scanRoot || ".claude/cco"}`);
352
+ lines.push(`Healthy: ${health.summary?.healthy ?? 0} Warn: ${health.summary?.warn ?? 0} Error: ${health.summary?.error ?? 0}`);
353
+
354
+ const errors = (health.issues || []).filter((i) => i.severity === "error");
355
+ const warns = (health.issues || []).filter((i) => i.severity === "warn");
356
+ const infos = (health.issues || []).filter((i) => i.severity === "info");
357
+
358
+ if (errors.length > 0) {
359
+ lines.push("");
360
+ lines.push("Errors:");
361
+ errors.forEach((i) => lines.push(` !! [${i.issueType}] ${i.artifactPath}: ${i.message}`));
362
+ }
363
+ if (warns.length > 0) {
364
+ lines.push("");
365
+ lines.push("Warnings:");
366
+ warns.forEach((i) => lines.push(` -- [${i.issueType}] ${i.artifactPath}: ${i.message}`));
367
+ }
368
+ if (options.debug && infos.length > 0) {
369
+ lines.push("");
370
+ lines.push("Info:");
371
+ infos.forEach((i) => lines.push(` [${i.issueType}] ${i.artifactPath}: ${i.message}`));
372
+ }
373
+
374
+ lines.push("");
375
+ lines.push(`Next step: ${health.nextAction || "All clear."}`);
376
+ return lines.join("\n");
377
+ }
378
+
379
+ // ── formatArtifactCleanupText ─────────────────────────────────────────────────
380
+
381
+ function formatArtifactCleanupText(cleanup, options = {}) {
382
+ if (!cleanup) return "No artifact cleanup data available.";
383
+ const lines = [];
384
+ const label = cleanup.dryRun ? "Artifact Cleanup (DRY-RUN)" : "Artifact Cleanup";
385
+ lines.push(label);
386
+ lines.push(`To delete: ${cleanup.summary?.toDelete ?? 0} To truncate: ${cleanup.summary?.toTruncate ?? 0}`);
387
+
388
+ if (cleanup.actions && cleanup.actions.length > 0) {
389
+ lines.push("");
390
+ lines.push("Planned actions:");
391
+ cleanup.actions.forEach((a) => lines.push(` ${a.action}: ${a.artifactPath} (${a.reason})`));
392
+ }
393
+
394
+ if (cleanup.applied && cleanup.applied.length > 0) {
395
+ lines.push("");
396
+ lines.push("Applied:");
397
+ cleanup.applied.forEach((a) => lines.push(` OK ${a.action}: ${a.artifactPath}`));
398
+ }
399
+
400
+ if (cleanup.skipped && cleanup.skipped.length > 0 && options.debug) {
401
+ lines.push("");
402
+ lines.push("Skipped:");
403
+ cleanup.skipped.forEach((a) => lines.push(` skipped: ${a.artifactPath} (${a.reason})`));
404
+ }
405
+
406
+ if (cleanup.errors && cleanup.errors.length > 0) {
407
+ lines.push("");
408
+ lines.push("Errors:");
409
+ cleanup.errors.forEach((a) => lines.push(` !! ${a.artifactPath}: ${a.error}`));
410
+ }
411
+
412
+ lines.push("");
413
+ lines.push(cleanup.note || (cleanup.dryRun ? "Dry-run mode. Pass --yes to apply." : "Cleanup complete."));
414
+ return lines.join("\n");
415
+ }
416
+
417
+ module.exports = {
418
+ HEALTH_CONTRACT,
419
+ CLEANUP_CONTRACT,
420
+ SCHEMA_VERSION,
421
+ LATEST_HEALTH_REL,
422
+ LATEST_CLEANUP_REL,
423
+ scanArtifactHealth,
424
+ classifyArtifactIssue,
425
+ buildArtifactCleanupPlan,
426
+ applyArtifactCleanup,
427
+ writeArtifactHealthReceipt,
428
+ writeArtifactCleanupReceipt,
429
+ formatArtifactHealthText,
430
+ formatArtifactCleanupText,
431
+ };