@thebackstoryis/engineering-with-ai 0.2.9

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 (334) hide show
  1. package/Docs/README.md +50 -0
  2. package/Docs/adoption/consultancy-and-multi-project-rollout.md +135 -0
  3. package/Docs/adoption/non-technical-team-guide.md +126 -0
  4. package/Docs/archaeology-technology-and-hosting-discovery.md +212 -0
  5. package/Docs/blast-radius-and-impact-routing-guide.md +325 -0
  6. package/Docs/blueprints/internal-blueprint-catalogue.md +146 -0
  7. package/Docs/blueprints/maintaining-organisation-blueprints.md +154 -0
  8. package/Docs/blueprints/validation-and-troubleshooting.md +168 -0
  9. package/Docs/cli-reference.md +113 -0
  10. package/Docs/completed-phase-evidence-amendments.md +74 -0
  11. package/Docs/consultancy-network-rollout-control-plane-guide.md +202 -0
  12. package/Docs/context-aware-delivery-companion-guide.md +198 -0
  13. package/Docs/context-aware-delivery-companion-user-guide.md +184 -0
  14. package/Docs/context-management-and-token-efficiency.md +113 -0
  15. package/Docs/design-systems/design-system-implementation-guide.md +85 -0
  16. package/Docs/design-systems/design-system-pack-authoring-guide.md +95 -0
  17. package/Docs/design-systems/design-system-review-guide.md +51 -0
  18. package/Docs/design-systems/design-system-user-guide.md +96 -0
  19. package/Docs/design-systems/product-owner-guide.md +49 -0
  20. package/Docs/designing-organisation-blueprint-packs.md +384 -0
  21. package/Docs/developer-delivery-guide.md +224 -0
  22. package/Docs/error-reporting-guide.md +110 -0
  23. package/Docs/error-reporting-provider-guide.md +49 -0
  24. package/Docs/examples/error-report-adapter.md +70 -0
  25. package/Docs/examples/meeting-review.md +76 -0
  26. package/Docs/examples/minimal-design-system.md +67 -0
  27. package/Docs/examples/prototype-review-inputs.md +175 -0
  28. package/Docs/examples/reproducible-archaeology-depth-example.md +144 -0
  29. package/Docs/examples/test-scenario-input.md +68 -0
  30. package/Docs/examples/worked-examples.md +147 -0
  31. package/Docs/existing-project-onboarding-guide.md +214 -0
  32. package/Docs/explanation/core-concepts.md +26 -0
  33. package/Docs/explanation/delivery-workflow.md +48 -0
  34. package/Docs/governance/governance-team-guide.md +139 -0
  35. package/Docs/governed-starter-project-materialisation-guide.md +284 -0
  36. package/Docs/guide-catalogue.md +117 -0
  37. package/Docs/guided-discovery-facilitator-guide.md +172 -0
  38. package/Docs/guided-intent-workspace-guide.md +109 -0
  39. package/Docs/guided-phase-evidence-drafting-guide.md +119 -0
  40. package/Docs/human-approval-and-assurance-guide.md +146 -0
  41. package/Docs/knowledge-proposals-implementer-guide.md +106 -0
  42. package/Docs/knowledge-proposals-user-guide.md +247 -0
  43. package/Docs/maintainers/context-benchmarks.md +29 -0
  44. package/Docs/maintainers/contributing.md +58 -0
  45. package/Docs/maintainers/evidence-depth-acceptance.md +72 -0
  46. package/Docs/maintainers/verification-walkthroughs.md +104 -0
  47. package/Docs/meeting-evidence-implementer-guide.md +132 -0
  48. package/Docs/meeting-evidence-user-guide.md +200 -0
  49. package/Docs/operations/dashboard-and-delivery-state.md +153 -0
  50. package/Docs/operations/dashboard-configuration.md +87 -0
  51. package/Docs/operations/installation-updating-and-entitlements.md +135 -0
  52. package/Docs/operations/premium-personas-setup.md +76 -0
  53. package/Docs/operations/troubleshooting-and-recovery.md +205 -0
  54. package/Docs/organisation-rollout-guide.md +142 -0
  55. package/Docs/persona-entitlement-provider-guide.md +199 -0
  56. package/Docs/persona-guided-prototype-iteration.md +129 -0
  57. package/Docs/personas/organisation-specific-personas.md +103 -0
  58. package/Docs/personas/persona-authoring-cookbook.md +176 -0
  59. package/Docs/personas/persona-engagement-ui.md +133 -0
  60. package/Docs/personas/persona-governance.md +118 -0
  61. package/Docs/platform-export-analysis-guide.md +336 -0
  62. package/Docs/policies/governance-owner-guide.md +36 -0
  63. package/Docs/policies/implementation-guide.md +42 -0
  64. package/Docs/policies/organisation-policy-design-gates.md +58 -0
  65. package/Docs/policies/policy-pack-authoring-guide.md +108 -0
  66. package/Docs/policies/product-owner-guide.md +43 -0
  67. package/Docs/policies/technical-owner-guide.md +37 -0
  68. package/Docs/product-owner-guide.md +327 -0
  69. package/Docs/project-portfolio-orchestration-guide.md +199 -0
  70. package/Docs/quality/manual-qa-and-acceptance.md +162 -0
  71. package/Docs/quality/persona-driven-test-scenarios.md +172 -0
  72. package/Docs/quality/reproducible-archaeology-depth-review-checklist.md +89 -0
  73. package/Docs/reference/capabilities-and-project-layout.md +678 -0
  74. package/Docs/reference/cli-and-configuration.md +398 -0
  75. package/Docs/reference/contributions-api.md +23 -0
  76. package/Docs/reference/security-adapter-authoring.md +81 -0
  77. package/Docs/reference/starter-adapter-authoring.md +74 -0
  78. package/Docs/repository-source-map-guide.md +381 -0
  79. package/Docs/reproducible-archaeology-and-discovery-depth.md +292 -0
  80. package/Docs/screen-prototype-creation-guide.md +324 -0
  81. package/Docs/security-validation-guide.md +353 -0
  82. package/Docs/solution-readiness-review-guide.md +123 -0
  83. package/Docs/standards/project-standards-authoring.md +157 -0
  84. package/Docs/team-hub-guide.md +162 -0
  85. package/Docs/team-hub-resource-registry-guide.md +167 -0
  86. package/Docs/tutorials/first-delivery.md +83 -0
  87. package/Docs/tutorials/first-session.md +62 -0
  88. package/Docs/using-lifecycle-hooks.md +381 -0
  89. package/Docs/working-with-personas.md +274 -0
  90. package/LICENSE +165 -0
  91. package/README.md +96 -0
  92. package/agents-src/claude/ewai-security-reviewer.md +15 -0
  93. package/bin/ewai +5 -0
  94. package/config/archaeology-record-families.yaml +59 -0
  95. package/config/delivery-artifacts.yaml +121 -0
  96. package/config/delivery-stages.yaml +77 -0
  97. package/config/design-system.schema.json +46 -0
  98. package/config/error-reporting.schema.json +79 -0
  99. package/config/evidence-depth.schema.json +53 -0
  100. package/config/intent.schema.json +90 -0
  101. package/config/knowledge-proposals-proposal.schema.json +34 -0
  102. package/config/lifecycle-event.schema.json +68 -0
  103. package/config/lifecycle-handler.schema.json +45 -0
  104. package/config/lifecycle-hook-ack.schema.json +19 -0
  105. package/config/meeting-evidence-candidate.schema.json +102 -0
  106. package/config/organisation-policy.schema.json +137 -0
  107. package/config/pack.schema.json +250 -0
  108. package/config/persona-pack.schema.json +21 -0
  109. package/config/persona.schema.json +17 -0
  110. package/config/policy-evaluation.schema.json +77 -0
  111. package/config/policy-facts.schema.json +140 -0
  112. package/config/portfolio.schema.json +68 -0
  113. package/config/project.schema.json +313 -0
  114. package/config/prototype-iteration.schema.json +128 -0
  115. package/config/rollout.schema.json +87 -0
  116. package/config/security-adapter.schema.json +31 -0
  117. package/config/security-scan-request.schema.json +64 -0
  118. package/config/security-scan-response.schema.json +52 -0
  119. package/config/security-validation-policy.schema.json +74 -0
  120. package/config/starter-source-acknowledgement.schema.json +13 -0
  121. package/config/starter-source-adapter.schema.json +38 -0
  122. package/config/starter-source-request.schema.json +61 -0
  123. package/package.json +77 -0
  124. package/packs/core/pack.yaml +7 -0
  125. package/packs/design-systems/default/experience-promise.md +9 -0
  126. package/packs/design-systems/default/intentional-review.md +10 -0
  127. package/packs/design-systems/default/interaction-and-entry.md +9 -0
  128. package/packs/design-systems/default/meaningful-content-and-states.md +9 -0
  129. package/packs/design-systems/default/pack.yaml +44 -0
  130. package/packs/design-systems/default/principles.md +10 -0
  131. package/packs/personas/core/pack.yaml +7 -0
  132. package/packs/personas/core/personas/archaeologist.md +37 -0
  133. package/packs/personas/core/personas/end-user.md +17 -0
  134. package/packs/personas/core/personas/maintainer.md +17 -0
  135. package/packs/personas/core/personas/operator.md +17 -0
  136. package/packs/personas/core/personas/specs-knowledge-curator.md +35 -0
  137. package/packs/technologies/laravel/pack.yaml +30 -0
  138. package/packs/technologies/laravel-nuxt/pack.yaml +30 -0
  139. package/packs/technologies/nuxt/pack.yaml +30 -0
  140. package/packs/technologies/power-platform/pack.yaml +31 -0
  141. package/packs/technologies/salesforce/pack.yaml +25 -0
  142. package/public/app.js +4896 -0
  143. package/public/apple-touch-icon.png +0 -0
  144. package/public/assets/backstory-icon.png +0 -0
  145. package/public/dashboard-navigation.js +98 -0
  146. package/public/favicon-16.png +0 -0
  147. package/public/favicon-32.png +0 -0
  148. package/public/favicon.ico +0 -0
  149. package/public/index.html +789 -0
  150. package/public/styles.css +2693 -0
  151. package/public/team-hub/app.js +202 -0
  152. package/public/team-hub/index.html +79 -0
  153. package/public/team-hub/styles.css +90 -0
  154. package/scripts/publication-check.mjs +140 -0
  155. package/scripts/setup.mjs +21 -0
  156. package/skills-src/ewai-archaeology/SKILL.md +334 -0
  157. package/skills-src/ewai-archaeology/agents/openai.yaml +4 -0
  158. package/skills-src/ewai-archaeology/references/archaeology-contract.md +201 -0
  159. package/skills-src/ewai-archaeology/references/lifecycle-reconstruction.md +177 -0
  160. package/skills-src/ewai-archaeology/references/maximum-detail-reconstruction.md +97 -0
  161. package/skills-src/ewai-archaeology/references/model-routing.md +26 -0
  162. package/skills-src/ewai-architecture/SKILL.md +108 -0
  163. package/skills-src/ewai-architecture/agents/openai.yaml +4 -0
  164. package/skills-src/ewai-architecture/references/architecture-contract.md +176 -0
  165. package/skills-src/ewai-context/SKILL.md +68 -0
  166. package/skills-src/ewai-context/agents/openai.yaml +4 -0
  167. package/skills-src/ewai-context-import/SKILL.md +118 -0
  168. package/skills-src/ewai-context-import/agents/openai.yaml +4 -0
  169. package/skills-src/ewai-context-import/references/context-import-contract.md +106 -0
  170. package/skills-src/ewai-dashboard-configuration/SKILL.md +20 -0
  171. package/skills-src/ewai-deliver/SKILL.md +122 -0
  172. package/skills-src/ewai-deliver/references/delivery-evidence.md +92 -0
  173. package/skills-src/ewai-deliver/references/phase-routing.md +31 -0
  174. package/skills-src/ewai-design-system-apply/SKILL.md +27 -0
  175. package/skills-src/ewai-design-system-apply/agents/openai.yaml +4 -0
  176. package/skills-src/ewai-design-system-apply/references/application-contract.md +36 -0
  177. package/skills-src/ewai-design-system-author/SKILL.md +28 -0
  178. package/skills-src/ewai-design-system-author/agents/openai.yaml +4 -0
  179. package/skills-src/ewai-design-system-author/references/authoring-contract.md +38 -0
  180. package/skills-src/ewai-design-system-review/SKILL.md +26 -0
  181. package/skills-src/ewai-design-system-review/agents/openai.yaml +4 -0
  182. package/skills-src/ewai-design-system-review/references/review-contract.md +40 -0
  183. package/skills-src/ewai-error-reporting/SKILL.md +46 -0
  184. package/skills-src/ewai-error-reporting/agents/openai.yaml +4 -0
  185. package/skills-src/ewai-error-reporting/references/provider-contract.md +74 -0
  186. package/skills-src/ewai-evidence-depth/SKILL.md +72 -0
  187. package/skills-src/ewai-evidence-depth/agents/openai.yaml +4 -0
  188. package/skills-src/ewai-evidence-depth/references/evidence-depth-contract.md +127 -0
  189. package/skills-src/ewai-intent/SKILL.md +68 -0
  190. package/skills-src/ewai-intent/agents/openai.yaml +4 -0
  191. package/skills-src/ewai-intent/references/intent-contract.md +42 -0
  192. package/skills-src/ewai-knowledge-proposals/SKILL.md +105 -0
  193. package/skills-src/ewai-knowledge-proposals/agents/openai.yaml +4 -0
  194. package/skills-src/ewai-knowledge-proposals/references/proposal-contract.md +59 -0
  195. package/skills-src/ewai-meeting-evidence/SKILL.md +106 -0
  196. package/skills-src/ewai-meeting-evidence/agents/openai.yaml +4 -0
  197. package/skills-src/ewai-meeting-evidence/references/candidate-contract.md +64 -0
  198. package/skills-src/ewai-organisation-policy/SKILL.md +62 -0
  199. package/skills-src/ewai-organisation-policy/agents/openai.yaml +4 -0
  200. package/skills-src/ewai-organisation-policy/references/policy-contract.md +94 -0
  201. package/skills-src/ewai-palace-housekeeping/SKILL.md +55 -0
  202. package/skills-src/ewai-palace-housekeeping/agents/openai.yaml +4 -0
  203. package/skills-src/ewai-persona-entitlement/SKILL.md +60 -0
  204. package/skills-src/ewai-persona-entitlement/agents/openai.yaml +4 -0
  205. package/skills-src/ewai-phase-evidence/SKILL.md +79 -0
  206. package/skills-src/ewai-phase-evidence/agents/openai.yaml +4 -0
  207. package/skills-src/ewai-pipeline/SKILL.md +130 -0
  208. package/skills-src/ewai-pipeline/agents/openai.yaml +4 -0
  209. package/skills-src/ewai-pipeline/references/cli.md +86 -0
  210. package/skills-src/ewai-pipeline/references/specs-contract.md +16 -0
  211. package/skills-src/ewai-portfolio/SKILL.md +70 -0
  212. package/skills-src/ewai-portfolio/agents/openai.yaml +4 -0
  213. package/skills-src/ewai-portfolio/references/portfolio-contract.md +67 -0
  214. package/skills-src/ewai-project-discovery/SKILL.md +95 -0
  215. package/skills-src/ewai-project-discovery/agents/openai.yaml +4 -0
  216. package/skills-src/ewai-project-discovery/references/discovery-contract.md +34 -0
  217. package/skills-src/ewai-prototype-iteration/SKILL.md +30 -0
  218. package/skills-src/ewai-prototype-iteration/agents/openai.yaml +4 -0
  219. package/skills-src/ewai-prototype-iteration/references/review-contract.md +49 -0
  220. package/skills-src/ewai-retro/SKILL.md +48 -0
  221. package/skills-src/ewai-retro/agents/openai.yaml +4 -0
  222. package/skills-src/ewai-retro/references/asset-routing.md +14 -0
  223. package/skills-src/ewai-rollout/SKILL.md +74 -0
  224. package/skills-src/ewai-rollout/agents/openai.yaml +4 -0
  225. package/skills-src/ewai-rollout/references/rollout-contract.md +74 -0
  226. package/skills-src/ewai-shape-intents/SKILL.md +84 -0
  227. package/skills-src/ewai-shape-intents/agents/openai.yaml +4 -0
  228. package/skills-src/ewai-shape-intents/references/intent-mapping-contract.md +109 -0
  229. package/skills-src/ewai-solution-readiness/SKILL.md +55 -0
  230. package/skills-src/ewai-solution-readiness/agents/openai.yaml +4 -0
  231. package/skills-src/ewai-standards-check/SKILL.md +93 -0
  232. package/skills-src/ewai-standards-check/agents/openai.yaml +4 -0
  233. package/skills-src/ewai-standards-check/references/report-contract.md +116 -0
  234. package/skills-src/ewai-test-scenarios/SKILL.md +94 -0
  235. package/skills-src/ewai-test-scenarios/agents/openai.yaml +4 -0
  236. package/skills-src/ewai-test-scenarios/references/scenario-contract.md +88 -0
  237. package/src/afk-worker.mjs +16 -0
  238. package/src/archaeology.mjs +1333 -0
  239. package/src/checkin.mjs +261 -0
  240. package/src/cli.mjs +2427 -0
  241. package/src/companion-guidance.mjs +257 -0
  242. package/src/companion-opening.mjs +62 -0
  243. package/src/companion.mjs +256 -0
  244. package/src/context.mjs +210 -0
  245. package/src/dashboard-preferences.mjs +80 -0
  246. package/src/delivery-artifacts.mjs +204 -0
  247. package/src/delivery-documents.mjs +248 -0
  248. package/src/delivery-gates.mjs +317 -0
  249. package/src/delivery.mjs +1433 -0
  250. package/src/design-system-application.mjs +291 -0
  251. package/src/design-system-authoring.mjs +101 -0
  252. package/src/design-systems.mjs +466 -0
  253. package/src/discovery.mjs +1314 -0
  254. package/src/error-reporting.mjs +323 -0
  255. package/src/evidence-depth.mjs +543 -0
  256. package/src/execution-state.mjs +243 -0
  257. package/src/install.mjs +166 -0
  258. package/src/intent-dependencies.mjs +117 -0
  259. package/src/intent-maps.mjs +402 -0
  260. package/src/intents.mjs +747 -0
  261. package/src/knowledge-proposals.mjs +717 -0
  262. package/src/launcher.mjs +51 -0
  263. package/src/meeting-evidence.mjs +703 -0
  264. package/src/network-rollout.mjs +386 -0
  265. package/src/organisation-blueprints.mjs +438 -0
  266. package/src/organisation-policies.mjs +448 -0
  267. package/src/packs.mjs +44 -0
  268. package/src/paths.mjs +62 -0
  269. package/src/persona-entitlements.mjs +438 -0
  270. package/src/persona-licence-config.mjs +98 -0
  271. package/src/persona-website-provider.mjs +134 -0
  272. package/src/persona-zip.mjs +87 -0
  273. package/src/personas.mjs +159 -0
  274. package/src/platform-metadata-analysis.mjs +314 -0
  275. package/src/policy-design-gates.mjs +623 -0
  276. package/src/policy-gate-integration.mjs +318 -0
  277. package/src/portfolio.mjs +509 -0
  278. package/src/power-platform-source-map.mjs +190 -0
  279. package/src/project.mjs +449 -0
  280. package/src/prototype-iterations.mjs +730 -0
  281. package/src/repository-source-map.mjs +603 -0
  282. package/src/runtime/afk-conductor.mjs +973 -0
  283. package/src/runtime/context-assembly.mjs +457 -0
  284. package/src/runtime/context-benchmarks.mjs +115 -0
  285. package/src/runtime/dashboard-actions.mjs +109 -0
  286. package/src/runtime/dashboard-handoffs.mjs +149 -0
  287. package/src/runtime/dashboard-server.mjs +1272 -0
  288. package/src/runtime/dashboard.mjs +197 -0
  289. package/src/runtime/database.mjs +789 -0
  290. package/src/runtime/error-reporting.mjs +581 -0
  291. package/src/runtime/evidence-depth-workspace.mjs +412 -0
  292. package/src/runtime/execution-leases.mjs +299 -0
  293. package/src/runtime/guided-discovery.mjs +350 -0
  294. package/src/runtime/guided-intents.mjs +517 -0
  295. package/src/runtime/impact-analysis.mjs +535 -0
  296. package/src/runtime/intents.mjs +222 -0
  297. package/src/runtime/knowledge.mjs +86 -0
  298. package/src/runtime/lifecycle-hooks.mjs +1239 -0
  299. package/src/runtime/mcp-config.mjs +110 -0
  300. package/src/runtime/mcp-server.mjs +1885 -0
  301. package/src/runtime/palace.mjs +362 -0
  302. package/src/runtime/paths.mjs +58 -0
  303. package/src/runtime/persona-engagement.mjs +255 -0
  304. package/src/runtime/phase-contributions.mjs +594 -0
  305. package/src/runtime/policy-workspace.mjs +170 -0
  306. package/src/runtime/prototype-iterations.mjs +235 -0
  307. package/src/runtime/provider-adapters.mjs +163 -0
  308. package/src/runtime/repository-index.mjs +838 -0
  309. package/src/runtime/runs.mjs +185 -0
  310. package/src/runtime/security-validation.mjs +1230 -0
  311. package/src/runtime/starter-materialisation.mjs +1155 -0
  312. package/src/runtime/team-hub-client.mjs +479 -0
  313. package/src/runtime/team-hub-database.mjs +288 -0
  314. package/src/runtime/team-hub-server.mjs +191 -0
  315. package/src/runtime/team-hub.mjs +110 -0
  316. package/src/runtime/tree-sitter-index.mjs +390 -0
  317. package/src/runtime/version.mjs +1 -0
  318. package/src/runtime/work.mjs +633 -0
  319. package/src/salesforce-source-map.mjs +212 -0
  320. package/src/security-validation-config.mjs +224 -0
  321. package/src/solution-readiness.mjs +620 -0
  322. package/src/starter-materialisation-contract.mjs +407 -0
  323. package/src/task-graph.mjs +544 -0
  324. package/src/team-hub-resources.mjs +239 -0
  325. package/src/team-hub.mjs +242 -0
  326. package/src/test-scenarios.mjs +622 -0
  327. package/src/validation-config.mjs +289 -0
  328. package/templates/SPECS/1.Scope/personas/registry.yaml +12 -0
  329. package/templates/SPECS/5.Strategy/patterns/context-packet.md +119 -0
  330. package/templates/SPECS/6.Build/_tracker-template.md +16 -0
  331. package/templates/SPECS/pipeline.yaml +62 -0
  332. package/templates/discovery-answers.yaml +86 -0
  333. package/templates/intent-body.md +21 -0
  334. package/tests/fixtures/context-benchmarks.json +9 -0
