@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.
- package/Docs/README.md +50 -0
- package/Docs/adoption/consultancy-and-multi-project-rollout.md +135 -0
- package/Docs/adoption/non-technical-team-guide.md +126 -0
- package/Docs/archaeology-technology-and-hosting-discovery.md +212 -0
- package/Docs/blast-radius-and-impact-routing-guide.md +325 -0
- package/Docs/blueprints/internal-blueprint-catalogue.md +146 -0
- package/Docs/blueprints/maintaining-organisation-blueprints.md +154 -0
- package/Docs/blueprints/validation-and-troubleshooting.md +168 -0
- package/Docs/cli-reference.md +113 -0
- package/Docs/completed-phase-evidence-amendments.md +74 -0
- package/Docs/consultancy-network-rollout-control-plane-guide.md +202 -0
- package/Docs/context-aware-delivery-companion-guide.md +198 -0
- package/Docs/context-aware-delivery-companion-user-guide.md +184 -0
- package/Docs/context-management-and-token-efficiency.md +113 -0
- package/Docs/design-systems/design-system-implementation-guide.md +85 -0
- package/Docs/design-systems/design-system-pack-authoring-guide.md +95 -0
- package/Docs/design-systems/design-system-review-guide.md +51 -0
- package/Docs/design-systems/design-system-user-guide.md +96 -0
- package/Docs/design-systems/product-owner-guide.md +49 -0
- package/Docs/designing-organisation-blueprint-packs.md +384 -0
- package/Docs/developer-delivery-guide.md +224 -0
- package/Docs/error-reporting-guide.md +110 -0
- package/Docs/error-reporting-provider-guide.md +49 -0
- package/Docs/examples/error-report-adapter.md +70 -0
- package/Docs/examples/meeting-review.md +76 -0
- package/Docs/examples/minimal-design-system.md +67 -0
- package/Docs/examples/prototype-review-inputs.md +175 -0
- package/Docs/examples/reproducible-archaeology-depth-example.md +144 -0
- package/Docs/examples/test-scenario-input.md +68 -0
- package/Docs/examples/worked-examples.md +147 -0
- package/Docs/existing-project-onboarding-guide.md +214 -0
- package/Docs/explanation/core-concepts.md +26 -0
- package/Docs/explanation/delivery-workflow.md +48 -0
- package/Docs/governance/governance-team-guide.md +139 -0
- package/Docs/governed-starter-project-materialisation-guide.md +284 -0
- package/Docs/guide-catalogue.md +117 -0
- package/Docs/guided-discovery-facilitator-guide.md +172 -0
- package/Docs/guided-intent-workspace-guide.md +109 -0
- package/Docs/guided-phase-evidence-drafting-guide.md +119 -0
- package/Docs/human-approval-and-assurance-guide.md +146 -0
- package/Docs/knowledge-proposals-implementer-guide.md +106 -0
- package/Docs/knowledge-proposals-user-guide.md +247 -0
- package/Docs/maintainers/context-benchmarks.md +29 -0
- package/Docs/maintainers/contributing.md +58 -0
- package/Docs/maintainers/evidence-depth-acceptance.md +72 -0
- package/Docs/maintainers/verification-walkthroughs.md +104 -0
- package/Docs/meeting-evidence-implementer-guide.md +132 -0
- package/Docs/meeting-evidence-user-guide.md +200 -0
- package/Docs/operations/dashboard-and-delivery-state.md +153 -0
- package/Docs/operations/dashboard-configuration.md +87 -0
- package/Docs/operations/installation-updating-and-entitlements.md +135 -0
- package/Docs/operations/premium-personas-setup.md +76 -0
- package/Docs/operations/troubleshooting-and-recovery.md +205 -0
- package/Docs/organisation-rollout-guide.md +142 -0
- package/Docs/persona-entitlement-provider-guide.md +199 -0
- package/Docs/persona-guided-prototype-iteration.md +129 -0
- package/Docs/personas/organisation-specific-personas.md +103 -0
- package/Docs/personas/persona-authoring-cookbook.md +176 -0
- package/Docs/personas/persona-engagement-ui.md +133 -0
- package/Docs/personas/persona-governance.md +118 -0
- package/Docs/platform-export-analysis-guide.md +336 -0
- package/Docs/policies/governance-owner-guide.md +36 -0
- package/Docs/policies/implementation-guide.md +42 -0
- package/Docs/policies/organisation-policy-design-gates.md +58 -0
- package/Docs/policies/policy-pack-authoring-guide.md +108 -0
- package/Docs/policies/product-owner-guide.md +43 -0
- package/Docs/policies/technical-owner-guide.md +37 -0
- package/Docs/product-owner-guide.md +327 -0
- package/Docs/project-portfolio-orchestration-guide.md +199 -0
- package/Docs/quality/manual-qa-and-acceptance.md +162 -0
- package/Docs/quality/persona-driven-test-scenarios.md +172 -0
- package/Docs/quality/reproducible-archaeology-depth-review-checklist.md +89 -0
- package/Docs/reference/capabilities-and-project-layout.md +678 -0
- package/Docs/reference/cli-and-configuration.md +398 -0
- package/Docs/reference/contributions-api.md +23 -0
- package/Docs/reference/security-adapter-authoring.md +81 -0
- package/Docs/reference/starter-adapter-authoring.md +74 -0
- package/Docs/repository-source-map-guide.md +381 -0
- package/Docs/reproducible-archaeology-and-discovery-depth.md +292 -0
- package/Docs/screen-prototype-creation-guide.md +324 -0
- package/Docs/security-validation-guide.md +353 -0
- package/Docs/solution-readiness-review-guide.md +123 -0
- package/Docs/standards/project-standards-authoring.md +157 -0
- package/Docs/team-hub-guide.md +162 -0
- package/Docs/team-hub-resource-registry-guide.md +167 -0
- package/Docs/tutorials/first-delivery.md +83 -0
- package/Docs/tutorials/first-session.md +62 -0
- package/Docs/using-lifecycle-hooks.md +381 -0
- package/Docs/working-with-personas.md +274 -0
- package/LICENSE +165 -0
- package/README.md +96 -0
- package/agents-src/claude/ewai-security-reviewer.md +15 -0
- package/bin/ewai +5 -0
- package/config/archaeology-record-families.yaml +59 -0
- package/config/delivery-artifacts.yaml +121 -0
- package/config/delivery-stages.yaml +77 -0
- package/config/design-system.schema.json +46 -0
- package/config/error-reporting.schema.json +79 -0
- package/config/evidence-depth.schema.json +53 -0
- package/config/intent.schema.json +90 -0
- package/config/knowledge-proposals-proposal.schema.json +34 -0
- package/config/lifecycle-event.schema.json +68 -0
- package/config/lifecycle-handler.schema.json +45 -0
- package/config/lifecycle-hook-ack.schema.json +19 -0
- package/config/meeting-evidence-candidate.schema.json +102 -0
- package/config/organisation-policy.schema.json +137 -0
- package/config/pack.schema.json +250 -0
- package/config/persona-pack.schema.json +21 -0
- package/config/persona.schema.json +17 -0
- package/config/policy-evaluation.schema.json +77 -0
- package/config/policy-facts.schema.json +140 -0
- package/config/portfolio.schema.json +68 -0
- package/config/project.schema.json +313 -0
- package/config/prototype-iteration.schema.json +128 -0
- package/config/rollout.schema.json +87 -0
- package/config/security-adapter.schema.json +31 -0
- package/config/security-scan-request.schema.json +64 -0
- package/config/security-scan-response.schema.json +52 -0
- package/config/security-validation-policy.schema.json +74 -0
- package/config/starter-source-acknowledgement.schema.json +13 -0
- package/config/starter-source-adapter.schema.json +38 -0
- package/config/starter-source-request.schema.json +61 -0
- package/package.json +77 -0
- package/packs/core/pack.yaml +7 -0
- package/packs/design-systems/default/experience-promise.md +9 -0
- package/packs/design-systems/default/intentional-review.md +10 -0
- package/packs/design-systems/default/interaction-and-entry.md +9 -0
- package/packs/design-systems/default/meaningful-content-and-states.md +9 -0
- package/packs/design-systems/default/pack.yaml +44 -0
- package/packs/design-systems/default/principles.md +10 -0
- package/packs/personas/core/pack.yaml +7 -0
- package/packs/personas/core/personas/archaeologist.md +37 -0
- package/packs/personas/core/personas/end-user.md +17 -0
- package/packs/personas/core/personas/maintainer.md +17 -0
- package/packs/personas/core/personas/operator.md +17 -0
- package/packs/personas/core/personas/specs-knowledge-curator.md +35 -0
- package/packs/technologies/laravel/pack.yaml +30 -0
- package/packs/technologies/laravel-nuxt/pack.yaml +30 -0
- package/packs/technologies/nuxt/pack.yaml +30 -0
- package/packs/technologies/power-platform/pack.yaml +31 -0
- package/packs/technologies/salesforce/pack.yaml +25 -0
- package/public/app.js +4896 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/assets/backstory-icon.png +0 -0
- package/public/dashboard-navigation.js +98 -0
- package/public/favicon-16.png +0 -0
- package/public/favicon-32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +789 -0
- package/public/styles.css +2693 -0
- package/public/team-hub/app.js +202 -0
- package/public/team-hub/index.html +79 -0
- package/public/team-hub/styles.css +90 -0
- package/scripts/publication-check.mjs +140 -0
- package/scripts/setup.mjs +21 -0
- package/skills-src/ewai-archaeology/SKILL.md +334 -0
- package/skills-src/ewai-archaeology/agents/openai.yaml +4 -0
- package/skills-src/ewai-archaeology/references/archaeology-contract.md +201 -0
- package/skills-src/ewai-archaeology/references/lifecycle-reconstruction.md +177 -0
- package/skills-src/ewai-archaeology/references/maximum-detail-reconstruction.md +97 -0
- package/skills-src/ewai-archaeology/references/model-routing.md +26 -0
- package/skills-src/ewai-architecture/SKILL.md +108 -0
- package/skills-src/ewai-architecture/agents/openai.yaml +4 -0
- package/skills-src/ewai-architecture/references/architecture-contract.md +176 -0
- package/skills-src/ewai-context/SKILL.md +68 -0
- package/skills-src/ewai-context/agents/openai.yaml +4 -0
- package/skills-src/ewai-context-import/SKILL.md +118 -0
- package/skills-src/ewai-context-import/agents/openai.yaml +4 -0
- package/skills-src/ewai-context-import/references/context-import-contract.md +106 -0
- package/skills-src/ewai-dashboard-configuration/SKILL.md +20 -0
- package/skills-src/ewai-deliver/SKILL.md +122 -0
- package/skills-src/ewai-deliver/references/delivery-evidence.md +92 -0
- package/skills-src/ewai-deliver/references/phase-routing.md +31 -0
- package/skills-src/ewai-design-system-apply/SKILL.md +27 -0
- package/skills-src/ewai-design-system-apply/agents/openai.yaml +4 -0
- package/skills-src/ewai-design-system-apply/references/application-contract.md +36 -0
- package/skills-src/ewai-design-system-author/SKILL.md +28 -0
- package/skills-src/ewai-design-system-author/agents/openai.yaml +4 -0
- package/skills-src/ewai-design-system-author/references/authoring-contract.md +38 -0
- package/skills-src/ewai-design-system-review/SKILL.md +26 -0
- package/skills-src/ewai-design-system-review/agents/openai.yaml +4 -0
- package/skills-src/ewai-design-system-review/references/review-contract.md +40 -0
- package/skills-src/ewai-error-reporting/SKILL.md +46 -0
- package/skills-src/ewai-error-reporting/agents/openai.yaml +4 -0
- package/skills-src/ewai-error-reporting/references/provider-contract.md +74 -0
- package/skills-src/ewai-evidence-depth/SKILL.md +72 -0
- package/skills-src/ewai-evidence-depth/agents/openai.yaml +4 -0
- package/skills-src/ewai-evidence-depth/references/evidence-depth-contract.md +127 -0
- package/skills-src/ewai-intent/SKILL.md +68 -0
- package/skills-src/ewai-intent/agents/openai.yaml +4 -0
- package/skills-src/ewai-intent/references/intent-contract.md +42 -0
- package/skills-src/ewai-knowledge-proposals/SKILL.md +105 -0
- package/skills-src/ewai-knowledge-proposals/agents/openai.yaml +4 -0
- package/skills-src/ewai-knowledge-proposals/references/proposal-contract.md +59 -0
- package/skills-src/ewai-meeting-evidence/SKILL.md +106 -0
- package/skills-src/ewai-meeting-evidence/agents/openai.yaml +4 -0
- package/skills-src/ewai-meeting-evidence/references/candidate-contract.md +64 -0
- package/skills-src/ewai-organisation-policy/SKILL.md +62 -0
- package/skills-src/ewai-organisation-policy/agents/openai.yaml +4 -0
- package/skills-src/ewai-organisation-policy/references/policy-contract.md +94 -0
- package/skills-src/ewai-palace-housekeeping/SKILL.md +55 -0
- package/skills-src/ewai-palace-housekeeping/agents/openai.yaml +4 -0
- package/skills-src/ewai-persona-entitlement/SKILL.md +60 -0
- package/skills-src/ewai-persona-entitlement/agents/openai.yaml +4 -0
- package/skills-src/ewai-phase-evidence/SKILL.md +79 -0
- package/skills-src/ewai-phase-evidence/agents/openai.yaml +4 -0
- package/skills-src/ewai-pipeline/SKILL.md +130 -0
- package/skills-src/ewai-pipeline/agents/openai.yaml +4 -0
- package/skills-src/ewai-pipeline/references/cli.md +86 -0
- package/skills-src/ewai-pipeline/references/specs-contract.md +16 -0
- package/skills-src/ewai-portfolio/SKILL.md +70 -0
- package/skills-src/ewai-portfolio/agents/openai.yaml +4 -0
- package/skills-src/ewai-portfolio/references/portfolio-contract.md +67 -0
- package/skills-src/ewai-project-discovery/SKILL.md +95 -0
- package/skills-src/ewai-project-discovery/agents/openai.yaml +4 -0
- package/skills-src/ewai-project-discovery/references/discovery-contract.md +34 -0
- package/skills-src/ewai-prototype-iteration/SKILL.md +30 -0
- package/skills-src/ewai-prototype-iteration/agents/openai.yaml +4 -0
- package/skills-src/ewai-prototype-iteration/references/review-contract.md +49 -0
- package/skills-src/ewai-retro/SKILL.md +48 -0
- package/skills-src/ewai-retro/agents/openai.yaml +4 -0
- package/skills-src/ewai-retro/references/asset-routing.md +14 -0
- package/skills-src/ewai-rollout/SKILL.md +74 -0
- package/skills-src/ewai-rollout/agents/openai.yaml +4 -0
- package/skills-src/ewai-rollout/references/rollout-contract.md +74 -0
- package/skills-src/ewai-shape-intents/SKILL.md +84 -0
- package/skills-src/ewai-shape-intents/agents/openai.yaml +4 -0
- package/skills-src/ewai-shape-intents/references/intent-mapping-contract.md +109 -0
- package/skills-src/ewai-solution-readiness/SKILL.md +55 -0
- package/skills-src/ewai-solution-readiness/agents/openai.yaml +4 -0
- package/skills-src/ewai-standards-check/SKILL.md +93 -0
- package/skills-src/ewai-standards-check/agents/openai.yaml +4 -0
- package/skills-src/ewai-standards-check/references/report-contract.md +116 -0
- package/skills-src/ewai-test-scenarios/SKILL.md +94 -0
- package/skills-src/ewai-test-scenarios/agents/openai.yaml +4 -0
- package/skills-src/ewai-test-scenarios/references/scenario-contract.md +88 -0
- package/src/afk-worker.mjs +16 -0
- package/src/archaeology.mjs +1333 -0
- package/src/checkin.mjs +261 -0
- package/src/cli.mjs +2427 -0
- package/src/companion-guidance.mjs +257 -0
- package/src/companion-opening.mjs +62 -0
- package/src/companion.mjs +256 -0
- package/src/context.mjs +210 -0
- package/src/dashboard-preferences.mjs +80 -0
- package/src/delivery-artifacts.mjs +204 -0
- package/src/delivery-documents.mjs +248 -0
- package/src/delivery-gates.mjs +317 -0
- package/src/delivery.mjs +1433 -0
- package/src/design-system-application.mjs +291 -0
- package/src/design-system-authoring.mjs +101 -0
- package/src/design-systems.mjs +466 -0
- package/src/discovery.mjs +1314 -0
- package/src/error-reporting.mjs +323 -0
- package/src/evidence-depth.mjs +543 -0
- package/src/execution-state.mjs +243 -0
- package/src/install.mjs +166 -0
- package/src/intent-dependencies.mjs +117 -0
- package/src/intent-maps.mjs +402 -0
- package/src/intents.mjs +747 -0
- package/src/knowledge-proposals.mjs +717 -0
- package/src/launcher.mjs +51 -0
- package/src/meeting-evidence.mjs +703 -0
- package/src/network-rollout.mjs +386 -0
- package/src/organisation-blueprints.mjs +438 -0
- package/src/organisation-policies.mjs +448 -0
- package/src/packs.mjs +44 -0
- package/src/paths.mjs +62 -0
- package/src/persona-entitlements.mjs +438 -0
- package/src/persona-licence-config.mjs +98 -0
- package/src/persona-website-provider.mjs +134 -0
- package/src/persona-zip.mjs +87 -0
- package/src/personas.mjs +159 -0
- package/src/platform-metadata-analysis.mjs +314 -0
- package/src/policy-design-gates.mjs +623 -0
- package/src/policy-gate-integration.mjs +318 -0
- package/src/portfolio.mjs +509 -0
- package/src/power-platform-source-map.mjs +190 -0
- package/src/project.mjs +449 -0
- package/src/prototype-iterations.mjs +730 -0
- package/src/repository-source-map.mjs +603 -0
- package/src/runtime/afk-conductor.mjs +973 -0
- package/src/runtime/context-assembly.mjs +457 -0
- package/src/runtime/context-benchmarks.mjs +115 -0
- package/src/runtime/dashboard-actions.mjs +109 -0
- package/src/runtime/dashboard-handoffs.mjs +149 -0
- package/src/runtime/dashboard-server.mjs +1272 -0
- package/src/runtime/dashboard.mjs +197 -0
- package/src/runtime/database.mjs +789 -0
- package/src/runtime/error-reporting.mjs +581 -0
- package/src/runtime/evidence-depth-workspace.mjs +412 -0
- package/src/runtime/execution-leases.mjs +299 -0
- package/src/runtime/guided-discovery.mjs +350 -0
- package/src/runtime/guided-intents.mjs +517 -0
- package/src/runtime/impact-analysis.mjs +535 -0
- package/src/runtime/intents.mjs +222 -0
- package/src/runtime/knowledge.mjs +86 -0
- package/src/runtime/lifecycle-hooks.mjs +1239 -0
- package/src/runtime/mcp-config.mjs +110 -0
- package/src/runtime/mcp-server.mjs +1885 -0
- package/src/runtime/palace.mjs +362 -0
- package/src/runtime/paths.mjs +58 -0
- package/src/runtime/persona-engagement.mjs +255 -0
- package/src/runtime/phase-contributions.mjs +594 -0
- package/src/runtime/policy-workspace.mjs +170 -0
- package/src/runtime/prototype-iterations.mjs +235 -0
- package/src/runtime/provider-adapters.mjs +163 -0
- package/src/runtime/repository-index.mjs +838 -0
- package/src/runtime/runs.mjs +185 -0
- package/src/runtime/security-validation.mjs +1230 -0
- package/src/runtime/starter-materialisation.mjs +1155 -0
- package/src/runtime/team-hub-client.mjs +479 -0
- package/src/runtime/team-hub-database.mjs +288 -0
- package/src/runtime/team-hub-server.mjs +191 -0
- package/src/runtime/team-hub.mjs +110 -0
- package/src/runtime/tree-sitter-index.mjs +390 -0
- package/src/runtime/version.mjs +1 -0
- package/src/runtime/work.mjs +633 -0
- package/src/salesforce-source-map.mjs +212 -0
- package/src/security-validation-config.mjs +224 -0
- package/src/solution-readiness.mjs +620 -0
- package/src/starter-materialisation-contract.mjs +407 -0
- package/src/task-graph.mjs +544 -0
- package/src/team-hub-resources.mjs +239 -0
- package/src/team-hub.mjs +242 -0
- package/src/test-scenarios.mjs +622 -0
- package/src/validation-config.mjs +289 -0
- package/templates/SPECS/1.Scope/personas/registry.yaml +12 -0
- package/templates/SPECS/5.Strategy/patterns/context-packet.md +119 -0
- package/templates/SPECS/6.Build/_tracker-template.md +16 -0
- package/templates/SPECS/pipeline.yaml +62 -0
- package/templates/discovery-answers.yaml +86 -0
- package/templates/intent-body.md +21 -0
- 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
|
+
}
|