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,1367 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Avorelo Workspace Registry — Asset Inventory Foundation v1
5
+ *
6
+ * Contract: avorelo.workspaceRegistry.v1
7
+ *
8
+ * Responsibilities:
9
+ * - Build a normalized local asset index (what exists, type, trust, risk, relationships)
10
+ * - Feed Session Context, Brain Pack, Capability Recommender, Install Intake, Safe Path
11
+ * - Detect drift between snapshots
12
+ * - Write compact product-learning events
13
+ *
14
+ * Rules:
15
+ * - Deterministic, local-first, no network, no LLM, no package-registry lookups
16
+ * - No full filesystem scan outside repo
17
+ * - No secret reading, no raw receipts, no raw code/skill bodies
18
+ * - Fail open with unknown/deferred instead of false trust
19
+ */
20
+
21
+ const fs = require("fs");
22
+ const path = require("path");
23
+ const crypto = require("crypto");
24
+ const { ensureCcoDirs, nowIso, safeReadJson, safeWriteJson, safeWriteText } = require("./fsx");
25
+ const { appendProductLearningEvent } = require("./product-learning-events");
26
+
27
+ // ── Constants ──────────────────────────────────────────────────────────────────
28
+
29
+ const REGISTRY_CONTRACT = "avorelo.workspaceRegistry.v1";
30
+ const REGISTRY_SCHEMA_VERSION = 1;
31
+ const LATEST_REGISTRY_REL_PATH = ".claude/cco/orchestration/workspace-registry/latest-registry.json";
32
+ const LATEST_SUMMARY_MD_REL_PATH = ".claude/cco/orchestration/workspace-registry/latest-summary.md";
33
+ const REGISTRY_HISTORY_DIR_REL_PATH = ".claude/cco/orchestration/workspace-registry/history";
34
+ const REGISTRY_EVENT_LOG_REL_PATH = ".claude/cco/events/workspace-registry.jsonl";
35
+
36
+ const ALLOWED_ASSET_TYPES = new Set([
37
+ "source_file",
38
+ "test_file",
39
+ "doc_file",
40
+ "config_file",
41
+ "package_manifest",
42
+ "lockfile",
43
+ "npm_script",
44
+ "mcp_server_config",
45
+ "mcp_tool_config",
46
+ "vscode_extension_recommendation",
47
+ "cursor_rule",
48
+ "claude_guidance",
49
+ "agents_guidance",
50
+ "gemini_guidance",
51
+ "copilot_guidance",
52
+ "skill",
53
+ "skill_pack",
54
+ "adapter",
55
+ "generated_guidance",
56
+ "model_provider",
57
+ "local_model",
58
+ "orchestration_worker",
59
+ "browser_integration",
60
+ "proof_artifact",
61
+ "receipt",
62
+ "unknown",
63
+ ]);
64
+
65
+ const RISK_ORDER = Object.freeze({ low: 1, medium: 2, high: 3, critical: 4 });
66
+
67
+ const SECRET_PATTERN = /(?:sk-[A-Za-z0-9_-]{16,}|ghp_[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}|(?:api[_ -]?key|token|password|secret|authorization)\s*[:=]\s*\S+)/i;
68
+
69
+ const IGNORED_DIRS = new Set([
70
+ ".git", "node_modules", "dist", "build", "coverage",
71
+ ".next", ".turbo", ".cache", ".wasp",
72
+ ]);
73
+
74
+ // ── Utilities ──────────────────────────────────────────────────────────────────
75
+
76
+ function sha256short(value) {
77
+ return crypto.createHash("sha256").update(String(value || "")).digest("hex").slice(0, 12);
78
+ }
79
+
80
+ function assetId(type, name) {
81
+ return `reg-${type.slice(0, 8)}-${sha256short(`${type}:${name}`)}`;
82
+ }
83
+
84
+ function normalizeRelPath(value) {
85
+ return String(value || "").replace(/\\/g, "/").trim();
86
+ }
87
+
88
+ function maxRisk(a, b) {
89
+ return (RISK_ORDER[a] || 0) >= (RISK_ORDER[b] || 0) ? a : b;
90
+ }
91
+
92
+ function redactIfSecret(text) {
93
+ if (!text) return text;
94
+ const s = String(text);
95
+ return SECRET_PATTERN.test(s) ? "[REDACTED]" : s;
96
+ }
97
+
98
+ function safeExists(absPath) {
99
+ try { return fs.existsSync(absPath); } catch { return false; }
100
+ }
101
+
102
+ function safeReadText(absPath) {
103
+ try { return fs.readFileSync(absPath, "utf8"); } catch { return null; }
104
+ }
105
+
106
+ function safeListDir(absPath) {
107
+ try { return fs.readdirSync(absPath, { withFileTypes: true }); } catch { return []; }
108
+ }
109
+
110
+ function createRegistryId() {
111
+ return `wreg-${Date.now()}-${crypto.randomBytes(4).toString("hex")}`;
112
+ }
113
+
114
+ // ── Asset builders ─────────────────────────────────────────────────────────────
115
+
116
+ function makeAsset(fields) {
117
+ const type = ALLOWED_ASSET_TYPES.has(fields.type) ? fields.type : "unknown";
118
+ return Object.freeze({
119
+ assetId: fields.assetId || assetId(type, fields.name || fields.path || ""),
120
+ type,
121
+ name: String(fields.name || ""),
122
+ path: normalizeRelPath(fields.path || ""),
123
+ source: fields.source || "local",
124
+ detectedFrom: fields.detectedFrom || "workspace-registry",
125
+ ownerOrPublisher: fields.ownerOrPublisher || null,
126
+ version: fields.version || null,
127
+ languageOrRuntime: fields.languageOrRuntime || null,
128
+ reviewStatus: fields.reviewStatus || "unknown",
129
+ trustLevel: fields.trustLevel || "unknown",
130
+ riskLevel: fields.riskLevel || "low",
131
+ capabilities: fields.capabilities || [],
132
+ capabilityRelevance: fields.capabilityRelevance || [],
133
+ relationships: fields.relationships || [],
134
+ reasonCodes: fields.reasonCodes || [],
135
+ safeNextAction: fields.safeNextAction || null,
136
+ feedsSessionContext: fields.feedsSessionContext ?? false,
137
+ feedsBrainPack: fields.feedsBrainPack ?? false,
138
+ feedsCapabilityRecommender: fields.feedsCapabilityRecommender ?? false,
139
+ feedsInstallIntake: fields.feedsInstallIntake ?? false,
140
+ feedsSafePath: fields.feedsSafePath ?? false,
141
+ feedsWorkControl: fields.feedsWorkControl ?? false,
142
+ metadata: fields.metadata || {},
143
+ redacted: true,
144
+ });
145
+ }
146
+
147
+ // ── Package manifest detection ─────────────────────────────────────────────────
148
+
149
+ function detectPackageManifest(cwd) {
150
+ const assets = [];
151
+ const pkgPath = path.join(cwd, "package.json");
152
+ if (!safeExists(pkgPath)) return assets;
153
+
154
+ let pkg = {};
155
+ try { pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")); } catch { return assets; }
156
+
157
+ const pkgAsset = makeAsset({
158
+ type: "package_manifest",
159
+ name: "package.json",
160
+ path: "package.json",
161
+ source: "local",
162
+ detectedFrom: "package.json",
163
+ version: pkg.version || null,
164
+ languageOrRuntime: "node",
165
+ reviewStatus: "known",
166
+ trustLevel: "high",
167
+ riskLevel: "low",
168
+ feedsSessionContext: true,
169
+ feedsBrainPack: true,
170
+ feedsCapabilityRecommender: true,
171
+ feedsInstallIntake: true,
172
+ metadata: {
173
+ name: pkg.name || null,
174
+ scriptCount: Object.keys(pkg.scripts || {}).length,
175
+ depCount: Object.keys(pkg.dependencies || {}).length,
176
+ devDepCount: Object.keys(pkg.devDependencies || {}).length,
177
+ },
178
+ });
179
+ assets.push(pkgAsset);
180
+
181
+ // npm scripts
182
+ for (const [scriptName, scriptCmd] of Object.entries(pkg.scripts || {})) {
183
+ const isHighRisk = /deploy|publish|release|push|prod/i.test(scriptName);
184
+ const isMediumRisk = /build|bundle|compile/i.test(scriptName);
185
+ const riskLevel = isHighRisk ? "high" : isMediumRisk ? "medium" : "low";
186
+ const reasonCodes = [];
187
+ if (isHighRisk) reasonCodes.push("DEPLOY_SCRIPT_PRESENT");
188
+
189
+ assets.push(makeAsset({
190
+ type: "npm_script",
191
+ name: scriptName,
192
+ path: "package.json#scripts",
193
+ source: "local",
194
+ detectedFrom: "package.json",
195
+ languageOrRuntime: "node",
196
+ reviewStatus: isHighRisk ? "known" : "known",
197
+ trustLevel: "high",
198
+ riskLevel,
199
+ reasonCodes,
200
+ safeNextAction: isHighRisk
201
+ ? "Review the script before executing. Prefer dry-run or test-only mode first."
202
+ : null,
203
+ feedsInstallIntake: true,
204
+ feedsSafePath: isHighRisk,
205
+ relationships: [{ rel: "defined_by", target: "package.json" }],
206
+ metadata: { command: redactIfSecret(String(scriptCmd || "").slice(0, 120)) },
207
+ }));
208
+ }
209
+
210
+ // lockfile
211
+ for (const lockfile of ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]) {
212
+ if (safeExists(path.join(cwd, lockfile))) {
213
+ assets.push(makeAsset({
214
+ type: "lockfile",
215
+ name: lockfile,
216
+ path: lockfile,
217
+ source: "local",
218
+ detectedFrom: "filesystem",
219
+ reviewStatus: "known",
220
+ trustLevel: "high",
221
+ riskLevel: "low",
222
+ feedsBrainPack: true,
223
+ feedsInstallIntake: true,
224
+ relationships: [{ rel: "locks", target: "package.json" }],
225
+ }));
226
+ break;
227
+ }
228
+ }
229
+
230
+ return assets;
231
+ }
232
+
233
+ // ── Guidance file detection ────────────────────────────────────────────────────
234
+
235
+ const GUIDANCE_CANDIDATES = [
236
+ { rel: "AGENTS.md", type: "agents_guidance", name: "AGENTS.md", surface: "agents" },
237
+ { rel: "CLAUDE.md", type: "claude_guidance", name: "CLAUDE.md", surface: "claude" },
238
+ { rel: "GEMINI.md", type: "gemini_guidance", name: "GEMINI.md", surface: "gemini" },
239
+ { rel: ".github/copilot-instructions.md", type: "copilot_guidance", name: "copilot-instructions.md", surface: "copilot" },
240
+ { rel: ".cursor/rules", type: "cursor_rule", name: ".cursor/rules", surface: "cursor" },
241
+ ];
242
+
243
+ function detectGuidanceFiles(cwd) {
244
+ const assets = [];
245
+ for (const candidate of GUIDANCE_CANDIDATES) {
246
+ const absPath = path.join(cwd, candidate.rel);
247
+ if (!safeExists(absPath)) continue;
248
+ assets.push(makeAsset({
249
+ type: candidate.type,
250
+ name: candidate.name,
251
+ path: normalizeRelPath(candidate.rel),
252
+ source: "local",
253
+ detectedFrom: "filesystem",
254
+ reviewStatus: "known",
255
+ trustLevel: "high",
256
+ riskLevel: "low",
257
+ feedsSessionContext: true,
258
+ feedsBrainPack: true,
259
+ relationships: [{ rel: "guides", target: candidate.surface }],
260
+ metadata: { surface: candidate.surface },
261
+ }));
262
+ }
263
+
264
+ // GitHub instructions dir
265
+ const ghInstructionsDir = path.join(cwd, ".github", "instructions");
266
+ if (safeExists(ghInstructionsDir)) {
267
+ for (const entry of safeListDir(ghInstructionsDir)) {
268
+ if (!entry.isFile() || !entry.name.endsWith(".instructions.md")) continue;
269
+ const rel = `.github/instructions/${entry.name}`;
270
+ assets.push(makeAsset({
271
+ type: "copilot_guidance",
272
+ name: entry.name,
273
+ path: normalizeRelPath(rel),
274
+ source: "local",
275
+ detectedFrom: "filesystem",
276
+ reviewStatus: "known",
277
+ trustLevel: "high",
278
+ riskLevel: "low",
279
+ feedsSessionContext: true,
280
+ feedsBrainPack: true,
281
+ relationships: [{ rel: "guides", target: "copilot" }],
282
+ metadata: { surface: "copilot" },
283
+ }));
284
+ }
285
+ }
286
+
287
+ return assets;
288
+ }
289
+
290
+ // ── VSCode extensions detection ────────────────────────────────────────────────
291
+
292
+ function detectVscodeExtensions(cwd) {
293
+ const assets = [];
294
+ const extPath = path.join(cwd, ".vscode", "extensions.json");
295
+ if (!safeExists(extPath)) return assets;
296
+ let ext = {};
297
+ try { ext = JSON.parse(fs.readFileSync(extPath, "utf8")); } catch { return assets; }
298
+ const recommendations = ext.recommendations || [];
299
+ if (!recommendations.length) return assets;
300
+ assets.push(makeAsset({
301
+ type: "vscode_extension_recommendation",
302
+ name: ".vscode/extensions.json",
303
+ path: ".vscode/extensions.json",
304
+ source: "local",
305
+ detectedFrom: "filesystem",
306
+ reviewStatus: "known",
307
+ trustLevel: "medium",
308
+ riskLevel: "low",
309
+ feedsBrainPack: true,
310
+ feedsInstallIntake: true,
311
+ metadata: { count: recommendations.length, names: recommendations.slice(0, 10) },
312
+ }));
313
+ return assets;
314
+ }
315
+
316
+ // ── Skills and skill packs detection ──────────────────────────────────────────
317
+
318
+ function detectSkillAssets(cwd) {
319
+ const assets = [];
320
+
321
+ // Canonical avorelo skills dir
322
+ const aSkillsDir = path.join(cwd, "skills", "avorelo");
323
+ if (safeExists(aSkillsDir)) {
324
+ const skillFiles = safeListDir(aSkillsDir).filter((e) => e.isFile() && e.name.endsWith(".md"));
325
+ for (const skillFile of skillFiles) {
326
+ assets.push(makeAsset({
327
+ type: "skill",
328
+ name: skillFile.name,
329
+ path: normalizeRelPath(`skills/avorelo/${skillFile.name}`),
330
+ source: "local",
331
+ detectedFrom: "filesystem",
332
+ reviewStatus: "known",
333
+ trustLevel: "high",
334
+ riskLevel: "low",
335
+ feedsSessionContext: true,
336
+ feedsCapabilityRecommender: true,
337
+ relationships: [{ rel: "contained_by", target: "skills/avorelo" }],
338
+ }));
339
+ }
340
+ }
341
+
342
+ // Vendor skill packs
343
+ const vendorDir = path.join(cwd, "vendor", "skillpacks");
344
+ if (safeExists(vendorDir)) {
345
+ for (const entry of safeListDir(vendorDir)) {
346
+ if (!entry.isDirectory()) continue;
347
+ const packPath = `vendor/skillpacks/${entry.name}`;
348
+ const packAsset = makeAsset({
349
+ type: "skill_pack",
350
+ name: entry.name,
351
+ path: normalizeRelPath(packPath),
352
+ source: "vendor",
353
+ detectedFrom: "filesystem",
354
+ reviewStatus: "known",
355
+ trustLevel: "medium",
356
+ riskLevel: "low",
357
+ feedsSessionContext: true,
358
+ feedsCapabilityRecommender: true,
359
+ relationships: [],
360
+ });
361
+ assets.push(packAsset);
362
+
363
+ // Skills inside pack
364
+ const packSkillDir = path.join(vendorDir, entry.name);
365
+ for (const skillEntry of safeListDir(packSkillDir)) {
366
+ if (!skillEntry.isFile() || !skillEntry.name.endsWith(".md")) continue;
367
+ assets.push(makeAsset({
368
+ type: "skill",
369
+ name: skillEntry.name,
370
+ path: normalizeRelPath(`${packPath}/${skillEntry.name}`),
371
+ source: "vendor",
372
+ detectedFrom: "filesystem",
373
+ reviewStatus: "known",
374
+ trustLevel: "medium",
375
+ riskLevel: "low",
376
+ feedsSessionContext: true,
377
+ feedsCapabilityRecommender: true,
378
+ relationships: [{ rel: "contained_by", target: packPath }],
379
+ }));
380
+ }
381
+ }
382
+ }
383
+
384
+ return assets;
385
+ }
386
+
387
+ // ── Source / test / doc / config file detection ────────────────────────────────
388
+
389
+ function walkDir(cwd, relDir, matcher, limit = 60) {
390
+ const results = [];
391
+ const startAbs = path.join(cwd, relDir);
392
+ if (!safeExists(startAbs)) return results;
393
+
394
+ function walk(currentAbs, currentRel) {
395
+ if (results.length >= limit) return;
396
+ for (const entry of safeListDir(currentAbs)) {
397
+ if (results.length >= limit) break;
398
+ const entryRel = currentRel ? `${currentRel}/${entry.name}` : entry.name;
399
+ if (entry.isDirectory()) {
400
+ if (IGNORED_DIRS.has(entry.name)) continue;
401
+ walk(path.join(currentAbs, entry.name), entryRel);
402
+ continue;
403
+ }
404
+ if (matcher(normalizeRelPath(entryRel))) {
405
+ results.push(normalizeRelPath(entryRel));
406
+ }
407
+ }
408
+ }
409
+ walk(startAbs, normalizeRelPath(relDir));
410
+ return results;
411
+ }
412
+
413
+ function detectSourceFiles(cwd) {
414
+ const assets = [];
415
+
416
+ // Core source files — scripts/lib only, by path metadata (not content scan)
417
+ const sourceFiles = walkDir(cwd, "scripts/lib", (p) => p.endsWith(".js"), 50);
418
+ for (const relPath of sourceFiles) {
419
+ assets.push(makeAsset({
420
+ type: "source_file",
421
+ name: path.basename(relPath),
422
+ path: relPath,
423
+ source: "local",
424
+ detectedFrom: "filesystem",
425
+ languageOrRuntime: "node",
426
+ reviewStatus: "known",
427
+ trustLevel: "high",
428
+ riskLevel: "low",
429
+ feedsSessionContext: true,
430
+ feedsBrainPack: true,
431
+ }));
432
+ }
433
+
434
+ return assets;
435
+ }
436
+
437
+ function detectTestFiles(cwd) {
438
+ const assets = [];
439
+ const testFiles = walkDir(cwd, "tests", (p) => p.endsWith(".test.js") || p.endsWith(".spec.js"), 50);
440
+ for (const relPath of testFiles) {
441
+ assets.push(makeAsset({
442
+ type: "test_file",
443
+ name: path.basename(relPath),
444
+ path: relPath,
445
+ source: "local",
446
+ detectedFrom: "filesystem",
447
+ languageOrRuntime: "node",
448
+ reviewStatus: "known",
449
+ trustLevel: "high",
450
+ riskLevel: "low",
451
+ feedsSessionContext: true,
452
+ feedsBrainPack: true,
453
+ relationships: inferTestRelationship(relPath),
454
+ }));
455
+ }
456
+ return assets;
457
+ }
458
+
459
+ function inferTestRelationship(testRelPath) {
460
+ const base = path.basename(testRelPath).replace(/\.test\.js$|\.spec\.js$/, "");
461
+ const srcFile = `scripts/lib/${base}.js`;
462
+ return [{ rel: "validates", target: srcFile }];
463
+ }
464
+
465
+ function detectDocFiles(cwd) {
466
+ const assets = [];
467
+ const docFiles = walkDir(cwd, "docs", (p) => p.endsWith(".md"), 40);
468
+ for (const relPath of docFiles) {
469
+ assets.push(makeAsset({
470
+ type: "doc_file",
471
+ name: path.basename(relPath),
472
+ path: relPath,
473
+ source: "local",
474
+ detectedFrom: "filesystem",
475
+ reviewStatus: "known",
476
+ trustLevel: "high",
477
+ riskLevel: "low",
478
+ feedsSessionContext: relPath.includes("dogfood") || relPath.includes("Avorelo"),
479
+ feedsBrainPack: true,
480
+ }));
481
+ }
482
+ return assets;
483
+ }
484
+
485
+ function detectConfigFiles(cwd) {
486
+ const assets = [];
487
+ const CONFIG_CANDIDATES = [
488
+ "jest.config.js", "jest.config.ts", "vitest.config.js", "vitest.config.ts",
489
+ "tsconfig.json", ".eslintrc.js", ".eslintrc.json", ".eslintrc.cjs",
490
+ "playwright.config.ts", "playwright.config.js",
491
+ ".gitignore", ".npmignore",
492
+ "Dockerfile", "docker-compose.yml", "docker-compose.yaml",
493
+ "railway.json", "railway.toml",
494
+ ];
495
+ for (const candidate of CONFIG_CANDIDATES) {
496
+ if (safeExists(path.join(cwd, candidate))) {
497
+ const isDeployConfig = /railway|docker|Dockerfile/i.test(candidate);
498
+ assets.push(makeAsset({
499
+ type: "config_file",
500
+ name: candidate,
501
+ path: candidate,
502
+ source: "local",
503
+ detectedFrom: "filesystem",
504
+ reviewStatus: "known",
505
+ trustLevel: "high",
506
+ riskLevel: isDeployConfig ? "medium" : "low",
507
+ reasonCodes: isDeployConfig ? ["DEPLOY_CONFIG_PRESENT"] : [],
508
+ feedsBrainPack: true,
509
+ feedsInstallIntake: isDeployConfig,
510
+ feedsSafePath: isDeployConfig,
511
+ }));
512
+ }
513
+ }
514
+ return assets;
515
+ }
516
+
517
+ // ── MCP / tool config detection ───────────────────────────────────────────────
518
+
519
+ function detectMcpConfigs(cwd) {
520
+ const assets = [];
521
+ const MCP_CONFIG_CANDIDATES = [
522
+ ".mcp.json",
523
+ "mcp.json",
524
+ ".claude/mcp-config.json",
525
+ ".cursor/mcp.json",
526
+ ];
527
+ for (const candidate of MCP_CONFIG_CANDIDATES) {
528
+ const absPath = path.join(cwd, candidate);
529
+ if (!safeExists(absPath)) continue;
530
+
531
+ let mcpData = {};
532
+ try { mcpData = JSON.parse(fs.readFileSync(absPath, "utf8")); } catch { mcpData = {}; }
533
+
534
+ const serverNames = Object.keys(mcpData.mcpServers || mcpData.servers || {});
535
+ const hasUnknownServers = serverNames.length > 0;
536
+
537
+ assets.push(makeAsset({
538
+ type: "mcp_server_config",
539
+ name: candidate,
540
+ path: normalizeRelPath(candidate),
541
+ source: "local",
542
+ detectedFrom: "filesystem",
543
+ reviewStatus: hasUnknownServers ? "unknown" : "known",
544
+ trustLevel: hasUnknownServers ? "unknown" : "medium",
545
+ riskLevel: hasUnknownServers ? "high" : "medium",
546
+ reasonCodes: hasUnknownServers ? ["UNKNOWN_MCP_SERVER_PRESENT"] : [],
547
+ safeNextAction: hasUnknownServers
548
+ ? "Run `avorelo intake` to review unknown MCP server entries before use."
549
+ : "Review MCP config before use.",
550
+ feedsInstallIntake: true,
551
+ feedsSafePath: true,
552
+ feedsCapabilityRecommender: true,
553
+ relationships: serverNames.map((n) => ({ rel: "exposes", target: n })),
554
+ metadata: { serverCount: serverNames.length, serverNames: serverNames.slice(0, 8) },
555
+ }));
556
+ }
557
+ return assets;
558
+ }
559
+
560
+ // ── Model / worker / orchestration worker detection ───────────────────────────
561
+
562
+ function detectModelProviderAssets(cwd) {
563
+ const assets = [];
564
+
565
+ // Read from existing inventory if available
566
+ let modelInventory = null;
567
+ try {
568
+ const { loadInventory } = require("./orchestration/inventory");
569
+ modelInventory = loadInventory(cwd);
570
+ } catch { /* not available */ }
571
+
572
+ if (modelInventory && Array.isArray(modelInventory.tools)) {
573
+ for (const tool of modelInventory.tools) {
574
+ const isAvailable = tool.availability === "available";
575
+ assets.push(makeAsset({
576
+ type: tool.toolId === "lm_studio" ? "local_model" : "model_provider",
577
+ name: tool.label || tool.toolId,
578
+ path: "",
579
+ source: "local",
580
+ detectedFrom: "model-inventory",
581
+ version: tool.activeModel || null,
582
+ reviewStatus: "known",
583
+ trustLevel: isAvailable ? "high" : "medium",
584
+ riskLevel: "low",
585
+ feedsCapabilityRecommender: true,
586
+ feedsBrainPack: true,
587
+ metadata: {
588
+ toolId: tool.toolId,
589
+ availability: tool.availability,
590
+ costTier: tool.costTier,
591
+ workerState: tool.workerState,
592
+ supportedRoles: tool.supportedRoles || [],
593
+ },
594
+ }));
595
+ }
596
+ }
597
+
598
+ return assets;
599
+ }
600
+
601
+ // ── Generated guidance / adapter detection ────────────────────────────────────
602
+
603
+ function detectGeneratedGuidance(cwd) {
604
+ const assets = [];
605
+
606
+ // .avorelo/generated
607
+ const genDir = path.join(cwd, ".avorelo", "generated");
608
+ if (safeExists(genDir)) {
609
+ for (const entry of safeListDir(genDir)) {
610
+ if (!entry.isFile()) continue;
611
+ assets.push(makeAsset({
612
+ type: "generated_guidance",
613
+ name: entry.name,
614
+ path: normalizeRelPath(`.avorelo/generated/${entry.name}`),
615
+ source: "generated",
616
+ detectedFrom: "filesystem",
617
+ reviewStatus: "known",
618
+ trustLevel: "medium",
619
+ riskLevel: "low",
620
+ feedsSessionContext: true,
621
+ metadata: {},
622
+ }));
623
+ }
624
+ }
625
+
626
+ // .avorelo/skills/registry.json
627
+ const skillRegistryPath = path.join(cwd, ".avorelo", "skills", "registry.json");
628
+ if (safeExists(skillRegistryPath)) {
629
+ assets.push(makeAsset({
630
+ type: "generated_guidance",
631
+ name: "skills-registry.json",
632
+ path: ".avorelo/skills/registry.json",
633
+ source: "generated",
634
+ detectedFrom: "filesystem",
635
+ reviewStatus: "known",
636
+ trustLevel: "high",
637
+ riskLevel: "low",
638
+ feedsSessionContext: true,
639
+ feedsCapabilityRecommender: true,
640
+ }));
641
+ }
642
+
643
+ return assets;
644
+ }
645
+
646
+ // ── Latest receipt / proof asset references ───────────────────────────────────
647
+
648
+ function detectReceiptAssets(cwd) {
649
+ const assets = [];
650
+ const RECEIPT_PATHS = [
651
+ { path: ".claude/cco/orchestration/work-control/latest-receipt.json", name: "work-control-receipt", capability: "work_control" },
652
+ { path: ".claude/cco/security/install-intake/latest-receipt.json", name: "install-intake-receipt", capability: "install_intake_risk" },
653
+ { path: ".claude/cco/orchestration/session-context/latest-context.json", name: "session-context", capability: "compact_session_context_basic" },
654
+ ];
655
+ for (const rp of RECEIPT_PATHS) {
656
+ if (!safeExists(path.join(cwd, rp.path))) continue;
657
+ assets.push(makeAsset({
658
+ type: "receipt",
659
+ name: rp.name,
660
+ path: normalizeRelPath(rp.path),
661
+ source: "generated",
662
+ detectedFrom: "filesystem",
663
+ reviewStatus: "known",
664
+ trustLevel: "high",
665
+ riskLevel: "low",
666
+ feedsWorkControl: true,
667
+ feedsSessionContext: rp.name === "session-context",
668
+ relationships: [{ rel: "evidence_for", target: rp.capability }],
669
+ metadata: { redacted: true },
670
+ }));
671
+ }
672
+ return assets;
673
+ }
674
+
675
+ // ── Relationship inference ────────────────────────────────────────────────────
676
+
677
+ function inferCrossAssetRelationships(assets) {
678
+ const byPath = new Map(assets.map((a) => [a.path, a]));
679
+ const byType = new Map();
680
+ for (const a of assets) {
681
+ if (!byType.has(a.type)) byType.set(a.type, []);
682
+ byType.get(a.type).push(a);
683
+ }
684
+
685
+ const relationships = [];
686
+
687
+ // package_manifest -> defines -> npm_scripts
688
+ const pkgManifest = assets.find((a) => a.type === "package_manifest");
689
+ if (pkgManifest) {
690
+ for (const a of assets.filter((a) => a.type === "npm_script")) {
691
+ relationships.push({ from: pkgManifest.assetId, rel: "defines", to: a.assetId });
692
+ }
693
+ const lockfile = assets.find((a) => a.type === "lockfile");
694
+ if (lockfile) {
695
+ relationships.push({ from: pkgManifest.assetId, rel: "locked_by", to: lockfile.assetId });
696
+ }
697
+ }
698
+
699
+ // test_file -> validates -> source_file
700
+ for (const testAsset of assets.filter((a) => a.type === "test_file")) {
701
+ const baseName = path.basename(testAsset.path).replace(/\.test\.js$|\.spec\.js$/, "");
702
+ const srcPath = `scripts/lib/${baseName}.js`;
703
+ const srcAsset = byPath.get(srcPath);
704
+ if (srcAsset) {
705
+ relationships.push({ from: testAsset.assetId, rel: "validates", to: srcAsset.assetId });
706
+ }
707
+ }
708
+
709
+ // skill_pack -> contains -> skills
710
+ for (const packAsset of assets.filter((a) => a.type === "skill_pack")) {
711
+ for (const skillAsset of assets.filter((a) => a.type === "skill" && a.path.startsWith(packAsset.path))) {
712
+ relationships.push({ from: packAsset.assetId, rel: "contains", to: skillAsset.assetId });
713
+ }
714
+ }
715
+
716
+ // guidance -> guides -> agent_surface
717
+ for (const guideAsset of assets.filter((a) => ["claude_guidance", "agents_guidance", "gemini_guidance", "copilot_guidance", "cursor_rule"].includes(a.type))) {
718
+ relationships.push({
719
+ from: guideAsset.assetId,
720
+ rel: "guides",
721
+ to: guideAsset.metadata?.surface || "agent",
722
+ });
723
+ }
724
+
725
+ // receipt -> evidence_for -> capability
726
+ for (const rcptAsset of assets.filter((a) => a.type === "receipt")) {
727
+ for (const rel of rcptAsset.relationships) {
728
+ if (rel.rel === "evidence_for") {
729
+ relationships.push({ from: rcptAsset.assetId, rel: "evidence_for", to: rel.target });
730
+ }
731
+ }
732
+ }
733
+
734
+ return relationships;
735
+ }
736
+
737
+ // ── Drift detection ───────────────────────────────────────────────────────────
738
+
739
+ function computeDrift(prevSnapshot, currentAssets) {
740
+ if (!prevSnapshot || !Array.isArray(prevSnapshot.assets)) {
741
+ return {
742
+ previousRegistryId: null,
743
+ addedAssets: currentAssets.length,
744
+ removedAssets: 0,
745
+ changedAssets: 0,
746
+ unchangedAssets: 0,
747
+ };
748
+ }
749
+
750
+ const prevById = new Map((prevSnapshot.assets || []).map((a) => [a.assetId, a]));
751
+ const currById = new Map(currentAssets.map((a) => [a.assetId, a]));
752
+
753
+ let added = 0, removed = 0, changed = 0, unchanged = 0;
754
+
755
+ for (const [id, curr] of currById) {
756
+ const prev = prevById.get(id);
757
+ if (!prev) { added++; continue; }
758
+ if (prev.reviewStatus !== curr.reviewStatus || prev.riskLevel !== curr.riskLevel) {
759
+ changed++;
760
+ } else {
761
+ unchanged++;
762
+ }
763
+ }
764
+
765
+ for (const id of prevById.keys()) {
766
+ if (!currById.has(id)) removed++;
767
+ }
768
+
769
+ return {
770
+ previousRegistryId: prevSnapshot.registryId || null,
771
+ addedAssets: added,
772
+ removedAssets: removed,
773
+ changedAssets: changed,
774
+ unchangedAssets: unchanged,
775
+ };
776
+ }
777
+
778
+ // ── Integration readers ───────────────────────────────────────────────────────
779
+
780
+ function readLatestIntakeSummary(cwd) {
781
+ try {
782
+ const receipt = safeReadJson(cwd, ".claude/cco/security/install-intake/latest-receipt.json", null);
783
+ if (!receipt) return null;
784
+ return {
785
+ totalItems: receipt.summary?.totalItems || 0,
786
+ unknownItems: receipt.summary?.unknownItems || 0,
787
+ highRiskItems: receipt.summary?.highRiskItems || 0,
788
+ criticalRiskItems: receipt.summary?.criticalRiskItems || 0,
789
+ };
790
+ } catch { return null; }
791
+ }
792
+
793
+ function readLatestWorkControlSummary(cwd) {
794
+ try {
795
+ const receipt = safeReadJson(cwd, ".claude/cco/orchestration/work-control/latest-receipt.json", null);
796
+ if (!receipt) return null;
797
+ return {
798
+ finalState: receipt.workControl?.finalState || "missing",
799
+ nextSafestAction: receipt.workControl?.nextSafestAction || null,
800
+ };
801
+ } catch { return null; }
802
+ }
803
+
804
+ // ── Summary computation ───────────────────────────────────────────────────────
805
+
806
+ function computeSummary(assets) {
807
+ const assetsByType = {};
808
+ let reviewedAssets = 0, unknownAssets = 0, deferredAssets = 0, blockedAssets = 0;
809
+ let highRiskAssets = 0, criticalRiskAssets = 0;
810
+ let sessionContextAssets = 0, brainPackAssets = 0, capabilityRelevantAssets = 0;
811
+ let installIntakeAssets = 0, safePathRelevantAssets = 0;
812
+
813
+ for (const a of assets) {
814
+ assetsByType[a.type] = (assetsByType[a.type] || 0) + 1;
815
+ if (["reviewed", "known"].includes(a.reviewStatus)) reviewedAssets++;
816
+ if (a.reviewStatus === "unknown") unknownAssets++;
817
+ if (a.reviewStatus === "deferred") deferredAssets++;
818
+ if (a.reviewStatus === "blocked") blockedAssets++;
819
+ if (a.riskLevel === "high") highRiskAssets++;
820
+ if (a.riskLevel === "critical") criticalRiskAssets++;
821
+ if (a.feedsSessionContext) sessionContextAssets++;
822
+ if (a.feedsBrainPack) brainPackAssets++;
823
+ if (a.feedsCapabilityRecommender) capabilityRelevantAssets++;
824
+ if (a.feedsInstallIntake) installIntakeAssets++;
825
+ if (a.feedsSafePath) safePathRelevantAssets++;
826
+ }
827
+
828
+ return {
829
+ totalAssets: assets.length,
830
+ assetsByType,
831
+ reviewedAssets,
832
+ unknownAssets,
833
+ deferredAssets,
834
+ blockedAssets,
835
+ highRiskAssets,
836
+ criticalRiskAssets,
837
+ sessionContextAssets,
838
+ brainPackAssets,
839
+ capabilityRelevantAssets,
840
+ installIntakeAssets,
841
+ safePathRelevantAssets,
842
+ };
843
+ }
844
+
845
+ // ── Recommendations ───────────────────────────────────────────────────────────
846
+
847
+ function computeRecommendations(assets, summary, intakeSummary, wcSummary) {
848
+ const topCapabilityRecommendations = [];
849
+ const intakeReviewNeeded = [];
850
+ const safePathReviewNeeded = [];
851
+
852
+ if (summary.unknownAssets > 0) {
853
+ topCapabilityRecommendations.push("install_intake_risk");
854
+ intakeReviewNeeded.push(...assets.filter((a) => a.reviewStatus === "unknown").map((a) => a.path).slice(0, 5));
855
+ }
856
+ if (summary.highRiskAssets > 0 || summary.criticalRiskAssets > 0) {
857
+ topCapabilityRecommendations.push("safe_path_engine");
858
+ safePathReviewNeeded.push(...assets.filter((a) => ["high", "critical"].includes(a.riskLevel)).map((a) => a.path).slice(0, 5));
859
+ }
860
+ if (summary.sessionContextAssets > 0) {
861
+ topCapabilityRecommendations.push("compact_session_context_basic");
862
+ }
863
+ if (summary.capabilityRelevantAssets > 0) {
864
+ topCapabilityRecommendations.push("workspace_registry_basic");
865
+ }
866
+
867
+ const sessionContextRelevantFiles = assets
868
+ .filter((a) => a.feedsSessionContext && a.path)
869
+ .map((a) => a.path)
870
+ .filter(Boolean)
871
+ .slice(0, 20);
872
+
873
+ const brainPackContextHints = {
874
+ packageManager: assets.some((a) => a.type === "lockfile" && a.name.includes("pnpm")) ? "pnpm"
875
+ : assets.some((a) => a.type === "lockfile" && a.name.includes("yarn")) ? "yarn"
876
+ : assets.some((a) => a.type === "lockfile") ? "npm" : "unknown",
877
+ testCommand: "node --test",
878
+ totalSourceFiles: summary.assetsByType["source_file"] || 0,
879
+ totalTestFiles: summary.assetsByType["test_file"] || 0,
880
+ totalDocFiles: summary.assetsByType["doc_file"] || 0,
881
+ skillCount: (summary.assetsByType["skill"] || 0) + (summary.assetsByType["skill_pack"] || 0),
882
+ hasGuidanceFiles: (summary.assetsByType["claude_guidance"] || 0) > 0
883
+ || (summary.assetsByType["agents_guidance"] || 0) > 0,
884
+ };
885
+
886
+ let nextAction = wcSummary?.nextSafestAction || null;
887
+ if (!nextAction && summary.unknownAssets > 0) {
888
+ nextAction = "Run `avorelo intake` to review unknown assets before proceeding.";
889
+ }
890
+ if (!nextAction && summary.highRiskAssets > 0) {
891
+ nextAction = "Review high-risk assets (deploy scripts, MCP configs) before executing.";
892
+ }
893
+ if (!nextAction) {
894
+ nextAction = "Registry is current. Run `avorelo session-context --handoff` to generate a compact session handoff.";
895
+ }
896
+
897
+ return {
898
+ topCapabilityRecommendations: [...new Set(topCapabilityRecommendations)],
899
+ sessionContextRelevantFiles,
900
+ brainPackContextHints,
901
+ intakeReviewNeeded,
902
+ safePathReviewNeeded,
903
+ nextAction,
904
+ };
905
+ }
906
+
907
+ // ── Value measurement ─────────────────────────────────────────────────────────
908
+
909
+ function computeValueMeasurement(assets, summary, drift) {
910
+ const unknownCount = summary.unknownAssets;
911
+ const highRiskCount = summary.highRiskAssets;
912
+ const assetCount = summary.totalAssets;
913
+ const sessionContextCount = summary.sessionContextAssets;
914
+
915
+ const valueAddSummary = `Workspace registry indexed ${assetCount} assets (${summary.assetsByType["source_file"] || 0} source, ${summary.assetsByType["test_file"] || 0} test, ${summary.assetsByType["doc_file"] || 0} doc, ${summary.assetsByType["skill"] || 0} skills) without broad scanning or context dumping.`;
916
+
917
+ const timeSaved = `Eliminated manual file discovery across ${assetCount} assets. Session Context now receives ${sessionContextCount} pre-indexed relevant files instead of requiring broad repo scan.`;
918
+
919
+ const tokenSavedOrContextReduced = `Registry summary replaces broad workspace dump. ${sessionContextCount} session-context assets surfaced compactly (summary only, no file content). Drift detection avoids re-scanning ${drift.unchangedAssets} unchanged assets.`;
920
+
921
+ const riskAvoided = unknownCount > 0
922
+ ? `Flagged ${unknownCount} unknown-review assets and ${highRiskCount} high-risk assets (MCP configs, deploy scripts) before use — routed to Install Intake / Safe Path instead of silent trust.`
923
+ : `No unknown assets in current workspace. ${highRiskCount} high-risk assets (deploy scripts) pre-flagged for Safe Path review.`;
924
+
925
+ const clarityImproved = `Registry provides normalized asset types, capability relevance flags, and integration bridges (feedsSessionContext, feedsBrainPack, feedsInstallIntake, feedsSafePath) without requiring the model to re-derive these from raw file scans.`;
926
+
927
+ const seamlessnessScore = unknownCount === 0 && highRiskCount <= 2 ? "high" : highRiskCount > 5 ? "medium" : "high";
928
+
929
+ const willingnessToPaySignal = sessionContextCount > 10
930
+ ? `"Avorelo indexes your entire AI workspace so the next session knows exactly which files, tools, and commands to start with — no setup, no scanning."`
931
+ : null;
932
+
933
+ const remainingFriction = [
934
+ "Registry rebuilds from scratch each run (no incremental file-hash tracking yet).",
935
+ "MCP tool-level asset detection requires fixture file to be present.",
936
+ "Model/worker assets depend on inventory being pre-loaded.",
937
+ ].join(" ");
938
+
939
+ const recommendedProductChange = "Add file-hash based incremental registry update to avoid full rebuild on each run. Add registry CLI filter by capabilityRelevance to support Brain Pack queries.";
940
+
941
+ return {
942
+ valueAddSummary,
943
+ timeSaved,
944
+ tokenSavedOrContextReduced,
945
+ riskAvoided,
946
+ clarityImproved,
947
+ seamlessnessScore,
948
+ willingnessToPaySignal,
949
+ remainingFriction,
950
+ recommendedProductChange,
951
+ };
952
+ }
953
+
954
+ // ── Main build function ───────────────────────────────────────────────────────
955
+
956
+ function buildWorkspaceRegistry(cwd, options = {}) {
957
+ ensureCcoDirs(cwd);
958
+
959
+ // Ensure registry history dir
960
+ const historyDir = path.join(cwd, REGISTRY_HISTORY_DIR_REL_PATH);
961
+ try { fs.mkdirSync(historyDir, { recursive: true }); } catch {}
962
+
963
+ const registryId = createRegistryId();
964
+ const prevSnapshot = readLatestRegistry(cwd);
965
+
966
+ // Detect all asset categories
967
+ const detectedAssets = [
968
+ ...detectPackageManifest(cwd),
969
+ ...detectGuidanceFiles(cwd),
970
+ ...detectVscodeExtensions(cwd),
971
+ ...detectSkillAssets(cwd),
972
+ ...detectSourceFiles(cwd),
973
+ ...detectTestFiles(cwd),
974
+ ...detectDocFiles(cwd),
975
+ ...detectConfigFiles(cwd),
976
+ ...detectMcpConfigs(cwd),
977
+ ...detectModelProviderAssets(cwd),
978
+ ...detectGeneratedGuidance(cwd),
979
+ ...detectReceiptAssets(cwd),
980
+ ];
981
+
982
+ // Deduplicate by assetId
983
+ const seenIds = new Set();
984
+ const assets = detectedAssets.filter((a) => {
985
+ if (seenIds.has(a.assetId)) return false;
986
+ seenIds.add(a.assetId);
987
+ return true;
988
+ });
989
+
990
+ const relationships = inferCrossAssetRelationships(assets);
991
+ const summary = computeSummary(assets);
992
+ const drift = computeDrift(prevSnapshot, assets);
993
+
994
+ const intakeSummary = readLatestIntakeSummary(cwd);
995
+ const wcSummary = readLatestWorkControlSummary(cwd);
996
+ const recommendations = computeRecommendations(assets, summary, intakeSummary, wcSummary);
997
+ const valueMeasurement = computeValueMeasurement(assets, summary, drift);
998
+
999
+ const scannedSources = [
1000
+ "package.json",
1001
+ "guidance files (AGENTS.md, CLAUDE.md, GEMINI.md, .cursor/rules, .github/copilot-instructions.md)",
1002
+ ".vscode/extensions.json",
1003
+ "skills/avorelo",
1004
+ "vendor/skillpacks",
1005
+ "scripts/lib (source files)",
1006
+ "tests (test files)",
1007
+ "docs (doc files)",
1008
+ "config files (tsconfig, jest, playwright, etc.)",
1009
+ "mcp config candidates",
1010
+ "model-inventory (if available)",
1011
+ ".avorelo/generated",
1012
+ "orchestration receipts (by path only)",
1013
+ ];
1014
+
1015
+ const registry = {
1016
+ schemaVersion: REGISTRY_SCHEMA_VERSION,
1017
+ contract: REGISTRY_CONTRACT,
1018
+ registryId,
1019
+ createdAt: nowIso(),
1020
+ cwd,
1021
+ scannedSources,
1022
+ assets,
1023
+ relationships,
1024
+ summary,
1025
+ drift,
1026
+ recommendations,
1027
+ valueMeasurement,
1028
+ productLearning: {
1029
+ eventsWritten: [],
1030
+ suggestedEvents: [
1031
+ "workspace_registry_generated",
1032
+ "workspace_registry_value_measured",
1033
+ ],
1034
+ },
1035
+ redacted: true,
1036
+ };
1037
+
1038
+ return registry;
1039
+ }
1040
+
1041
+ // ── Write / read ──────────────────────────────────────────────────────────────
1042
+
1043
+ function writeWorkspaceRegistry(cwd, registry, options = {}) {
1044
+ ensureCcoDirs(cwd);
1045
+
1046
+ const historyDir = path.join(cwd, REGISTRY_HISTORY_DIR_REL_PATH);
1047
+ try { fs.mkdirSync(historyDir, { recursive: true }); } catch {}
1048
+
1049
+ // Write latest
1050
+ safeWriteJson(cwd, LATEST_REGISTRY_REL_PATH, registry);
1051
+
1052
+ // Write history entry
1053
+ const histRelPath = `${REGISTRY_HISTORY_DIR_REL_PATH}/${registry.registryId}.json`;
1054
+ safeWriteJson(cwd, histRelPath, registry);
1055
+
1056
+ // Write Markdown summary
1057
+ const summaryMd = formatRegistrySummaryMarkdown(registry);
1058
+ safeWriteText(cwd, LATEST_SUMMARY_MD_REL_PATH, summaryMd);
1059
+
1060
+ // Product learning events
1061
+ const eventsWritten = [];
1062
+ try {
1063
+ appendProductLearningEvent(cwd, "workspace_registry_generated", {
1064
+ registryId: registry.registryId,
1065
+ totalAssets: registry.summary.totalAssets,
1066
+ unknownAssets: registry.summary.unknownAssets,
1067
+ highRiskAssets: registry.summary.highRiskAssets,
1068
+ sessionContextAssets: registry.summary.sessionContextAssets,
1069
+ driftAdded: registry.drift.addedAssets,
1070
+ driftRemoved: registry.drift.removedAssets,
1071
+ });
1072
+ eventsWritten.push("workspace_registry_generated");
1073
+ } catch {}
1074
+
1075
+ if (registry.summary.unknownAssets > 0) {
1076
+ try {
1077
+ appendProductLearningEvent(cwd, "workspace_asset_unknown_detected", {
1078
+ registryId: registry.registryId,
1079
+ unknownCount: registry.summary.unknownAssets,
1080
+ capabilityRecommended: "install_intake_risk",
1081
+ });
1082
+ eventsWritten.push("workspace_asset_unknown_detected");
1083
+ } catch {}
1084
+ }
1085
+
1086
+ if (registry.summary.highRiskAssets > 0) {
1087
+ try {
1088
+ appendProductLearningEvent(cwd, "workspace_asset_high_risk_detected", {
1089
+ registryId: registry.registryId,
1090
+ highRiskCount: registry.summary.highRiskAssets,
1091
+ capabilityRecommended: "safe_path_engine",
1092
+ });
1093
+ eventsWritten.push("workspace_asset_high_risk_detected");
1094
+ } catch {}
1095
+ }
1096
+
1097
+ if (registry.drift.addedAssets > 0 || registry.drift.removedAssets > 0) {
1098
+ try {
1099
+ appendProductLearningEvent(cwd, "workspace_registry_drift_detected", {
1100
+ registryId: registry.registryId,
1101
+ previousRegistryId: registry.drift.previousRegistryId,
1102
+ addedAssets: registry.drift.addedAssets,
1103
+ removedAssets: registry.drift.removedAssets,
1104
+ changedAssets: registry.drift.changedAssets,
1105
+ });
1106
+ eventsWritten.push("workspace_registry_drift_detected");
1107
+ } catch {}
1108
+ }
1109
+
1110
+ try {
1111
+ appendProductLearningEvent(cwd, "workspace_registry_value_measured", {
1112
+ registryId: registry.registryId,
1113
+ seamlessnessScore: registry.valueMeasurement.seamlessnessScore,
1114
+ sessionContextAssets: registry.summary.sessionContextAssets,
1115
+ totalAssets: registry.summary.totalAssets,
1116
+ hasWillingnessToPaySignal: Boolean(registry.valueMeasurement.willingnessToPaySignal),
1117
+ });
1118
+ eventsWritten.push("workspace_registry_value_measured");
1119
+ } catch {}
1120
+
1121
+ registry.productLearning.eventsWritten = eventsWritten;
1122
+
1123
+ return {
1124
+ ok: true,
1125
+ registryPath: LATEST_REGISTRY_REL_PATH,
1126
+ summaryPath: LATEST_SUMMARY_MD_REL_PATH,
1127
+ historyPath: histRelPath,
1128
+ eventLog: REGISTRY_EVENT_LOG_REL_PATH,
1129
+ };
1130
+ }
1131
+
1132
+ function readLatestRegistry(cwd) {
1133
+ return safeReadJson(cwd, LATEST_REGISTRY_REL_PATH, null);
1134
+ }
1135
+
1136
+ // ── Formatting ────────────────────────────────────────────────────────────────
1137
+
1138
+ function formatRegistrySummaryMarkdown(registry) {
1139
+ const s = registry.summary;
1140
+ const r = registry.recommendations;
1141
+ const vm = registry.valueMeasurement;
1142
+ const drift = registry.drift;
1143
+
1144
+ const lines = [
1145
+ `# Avorelo Workspace Registry — Summary`,
1146
+ `> Generated: ${registry.createdAt} | Registry ID: ${registry.registryId}`,
1147
+ `> Contract: ${registry.contract}`,
1148
+ ``,
1149
+ `## Asset Summary`,
1150
+ `- **Total assets:** ${s.totalAssets}`,
1151
+ `- **Source files:** ${s.assetsByType["source_file"] || 0}`,
1152
+ `- **Test files:** ${s.assetsByType["test_file"] || 0}`,
1153
+ `- **Doc files:** ${s.assetsByType["doc_file"] || 0}`,
1154
+ `- **Config files:** ${s.assetsByType["config_file"] || 0}`,
1155
+ `- **Skills / Skill packs:** ${(s.assetsByType["skill"] || 0)} / ${(s.assetsByType["skill_pack"] || 0)}`,
1156
+ `- **Guidance files:** ${(s.assetsByType["claude_guidance"] || 0) + (s.assetsByType["agents_guidance"] || 0) + (s.assetsByType["gemini_guidance"] || 0) + (s.assetsByType["copilot_guidance"] || 0)}`,
1157
+ `- **MCP configs:** ${s.assetsByType["mcp_server_config"] || 0}`,
1158
+ `- **npm scripts:** ${s.assetsByType["npm_script"] || 0}`,
1159
+ ``,
1160
+ `## Risk / Trust`,
1161
+ `- Reviewed: ${s.reviewedAssets} | Unknown: ${s.unknownAssets} | Deferred: ${s.deferredAssets} | Blocked: ${s.blockedAssets}`,
1162
+ `- High risk: ${s.highRiskAssets} | Critical: ${s.criticalRiskAssets}`,
1163
+ ``,
1164
+ `## Integration Signals`,
1165
+ `- Feeds Session Context: ${s.sessionContextAssets} assets`,
1166
+ `- Feeds Brain Pack: ${s.brainPackAssets} assets`,
1167
+ `- Feeds Capability Recommender: ${s.capabilityRelevantAssets} assets`,
1168
+ `- Feeds Install Intake: ${s.installIntakeAssets} assets`,
1169
+ `- Feeds Safe Path: ${s.safePathRelevantAssets} assets`,
1170
+ ``,
1171
+ `## Drift vs Previous`,
1172
+ drift.previousRegistryId
1173
+ ? `- Previous: ${drift.previousRegistryId} | Added: ${drift.addedAssets} | Removed: ${drift.removedAssets} | Changed: ${drift.changedAssets} | Unchanged: ${drift.unchangedAssets}`
1174
+ : `- First registry snapshot — no previous baseline.`,
1175
+ ``,
1176
+ `## Recommendations`,
1177
+ `- Next action: ${r.nextAction}`,
1178
+ r.intakeReviewNeeded.length ? `- Intake review needed: ${r.intakeReviewNeeded.join(", ")}` : null,
1179
+ r.safePathReviewNeeded.length ? `- Safe path review needed: ${r.safePathReviewNeeded.join(", ")}` : null,
1180
+ ``,
1181
+ `## Value Measurement`,
1182
+ `- **Value:** ${vm.valueAddSummary}`,
1183
+ `- **Time saved:** ${vm.timeSaved}`,
1184
+ `- **Token/context:** ${vm.tokenSavedOrContextReduced}`,
1185
+ `- **Risk avoided:** ${vm.riskAvoided}`,
1186
+ `- **Seamlessness:** ${vm.seamlessnessScore}`,
1187
+ vm.willingnessToPaySignal ? `- **Willingness to pay:** ${vm.willingnessToPaySignal}` : null,
1188
+ `- **Remaining friction:** ${vm.remainingFriction}`,
1189
+ ``,
1190
+ ].filter((l) => l !== null).join("\n");
1191
+
1192
+ return lines;
1193
+ }
1194
+
1195
+ function formatRegistryText(registry) {
1196
+ const s = registry.summary;
1197
+ const r = registry.recommendations;
1198
+ const drift = registry.drift;
1199
+
1200
+ return [
1201
+ `Avorelo Workspace Registry`,
1202
+ `Registry ID: ${registry.registryId} Created: ${registry.createdAt}`,
1203
+ ``,
1204
+ `Assets: ${s.totalAssets} total · ${s.reviewedAssets} reviewed · ${s.unknownAssets} unknown · ${s.highRiskAssets} high-risk`,
1205
+ `Types: ${Object.entries(s.assetsByType).map(([t, n]) => `${t}:${n}`).join(" · ")}`,
1206
+ ``,
1207
+ `Feeds: session-context:${s.sessionContextAssets} · brain-pack:${s.brainPackAssets} · capability:${s.capabilityRelevantAssets} · intake:${s.installIntakeAssets} · safe-path:${s.safePathRelevantAssets}`,
1208
+ ``,
1209
+ drift.previousRegistryId
1210
+ ? `Drift: +${drift.addedAssets} added · -${drift.removedAssets} removed · ${drift.changedAssets} changed (vs ${drift.previousRegistryId})`
1211
+ : `Drift: first snapshot`,
1212
+ ``,
1213
+ `Next: ${r.nextAction}`,
1214
+ `Registry: ${LATEST_REGISTRY_REL_PATH}`,
1215
+ `Summary: ${LATEST_SUMMARY_MD_REL_PATH}`,
1216
+ ].join("\n");
1217
+ }
1218
+
1219
+ // ── Dashboard / status surface ────────────────────────────────────────────────
1220
+
1221
+ function buildWorkspaceRegistrySurface(cwd) {
1222
+ const registry = readLatestRegistry(cwd);
1223
+ if (!registry) {
1224
+ return {
1225
+ status: "missing",
1226
+ latestRegistryPath: null,
1227
+ totalAssets: 0,
1228
+ assetsByType: {},
1229
+ unknownAssets: 0,
1230
+ highRiskAssets: 0,
1231
+ sessionContextAssets: 0,
1232
+ brainPackAssets: 0,
1233
+ capabilityRelevantAssets: 0,
1234
+ driftSummary: null,
1235
+ nextAction: "Run `avorelo workspace-registry` to build the first asset inventory.",
1236
+ };
1237
+ }
1238
+
1239
+ const s = registry.summary;
1240
+ const drift = registry.drift;
1241
+
1242
+ return {
1243
+ status: s.unknownAssets > 0 ? "attention" : s.highRiskAssets > 0 ? "review" : "ok",
1244
+ latestRegistryPath: LATEST_REGISTRY_REL_PATH,
1245
+ registryId: registry.registryId,
1246
+ createdAt: registry.createdAt,
1247
+ totalAssets: s.totalAssets,
1248
+ assetsByType: s.assetsByType,
1249
+ unknownAssets: s.unknownAssets,
1250
+ highRiskAssets: s.highRiskAssets,
1251
+ sessionContextAssets: s.sessionContextAssets,
1252
+ brainPackAssets: s.brainPackAssets,
1253
+ capabilityRelevantAssets: s.capabilityRelevantAssets,
1254
+ driftSummary: drift.previousRegistryId
1255
+ ? `+${drift.addedAssets}/-${drift.removedAssets}/${drift.changedAssets}ch vs ${drift.previousRegistryId}`
1256
+ : "first snapshot",
1257
+ nextAction: registry.recommendations?.nextAction || null,
1258
+ };
1259
+ }
1260
+
1261
+ // ── Session Context integration ───────────────────────────────────────────────
1262
+
1263
+ function enrichSessionContextWithRegistry(cwd, sessionContextData) {
1264
+ const registry = readLatestRegistry(cwd);
1265
+ if (!registry) return sessionContextData;
1266
+
1267
+ const registryRelevantFiles = (registry.recommendations?.sessionContextRelevantFiles || []).slice(0, 15);
1268
+ const existingFiles = sessionContextData.relevantFiles || [];
1269
+ const merged = [...new Set([...existingFiles, ...registryRelevantFiles])].slice(0, 20);
1270
+
1271
+ const enriched = { ...sessionContextData, relevantFiles: merged };
1272
+
1273
+ if (registry.summary.unknownAssets > 0) {
1274
+ enriched.assetWarnings = enriched.assetWarnings || [];
1275
+ enriched.assetWarnings.push(`Registry: ${registry.summary.unknownAssets} unknown-review assets detected — run intake before proceeding.`);
1276
+ }
1277
+ if (registry.summary.highRiskAssets > 0) {
1278
+ enriched.assetWarnings = enriched.assetWarnings || [];
1279
+ enriched.assetWarnings.push(`Registry: ${registry.summary.highRiskAssets} high-risk assets (deploy scripts, MCP configs) — review before executing.`);
1280
+ }
1281
+
1282
+ enriched.registrySummary = {
1283
+ registryId: registry.registryId,
1284
+ totalAssets: registry.summary.totalAssets,
1285
+ sessionContextAssets: registry.summary.sessionContextAssets,
1286
+ highRiskAssets: registry.summary.highRiskAssets,
1287
+ nextAction: registry.recommendations?.nextAction,
1288
+ };
1289
+
1290
+ try {
1291
+ appendProductLearningEvent(cwd, "workspace_registry_used_in_session_context", {
1292
+ registryId: registry.registryId,
1293
+ filesAdded: merged.length - existingFiles.length,
1294
+ totalRelevantFiles: merged.length,
1295
+ });
1296
+ } catch {}
1297
+
1298
+ return enriched;
1299
+ }
1300
+
1301
+ // ── Brain Pack integration ────────────────────────────────────────────────────
1302
+
1303
+ function buildRegistryBrainPackHints(cwd) {
1304
+ const registry = readLatestRegistry(cwd);
1305
+ if (!registry) return null;
1306
+
1307
+ const hints = registry.recommendations?.brainPackContextHints || {};
1308
+
1309
+ try {
1310
+ appendProductLearningEvent(cwd, "workspace_registry_used_in_brain_pack", {
1311
+ registryId: registry.registryId,
1312
+ totalAssets: registry.summary.totalAssets,
1313
+ brainPackAssets: registry.summary.brainPackAssets,
1314
+ });
1315
+ } catch {}
1316
+
1317
+ return {
1318
+ packageManager: hints.packageManager || "unknown",
1319
+ testCommand: hints.testCommand || "node --test",
1320
+ totalSourceFiles: hints.totalSourceFiles || 0,
1321
+ totalTestFiles: hints.totalTestFiles || 0,
1322
+ totalDocFiles: hints.totalDocFiles || 0,
1323
+ skillCount: hints.skillCount || 0,
1324
+ hasGuidanceFiles: hints.hasGuidanceFiles || false,
1325
+ registryId: registry.registryId,
1326
+ registryAssets: registry.summary.totalAssets,
1327
+ registryCreatedAt: registry.createdAt,
1328
+ };
1329
+ }
1330
+
1331
+ // ── Capability recommender signal ─────────────────────────────────────────────
1332
+
1333
+ function buildRegistryCapabilitySignal(cwd) {
1334
+ const registry = readLatestRegistry(cwd);
1335
+ if (!registry) return null;
1336
+
1337
+ return {
1338
+ hasHighRiskAssets: registry.summary.highRiskAssets > 0,
1339
+ hasUnknownAssets: registry.summary.unknownAssets > 0,
1340
+ hasMcpConfigs: (registry.summary.assetsByType["mcp_server_config"] || 0) > 0,
1341
+ hasLargeSourceSet: (registry.summary.assetsByType["source_file"] || 0) > 20,
1342
+ hasSessionContextAssets: registry.summary.sessionContextAssets > 0,
1343
+ sessionContextAssets: registry.summary.sessionContextAssets,
1344
+ totalAssets: registry.summary.totalAssets,
1345
+ topCapabilityRecommendations: registry.recommendations?.topCapabilityRecommendations || [],
1346
+ };
1347
+ }
1348
+
1349
+ // ── Exports ───────────────────────────────────────────────────────────────────
1350
+
1351
+ module.exports = {
1352
+ REGISTRY_CONTRACT,
1353
+ REGISTRY_SCHEMA_VERSION,
1354
+ LATEST_REGISTRY_REL_PATH,
1355
+ LATEST_SUMMARY_MD_REL_PATH,
1356
+ REGISTRY_HISTORY_DIR_REL_PATH,
1357
+ REGISTRY_EVENT_LOG_REL_PATH,
1358
+ buildWorkspaceRegistry,
1359
+ writeWorkspaceRegistry,
1360
+ readLatestRegistry,
1361
+ buildWorkspaceRegistrySurface,
1362
+ enrichSessionContextWithRegistry,
1363
+ buildRegistryBrainPackHints,
1364
+ buildRegistryCapabilitySignal,
1365
+ formatRegistryText,
1366
+ formatRegistrySummaryMarkdown,
1367
+ };