@@ -0,0 +1,838 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
2
+ import { relative, resolve } from 'node:path';
3
+ import { loadProjectConfig } from '../project.mjs';
4
+ import { projectPaths } from '../paths.mjs';
5
+ import {
6
+ analyseRepositoryFile,
7
+ fingerprintRepositoryBuffer,
8
+ fingerprintRepositoryFile,
9
+ resolveSourceMapProfiles,
10
+ selectSourceMapProfile,
11
+ sourceMapProfileFileLimit
12
+ } from '../repository-source-map.mjs';
13
+ import { openRuntimeDatabase } from './database.mjs';
14
+ import { syncWorkItems } from './work.mjs';
15
+
16
+ // Tree-sitter extractTreeSitterFacts execution is delegated through analyseRepositoryFile.
17
+
18
+ const skippedDirectories = new Set([
19
+ '.git', '.nuxt', '.output', '.ewai-pipeline', '.phpunit.cache', '.vite',
20
+ 'build', 'coverage', 'dist', 'node_modules', 'storage', 'vendor'
21
+ ]);
22
+ const canonicalSpecsDirectories = Object.freeze([
23
+ '1.Scope', '2.Purpose', '3.Evidence', '4.Constraints', '5.Strategy', '6.Build'
24
+ ]);
25
+
26
+ function walk(root, files = [], excludedRoots = []) {
27
+ if (!existsSync(root)) return files;
28
+ if (excludedRoots.includes(resolve(root))) return files;
29
+ let entries;
30
+ try {
31
+ entries = readdirSync(root, { withFileTypes: true });
32
+ } catch {
33
+ return files;
34
+ }
35
+ for (const entry of entries) {
36
+ if (entry.isSymbolicLink()) continue;
37
+ const path = resolve(root, entry.name);
38
+ if (entry.isDirectory()) {
39
+ if (!skippedDirectories.has(entry.name)) walk(path, files, excludedRoots);
40
+ continue;
41
+ }
42
+ if (!entry.isFile()) continue;
43
+ try {
44
+ files.push({ path, stat: statSync(path) });
45
+ } catch {
46
+ files.push({ path, stat: null, statError: true });
47
+ }
48
+ }
49
+ return files;
50
+ }
51
+
52
+ function repositoryExclusions(repositoryRoot, specsRoot) {
53
+ const relation = relative(repositoryRoot, specsRoot);
54
+ if (relation.startsWith('..')) return [];
55
+ if (relation) return [resolve(specsRoot)];
56
+ return canonicalSpecsDirectories.map((directory) => resolve(specsRoot, directory));
57
+ }
58
+
59
+ function repositories(projectRoot) {
60
+ const { config, paths } = loadProjectConfig(projectRoot);
61
+ return (config.repositories ?? []).map((repository, index) => {
62
+ const root = resolve(paths.projectRoot, repository.path || '.');
63
+ return {
64
+ name: repository.name || `repository-${index + 1}`,
65
+ role: repository.role || 'application',
66
+ root,
67
+ excludedRoots: repositoryExclusions(root, paths.specsRoot)
68
+ };
69
+ });
70
+ }
71
+
72
+ function asArray(value) {
73
+ if (Array.isArray(value)) return value;
74
+ if (value && typeof value === 'object') return Object.values(value).flatMap(asArray);
75
+ return [];
76
+ }
77
+
78
+ function readJson(path) {
79
+ if (!existsSync(path)) return null;
80
+ try {
81
+ return JSON.parse(readFileSync(path, 'utf8'));
82
+ } catch {
83
+ return null;
84
+ }
85
+ }
86
+
87
+ function planStandardsEvidence(plan) {
88
+ if (Array.isArray(plan?.standards_evidence)) return plan.standards_evidence;
89
+ return asArray(plan?.standards).map((standard) => ({
90
+ ...standard,
91
+ source_path: standard.source_path ?? standard.path ?? '',
92
+ rule_summary: standard.rule_summary ?? standard.application ?? '',
93
+ }));
94
+ }
95
+
96
+ function capabilityFingerprint(projectRoot, item) {
97
+ const buildRoot = projectPaths(projectRoot).buildRoot;
98
+ const plan = readJson(resolve(buildRoot, item.slug, 'gates/plan/plan-contract.json'))
99
+ ?? readJson(resolve(buildRoot, item.slug, 'plan-contract.json'));
100
+ const endpoints = asArray(plan?.backend_endpoints);
101
+ const chains = asArray(plan?.store_service_chains);
102
+ const components = asArray(plan?.component_contracts);
103
+ const standards = planStandardsEvidence(plan);
104
+ const tests = asArray(plan?.test_obligations);
105
+ const verticalSlices = asArray(plan?.vertical_slices);
106
+ const surfaces = [];
107
+ if (endpoints.length) surfaces.push('api');
108
+ if (chains.length) surfaces.push('state-management');
109
+ if (components.length) surfaces.push('components');
110
+ if (verticalSlices.length) surfaces.push('vertical-slices');
111
+ if (tests.length) surfaces.push('tests');
112
+ return {
113
+ slug: item.slug,
114
+ title: item.title,
115
+ domain: item.domain,
116
+ status: item.state,
117
+ surfaces,
118
+ routeShapes: [...new Set(endpoints.map((entry) => entry.route_class).filter(Boolean))],
119
+ components: [...new Set(components.map((entry) => entry.component ?? entry.name).filter(Boolean))],
120
+ standards: [...new Set(standards.map((entry) => entry.id ?? entry.standard_id ?? entry.source_path).filter(Boolean))],
121
+ standardsEvidence: standards,
122
+ metadata: {
123
+ endpointCount: endpoints.length,
124
+ storeChainCount: chains.length,
125
+ componentCount: components.length,
126
+ testObligationCount: tests.length,
127
+ planContractPresent: Boolean(plan)
128
+ }
129
+ };
130
+ }
131
+
132
+ function updateCapabilityProjections(database, projectRoot, runId) {
133
+ database.exec('DELETE FROM capability_fingerprints; DELETE FROM standards_applicability;');
134
+ const insertFingerprint = database.prepare(`
135
+ INSERT INTO capability_fingerprints (
136
+ slug, title, domain, status, surfaces_json, route_shapes_json,
137
+ components_json, standards_json, metadata_json
138
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
139
+ `);
140
+ const insertStandard = database.prepare(`
141
+ INSERT INTO standards_applicability (
142
+ run_id, target_kind, target_path, standard_id, source_path,
143
+ rule_summary, confidence, required, metadata_json
144
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
145
+ `);
146
+ const items = database.prepare('SELECT * FROM work_items ORDER BY slug').all();
147
+ for (const item of items) {
148
+ const fingerprint = capabilityFingerprint(projectRoot, item);
149
+ insertFingerprint.run(
150
+ fingerprint.slug,
151
+ fingerprint.title,
152
+ fingerprint.domain,
153
+ fingerprint.status,
154
+ JSON.stringify(fingerprint.surfaces),
155
+ JSON.stringify(fingerprint.routeShapes),
156
+ JSON.stringify(fingerprint.components),
157
+ JSON.stringify(fingerprint.standards),
158
+ JSON.stringify(fingerprint.metadata)
159
+ );
160
+ for (const standard of fingerprint.standardsEvidence) {
161
+ const id = standard.id ?? standard.standard_id ?? standard.source_path;
162
+ if (!id) continue;
163
+ const targets = asArray(standard.targets ?? standard.target_paths ?? [item.slug]);
164
+ for (const target of targets.length ? targets : [item.slug]) {
165
+ insertStandard.run(
166
+ runId,
167
+ 'delivery-plan',
168
+ typeof target === 'string' ? target : target.path ?? item.slug,
169
+ id,
170
+ standard.source_path ?? standard.sourcePath ?? '',
171
+ standard.rule_summary ?? standard.ruleSummary ?? '',
172
+ Number(standard.confidence ?? 1),
173
+ standard.required === false ? 0 : 1,
174
+ JSON.stringify({ slug: item.slug })
175
+ );
176
+ }
177
+ }
178
+ }
179
+ return { capabilities: items.length };
180
+ }
181
+
182
+ function insertFacts(database, runId, repository, path, facts) {
183
+ const storedPath = `${repository.name}:${relative(repository.root, path).replaceAll('\\', '/')}`;
184
+ database.prepare(`
185
+ INSERT INTO repo_files (
186
+ run_id, repo, path, language, parser, parser_status, content_hash,
187
+ size_bytes, line_count, parse_error_count, classification, profile_id,
188
+ analyser, analysis_depth, analysis_outcome, fingerprint_kind, fingerprint,
189
+ metadata_json
190
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
191
+ `).run(
192
+ runId, repository.name, storedPath, facts.file.language, facts.file.parser,
193
+ facts.file.parserStatus, facts.file.fingerprint, facts.file.sizeBytes, facts.file.lineCount,
194
+ facts.file.parseErrorCount, facts.file.classification, facts.file.profileId,
195
+ facts.file.analyser, facts.file.analysisDepth, facts.file.analysisOutcome,
196
+ facts.file.fingerprintKind, facts.file.fingerprint, JSON.stringify(facts.file.metadata ?? {})
197
+ );
198
+ const symbol = database.prepare(`
199
+ INSERT INTO repo_symbols (
200
+ run_id, repo, symbol_kind, symbol_name, path, line, signature,
201
+ tags_json, metadata_json, content_hash
202
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
203
+ `);
204
+ for (const item of facts.symbols) {
205
+ symbol.run(
206
+ runId, repository.name, item.kind, item.name, storedPath, item.line ?? null,
207
+ item.signature ?? '', JSON.stringify(item.tags ?? []),
208
+ JSON.stringify(item.metadata ?? {}), facts.file.fingerprint
209
+ );
210
+ }
211
+ const span = database.prepare(`
212
+ INSERT INTO repo_spans (
213
+ run_id, repo, path, symbol_kind, symbol_name, start_line, end_line,
214
+ start_byte, end_byte, evidence_snippet, parser, metadata_json
215
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
216
+ `);
217
+ for (const item of facts.spans) {
218
+ span.run(
219
+ runId, repository.name, storedPath, item.symbolKind, item.symbolName,
220
+ item.startLine, item.endLine, item.startByte ?? 0, item.endByte ?? 0,
221
+ item.evidenceSnippet ?? '', item.parser ?? '', JSON.stringify(item.metadata ?? {})
222
+ );
223
+ }
224
+ const imported = database.prepare(`
225
+ INSERT INTO repo_imports (
226
+ run_id, repo, path, source_module, imported_name, local_name,
227
+ import_kind, line, parser, metadata_json
228
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
229
+ `);
230
+ for (const item of facts.imports) {
231
+ imported.run(
232
+ runId, repository.name, storedPath, item.sourceModule ?? '', item.importedName ?? '',
233
+ item.localName ?? '', item.importKind ?? '', item.line ?? null, item.parser ?? '',
234
+ JSON.stringify(item.metadata ?? {})
235
+ );
236
+ }
237
+ const relationship = database.prepare(`
238
+ INSERT INTO repo_relationships (
239
+ run_id, source_kind, source_name, source_path, relationship_kind,
240
+ target_kind, target_name, target_path, metadata_json
241
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
242
+ `);
243
+ for (const item of facts.relationships) {
244
+ relationship.run(
245
+ runId, item.source_kind, item.source_name, storedPath, item.relationship_kind,
246
+ item.target_kind, item.target_name, item.target_path ?? '',
247
+ JSON.stringify(item.metadata ?? {})
248
+ );
249
+ }
250
+ }
251
+
252
+ function registerProfiles(database, runId, profiles) {
253
+ const insert = database.prepare(`
254
+ INSERT INTO repo_index_profiles (
255
+ run_id, profile_id, source_kind, source_ref, classification,
256
+ analyser, analysis_depth, priority, metadata_json
257
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
258
+ `);
259
+ for (const profile of profiles) {
260
+ insert.run(
261
+ runId, profile.id, profile.sourceKind, profile.sourceRef,
262
+ profile.classification, profile.analyser, profile.analysisDepth, profile.priority,
263
+ JSON.stringify({
264
+ ...(profile.sourceVersion ? { source_version: profile.sourceVersion } : {}),
265
+ patterns: [...(profile.patterns ?? [])],
266
+ repositories: [...(profile.repositories ?? [])],
267
+ max_bytes: profile.maxBytes
268
+ })
269
+ );
270
+ }
271
+ }
272
+
273
+ function incrementOutcome(outcomes, outcome) {
274
+ outcomes[outcome] = Number(outcomes[outcome] ?? 0) + 1;
275
+ }
276
+
277
+ export function refreshRepositoryIndex(projectRoot) {
278
+ const started = Date.now();
279
+ const repos = repositories(projectRoot);
280
+ const profileResolution = resolveSourceMapProfiles(projectRoot);
281
+ const database = openRuntimeDatabase(projectRoot);
282
+ syncWorkItems(projectRoot, database);
283
+ const profileDigest = profileResolution.digest;
284
+ const run = database.prepare(`
285
+ INSERT INTO repo_index_runs (status, repos_json, profile_digest, profile_summary_json)
286
+ VALUES ('running', ?, ?, ?) RETURNING id
287
+ `).get(
288
+ JSON.stringify(Object.fromEntries(repos.map((repo) => [repo.name, repo.root]))),
289
+ profileDigest,
290
+ JSON.stringify(profileResolution.profiles.map(({ id, classification, analyser, analysisDepth, sourceKind, sourceRef, sourceVersion }) => ({
291
+ id, classification, analyser, analysisDepth, sourceKind, sourceRef, sourceVersion
292
+ })))
293
+ );
294
+ const summary = {
295
+ repositories: repos.length,
296
+ files: 0,
297
+ parsed: 0,
298
+ failed: 0,
299
+ symbols: 0,
300
+ outcomes: {
301
+ analysed: 0,
302
+ inventory_only: 0,
303
+ skipped_sensitive: 0,
304
+ skipped_oversized: 0,
305
+ analysis_failed: 0
306
+ }
307
+ };
308
+ try {
309
+ database.exec('BEGIN IMMEDIATE;');
310
+ registerProfiles(database, run.id, profileResolution.profiles);
311
+ for (const repository of repos) {
312
+ for (const entry of walk(repository.root, [], repository.excludedRoots)) {
313
+ const { path, stat } = entry;
314
+ const relativePath = relative(repository.root, path).replaceAll('\\', '/');
315
+ const selectedProfile = selectSourceMapProfile(relativePath, {
316
+ profiles: profileResolution.profiles,
317
+ repository
318
+ });
319
+ const fileLimit = sourceMapProfileFileLimit(selectedProfile);
320
+ let buffer = null;
321
+ let readError = Boolean(entry.statError);
322
+ if (!readError && selectedProfile.id !== 'core-sensitive' && Number(stat?.size ?? 0) <= fileLimit) {
323
+ try {
324
+ buffer = readFileSync(path);
325
+ } catch {
326
+ readError = true;
327
+ }
328
+ }
329
+ const fingerprint = buffer
330
+ ? fingerprintRepositoryBuffer(buffer)
331
+ : fingerprintRepositoryFile(path, stat, selectedProfile);
332
+ const facts = analyseRepositoryFile(path, {
333
+ stat,
334
+ buffer,
335
+ readError,
336
+ fingerprint,
337
+ profile: selectedProfile,
338
+ repo: repository.name,
339
+ repoRelative: () => relativePath
340
+ });
341
+ insertFacts(database, run.id, repository, path, facts);
342
+ summary.files += 1;
343
+ summary.symbols += facts.symbols.length;
344
+ incrementOutcome(summary.outcomes, facts.file.analysisOutcome);
345
+ if (facts.file.parserStatus === 'parsed') summary.parsed += 1;
346
+ if (facts.file.analysisOutcome === 'analysis_failed') summary.failed += 1;
347
+ }
348
+ }
349
+ Object.assign(summary, updateCapabilityProjections(database, projectRoot, run.id));
350
+ database.prepare(`
351
+ UPDATE repo_index_runs SET status = 'completed', completed_at = CURRENT_TIMESTAMP,
352
+ duration_ms = ?, summary = ?, outcome_totals_json = ? WHERE id = ?
353
+ `).run(Date.now() - started, JSON.stringify(summary), JSON.stringify(summary.outcomes), run.id);
354
+ database.exec('COMMIT;');
355
+ return {
356
+ schema: 'ewai.repository-index/v1',
357
+ runId: run.id,
358
+ status: 'completed',
359
+ profileDigest,
360
+ ...summary,
361
+ durationMs: Date.now() - started
362
+ };
363
+ } catch (error) {
364
+ database.exec('ROLLBACK;');
365
+ database.prepare(`UPDATE repo_index_runs SET status = 'failed', completed_at = CURRENT_TIMESTAMP, summary = ? WHERE id = ?`)
366
+ .run(error.message, run.id);
367
+ throw error;
368
+ } finally {
369
+ database.close();
370
+ }
371
+ }
372
+
373
+ export function repositoryIndexFreshness(projectRoot) {
374
+ const repos = repositories(projectRoot);
375
+ const profileResolution = resolveSourceMapProfiles(projectRoot);
376
+ const database = openRuntimeDatabase(projectRoot);
377
+ try {
378
+ const run = database.prepare("SELECT * FROM repo_index_runs WHERE status = 'completed' ORDER BY id DESC LIMIT 1").get();
379
+ if (!run) return { schema: 'ewai.repository-freshness/v1', status: 'stale', stale: true, reason: 'no-completed-index-run' };
380
+ if (run.profile_digest !== profileResolution.digest) {
381
+ return {
382
+ schema: 'ewai.repository-freshness/v1',
383
+ status: 'stale',
384
+ stale: true,
385
+ runId: run.id,
386
+ reason: 'profile-catalogue-changed'
387
+ };
388
+ }
389
+ const indexed = new Map(database.prepare('SELECT path, fingerprint FROM repo_files WHERE run_id = ?').all(run.id)
390
+ .map((row) => [row.path, row.fingerprint]));
391
+ const current = new Map();
392
+ for (const repository of repos) {
393
+ for (const entry of walk(repository.root, [], repository.excludedRoots)) {
394
+ const { path, stat } = entry;
395
+ const relativePath = relative(repository.root, path).replaceAll('\\', '/');
396
+ const stored = `${repository.name}:${relativePath}`;
397
+ const selectedProfile = selectSourceMapProfile(relativePath, {
398
+ profiles: profileResolution.profiles,
399
+ repository
400
+ });
401
+ current.set(stored, fingerprintRepositoryFile(path, stat, selectedProfile).value);
402
+ }
403
+ }
404
+ const missing = [...current.keys()].filter((path) => !indexed.has(path));
405
+ const changed = [...current].filter(([path, value]) => indexed.has(path) && indexed.get(path) !== value).map(([path]) => path);
406
+ const deleted = [...indexed.keys()].filter((path) => !current.has(path));
407
+ const stale = Boolean(missing.length || changed.length || deleted.length);
408
+ return {
409
+ schema: 'ewai.repository-freshness/v1',
410
+ status: stale ? 'stale' : 'fresh',
411
+ stale,
412
+ runId: run.id,
413
+ summary: { tracked: indexed.size, current: current.size, missing: missing.length, changed: changed.length, deleted: deleted.length },
414
+ samples: { missing: missing.slice(0, 20), changed: changed.slice(0, 20), deleted: deleted.slice(0, 20) }
415
+ };
416
+ } finally {
417
+ database.close();
418
+ }
419
+ }
420
+
421
+ function parsedFingerprint(row) {
422
+ if (!row) return null;
423
+ return {
424
+ slug: row.slug,
425
+ title: row.title,
426
+ domain: row.domain,
427
+ status: row.status,
428
+ surfaces: JSON.parse(row.surfaces_json || '[]'),
429
+ routeShapes: JSON.parse(row.route_shapes_json || '[]'),
430
+ components: JSON.parse(row.components_json || '[]'),
431
+ standards: JSON.parse(row.standards_json || '[]'),
432
+ metadata: JSON.parse(row.metadata_json || '{}')
433
+ };
434
+ }
435
+
436
+ function similarity(left, right) {
437
+ const weighted = [['surfaces', 3], ['routeShapes', 4], ['components', 2], ['standards', 1]];
438
+ let score = left.domain && left.domain === right.domain ? 1 : 0;
439
+ let maximum = 1;
440
+ for (const [key, weight] of weighted) {
441
+ const a = new Set(left[key] ?? []);
442
+ const b = new Set(right[key] ?? []);
443
+ const union = new Set([...a, ...b]);
444
+ if (!union.size) continue;
445
+ score += ([...a].filter((value) => b.has(value)).length / union.size) * weight;
446
+ maximum += weight;
447
+ }
448
+ return maximum ? score / maximum : 0;
449
+ }
450
+
451
+ export function similarRepositoryCapabilities(projectRoot, slug, options = {}) {
452
+ const database = openRuntimeDatabase(projectRoot);
453
+ try {
454
+ const target = parsedFingerprint(database.prepare('SELECT * FROM capability_fingerprints WHERE slug = ?').get(slug));
455
+ if (!target) return { schema: 'ewai.similar-capabilities/v1', slug, target: null, matches: [] };
456
+ const matches = database.prepare('SELECT * FROM capability_fingerprints WHERE slug <> ?').all(slug)
457
+ .map(parsedFingerprint)
458
+ .map((candidate) => ({ ...candidate, similarity: Number(similarity(target, candidate).toFixed(3)) }))
459
+ .filter((candidate) => candidate.similarity > 0)
460
+ .sort((left, right) => right.similarity - left.similarity)
461
+ .slice(0, Number(options.limit ?? 12));
462
+ return { schema: 'ewai.similar-capabilities/v1', slug, target, matches };
463
+ } finally {
464
+ database.close();
465
+ }
466
+ }
467
+
468
+ export function repositoryStandards(projectRoot, target) {
469
+ const database = openRuntimeDatabase(projectRoot);
470
+ try {
471
+ const needle = `%${String(target).toLowerCase()}%`;
472
+ const standards = database.prepare(`
473
+ SELECT * FROM standards_applicability
474
+ WHERE lower(target_path) LIKE ? OR lower(target_kind) LIKE ?
475
+ ORDER BY required DESC, confidence DESC, standard_id LIMIT 100
476
+ `).all(needle, needle).map((row) => ({
477
+ standardId: row.standard_id,
478
+ sourcePath: row.source_path,
479
+ targetKind: row.target_kind,
480
+ targetPath: row.target_path,
481
+ ruleSummary: row.rule_summary,
482
+ confidence: row.confidence,
483
+ required: Boolean(row.required)
484
+ }));
485
+ return { schema: 'ewai.repository-standards/v1', target, standards };
486
+ } finally {
487
+ database.close();
488
+ }
489
+ }
490
+
491
+ export function repositoryTruth(projectRoot, slug, options = {}) {
492
+ const database = openRuntimeDatabase(projectRoot);
493
+ let item;
494
+ try {
495
+ syncWorkItems(projectRoot, database);
496
+ item = database.prepare('SELECT slug, title, domain, state, current_phase FROM work_items WHERE slug = ?').get(slug) ?? null;
497
+ } finally {
498
+ database.close();
499
+ }
500
+ const terms = [...new Set(`${slug} ${item?.title ?? ''} ${item?.domain ?? ''}`
501
+ .toLowerCase().split(/[^a-z0-9]+/).filter((term) => term.length >= 3))];
502
+ const searches = terms.map((term) => searchRepositoryIndex(projectRoot, term, options));
503
+ const symbols = [...new Map(searches.flatMap((search) => search.symbols)
504
+ .map((symbol) => [`${symbol.path}:${symbol.line}:${symbol.symbol_name}`, symbol])).values()];
505
+ const files = [...new Map(searches.flatMap((search) => search.files)
506
+ .map((file) => [file.path, file])).values()];
507
+ return {
508
+ schema: 'ewai.repository-truth/v1',
509
+ slug,
510
+ item,
511
+ symbols,
512
+ files,
513
+ similarCapabilities: similarRepositoryCapabilities(projectRoot, slug, options).matches,
514
+ standards: repositoryStandards(projectRoot, slug).standards
515
+ };
516
+ }
517
+
518
+ export function repositoryStandardsCoverage(projectRoot, slug) {
519
+ const deliveryRoot = resolve(projectPaths(projectRoot).buildRoot, slug);
520
+ const planPath = resolve(deliveryRoot, 'gates/plan/plan-contract.json');
521
+ const legacyPlanPath = resolve(deliveryRoot, 'plan-contract.json');
522
+ const claimsPath = resolve(deliveryRoot, 'gates/plan/claim-ledger.json');
523
+ const resolvedPlanPath = existsSync(planPath) ? planPath : legacyPlanPath;
524
+ const plan = readJson(resolvedPlanPath);
525
+ const claims = readJson(claimsPath);
526
+ const freshness = repositoryIndexFreshness(projectRoot);
527
+ const findings = [];
528
+ if (!plan) findings.push({ severity: 'high', id: 'missing-plan-contract', path: resolvedPlanPath });
529
+ if (!claims) findings.push({ severity: 'medium', id: 'missing-claim-ledger', path: claimsPath });
530
+ if (freshness.stale) findings.push({ severity: 'high', id: 'stale-repository-index', details: freshness.summary ?? freshness.reason });
531
+ const evidence = planStandardsEvidence(plan);
532
+ if (plan && !evidence.length) findings.push({ severity: 'high', id: 'missing-standards-evidence', path: resolvedPlanPath });
533
+ return {
534
+ schema: 'ewai.standards-coverage/v1',
535
+ slug,
536
+ status: findings.some((finding) => finding.severity === 'high') ? 'blocked' : 'pass',
537
+ planPath: resolvedPlanPath,
538
+ claimsPath,
539
+ standardsEvidence: evidence,
540
+ freshness,
541
+ findings
542
+ };
543
+ }
544
+
545
+ export function repositoryIndexStatus(projectRoot) {
546
+ const database = openRuntimeDatabase(projectRoot);
547
+ try {
548
+ const run = database.prepare('SELECT * FROM repo_index_runs ORDER BY id DESC LIMIT 1').get();
549
+ if (!run) return { schema: 'ewai.repository-index-status/v1', status: 'missing', run: null };
550
+ const counts = database.prepare(`
551
+ SELECT COUNT(*) AS files,
552
+ SUM(CASE WHEN parser_status = 'parsed' THEN 1 ELSE 0 END) AS parsed,
553
+ SUM(CASE WHEN analysis_outcome = 'analysis_failed' THEN 1 ELSE 0 END) AS failed,
554
+ SUM(CASE WHEN analysis_outcome = 'analysed' THEN 1 ELSE 0 END) AS analysed,
555
+ SUM(CASE WHEN analysis_outcome = 'inventory_only' THEN 1 ELSE 0 END) AS inventory_only,
556
+ SUM(CASE WHEN analysis_outcome = 'skipped_sensitive' THEN 1 ELSE 0 END) AS skipped_sensitive,
557
+ SUM(CASE WHEN analysis_outcome = 'skipped_oversized' THEN 1 ELSE 0 END) AS skipped_oversized
558
+ FROM repo_files WHERE run_id = ?
559
+ `).get(run.id);
560
+ return { schema: 'ewai.repository-index-status/v1', status: run.status, run, counts };
561
+ } finally {
562
+ database.close();
563
+ }
564
+ }
565
+
566
+ function sourceMapCounts(rows, key, expected = []) {
567
+ const counts = Object.fromEntries(expected.map((value) => [value, 0]));
568
+ for (const row of rows) counts[row[key]] = Number(row.count ?? 0);
569
+ return counts;
570
+ }
571
+
572
+ function safeLimit(value, fallback = 50) {
573
+ return Math.max(1, Math.min(200, Number(value ?? fallback) || fallback));
574
+ }
575
+
576
+ function safeFilter(value, label, maxLength, allowed = null) {
577
+ const filter = String(value ?? '').trim();
578
+ if (filter.length > maxLength) throw new Error(`${label} exceeds ${maxLength} characters.`);
579
+ if (filter && allowed && !allowed.has(filter)) throw new Error(`Unsupported ${label}: ${filter}`);
580
+ return filter;
581
+ }
582
+
583
+ function latestCompletedRun(database) {
584
+ return database.prepare("SELECT * FROM repo_index_runs WHERE status = 'completed' ORDER BY id DESC LIMIT 1").get() ?? null;
585
+ }
586
+
587
+ function safeStoredPath(stored) {
588
+ const value = String(stored ?? '');
589
+ const separator = value.indexOf(':');
590
+ return separator === -1 ? value : value.slice(separator + 1);
591
+ }
592
+
593
+ export function repositorySourceMapCoverage(projectRoot) {
594
+ const freshness = repositoryIndexFreshness(projectRoot);
595
+ const database = openRuntimeDatabase(projectRoot);
596
+ try {
597
+ const run = latestCompletedRun(database);
598
+ const empty = {
599
+ analysed: 0,
600
+ inventory_only: 0,
601
+ skipped_sensitive: 0,
602
+ skipped_oversized: 0,
603
+ analysis_failed: 0
604
+ };
605
+ if (!run) {
606
+ return {
607
+ schema: 'ewai.repository-source-map-coverage/v1',
608
+ status: 'missing',
609
+ runId: null,
610
+ fresh: false,
611
+ totalFiles: 0,
612
+ outcomes: empty,
613
+ depths: { deep: 0, shallow: 0, inventory: 0 },
614
+ classifications: {},
615
+ analysers: {},
616
+ partial: 0,
617
+ profileCount: 0,
618
+ warnings: ['No completed Source Map run is available.'],
619
+ freshness,
620
+ guidance: { advisory: true, completeUnderstanding: false, humanReviewRequired: true }
621
+ };
622
+ }
623
+ const outcomes = sourceMapCounts(database.prepare(`
624
+ SELECT analysis_outcome AS value, COUNT(*) AS count FROM repo_files
625
+ WHERE run_id = ? GROUP BY analysis_outcome
626
+ `).all(run.id), 'value', Object.keys(empty));
627
+ const depths = sourceMapCounts(database.prepare(`
628
+ SELECT analysis_depth AS value, COUNT(*) AS count FROM repo_files
629
+ WHERE run_id = ? GROUP BY analysis_depth
630
+ `).all(run.id), 'value', ['deep', 'shallow', 'inventory']);
631
+ const classifications = sourceMapCounts(database.prepare(`
632
+ SELECT classification AS value, COUNT(*) AS count FROM repo_files
633
+ WHERE run_id = ? GROUP BY classification ORDER BY classification
634
+ `).all(run.id), 'value');
635
+ const analysers = sourceMapCounts(database.prepare(`
636
+ SELECT analyser AS value, COUNT(*) AS count FROM repo_files
637
+ WHERE run_id = ? GROUP BY analyser ORDER BY analyser
638
+ `).all(run.id), 'value');
639
+ const partial = Number(database.prepare(`
640
+ SELECT COUNT(*) AS count FROM repo_files
641
+ WHERE run_id = ? AND json_extract(metadata_json, '$.partial') = 1
642
+ `).get(run.id).count ?? 0);
643
+ const profileCount = Number(database.prepare('SELECT COUNT(*) AS count FROM repo_index_profiles WHERE run_id = ?').get(run.id).count);
644
+ const totalFiles = Object.values(outcomes).reduce((total, count) => total + Number(count), 0);
645
+ const warnings = [];
646
+ if (outcomes.inventory_only) warnings.push(`${outcomes.inventory_only} file(s) have inventory-only evidence.`);
647
+ if (outcomes.skipped_sensitive) warnings.push(`${outcomes.skipped_sensitive} sensitive file(s) were not opened.`);
648
+ if (outcomes.skipped_oversized) warnings.push(`${outcomes.skipped_oversized} oversized file(s) were not opened.`);
649
+ if (outcomes.analysis_failed) warnings.push(`${outcomes.analysis_failed} file(s) have bounded analysis failure evidence.`);
650
+ if (partial) warnings.push(`${partial} file(s) have partial platform evidence.`);
651
+ if (depths.shallow) warnings.push(`${depths.shallow} file(s) have shallow rather than dependency-level evidence.`);
652
+ return {
653
+ schema: 'ewai.repository-source-map-coverage/v1',
654
+ status: run.status,
655
+ runId: Number(run.id),
656
+ fresh: !freshness.stale,
657
+ totalFiles,
658
+ outcomes,
659
+ depths,
660
+ classifications,
661
+ analysers,
662
+ partial,
663
+ profileCount,
664
+ profileDigest: run.profile_digest,
665
+ warnings,
666
+ freshness,
667
+ guidance: { advisory: true, completeUnderstanding: false, humanReviewRequired: true }
668
+ };
669
+ } finally {
670
+ database.close();
671
+ }
672
+ }
673
+
674
+ export function repositorySourceMapProfiles(projectRoot, options = {}) {
675
+ const database = openRuntimeDatabase(projectRoot);
676
+ try {
677
+ const run = latestCompletedRun(database);
678
+ if (!run) return { schema: 'ewai.repository-source-map-profiles/v1', runId: null, profiles: [] };
679
+ const sourceKind = safeFilter(options.sourceKind, 'Source Map profile source', 20,
680
+ new Set(['core', 'technology', 'stack', 'organisation', 'project']));
681
+ const analyser = safeFilter(options.analyser, 'Source Map analyser', 32,
682
+ new Set(['inventory-only', 'text-summary', 'structured-keys', 'tree-sitter',
683
+ 'power-platform-metadata', 'salesforce-metadata']));
684
+ const clauses = ['profiles.run_id = ?'];
685
+ const parameters = [run.id];
686
+ if (sourceKind) {
687
+ clauses.push('profiles.source_kind = ?');
688
+ parameters.push(sourceKind);
689
+ }
690
+ if (analyser) {
691
+ clauses.push('profiles.analyser = ?');
692
+ parameters.push(analyser);
693
+ }
694
+ parameters.push(safeLimit(options.limit, 100));
695
+ const rows = database.prepare(`
696
+ SELECT profiles.profile_id, profiles.source_kind, profiles.source_ref,
697
+ profiles.classification, profiles.analyser, profiles.analysis_depth,
698
+ profiles.priority, profiles.metadata_json,
699
+ (SELECT COUNT(*) FROM repo_files files
700
+ WHERE files.run_id = profiles.run_id AND files.profile_id = profiles.profile_id) AS match_count
701
+ FROM repo_index_profiles profiles WHERE ${clauses.join(' AND ')}
702
+ ORDER BY profiles.source_kind, profiles.priority DESC, profiles.profile_id LIMIT ?
703
+ `).all(...parameters);
704
+ const profiles = rows.map((row) => {
705
+ const metadata = JSON.parse(row.metadata_json || '{}');
706
+ return {
707
+ id: row.profile_id,
708
+ sourceKind: row.source_kind,
709
+ sourceRef: row.source_ref,
710
+ sourceVersion: metadata.source_version ?? '',
711
+ classification: row.classification,
712
+ analyser: row.analyser,
713
+ analysisDepth: row.analysis_depth,
714
+ priority: Number(row.priority),
715
+ patterns: Array.isArray(metadata.patterns) ? metadata.patterns : [],
716
+ repositories: Array.isArray(metadata.repositories) ? metadata.repositories : [],
717
+ maxBytes: Number(metadata.max_bytes ?? 0),
718
+ matchCount: Number(row.match_count ?? 0)
719
+ };
720
+ });
721
+ return { schema: 'ewai.repository-source-map-profiles/v1', runId: Number(run.id), profiles };
722
+ } finally {
723
+ database.close();
724
+ }
725
+ }
726
+
727
+ export function repositorySourceMapFiles(projectRoot, options = {}) {
728
+ const database = openRuntimeDatabase(projectRoot);
729
+ try {
730
+ const run = latestCompletedRun(database);
731
+ if (!run) return { schema: 'ewai.repository-source-map-files/v1', runId: null, filters: {}, files: [] };
732
+ const clauses = ['run_id = ?'];
733
+ const parameters = [run.id];
734
+ const filters = {
735
+ outcome: safeFilter(options.outcome, 'Source Map outcome', 32,
736
+ new Set(['analysed', 'inventory_only', 'skipped_sensitive', 'skipped_oversized', 'analysis_failed'])),
737
+ classification: safeFilter(options.classification, 'Source Map classification', 100),
738
+ profileId: safeFilter(options.profileId, 'Source Map profile ID', 240),
739
+ repository: safeFilter(options.repository, 'Source Map repository', 160),
740
+ query: safeFilter(options.query, 'Source Map path query', 240),
741
+ attentionOnly: options.attentionOnly === true
742
+ };
743
+ for (const [field, column] of [
744
+ ['outcome', 'analysis_outcome'],
745
+ ['classification', 'classification'],
746
+ ['profileId', 'profile_id'],
747
+ ['repository', 'repo']
748
+ ]) {
749
+ if (!filters[field]) continue;
750
+ clauses.push(`${column} = ?`);
751
+ parameters.push(filters[field]);
752
+ }
753
+ if (filters.attentionOnly) {
754
+ clauses.push('analysis_outcome != ?');
755
+ parameters.push('analysed');
756
+ }
757
+ if (filters.query) {
758
+ clauses.push('lower(path) LIKE ?');
759
+ parameters.push(`%${filters.query.toLowerCase()}%`);
760
+ }
761
+ parameters.push(safeLimit(options.limit, 50));
762
+ const rows = database.prepare(`
763
+ SELECT repo, path, language, parser_status, size_bytes, line_count,
764
+ parse_error_count, classification, profile_id, analyser,
765
+ analysis_depth, analysis_outcome
766
+ FROM repo_files WHERE ${clauses.join(' AND ')} ORDER BY path LIMIT ?
767
+ `).all(...parameters);
768
+ return {
769
+ schema: 'ewai.repository-source-map-files/v1',
770
+ runId: Number(run.id),
771
+ filters,
772
+ files: rows.map((row) => ({
773
+ repository: row.repo,
774
+ path: safeStoredPath(row.path),
775
+ language: row.language,
776
+ parserStatus: row.parser_status,
777
+ sizeBytes: Number(row.size_bytes),
778
+ lineCount: Number(row.line_count),
779
+ parseErrorCount: Number(row.parse_error_count),
780
+ classification: row.classification,
781
+ profileId: row.profile_id,
782
+ analyser: row.analyser,
783
+ analysisDepth: row.analysis_depth,
784
+ analysisOutcome: row.analysis_outcome
785
+ }))
786
+ };
787
+ } finally {
788
+ database.close();
789
+ }
790
+ }
791
+
792
+ export function searchRepositoryIndex(projectRoot, query, options = {}) {
793
+ const database = openRuntimeDatabase(projectRoot);
794
+ try {
795
+ const run = database.prepare("SELECT id FROM repo_index_runs WHERE status = 'completed' ORDER BY id DESC LIMIT 1").get();
796
+ if (!run) throw new Error('Repository index is missing; refresh it before searching.');
797
+ const needle = `%${String(query).toLowerCase()}%`;
798
+ const limit = Math.max(1, Math.min(200, Number(options.limit ?? 50)));
799
+ const symbols = database.prepare(`
800
+ SELECT symbol_kind, symbol_name, path, line, signature FROM repo_symbols
801
+ WHERE run_id = ? AND lower(symbol_name || ' ' || path || ' ' || signature) LIKE ?
802
+ ORDER BY symbol_name LIMIT ?
803
+ `).all(run.id, needle, limit);
804
+ const files = database.prepare(`
805
+ SELECT repo, path, language, parser_status, line_count, classification,
806
+ profile_id, analyser, analysis_depth, analysis_outcome
807
+ FROM repo_files
808
+ WHERE run_id = ? AND lower(path) LIKE ? ORDER BY path LIMIT ?
809
+ `).all(run.id, needle, limit);
810
+ return { schema: 'ewai.repository-search/v1', runId: run.id, query, symbols, files };
811
+ } finally {
812
+ database.close();
813
+ }
814
+ }
815
+
816
+ export function repositoryGraph(projectRoot, target, options = {}) {
817
+ const database = openRuntimeDatabase(projectRoot);
818
+ try {
819
+ const run = database.prepare("SELECT id FROM repo_index_runs WHERE status = 'completed' ORDER BY id DESC LIMIT 1").get();
820
+ if (!run) throw new Error('Repository index is missing; refresh it before graph lookup.');
821
+ const needle = `%${String(target).toLowerCase()}%`;
822
+ const limit = Math.max(1, Math.min(200, Number(options.limit ?? 80)));
823
+ const relationships = database.prepare(`
824
+ SELECT * FROM repo_relationships WHERE run_id = ? AND (
825
+ lower(source_name) LIKE ? OR lower(source_path) LIKE ? OR
826
+ lower(target_name) LIKE ? OR lower(target_path) LIKE ?
827
+ ) ORDER BY relationship_kind LIMIT ?
828
+ `).all(run.id, needle, needle, needle, needle, limit);
829
+ const imports = database.prepare(`
830
+ SELECT * FROM repo_imports WHERE run_id = ? AND (
831
+ lower(path) LIKE ? OR lower(source_module) LIKE ? OR lower(imported_name) LIKE ?
832
+ ) ORDER BY path LIMIT ?
833
+ `).all(run.id, needle, needle, needle, limit);
834
+ return { schema: 'ewai.repository-graph/v1', runId: run.id, target, relationships, imports };
835
+ } finally {
836
+ database.close();
837
+ }
838
+ }