@rkarim08/sia 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +35 -0
- package/.claude-plugin/plugin.json +27 -0
- package/.mcp.json +13 -0
- package/CLAUDE.md +226 -0
- package/LICENSE +202 -0
- package/PLUGIN_README.md +253 -0
- package/README.md +1013 -0
- package/agents/sia-changelog-writer.md +89 -0
- package/agents/sia-code-reviewer.md +86 -0
- package/agents/sia-conflict-resolver.md +100 -0
- package/agents/sia-convention-enforcer.md +69 -0
- package/agents/sia-debug.md +106 -0
- package/agents/sia-decision-reviewer.md +101 -0
- package/agents/sia-dependency-tracker.md +80 -0
- package/agents/sia-explain.md +126 -0
- package/agents/sia-feature.md +116 -0
- package/agents/sia-knowledge-capture.md +117 -0
- package/agents/sia-lead-architecture-advisor.md +93 -0
- package/agents/sia-lead-team-health.md +107 -0
- package/agents/sia-migration.md +100 -0
- package/agents/sia-onboarding.md +115 -0
- package/agents/sia-orientation.md +99 -0
- package/agents/sia-pm-briefing.md +106 -0
- package/agents/sia-pm-risk-advisor.md +82 -0
- package/agents/sia-qa-analyst.md +116 -0
- package/agents/sia-qa-regression-map.md +94 -0
- package/agents/sia-refactor.md +115 -0
- package/agents/sia-regression.md +112 -0
- package/agents/sia-security-audit.md +125 -0
- package/agents/sia-test-advisor.md +91 -0
- package/hooks/hooks.json +98 -0
- package/migrations/bridge/001_initial.sql +34 -0
- package/migrations/episodic/001_initial.sql +35 -0
- package/migrations/meta/001_initial.sql +68 -0
- package/migrations/semantic/001_initial.sql +292 -0
- package/migrations/semantic/002_ontology.sql +89 -0
- package/migrations/semantic/003_freshness.sql +63 -0
- package/migrations/semantic/004_v5_unified_schema.sql +194 -0
- package/migrations/semantic/005_backfill_event_kinds.sql +8 -0
- package/migrations/semantic/006_tree_sitter.sql +6 -0
- package/migrations/semantic/007_branch_snapshots.sql +22 -0
- package/package.json +110 -0
- package/scripts/branch-switch.sh +13 -0
- package/scripts/build-wasm-grammars.sh +81 -0
- package/scripts/post-compact.sh +8 -0
- package/scripts/post-tool-use.sh +10 -0
- package/scripts/pre-compact.sh +8 -0
- package/scripts/session-end.sh +8 -0
- package/scripts/session-start.sh +8 -0
- package/scripts/start-mcp.ts +45 -0
- package/scripts/stop-hook.sh +8 -0
- package/scripts/user-prompt-submit.sh +8 -0
- package/scripts/viz-server.ts +152 -0
- package/skills/sia-brainstorm/SKILL.md +156 -0
- package/skills/sia-brainstorm/scripts/frame-template.html +214 -0
- package/skills/sia-brainstorm/scripts/helper.js +95 -0
- package/skills/sia-brainstorm/scripts/server.cjs +338 -0
- package/skills/sia-brainstorm/scripts/start-server.sh +153 -0
- package/skills/sia-brainstorm/scripts/stop-server.sh +55 -0
- package/skills/sia-brainstorm/spec-document-reviewer-prompt.md +49 -0
- package/skills/sia-brainstorm/visual-companion.md +286 -0
- package/skills/sia-capture/SKILL.md +64 -0
- package/skills/sia-compare/SKILL.md +33 -0
- package/skills/sia-conflicts/SKILL.md +38 -0
- package/skills/sia-debug-workflow/SKILL.md +120 -0
- package/skills/sia-debug-workflow/root-cause-tracing.md +70 -0
- package/skills/sia-debug-workflow/scripts/find-polluter.sh +64 -0
- package/skills/sia-debug-workflow/temporal-investigation.md +72 -0
- package/skills/sia-digest/SKILL.md +23 -0
- package/skills/sia-dispatch/SKILL.md +69 -0
- package/skills/sia-dispatch/agent-task-template.md +99 -0
- package/skills/sia-doctor/SKILL.md +39 -0
- package/skills/sia-execute/SKILL.md +70 -0
- package/skills/sia-execute-plan/SKILL.md +85 -0
- package/skills/sia-export-import/SKILL.md +49 -0
- package/skills/sia-export-knowledge/SKILL.md +46 -0
- package/skills/sia-finish/SKILL.md +100 -0
- package/skills/sia-finish/pr-summary-template.md +54 -0
- package/skills/sia-freshness/SKILL.md +38 -0
- package/skills/sia-history/SKILL.md +42 -0
- package/skills/sia-impact/SKILL.md +70 -0
- package/skills/sia-index/SKILL.md +54 -0
- package/skills/sia-install/SKILL.md +39 -0
- package/skills/sia-lead-compliance/SKILL.md +16 -0
- package/skills/sia-lead-drift-report/SKILL.md +16 -0
- package/skills/sia-lead-knowledge-map/SKILL.md +16 -0
- package/skills/sia-learn/SKILL.md +58 -0
- package/skills/sia-plan/SKILL.md +68 -0
- package/skills/sia-plan/plan-reviewer-prompt.md +63 -0
- package/skills/sia-playbooks/SKILL.md +29 -0
- package/skills/sia-playbooks/reference-feature.md +100 -0
- package/skills/sia-playbooks/reference-flagging.md +50 -0
- package/skills/sia-playbooks/reference-orientation.md +92 -0
- package/skills/sia-playbooks/reference-regression.md +115 -0
- package/skills/sia-playbooks/reference-review.md +64 -0
- package/skills/sia-playbooks/reference-tools.md +239 -0
- package/skills/sia-pm-decision-log/SKILL.md +28 -0
- package/skills/sia-pm-risk-dashboard/SKILL.md +24 -0
- package/skills/sia-pm-sprint-summary/SKILL.md +27 -0
- package/skills/sia-prune/SKILL.md +45 -0
- package/skills/sia-qa-coverage/SKILL.md +28 -0
- package/skills/sia-qa-flaky/SKILL.md +20 -0
- package/skills/sia-qa-report/SKILL.md +26 -0
- package/skills/sia-reindex/SKILL.md +30 -0
- package/skills/sia-review-respond/SKILL.md +88 -0
- package/skills/sia-review-respond/pushback-patterns.md +90 -0
- package/skills/sia-search/SKILL.md +47 -0
- package/skills/sia-setup/SKILL.md +82 -0
- package/skills/sia-setup/setup-checklist.md +97 -0
- package/skills/sia-stats/SKILL.md +36 -0
- package/skills/sia-status/SKILL.md +44 -0
- package/skills/sia-sync/SKILL.md +46 -0
- package/skills/sia-team/SKILL.md +64 -0
- package/skills/sia-test/SKILL.md +92 -0
- package/skills/sia-test/testing-anti-patterns.md +104 -0
- package/skills/sia-tour/SKILL.md +29 -0
- package/skills/sia-upgrade/SKILL.md +43 -0
- package/skills/sia-verify/SKILL.md +81 -0
- package/skills/sia-visualize/SKILL.md +28 -0
- package/skills/sia-visualize-live/SKILL.md +55 -0
- package/skills/sia-visualize-live/scripts/graph-template.html +389 -0
- package/skills/sia-visualize-live/scripts/start-visualizer.sh +161 -0
- package/skills/sia-visualize-live/scripts/stop-visualizer.sh +55 -0
- package/skills/sia-visualize-live/scripts/visualizer-server.cjs +264 -0
- package/skills/sia-workspace/SKILL.md +57 -0
- package/src/agent/claude-md-template-flagging.md +219 -0
- package/src/agent/claude-md-template.md +213 -0
- package/src/agent/modules/sia-feature.md +100 -0
- package/src/agent/modules/sia-flagging.md +50 -0
- package/src/agent/modules/sia-orientation.md +92 -0
- package/src/agent/modules/sia-regression.md +115 -0
- package/src/agent/modules/sia-review.md +64 -0
- package/src/agent/modules/sia-tools.md +239 -0
- package/src/ast/extractors/c-include.ts +189 -0
- package/src/ast/extractors/csharp-project.ts +260 -0
- package/src/ast/extractors/prisma-schema.ts +44 -0
- package/src/ast/extractors/project-manifest.ts +111 -0
- package/src/ast/extractors/sql-schema.ts +67 -0
- package/src/ast/extractors/tier-a.ts +423 -0
- package/src/ast/extractors/tier-b.ts +289 -0
- package/src/ast/extractors/tier-dispatch.ts +247 -0
- package/src/ast/index-worker.ts +108 -0
- package/src/ast/indexer.ts +484 -0
- package/src/ast/languages.ts +408 -0
- package/src/ast/pagerank-builder.ts +125 -0
- package/src/ast/path-utils.ts +137 -0
- package/src/ast/tree-sitter/backends/native.ts +57 -0
- package/src/ast/tree-sitter/backends/wasm.ts +39 -0
- package/src/ast/tree-sitter/call-walker.ts +44 -0
- package/src/ast/tree-sitter/edit-computer.ts +55 -0
- package/src/ast/tree-sitter/query-runner.ts +46 -0
- package/src/ast/tree-sitter/service.ts +174 -0
- package/src/ast/tree-sitter/tree-cache.ts +39 -0
- package/src/ast/tree-sitter/types.ts +79 -0
- package/src/ast/watcher.ts +322 -0
- package/src/capture/chunker.ts +169 -0
- package/src/capture/consolidate.ts +127 -0
- package/src/capture/edge-inferrer.ts +161 -0
- package/src/capture/embedder.ts +166 -0
- package/src/capture/embedding-cache.ts +73 -0
- package/src/capture/flag-processor.ts +64 -0
- package/src/capture/hook.ts +67 -0
- package/src/capture/pipeline.ts +450 -0
- package/src/capture/prompts/consolidate.ts +25 -0
- package/src/capture/prompts/edge-infer.ts +29 -0
- package/src/capture/prompts/extract-flagged.ts +36 -0
- package/src/capture/prompts/extract.ts +42 -0
- package/src/capture/tokenizer.ts +147 -0
- package/src/capture/track-a-ast.ts +93 -0
- package/src/capture/track-b-llm.ts +149 -0
- package/src/capture/types.ts +64 -0
- package/src/cli/commands/community.ts +137 -0
- package/src/cli/commands/compare.ts +123 -0
- package/src/cli/commands/conflicts.ts +41 -0
- package/src/cli/commands/digest.ts +197 -0
- package/src/cli/commands/disable-flagging.ts +34 -0
- package/src/cli/commands/doctor.ts +240 -0
- package/src/cli/commands/download-model.ts +161 -0
- package/src/cli/commands/enable-flagging.ts +34 -0
- package/src/cli/commands/export-knowledge.ts +208 -0
- package/src/cli/commands/export.ts +85 -0
- package/src/cli/commands/freshness.ts +164 -0
- package/src/cli/commands/graph.ts +51 -0
- package/src/cli/commands/history.ts +139 -0
- package/src/cli/commands/import.ts +335 -0
- package/src/cli/commands/install.ts +156 -0
- package/src/cli/commands/lead-report.ts +241 -0
- package/src/cli/commands/learn.ts +321 -0
- package/src/cli/commands/pm-report.ts +413 -0
- package/src/cli/commands/prune.ts +75 -0
- package/src/cli/commands/qa-report.ts +278 -0
- package/src/cli/commands/reindex.ts +104 -0
- package/src/cli/commands/rollback.ts +70 -0
- package/src/cli/commands/search.ts +103 -0
- package/src/cli/commands/server.ts +91 -0
- package/src/cli/commands/share.ts +33 -0
- package/src/cli/commands/stats.ts +79 -0
- package/src/cli/commands/status.ts +176 -0
- package/src/cli/commands/sync.ts +96 -0
- package/src/cli/commands/team.ts +118 -0
- package/src/cli/commands/tour.ts +157 -0
- package/src/cli/commands/visualize-live.ts +162 -0
- package/src/cli/commands/workspace.ts +117 -0
- package/src/cli/index.ts +424 -0
- package/src/cli/learn-progress.ts +87 -0
- package/src/community/detection-bridge.ts +344 -0
- package/src/community/leiden.ts +462 -0
- package/src/community/raptor.ts +210 -0
- package/src/community/scheduler.ts +74 -0
- package/src/community/summarize.ts +115 -0
- package/src/decay/archiver.ts +73 -0
- package/src/decay/bridge-orphan-cleanup.ts +212 -0
- package/src/decay/consolidation-sweep.ts +112 -0
- package/src/decay/decay.ts +116 -0
- package/src/decay/deep-validator.ts +62 -0
- package/src/decay/episodic-promoter.ts +132 -0
- package/src/decay/maintenance-scheduler.ts +326 -0
- package/src/decay/scheduler.ts +6 -0
- package/src/decay/session-sweeper.ts +79 -0
- package/src/decay/types.ts +17 -0
- package/src/freshness/confidence-decay.ts +122 -0
- package/src/freshness/cuckoo-filter.ts +176 -0
- package/src/freshness/deep-validation.ts +345 -0
- package/src/freshness/dirty-tracker.ts +237 -0
- package/src/freshness/file-watcher-layer.ts +119 -0
- package/src/freshness/firewall.ts +64 -0
- package/src/freshness/git-reconcile-layer.ts +161 -0
- package/src/freshness/inverted-index.ts +158 -0
- package/src/freshness/stale-read-layer.ts +222 -0
- package/src/graph/audit.ts +69 -0
- package/src/graph/bridge-db.ts +141 -0
- package/src/graph/communities.ts +195 -0
- package/src/graph/db-interface.ts +259 -0
- package/src/graph/edges.ts +163 -0
- package/src/graph/entities.ts +327 -0
- package/src/graph/episodic-db.ts +113 -0
- package/src/graph/flags.ts +31 -0
- package/src/graph/meta-db.ts +200 -0
- package/src/graph/semantic-db.ts +101 -0
- package/src/graph/session-resume.ts +56 -0
- package/src/graph/snapshots.ts +342 -0
- package/src/graph/staging.ts +151 -0
- package/src/graph/types.ts +128 -0
- package/src/hooks/adapters/claude-code.ts +21 -0
- package/src/hooks/adapters/cline.ts +43 -0
- package/src/hooks/adapters/cursor.ts +65 -0
- package/src/hooks/adapters/generic.ts +12 -0
- package/src/hooks/agent-detect.ts +34 -0
- package/src/hooks/claude-md-directives.ts +32 -0
- package/src/hooks/event-router.ts +182 -0
- package/src/hooks/extractors/pattern-detector.ts +111 -0
- package/src/hooks/handlers/post-compact.ts +30 -0
- package/src/hooks/handlers/post-tool-use.ts +403 -0
- package/src/hooks/handlers/pre-compact.ts +100 -0
- package/src/hooks/handlers/session-end.ts +47 -0
- package/src/hooks/handlers/session-start.ts +154 -0
- package/src/hooks/handlers/stop.ts +128 -0
- package/src/hooks/handlers/user-prompt-submit.ts +68 -0
- package/src/hooks/plugin-branch-switch.ts +68 -0
- package/src/hooks/plugin-common.ts +47 -0
- package/src/hooks/plugin-post-compact.ts +28 -0
- package/src/hooks/plugin-post-tool-use.ts +38 -0
- package/src/hooks/plugin-pre-compact.ts +37 -0
- package/src/hooks/plugin-session-end.ts +37 -0
- package/src/hooks/plugin-session-start.ts +75 -0
- package/src/hooks/plugin-stop.ts +61 -0
- package/src/hooks/plugin-user-prompt-submit.ts +47 -0
- package/src/hooks/types.ts +43 -0
- package/src/knowledge/discovery.ts +238 -0
- package/src/knowledge/external-refs.ts +98 -0
- package/src/knowledge/freshness.ts +221 -0
- package/src/knowledge/ingest.ts +330 -0
- package/src/knowledge/markdown-export.ts +229 -0
- package/src/knowledge/markdown-import.ts +359 -0
- package/src/knowledge/patterns.ts +74 -0
- package/src/knowledge/templates.ts +307 -0
- package/src/llm/ai-sdk-adapter.ts +46 -0
- package/src/llm/config.ts +88 -0
- package/src/llm/cost-tracker.ts +110 -0
- package/src/llm/prompts/extraction.ts +55 -0
- package/src/llm/prompts/summarization.ts +36 -0
- package/src/llm/prompts/validation.ts +37 -0
- package/src/llm/provider-registry.ts +68 -0
- package/src/llm/reliability.ts +179 -0
- package/src/llm/schemas.ts +52 -0
- package/src/mcp/freshness-annotator.ts +69 -0
- package/src/mcp/server.ts +949 -0
- package/src/mcp/tools/sia-ast-query.ts +225 -0
- package/src/mcp/tools/sia-at-time.ts +151 -0
- package/src/mcp/tools/sia-backlinks.ts +87 -0
- package/src/mcp/tools/sia-batch-execute.ts +169 -0
- package/src/mcp/tools/sia-by-file.ts +89 -0
- package/src/mcp/tools/sia-community.ts +113 -0
- package/src/mcp/tools/sia-doctor.ts +73 -0
- package/src/mcp/tools/sia-execute-file.ts +122 -0
- package/src/mcp/tools/sia-execute.ts +104 -0
- package/src/mcp/tools/sia-expand.ts +158 -0
- package/src/mcp/tools/sia-fetch-and-index.ts +241 -0
- package/src/mcp/tools/sia-flag.ts +65 -0
- package/src/mcp/tools/sia-index.ts +111 -0
- package/src/mcp/tools/sia-note.ts +134 -0
- package/src/mcp/tools/sia-search.ts +105 -0
- package/src/mcp/tools/sia-stats.ts +63 -0
- package/src/mcp/tools/sia-sync-status.ts +44 -0
- package/src/mcp/tools/sia-upgrade.ts +247 -0
- package/src/mcp/truncate.ts +231 -0
- package/src/native/bridge.ts +167 -0
- package/src/native/fallback-ast-diff.ts +144 -0
- package/src/native/fallback-graph.ts +325 -0
- package/src/ontology/constraints.ts +56 -0
- package/src/ontology/errors.ts +8 -0
- package/src/ontology/middleware.ts +266 -0
- package/src/retrieval/bm25-search.ts +151 -0
- package/src/retrieval/context-assembly.ts +76 -0
- package/src/retrieval/graph-traversal.ts +168 -0
- package/src/retrieval/pagerank.ts +40 -0
- package/src/retrieval/query-classifier.ts +106 -0
- package/src/retrieval/reranker.ts +156 -0
- package/src/retrieval/search.ts +236 -0
- package/src/retrieval/throttle.ts +102 -0
- package/src/retrieval/vector-search.ts +203 -0
- package/src/retrieval/workspace-search.ts +130 -0
- package/src/sandbox/context-mode.ts +285 -0
- package/src/sandbox/credential-pass.ts +55 -0
- package/src/sandbox/executor.ts +235 -0
- package/src/security/pattern-detector.ts +127 -0
- package/src/security/rule-of-two.ts +50 -0
- package/src/security/sanitize.ts +46 -0
- package/src/security/semantic-consistency.ts +93 -0
- package/src/security/staging-promoter.ts +154 -0
- package/src/shared/config.ts +302 -0
- package/src/shared/diagnostics.ts +210 -0
- package/src/shared/errors.ts +48 -0
- package/src/shared/git-utils.ts +143 -0
- package/src/shared/llm-client.ts +120 -0
- package/src/shared/logger.ts +99 -0
- package/src/shared/types.ts +79 -0
- package/src/sync/client.ts +43 -0
- package/src/sync/conflict.ts +106 -0
- package/src/sync/dedup.ts +183 -0
- package/src/sync/hlc.ts +117 -0
- package/src/sync/keychain.ts +144 -0
- package/src/sync/pull.ts +232 -0
- package/src/sync/push.ts +131 -0
- package/src/types/chokidar.d.ts +23 -0
- package/src/visualization/graph-renderer.ts +312 -0
- package/src/visualization/subgraph-extract.ts +208 -0
- package/src/visualization/views/community-clusters.ts +246 -0
- package/src/visualization/views/dependency-map.ts +189 -0
- package/src/visualization/views/graph-explorer.ts +364 -0
- package/src/visualization/views/timeline.ts +247 -0
- package/src/workspace/api-contracts.ts +226 -0
- package/src/workspace/cross-repo.ts +61 -0
- package/src/workspace/detector.ts +190 -0
- package/src/workspace/manifest.ts +141 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// Module: api-contracts — API contract auto-detection scanner
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
4
|
+
import { join, relative } from "node:path";
|
|
5
|
+
import type { SiaDb } from "@/graph/db-interface";
|
|
6
|
+
|
|
7
|
+
export interface DetectedContract {
|
|
8
|
+
type: string;
|
|
9
|
+
specPath: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Scan a repository directory for API contracts.
|
|
14
|
+
* Returns detected contracts — does NOT write to the database.
|
|
15
|
+
*/
|
|
16
|
+
export async function detectApiContracts(repoPath: string): Promise<DetectedContract[]> {
|
|
17
|
+
const contracts: DetectedContract[] = [];
|
|
18
|
+
|
|
19
|
+
// OpenAPI / Swagger
|
|
20
|
+
for (const name of ["openapi.yaml", "openapi.json", "swagger.yaml", "swagger.json"]) {
|
|
21
|
+
if (existsSync(join(repoPath, name))) {
|
|
22
|
+
contracts.push({ type: "openapi", specPath: name });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// GraphQL — root level
|
|
27
|
+
const rootFiles = safeReaddir(repoPath);
|
|
28
|
+
for (const entry of rootFiles) {
|
|
29
|
+
if (entry.isFile() && entry.name.endsWith(".graphql")) {
|
|
30
|
+
contracts.push({ type: "graphql", specPath: entry.name });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// GraphQL — recursive (depth 3)
|
|
34
|
+
findFilesRecursive(repoPath, ".graphql", contracts, repoPath, 3);
|
|
35
|
+
|
|
36
|
+
// TypeScript project references
|
|
37
|
+
const tsconfigPath = join(repoPath, "tsconfig.json");
|
|
38
|
+
if (existsSync(tsconfigPath)) {
|
|
39
|
+
try {
|
|
40
|
+
const tsconfig = JSON.parse(readFileSync(tsconfigPath, "utf-8"));
|
|
41
|
+
if (Array.isArray(tsconfig.references)) {
|
|
42
|
+
for (const ref of tsconfig.references) {
|
|
43
|
+
if (typeof ref.path === "string") {
|
|
44
|
+
contracts.push({ type: "ts-reference", specPath: ref.path });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
/* ignore */
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// C# .csproj ProjectReference
|
|
54
|
+
for (const entry of rootFiles) {
|
|
55
|
+
if (entry.isFile() && entry.name.endsWith(".csproj")) {
|
|
56
|
+
try {
|
|
57
|
+
const content = readFileSync(join(repoPath, entry.name), "utf-8");
|
|
58
|
+
const refPattern = /ProjectReference\s+Include="([^"]+)"/g;
|
|
59
|
+
let match: RegExpExecArray | null = refPattern.exec(content);
|
|
60
|
+
while (match !== null) {
|
|
61
|
+
contracts.push({ type: "csproj-reference", specPath: match[1] });
|
|
62
|
+
match = refPattern.exec(content);
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
/* ignore */
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Cargo.toml workspace members
|
|
71
|
+
const cargoPath = join(repoPath, "Cargo.toml");
|
|
72
|
+
if (existsSync(cargoPath)) {
|
|
73
|
+
try {
|
|
74
|
+
const content = readFileSync(cargoPath, "utf-8");
|
|
75
|
+
const membersMatch = content.match(/members\s*=\s*\[([^\]]+)\]/);
|
|
76
|
+
if (membersMatch) {
|
|
77
|
+
const members = membersMatch[1]
|
|
78
|
+
.split(",")
|
|
79
|
+
.map((m) => m.trim().replace(/^["']|["']$/g, ""))
|
|
80
|
+
.filter(Boolean);
|
|
81
|
+
for (const member of members) {
|
|
82
|
+
contracts.push({ type: "cargo-dependency", specPath: member });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
/* ignore */
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// go.mod replace directives
|
|
91
|
+
const goModPath = join(repoPath, "go.mod");
|
|
92
|
+
if (existsSync(goModPath)) {
|
|
93
|
+
try {
|
|
94
|
+
const content = readFileSync(goModPath, "utf-8");
|
|
95
|
+
const replacePattern = /replace\s+\S+\s+=>\s+(\S+)/g;
|
|
96
|
+
let match: RegExpExecArray | null = replacePattern.exec(content);
|
|
97
|
+
while (match !== null) {
|
|
98
|
+
contracts.push({ type: "go-mod-replace", specPath: match[1] });
|
|
99
|
+
match = replacePattern.exec(content);
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
/* ignore */
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// pyproject.toml path dependencies
|
|
107
|
+
const pyprojectPath = join(repoPath, "pyproject.toml");
|
|
108
|
+
if (existsSync(pyprojectPath)) {
|
|
109
|
+
try {
|
|
110
|
+
const content = readFileSync(pyprojectPath, "utf-8");
|
|
111
|
+
const pathPattern = /path\s*=\s*"([^"]+)"/g;
|
|
112
|
+
let match: RegExpExecArray | null = pathPattern.exec(content);
|
|
113
|
+
while (match !== null) {
|
|
114
|
+
contracts.push({ type: "python-path-dep", specPath: match[1] });
|
|
115
|
+
match = pathPattern.exec(content);
|
|
116
|
+
}
|
|
117
|
+
} catch {
|
|
118
|
+
/* ignore */
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Gradle settings.gradle / settings.gradle.kts
|
|
123
|
+
for (const name of ["settings.gradle", "settings.gradle.kts"]) {
|
|
124
|
+
const gradlePath = join(repoPath, name);
|
|
125
|
+
if (existsSync(gradlePath)) {
|
|
126
|
+
try {
|
|
127
|
+
const content = readFileSync(gradlePath, "utf-8");
|
|
128
|
+
const includePattern = /include\s*\(?([^\n)]+)\)?/g;
|
|
129
|
+
let lineMatch: RegExpExecArray | null = includePattern.exec(content);
|
|
130
|
+
while (lineMatch !== null) {
|
|
131
|
+
const parts = lineMatch[1].split(",");
|
|
132
|
+
for (const part of parts) {
|
|
133
|
+
const cleaned = part
|
|
134
|
+
.trim()
|
|
135
|
+
.replace(/^\(/, "")
|
|
136
|
+
.replace(/\)$/, "")
|
|
137
|
+
.replace(/^['"]/, "")
|
|
138
|
+
.replace(/['"]$/, "");
|
|
139
|
+
if (cleaned.startsWith(":")) {
|
|
140
|
+
contracts.push({
|
|
141
|
+
type: "gradle-project",
|
|
142
|
+
specPath: cleaned.slice(1).replace(/:/g, "/"),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
lineMatch = includePattern.exec(content);
|
|
147
|
+
}
|
|
148
|
+
} catch {
|
|
149
|
+
/* ignore */
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return contracts;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function safeReaddir(dirPath: string) {
|
|
158
|
+
try {
|
|
159
|
+
return readdirSync(dirPath, { withFileTypes: true });
|
|
160
|
+
} catch {
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Recursively find files by extension, avoiding duplicates with root-level scan. */
|
|
166
|
+
function findFilesRecursive(
|
|
167
|
+
dir: string,
|
|
168
|
+
ext: string,
|
|
169
|
+
contracts: DetectedContract[],
|
|
170
|
+
rootDir: string,
|
|
171
|
+
maxDepth: number,
|
|
172
|
+
): void {
|
|
173
|
+
if (maxDepth <= 0) return;
|
|
174
|
+
const entries = safeReaddir(dir);
|
|
175
|
+
for (const entry of entries) {
|
|
176
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
177
|
+
if (entry.isDirectory()) {
|
|
178
|
+
const childDir = join(dir, entry.name);
|
|
179
|
+
const childEntries = safeReaddir(childDir);
|
|
180
|
+
for (const child of childEntries) {
|
|
181
|
+
if (child.isFile() && child.name.endsWith(ext)) {
|
|
182
|
+
const relPath = relative(rootDir, join(childDir, child.name));
|
|
183
|
+
// Don't duplicate root-level files
|
|
184
|
+
if (!contracts.some((c) => c.specPath === relPath)) {
|
|
185
|
+
contracts.push({ type: "graphql", specPath: relPath });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
findFilesRecursive(childDir, ext, contracts, rootDir, maxDepth - 1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Write detected contracts to api_contracts in meta.db with trust_tier=2.
|
|
196
|
+
* Idempotent: upserts by (provider_repo_id, contract_type, spec_path).
|
|
197
|
+
*/
|
|
198
|
+
export async function writeDetectedContracts(
|
|
199
|
+
db: SiaDb,
|
|
200
|
+
providerRepoId: string,
|
|
201
|
+
contracts: DetectedContract[],
|
|
202
|
+
): Promise<void> {
|
|
203
|
+
const now = Date.now();
|
|
204
|
+
|
|
205
|
+
for (const contract of contracts) {
|
|
206
|
+
const existing = await db.execute(
|
|
207
|
+
`SELECT id FROM api_contracts
|
|
208
|
+
WHERE provider_repo_id = ? AND contract_type = ? AND spec_path = ?`,
|
|
209
|
+
[providerRepoId, contract.type, contract.specPath],
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
if (existing.rows.length > 0) {
|
|
213
|
+
await db.execute("UPDATE api_contracts SET detected_at = ? WHERE id = ?", [
|
|
214
|
+
now,
|
|
215
|
+
existing.rows[0]?.id as string,
|
|
216
|
+
]);
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
await db.execute(
|
|
221
|
+
`INSERT INTO api_contracts (id, provider_repo_id, consumer_repo_id, contract_type, spec_path, trust_tier, detected_at)
|
|
222
|
+
VALUES (?, ?, ?, ?, ?, 2, ?)`,
|
|
223
|
+
[randomUUID(), providerRepoId, providerRepoId, contract.type, contract.specPath, now],
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Module: cross-repo — ATTACH/DETACH helpers and peer repo discovery
|
|
2
|
+
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import type { SiaDb } from "@/graph/db-interface";
|
|
6
|
+
import { getWorkspaceRepos } from "@/graph/meta-db";
|
|
7
|
+
import { SIA_HOME } from "@/shared/config";
|
|
8
|
+
|
|
9
|
+
/** Info about a peer repo that can be ATTACHed for workspace search. */
|
|
10
|
+
export interface PeerRepo {
|
|
11
|
+
repoId: string;
|
|
12
|
+
graphDbPath: string;
|
|
13
|
+
name: string | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Attach a peer repository database under the given alias.
|
|
18
|
+
*
|
|
19
|
+
* Uses a plain ATTACH DATABASE statement. Does NOT set WAL pragma on the
|
|
20
|
+
* attached connection — the caller opens it read-only.
|
|
21
|
+
*/
|
|
22
|
+
export async function attachPeerRepo(db: SiaDb, peerDbPath: string, alias: string): Promise<void> {
|
|
23
|
+
await db.execute(`ATTACH DATABASE ? AS ${alias}`, [peerDbPath]);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Detach a previously attached peer database.
|
|
28
|
+
*/
|
|
29
|
+
export async function detachPeerRepo(db: SiaDb, alias: string): Promise<void> {
|
|
30
|
+
await db.execute(`DETACH DATABASE ${alias}`, []);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get peer repos for workspace search (all workspace repos except primaryRepoId).
|
|
35
|
+
* Only returns peers whose graph.db file exists on disk.
|
|
36
|
+
*/
|
|
37
|
+
export async function getPeerRepos(
|
|
38
|
+
metaDb: SiaDb,
|
|
39
|
+
workspaceId: string,
|
|
40
|
+
primaryRepoId: string,
|
|
41
|
+
siaHome: string = SIA_HOME,
|
|
42
|
+
): Promise<PeerRepo[]> {
|
|
43
|
+
const allRepos = await getWorkspaceRepos(metaDb, workspaceId);
|
|
44
|
+
const peers: PeerRepo[] = [];
|
|
45
|
+
|
|
46
|
+
for (const repo of allRepos) {
|
|
47
|
+
const repoId = repo.id as string;
|
|
48
|
+
if (repoId === primaryRepoId) continue;
|
|
49
|
+
|
|
50
|
+
const graphDbPath = join(siaHome, "repos", repoId, "graph.db");
|
|
51
|
+
if (existsSync(graphDbPath)) {
|
|
52
|
+
peers.push({
|
|
53
|
+
repoId,
|
|
54
|
+
graphDbPath,
|
|
55
|
+
name: (repo.name as string | null) ?? (repo.path as string | null) ?? null,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return peers;
|
|
61
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// Module: detector — Monorepo auto-detection and package registration
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
4
|
+
import { join, relative, resolve } from "node:path";
|
|
5
|
+
import type { SiaDb } from "@/graph/db-interface";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Detect monorepo packages from the repo root.
|
|
9
|
+
* Detection precedence:
|
|
10
|
+
* 1. pnpm-workspace.yaml
|
|
11
|
+
* 2. package.json "workspaces"
|
|
12
|
+
* 3. nx.json + project.json files
|
|
13
|
+
* 4. settings.gradle / settings.gradle.kts
|
|
14
|
+
* Turborepo (turbo.json) is informational only.
|
|
15
|
+
*/
|
|
16
|
+
export async function detectMonorepoPackages(repoRoot: string): Promise<string[]> {
|
|
17
|
+
// 1. pnpm-workspace.yaml
|
|
18
|
+
const pnpmPath = join(repoRoot, "pnpm-workspace.yaml");
|
|
19
|
+
if (existsSync(pnpmPath)) {
|
|
20
|
+
const content = readFileSync(pnpmPath, "utf-8");
|
|
21
|
+
const patterns = parsePnpmWorkspace(content);
|
|
22
|
+
if (patterns.length > 0) return expandGlobs(repoRoot, patterns);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 2. package.json "workspaces"
|
|
26
|
+
const pkgJsonPath = join(repoRoot, "package.json");
|
|
27
|
+
if (existsSync(pkgJsonPath)) {
|
|
28
|
+
try {
|
|
29
|
+
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
30
|
+
const workspaces = pkgJson?.workspaces;
|
|
31
|
+
if (workspaces) {
|
|
32
|
+
const patterns = Array.isArray(workspaces) ? workspaces : (workspaces.packages ?? []);
|
|
33
|
+
if (patterns.length > 0) return expandGlobs(repoRoot, patterns as string[]);
|
|
34
|
+
}
|
|
35
|
+
} catch {
|
|
36
|
+
/* ignore malformed package.json */
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 3. Nx (nx.json + project.json files in subdirs)
|
|
41
|
+
if (existsSync(join(repoRoot, "nx.json"))) {
|
|
42
|
+
return findProjectJsonRoots(repoRoot);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 4. Gradle (settings.gradle or settings.gradle.kts)
|
|
46
|
+
for (const name of ["settings.gradle", "settings.gradle.kts"]) {
|
|
47
|
+
const gradlePath = join(repoRoot, name);
|
|
48
|
+
if (existsSync(gradlePath)) {
|
|
49
|
+
const content = readFileSync(gradlePath, "utf-8");
|
|
50
|
+
const includes = parseGradleIncludes(content);
|
|
51
|
+
if (includes.length > 0) return includes;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Turborepo: informational only
|
|
56
|
+
if (existsSync(join(repoRoot, "turbo.json"))) {
|
|
57
|
+
console.info(
|
|
58
|
+
"Turborepo project detected; package paths sourced from underlying package manager",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parsePnpmWorkspace(content: string): string[] {
|
|
66
|
+
const patterns: string[] = [];
|
|
67
|
+
let inPackages = false;
|
|
68
|
+
|
|
69
|
+
for (const line of content.split("\n")) {
|
|
70
|
+
const trimmed = line.trim();
|
|
71
|
+
if (/^packages:\s*$/.test(trimmed) || trimmed === "packages:") {
|
|
72
|
+
inPackages = true;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (inPackages) {
|
|
76
|
+
if (trimmed.startsWith("- ")) {
|
|
77
|
+
const pattern = trimmed.slice(2).trim().replace(/^['"]/, "").replace(/['"]$/, "");
|
|
78
|
+
if (pattern) patterns.push(pattern);
|
|
79
|
+
} else if (trimmed && !trimmed.startsWith("#")) {
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return patterns;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function expandGlobs(repoRoot: string, patterns: string[]): string[] {
|
|
88
|
+
const results: string[] = [];
|
|
89
|
+
for (const pattern of patterns) {
|
|
90
|
+
if (pattern.endsWith("/*")) {
|
|
91
|
+
const prefix = pattern.slice(0, -2);
|
|
92
|
+
const parentDir = join(repoRoot, prefix);
|
|
93
|
+
if (existsSync(parentDir)) {
|
|
94
|
+
const entries = readdirSync(parentDir, { withFileTypes: true });
|
|
95
|
+
for (const entry of entries) {
|
|
96
|
+
if (entry.isDirectory()) {
|
|
97
|
+
results.push(`${prefix}/${entry.name}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
if (existsSync(join(repoRoot, pattern))) {
|
|
103
|
+
results.push(pattern);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return results.sort();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function findProjectJsonRoots(repoRoot: string): string[] {
|
|
111
|
+
const results: string[] = [];
|
|
112
|
+
walkForFile(repoRoot, "project.json", results, repoRoot, 3);
|
|
113
|
+
return results.sort();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function walkForFile(
|
|
117
|
+
dir: string,
|
|
118
|
+
filename: string,
|
|
119
|
+
results: string[],
|
|
120
|
+
rootDir: string,
|
|
121
|
+
maxDepth: number,
|
|
122
|
+
): void {
|
|
123
|
+
if (maxDepth <= 0) return;
|
|
124
|
+
let entries: import("node:fs").Dirent[];
|
|
125
|
+
try {
|
|
126
|
+
entries = readdirSync(dir, { withFileTypes: true }) as import("node:fs").Dirent[];
|
|
127
|
+
} catch {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
for (const entry of entries) {
|
|
131
|
+
if ((entry.name as string) === "node_modules" || (entry.name as string) === ".git") continue;
|
|
132
|
+
if (entry.isDirectory()) {
|
|
133
|
+
const childPath = join(dir, entry.name as string);
|
|
134
|
+
if (existsSync(join(childPath, filename))) {
|
|
135
|
+
results.push(relative(rootDir, childPath));
|
|
136
|
+
}
|
|
137
|
+
walkForFile(childPath, filename, results, rootDir, maxDepth - 1);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function parseGradleIncludes(content: string): string[] {
|
|
143
|
+
const results: string[] = [];
|
|
144
|
+
const includePattern = /include\s*\(?([^\n)]+)\)?/g;
|
|
145
|
+
let lineMatch: RegExpExecArray | null = includePattern.exec(content);
|
|
146
|
+
while (lineMatch !== null) {
|
|
147
|
+
const parts = lineMatch[1].split(",");
|
|
148
|
+
for (const part of parts) {
|
|
149
|
+
const cleaned = part
|
|
150
|
+
.trim()
|
|
151
|
+
.replace(/^\(/, "")
|
|
152
|
+
.replace(/\)$/, "")
|
|
153
|
+
.replace(/^['"]/, "")
|
|
154
|
+
.replace(/['"]$/, "");
|
|
155
|
+
if (cleaned.startsWith(":")) {
|
|
156
|
+
results.push(cleaned.slice(1).replace(/:/g, "/"));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
lineMatch = includePattern.exec(content);
|
|
160
|
+
}
|
|
161
|
+
return [...new Set(results)].sort();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Register detected monorepo packages in meta.db.
|
|
166
|
+
*/
|
|
167
|
+
export async function registerMonorepoPackages(
|
|
168
|
+
db: SiaDb,
|
|
169
|
+
rootRepoId: string,
|
|
170
|
+
rootPath: string,
|
|
171
|
+
packagePaths: string[],
|
|
172
|
+
): Promise<void> {
|
|
173
|
+
await db.execute("UPDATE repos SET detected_type = 'monorepo_root' WHERE id = ?", [rootRepoId]);
|
|
174
|
+
|
|
175
|
+
for (const pkgPath of packagePaths) {
|
|
176
|
+
const fullPath = resolve(rootPath, pkgPath);
|
|
177
|
+
const id = createHash("sha256").update(fullPath).digest("hex");
|
|
178
|
+
const now = Date.now();
|
|
179
|
+
|
|
180
|
+
await db.execute(
|
|
181
|
+
`INSERT INTO repos (id, path, name, detected_type, monorepo_root_id, created_at, last_accessed)
|
|
182
|
+
VALUES (?, ?, ?, 'monorepo_package', ?, ?, ?)
|
|
183
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
184
|
+
detected_type = 'monorepo_package',
|
|
185
|
+
monorepo_root_id = ?,
|
|
186
|
+
last_accessed = ?`,
|
|
187
|
+
[id, fullPath, pkgPath, rootRepoId, now, now, rootRepoId, now],
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Module: manifest — .sia-manifest.yaml parser and contract writer
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { parse as parseYaml } from "yaml";
|
|
4
|
+
import type { SiaDb } from "@/graph/db-interface";
|
|
5
|
+
|
|
6
|
+
export interface ManifestContract {
|
|
7
|
+
type: string;
|
|
8
|
+
path?: string;
|
|
9
|
+
package?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SiaManifest {
|
|
13
|
+
provides: ManifestContract[];
|
|
14
|
+
consumes: ManifestContract[];
|
|
15
|
+
depends_on: ManifestContract[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Parse a .sia-manifest.yaml string into a SiaManifest.
|
|
20
|
+
* Returns null on malformed YAML (logs warning, never throws).
|
|
21
|
+
*/
|
|
22
|
+
export function parseManifest(content: string): SiaManifest | null {
|
|
23
|
+
let doc: Record<string, unknown>;
|
|
24
|
+
try {
|
|
25
|
+
doc = parseYaml(content) as Record<string, unknown>;
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.warn("sia-manifest.yaml: malformed YAML, skipping", err);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!doc || typeof doc !== "object") {
|
|
32
|
+
return { provides: [], consumes: [], depends_on: [] };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
provides: parseContractList(doc.provides),
|
|
37
|
+
consumes: parseContractList(doc.consumes),
|
|
38
|
+
depends_on: parseContractList(doc.depends_on),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseContractList(raw: unknown): ManifestContract[] {
|
|
43
|
+
if (!Array.isArray(raw)) return [];
|
|
44
|
+
return raw
|
|
45
|
+
.filter(
|
|
46
|
+
(item): item is Record<string, unknown> =>
|
|
47
|
+
item !== null && typeof item === "object" && typeof item.type === "string",
|
|
48
|
+
)
|
|
49
|
+
.map((item) => ({
|
|
50
|
+
type: item.type as string,
|
|
51
|
+
path: typeof item.path === "string" ? item.path : undefined,
|
|
52
|
+
package: typeof item.package === "string" ? item.package : undefined,
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Write manifest contracts to api_contracts in meta.db.
|
|
58
|
+
* All writes idempotent (upsert by provider+consumer+type).
|
|
59
|
+
*/
|
|
60
|
+
export async function writeManifestContracts(
|
|
61
|
+
db: SiaDb,
|
|
62
|
+
providerRepoId: string,
|
|
63
|
+
consumerRepoId: string,
|
|
64
|
+
manifest: SiaManifest,
|
|
65
|
+
): Promise<void> {
|
|
66
|
+
const now = Date.now();
|
|
67
|
+
|
|
68
|
+
for (const contract of manifest.provides) {
|
|
69
|
+
await upsertContract(db, {
|
|
70
|
+
providerRepoId,
|
|
71
|
+
consumerRepoId,
|
|
72
|
+
type: contract.type,
|
|
73
|
+
specPath: contract.path ?? null,
|
|
74
|
+
trustTier: 1,
|
|
75
|
+
now,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (const contract of manifest.depends_on) {
|
|
80
|
+
await upsertContract(db, {
|
|
81
|
+
providerRepoId: consumerRepoId,
|
|
82
|
+
consumerRepoId: providerRepoId,
|
|
83
|
+
type: contract.type,
|
|
84
|
+
specPath: contract.path ?? null,
|
|
85
|
+
trustTier: 1,
|
|
86
|
+
now,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const contract of manifest.consumes) {
|
|
91
|
+
await upsertContract(db, {
|
|
92
|
+
providerRepoId: consumerRepoId,
|
|
93
|
+
consumerRepoId: providerRepoId,
|
|
94
|
+
type: contract.type,
|
|
95
|
+
specPath: contract.path ?? contract.package ?? null,
|
|
96
|
+
trustTier: 1,
|
|
97
|
+
now,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function upsertContract(
|
|
103
|
+
db: SiaDb,
|
|
104
|
+
opts: {
|
|
105
|
+
providerRepoId: string;
|
|
106
|
+
consumerRepoId: string;
|
|
107
|
+
type: string;
|
|
108
|
+
specPath: string | null;
|
|
109
|
+
trustTier: number;
|
|
110
|
+
now: number;
|
|
111
|
+
},
|
|
112
|
+
): Promise<void> {
|
|
113
|
+
const existing = await db.execute(
|
|
114
|
+
`SELECT id FROM api_contracts
|
|
115
|
+
WHERE provider_repo_id = ? AND consumer_repo_id = ? AND contract_type = ?`,
|
|
116
|
+
[opts.providerRepoId, opts.consumerRepoId, opts.type],
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (existing.rows.length > 0) {
|
|
120
|
+
await db.execute("UPDATE api_contracts SET detected_at = ?, spec_path = ? WHERE id = ?", [
|
|
121
|
+
opts.now,
|
|
122
|
+
opts.specPath,
|
|
123
|
+
existing.rows[0]?.id as string,
|
|
124
|
+
]);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
await db.execute(
|
|
129
|
+
`INSERT INTO api_contracts (id, provider_repo_id, consumer_repo_id, contract_type, spec_path, trust_tier, detected_at)
|
|
130
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
131
|
+
[
|
|
132
|
+
randomUUID(),
|
|
133
|
+
opts.providerRepoId,
|
|
134
|
+
opts.consumerRepoId,
|
|
135
|
+
opts.type,
|
|
136
|
+
opts.specPath,
|
|
137
|
+
opts.trustTier,
|
|
138
|
+
opts.now,
|
|
139
|
+
],
|
|
140
|
+
);
|
|
141
|
+
}
|