@pratik7368patil/anchor-core 0.1.21 → 0.1.22
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/dist/index.d.ts +105 -3
- package/dist/index.js +436 -208
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/db/schema.sql +13 -0
package/dist/index.js
CHANGED
|
@@ -657,6 +657,18 @@ CREATE TABLE IF NOT EXISTS org_anomaly_events (
|
|
|
657
657
|
created_at TEXT NOT NULL
|
|
658
658
|
);
|
|
659
659
|
|
|
660
|
+
CREATE TABLE IF NOT EXISTS org_graph_state (
|
|
661
|
+
org TEXT PRIMARY KEY,
|
|
662
|
+
last_built_at TEXT,
|
|
663
|
+
last_status TEXT NOT NULL DEFAULT 'unknown',
|
|
664
|
+
last_duration_ms INTEGER,
|
|
665
|
+
edge_count INTEGER NOT NULL DEFAULT 0,
|
|
666
|
+
api_contract_count INTEGER NOT NULL DEFAULT 0,
|
|
667
|
+
api_consumer_count INTEGER NOT NULL DEFAULT 0,
|
|
668
|
+
last_error TEXT,
|
|
669
|
+
updated_at TEXT NOT NULL
|
|
670
|
+
);
|
|
671
|
+
|
|
660
672
|
CREATE TABLE IF NOT EXISTS org_sync_checkpoints (
|
|
661
673
|
org TEXT NOT NULL,
|
|
662
674
|
repo TEXT NOT NULL,
|
|
@@ -694,6 +706,7 @@ CREATE INDEX IF NOT EXISTS idx_org_edges_target ON org_cross_repo_edges(org, tar
|
|
|
694
706
|
CREATE INDEX IF NOT EXISTS idx_org_consumers_provider ON org_api_consumers(org, provider_repo);
|
|
695
707
|
CREATE INDEX IF NOT EXISTS idx_org_consumers_consumer ON org_api_consumers(org, consumer_repo);
|
|
696
708
|
CREATE INDEX IF NOT EXISTS idx_org_anomalies_org ON org_anomaly_events(org, severity);
|
|
709
|
+
CREATE INDEX IF NOT EXISTS idx_org_graph_state_status ON org_graph_state(org, last_status);
|
|
697
710
|
`;
|
|
698
711
|
|
|
699
712
|
// src/rules/team-rules.ts
|
|
@@ -1573,7 +1586,8 @@ function checkSchema(db) {
|
|
|
1573
1586
|
"org_repo_state",
|
|
1574
1587
|
"org_cross_repo_edges",
|
|
1575
1588
|
"org_api_consumers",
|
|
1576
|
-
"org_anomaly_events"
|
|
1589
|
+
"org_anomaly_events",
|
|
1590
|
+
"org_graph_state"
|
|
1577
1591
|
].every(
|
|
1578
1592
|
(tableName) => db.prepare("SELECT name FROM sqlite_master WHERE name = ?").all(tableName).length > 0
|
|
1579
1593
|
);
|
|
@@ -7653,10 +7667,47 @@ function recordOrgIndexRun(db, input) {
|
|
|
7653
7667
|
JSON.stringify(input.failures ?? [])
|
|
7654
7668
|
);
|
|
7655
7669
|
}
|
|
7670
|
+
function recordOrgGraphState(db, input) {
|
|
7671
|
+
initializeSchema(db);
|
|
7672
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7673
|
+
db.prepare(
|
|
7674
|
+
`INSERT INTO org_graph_state
|
|
7675
|
+
(org, last_built_at, last_status, last_duration_ms, edge_count, api_contract_count,
|
|
7676
|
+
api_consumer_count, last_error, updated_at)
|
|
7677
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
7678
|
+
ON CONFLICT(org) DO UPDATE SET
|
|
7679
|
+
last_built_at = COALESCE(excluded.last_built_at, org_graph_state.last_built_at),
|
|
7680
|
+
last_status = excluded.last_status,
|
|
7681
|
+
last_duration_ms = COALESCE(excluded.last_duration_ms, org_graph_state.last_duration_ms),
|
|
7682
|
+
edge_count = excluded.edge_count,
|
|
7683
|
+
api_contract_count = excluded.api_contract_count,
|
|
7684
|
+
api_consumer_count = excluded.api_consumer_count,
|
|
7685
|
+
last_error = excluded.last_error,
|
|
7686
|
+
updated_at = excluded.updated_at`
|
|
7687
|
+
).run(
|
|
7688
|
+
input.org,
|
|
7689
|
+
input.builtAt ?? null,
|
|
7690
|
+
input.status,
|
|
7691
|
+
input.durationMs ?? null,
|
|
7692
|
+
input.edgeCount ?? 0,
|
|
7693
|
+
input.apiContractCount ?? 0,
|
|
7694
|
+
input.apiConsumerCount ?? 0,
|
|
7695
|
+
input.error ?? null,
|
|
7696
|
+
now
|
|
7697
|
+
);
|
|
7698
|
+
}
|
|
7656
7699
|
function count(db, table, where = "", params = []) {
|
|
7657
7700
|
const row = db.prepare(`SELECT COUNT(*) AS count FROM ${table} ${where}`).get(...params);
|
|
7658
7701
|
return row.count;
|
|
7659
7702
|
}
|
|
7703
|
+
function getOrgGraphCounts(db, org) {
|
|
7704
|
+
initializeSchema(db);
|
|
7705
|
+
return {
|
|
7706
|
+
edges: count(db, "org_cross_repo_edges", "WHERE org = ?", [org]),
|
|
7707
|
+
apiContracts: count(db, "org_api_contracts", "WHERE org = ?", [org]),
|
|
7708
|
+
apiConsumers: count(db, "org_api_consumers", "WHERE org = ?", [org])
|
|
7709
|
+
};
|
|
7710
|
+
}
|
|
7660
7711
|
function grade(score) {
|
|
7661
7712
|
if (score <= 0) return "empty";
|
|
7662
7713
|
if (score < 35) return "poor";
|
|
@@ -7678,8 +7729,10 @@ function getOrgStatus(db, config, baseDir) {
|
|
|
7678
7729
|
const codeChunkCount = count(db, "code_chunks");
|
|
7679
7730
|
const wisdomUnitCount = count(db, "wisdom_units");
|
|
7680
7731
|
const crossRepoEdgeCount = count(db, "org_cross_repo_edges", "WHERE org = ?", [config.org]);
|
|
7732
|
+
const apiContractCount = count(db, "org_api_contracts", "WHERE org = ?", [config.org]);
|
|
7681
7733
|
const apiConsumerCount = count(db, "org_api_consumers", "WHERE org = ?", [config.org]);
|
|
7682
7734
|
const anomalyCount = count(db, "org_anomaly_events", "WHERE org = ?", [config.org]);
|
|
7735
|
+
const graphState = db.prepare("SELECT * FROM org_graph_state WHERE org = ?").get(config.org);
|
|
7683
7736
|
let score = 0;
|
|
7684
7737
|
const reasons = [];
|
|
7685
7738
|
if (enabledRepos.length > 0) {
|
|
@@ -7718,8 +7771,13 @@ function getOrgStatus(db, config, baseDir) {
|
|
|
7718
7771
|
codeChunkCount,
|
|
7719
7772
|
wisdomUnitCount,
|
|
7720
7773
|
crossRepoEdgeCount,
|
|
7774
|
+
apiContractCount,
|
|
7721
7775
|
apiConsumerCount,
|
|
7722
7776
|
anomalyCount,
|
|
7777
|
+
graphLastBuiltAt: graphState?.last_built_at ?? void 0,
|
|
7778
|
+
graphLastStatus: graphState?.last_status ?? void 0,
|
|
7779
|
+
graphLastDurationMs: graphState?.last_duration_ms ?? void 0,
|
|
7780
|
+
graphLastError: graphState?.last_error ?? void 0,
|
|
7723
7781
|
coverageScore: score,
|
|
7724
7782
|
coverageGrade: grade(score),
|
|
7725
7783
|
coverageReasons: reasons,
|
|
@@ -7845,16 +7903,32 @@ async function cloneOrgRepos(input) {
|
|
|
7845
7903
|
const repo = repos[next];
|
|
7846
7904
|
next += 1;
|
|
7847
7905
|
if (!repo) continue;
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
|
|
7852
|
-
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7906
|
+
const current = next;
|
|
7907
|
+
input.onProgress?.({
|
|
7908
|
+
stage: "cloning_or_pulling_repo",
|
|
7909
|
+
org: input.config.org,
|
|
7910
|
+
repo: repo.fullName,
|
|
7911
|
+
current,
|
|
7912
|
+
total: repos.length
|
|
7913
|
+
});
|
|
7914
|
+
const result = cloneOrPullOrgRepo({
|
|
7915
|
+
org: input.config.org,
|
|
7916
|
+
repo,
|
|
7917
|
+
db: input.db,
|
|
7918
|
+
baseDir: input.baseDir,
|
|
7919
|
+
runner: input.runner
|
|
7920
|
+
});
|
|
7921
|
+
results.push(result);
|
|
7922
|
+
input.onProgress?.({
|
|
7923
|
+
stage: "cloned_or_pulled_repo",
|
|
7924
|
+
org: input.config.org,
|
|
7925
|
+
repo: repo.fullName,
|
|
7926
|
+
current,
|
|
7927
|
+
total: repos.length,
|
|
7928
|
+
cloned: result.cloned,
|
|
7929
|
+
pulled: result.pulled,
|
|
7930
|
+
error: result.error
|
|
7931
|
+
});
|
|
7858
7932
|
}
|
|
7859
7933
|
}
|
|
7860
7934
|
await Promise.all(Array.from({ length: Math.min(limit, repos.length) }, () => worker()));
|
|
@@ -7952,206 +8026,335 @@ function isApiConsumerText(text) {
|
|
|
7952
8026
|
function evidenceJson(evidence) {
|
|
7953
8027
|
return JSON.stringify(evidence);
|
|
7954
8028
|
}
|
|
7955
|
-
function
|
|
8029
|
+
function shouldEmitProgress(current, total, interval = 100) {
|
|
8030
|
+
return current === 1 || current === total || current % interval === 0;
|
|
8031
|
+
}
|
|
8032
|
+
function resolveOptions(baseDirOrOptions) {
|
|
8033
|
+
return typeof baseDirOrOptions === "string" ? { baseDir: baseDirOrOptions } : baseDirOrOptions ?? {};
|
|
8034
|
+
}
|
|
8035
|
+
function rebuildOrgGraph(db, config, baseDirOrOptions) {
|
|
7956
8036
|
initializeSchema(db);
|
|
7957
|
-
const
|
|
7958
|
-
const
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
7967
|
-
const
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
edge.targetPath ?? "",
|
|
7972
|
-
edge.relationship
|
|
7973
|
-
].join("\0");
|
|
7974
|
-
if (edges.some(
|
|
7975
|
-
(existing) => [
|
|
7976
|
-
existing.sourceRepo,
|
|
7977
|
-
existing.sourcePath,
|
|
7978
|
-
existing.targetRepo,
|
|
7979
|
-
existing.targetPath ?? "",
|
|
7980
|
-
existing.relationship
|
|
7981
|
-
].join("\0") === key
|
|
7982
|
-
)) {
|
|
7983
|
-
return;
|
|
7984
|
-
}
|
|
7985
|
-
edges.push(edge);
|
|
7986
|
-
};
|
|
7987
|
-
for (const repo of enabledRepos) {
|
|
7988
|
-
const manifest = readPackageManifest(orgRepoLocalPath(config.org, repo, baseDir));
|
|
7989
|
-
for (const dependency of dependenciesFor(manifest)) {
|
|
7990
|
-
const targetRepo = packageToRepo.get(dependency);
|
|
7991
|
-
if (!targetRepo || targetRepo === repo.fullName) continue;
|
|
7992
|
-
addEdge({
|
|
7993
|
-
org: config.org,
|
|
7994
|
-
sourceRepo: repo.fullName,
|
|
7995
|
-
sourcePath: "package.json",
|
|
7996
|
-
targetRepo,
|
|
7997
|
-
relationship: "depends_on_package",
|
|
7998
|
-
evidence: [fileEvidence(repo.fullName, "package.json", `depends on ${dependency}`)],
|
|
7999
|
-
confidence: 0.9
|
|
8000
|
-
});
|
|
8001
|
-
}
|
|
8002
|
-
}
|
|
8003
|
-
const imports = db.prepare(
|
|
8004
|
-
`SELECT r.full_name AS repo, ci.source_path, ci.specifier, ci.imported_path, ci.imported_symbols_json
|
|
8005
|
-
FROM code_imports ci
|
|
8006
|
-
JOIN repositories r ON r.id = ci.repo_id`
|
|
8007
|
-
).all();
|
|
8008
|
-
for (const item of imports) {
|
|
8009
|
-
const sourceRepo = repoByName.get(item.repo);
|
|
8010
|
-
if (!sourceRepo) continue;
|
|
8011
|
-
for (const [targetRepo, names] of packageNames.entries()) {
|
|
8012
|
-
if (targetRepo === item.repo) continue;
|
|
8013
|
-
const matchedName = names.find(
|
|
8014
|
-
(name) => item.specifier === name || item.specifier.startsWith(`${name}/`)
|
|
8015
|
-
);
|
|
8016
|
-
if (!matchedName) continue;
|
|
8017
|
-
addEdge({
|
|
8018
|
-
org: config.org,
|
|
8019
|
-
sourceRepo: item.repo,
|
|
8020
|
-
sourcePath: item.source_path,
|
|
8021
|
-
targetRepo,
|
|
8022
|
-
targetPath: item.imported_path ?? void 0,
|
|
8023
|
-
relationship: "imports",
|
|
8024
|
-
evidence: [
|
|
8025
|
-
fileEvidence(
|
|
8026
|
-
item.repo,
|
|
8027
|
-
item.source_path,
|
|
8028
|
-
`imports ${sanitizeHistoricalText(matchedName)}`
|
|
8029
|
-
)
|
|
8030
|
-
],
|
|
8031
|
-
confidence: parseJsonArray9(item.imported_symbols_json).length > 0 ? 0.88 : 0.76
|
|
8032
|
-
});
|
|
8033
|
-
}
|
|
8034
|
-
}
|
|
8035
|
-
const chunks = db.prepare(
|
|
8036
|
-
`SELECT r.full_name AS repo, cc.file_path, cc.sanitized_text, cc.symbols_json
|
|
8037
|
-
FROM code_chunks cc
|
|
8038
|
-
JOIN repositories r ON r.id = cc.repo_id`
|
|
8039
|
-
).all();
|
|
8040
|
-
const apiContracts = chunks.filter((chunk) => repoByName.has(chunk.repo) && isApiProviderPath(chunk.file_path)).flatMap(
|
|
8041
|
-
(chunk) => extractContracts(chunk.sanitized_text).map((contract) => ({
|
|
8042
|
-
repo: chunk.repo,
|
|
8043
|
-
filePath: chunk.file_path,
|
|
8044
|
-
contract,
|
|
8045
|
-
evidence: [fileEvidence(chunk.repo, chunk.file_path, `defines ${contract}`)],
|
|
8046
|
-
confidence: 0.74
|
|
8047
|
-
}))
|
|
8048
|
-
);
|
|
8049
|
-
const apiConsumers = [];
|
|
8050
|
-
for (const contract of apiContracts) {
|
|
8051
|
-
for (const chunk of chunks) {
|
|
8052
|
-
if (chunk.repo === contract.repo || !repoByName.has(chunk.repo)) continue;
|
|
8053
|
-
if (!isApiConsumerText(chunk.sanitized_text)) continue;
|
|
8054
|
-
if (!chunk.sanitized_text.includes(contract.contract)) continue;
|
|
8055
|
-
const consumer = {
|
|
8056
|
-
org: config.org,
|
|
8057
|
-
providerRepo: contract.repo,
|
|
8058
|
-
providerPath: contract.filePath,
|
|
8059
|
-
consumerRepo: chunk.repo,
|
|
8060
|
-
consumerPath: chunk.file_path,
|
|
8061
|
-
contract: contract.contract,
|
|
8062
|
-
evidence: [
|
|
8063
|
-
...contract.evidence,
|
|
8064
|
-
fileEvidence(chunk.repo, chunk.file_path, `consumes ${contract.contract}`)
|
|
8065
|
-
],
|
|
8066
|
-
confidence: 0.86
|
|
8067
|
-
};
|
|
8068
|
-
apiConsumers.push(consumer);
|
|
8069
|
-
addEdge({
|
|
8070
|
-
org: config.org,
|
|
8071
|
-
sourceRepo: chunk.repo,
|
|
8072
|
-
sourcePath: chunk.file_path,
|
|
8073
|
-
targetRepo: contract.repo,
|
|
8074
|
-
targetPath: contract.filePath,
|
|
8075
|
-
relationship: "api_consumer",
|
|
8076
|
-
evidence: consumer.evidence,
|
|
8077
|
-
confidence: consumer.confidence
|
|
8078
|
-
});
|
|
8037
|
+
const options = resolveOptions(baseDirOrOptions);
|
|
8038
|
+
const startedAt = Date.now();
|
|
8039
|
+
try {
|
|
8040
|
+
options.onProgress?.({
|
|
8041
|
+
stage: "loading_package_manifests",
|
|
8042
|
+
org: config.org,
|
|
8043
|
+
totalRepos: config.repos.filter((repo) => repo.enabled).length
|
|
8044
|
+
});
|
|
8045
|
+
const packageNames = repoPackageNames(config, options.baseDir);
|
|
8046
|
+
const enabledRepos = config.repos.filter((repo) => repo.enabled);
|
|
8047
|
+
const repoByName = new Map(enabledRepos.map((repo) => [repo.fullName, repo]));
|
|
8048
|
+
const packageToRepo = /* @__PURE__ */ new Map();
|
|
8049
|
+
for (const [repo, names] of packageNames.entries()) {
|
|
8050
|
+
for (const name of names) packageToRepo.set(name, repo);
|
|
8079
8051
|
}
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
const
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
for (const edge of edges) {
|
|
8092
|
-
insertEdge.run(
|
|
8093
|
-
`oge_${stableId([edge.org, edge.sourceRepo, edge.sourcePath, edge.targetRepo, edge.targetPath ?? "", edge.relationship])}`,
|
|
8094
|
-
edge.org,
|
|
8052
|
+
options.onProgress?.({
|
|
8053
|
+
stage: "loaded_package_manifests",
|
|
8054
|
+
org: config.org,
|
|
8055
|
+
repos: enabledRepos.length,
|
|
8056
|
+
packageNames: packageToRepo.size
|
|
8057
|
+
});
|
|
8058
|
+
const edges = [];
|
|
8059
|
+
const edgeKeys = /* @__PURE__ */ new Set();
|
|
8060
|
+
const addEdge = (edge) => {
|
|
8061
|
+
if (edge.sourceRepo === edge.targetRepo) return;
|
|
8062
|
+
const key = [
|
|
8095
8063
|
edge.sourceRepo,
|
|
8096
8064
|
edge.sourcePath,
|
|
8097
8065
|
edge.targetRepo,
|
|
8098
|
-
edge.targetPath ??
|
|
8099
|
-
edge.relationship
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
);
|
|
8104
|
-
}
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
|
|
8066
|
+
edge.targetPath ?? "",
|
|
8067
|
+
edge.relationship
|
|
8068
|
+
].join("\0");
|
|
8069
|
+
if (edgeKeys.has(key)) return;
|
|
8070
|
+
edgeKeys.add(key);
|
|
8071
|
+
edges.push(edge);
|
|
8072
|
+
};
|
|
8073
|
+
enabledRepos.forEach((repo, index) => {
|
|
8074
|
+
const manifest = readPackageManifest(orgRepoLocalPath(config.org, repo, options.baseDir));
|
|
8075
|
+
for (const dependency of dependenciesFor(manifest)) {
|
|
8076
|
+
const targetRepo = packageToRepo.get(dependency);
|
|
8077
|
+
if (!targetRepo || targetRepo === repo.fullName) continue;
|
|
8078
|
+
addEdge({
|
|
8079
|
+
org: config.org,
|
|
8080
|
+
sourceRepo: repo.fullName,
|
|
8081
|
+
sourcePath: "package.json",
|
|
8082
|
+
targetRepo,
|
|
8083
|
+
relationship: "depends_on_package",
|
|
8084
|
+
evidence: [
|
|
8085
|
+
fileEvidence(
|
|
8086
|
+
repo.fullName,
|
|
8087
|
+
"package.json",
|
|
8088
|
+
`depends on ${sanitizeHistoricalText(dependency)}`
|
|
8089
|
+
)
|
|
8090
|
+
],
|
|
8091
|
+
confidence: 0.9
|
|
8092
|
+
});
|
|
8093
|
+
}
|
|
8094
|
+
options.onProgress?.({
|
|
8095
|
+
stage: "building_package_edges",
|
|
8096
|
+
org: config.org,
|
|
8097
|
+
current: index + 1,
|
|
8098
|
+
total: enabledRepos.length,
|
|
8099
|
+
repo: repo.fullName,
|
|
8100
|
+
edges: edges.length
|
|
8101
|
+
});
|
|
8102
|
+
});
|
|
8103
|
+
options.onProgress?.({ stage: "loading_imports", org: config.org });
|
|
8104
|
+
const imports = db.prepare(
|
|
8105
|
+
`SELECT r.full_name AS repo, ci.source_path, ci.specifier, ci.imported_path, ci.imported_symbols_json
|
|
8106
|
+
FROM code_imports ci
|
|
8107
|
+
JOIN repositories r ON r.id = ci.repo_id`
|
|
8108
|
+
).all();
|
|
8109
|
+
const packageMatchers = [...packageNames.entries()].flatMap(([repo, names]) => names.map((name) => ({ repo, name }))).sort((a, b) => b.name.length - a.name.length);
|
|
8110
|
+
imports.forEach((item, index) => {
|
|
8111
|
+
const sourceRepo = repoByName.get(item.repo);
|
|
8112
|
+
if (!sourceRepo) return;
|
|
8113
|
+
for (const candidate of packageMatchers) {
|
|
8114
|
+
if (candidate.repo === item.repo) continue;
|
|
8115
|
+
const matched = item.specifier === candidate.name || item.specifier.startsWith(`${candidate.name}/`);
|
|
8116
|
+
if (!matched) continue;
|
|
8117
|
+
addEdge({
|
|
8118
|
+
org: config.org,
|
|
8119
|
+
sourceRepo: item.repo,
|
|
8120
|
+
sourcePath: item.source_path,
|
|
8121
|
+
targetRepo: candidate.repo,
|
|
8122
|
+
targetPath: item.imported_path ?? void 0,
|
|
8123
|
+
relationship: "imports",
|
|
8124
|
+
evidence: [
|
|
8125
|
+
fileEvidence(
|
|
8126
|
+
item.repo,
|
|
8127
|
+
item.source_path,
|
|
8128
|
+
`imports ${sanitizeHistoricalText(candidate.name)}`
|
|
8129
|
+
)
|
|
8130
|
+
],
|
|
8131
|
+
confidence: parseJsonArray9(item.imported_symbols_json).length > 0 ? 0.88 : 0.76
|
|
8132
|
+
});
|
|
8133
|
+
break;
|
|
8134
|
+
}
|
|
8135
|
+
if (shouldEmitProgress(index + 1, imports.length)) {
|
|
8136
|
+
options.onProgress?.({
|
|
8137
|
+
stage: "building_import_edges",
|
|
8138
|
+
org: config.org,
|
|
8139
|
+
current: index + 1,
|
|
8140
|
+
total: imports.length,
|
|
8141
|
+
sourcePath: item.source_path,
|
|
8142
|
+
edges: edges.length
|
|
8143
|
+
});
|
|
8144
|
+
}
|
|
8145
|
+
});
|
|
8146
|
+
options.onProgress?.({ stage: "loading_code_chunks", org: config.org });
|
|
8147
|
+
const chunks = db.prepare(
|
|
8148
|
+
`SELECT r.full_name AS repo, cc.file_path, cc.sanitized_text, cc.symbols_json
|
|
8149
|
+
FROM code_chunks cc
|
|
8150
|
+
JOIN repositories r ON r.id = cc.repo_id`
|
|
8151
|
+
).all();
|
|
8152
|
+
const providerChunks = chunks.filter(
|
|
8153
|
+
(chunk) => repoByName.has(chunk.repo) && isApiProviderPath(chunk.file_path)
|
|
8109
8154
|
);
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
contract
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
|
|
8155
|
+
const apiContracts = [];
|
|
8156
|
+
const contractKeys = /* @__PURE__ */ new Set();
|
|
8157
|
+
const contractsByToken = /* @__PURE__ */ new Map();
|
|
8158
|
+
providerChunks.forEach((chunk, index) => {
|
|
8159
|
+
for (const contract of extractContracts(chunk.sanitized_text)) {
|
|
8160
|
+
const sanitizedContract = sanitizeHistoricalText(contract);
|
|
8161
|
+
const key = [chunk.repo, chunk.file_path, sanitizedContract].join("\0");
|
|
8162
|
+
if (contractKeys.has(key)) continue;
|
|
8163
|
+
contractKeys.add(key);
|
|
8164
|
+
const apiContract = {
|
|
8165
|
+
repo: chunk.repo,
|
|
8166
|
+
filePath: chunk.file_path,
|
|
8167
|
+
contract: sanitizedContract,
|
|
8168
|
+
evidence: [fileEvidence(chunk.repo, chunk.file_path, `defines ${sanitizedContract}`)],
|
|
8169
|
+
confidence: 0.74
|
|
8170
|
+
};
|
|
8171
|
+
apiContracts.push(apiContract);
|
|
8172
|
+
const bucket = contractsByToken.get(sanitizedContract) ?? [];
|
|
8173
|
+
bucket.push(apiContract);
|
|
8174
|
+
contractsByToken.set(sanitizedContract, bucket);
|
|
8175
|
+
}
|
|
8176
|
+
if (shouldEmitProgress(index + 1, providerChunks.length)) {
|
|
8177
|
+
options.onProgress?.({
|
|
8178
|
+
stage: "extracting_api_contracts",
|
|
8179
|
+
org: config.org,
|
|
8180
|
+
current: index + 1,
|
|
8181
|
+
total: providerChunks.length,
|
|
8182
|
+
filePath: chunk.file_path,
|
|
8183
|
+
contracts: apiContracts.length
|
|
8184
|
+
});
|
|
8185
|
+
}
|
|
8186
|
+
});
|
|
8187
|
+
const apiConsumers = [];
|
|
8188
|
+
const consumerKeys = /* @__PURE__ */ new Set();
|
|
8189
|
+
const consumerChunks = chunks.filter(
|
|
8190
|
+
(chunk) => repoByName.has(chunk.repo) && isApiConsumerText(chunk.sanitized_text)
|
|
8126
8191
|
);
|
|
8127
|
-
|
|
8128
|
-
|
|
8129
|
-
|
|
8192
|
+
consumerChunks.forEach((chunk, index) => {
|
|
8193
|
+
const consumerTokens = extractContracts(chunk.sanitized_text);
|
|
8194
|
+
let chunkMatches = 0;
|
|
8195
|
+
for (const token of consumerTokens) {
|
|
8196
|
+
const contracts = contractsByToken.get(sanitizeHistoricalText(token));
|
|
8197
|
+
if (!contracts) continue;
|
|
8198
|
+
for (const contract of contracts) {
|
|
8199
|
+
if (chunk.repo === contract.repo) continue;
|
|
8200
|
+
const consumerKey = [
|
|
8201
|
+
contract.repo,
|
|
8202
|
+
contract.filePath,
|
|
8203
|
+
chunk.repo,
|
|
8204
|
+
chunk.file_path,
|
|
8205
|
+
contract.contract
|
|
8206
|
+
].join("\0");
|
|
8207
|
+
if (consumerKeys.has(consumerKey)) continue;
|
|
8208
|
+
consumerKeys.add(consumerKey);
|
|
8209
|
+
const consumer = {
|
|
8210
|
+
org: config.org,
|
|
8211
|
+
providerRepo: contract.repo,
|
|
8212
|
+
providerPath: contract.filePath,
|
|
8213
|
+
consumerRepo: chunk.repo,
|
|
8214
|
+
consumerPath: chunk.file_path,
|
|
8215
|
+
contract: contract.contract,
|
|
8216
|
+
evidence: [
|
|
8217
|
+
...contract.evidence,
|
|
8218
|
+
fileEvidence(chunk.repo, chunk.file_path, `consumes ${contract.contract}`)
|
|
8219
|
+
],
|
|
8220
|
+
confidence: 0.86
|
|
8221
|
+
};
|
|
8222
|
+
chunkMatches += 1;
|
|
8223
|
+
apiConsumers.push(consumer);
|
|
8224
|
+
addEdge({
|
|
8225
|
+
org: config.org,
|
|
8226
|
+
sourceRepo: chunk.repo,
|
|
8227
|
+
sourcePath: chunk.file_path,
|
|
8228
|
+
targetRepo: contract.repo,
|
|
8229
|
+
targetPath: contract.filePath,
|
|
8230
|
+
relationship: "api_consumer",
|
|
8231
|
+
evidence: consumer.evidence,
|
|
8232
|
+
confidence: consumer.confidence
|
|
8233
|
+
});
|
|
8234
|
+
}
|
|
8235
|
+
}
|
|
8236
|
+
if (shouldEmitProgress(index + 1, consumerChunks.length)) {
|
|
8237
|
+
options.onProgress?.({
|
|
8238
|
+
stage: "matching_api_consumers",
|
|
8239
|
+
org: config.org,
|
|
8240
|
+
current: index + 1,
|
|
8241
|
+
total: consumerChunks.length,
|
|
8242
|
+
filePath: chunk.file_path,
|
|
8243
|
+
matches: chunkMatches
|
|
8244
|
+
});
|
|
8245
|
+
}
|
|
8246
|
+
});
|
|
8247
|
+
options.onProgress?.({
|
|
8248
|
+
stage: "writing_org_graph",
|
|
8249
|
+
org: config.org,
|
|
8250
|
+
edges: edges.length,
|
|
8251
|
+
apiContracts: apiContracts.length,
|
|
8252
|
+
apiConsumers: apiConsumers.length
|
|
8253
|
+
});
|
|
8254
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8255
|
+
const transaction = db.transaction(() => {
|
|
8256
|
+
db.prepare("DELETE FROM org_cross_repo_edges WHERE org = ?").run(config.org);
|
|
8257
|
+
db.prepare("DELETE FROM org_api_contracts WHERE org = ?").run(config.org);
|
|
8258
|
+
db.prepare("DELETE FROM org_api_consumers WHERE org = ?").run(config.org);
|
|
8259
|
+
const insertEdge = db.prepare(
|
|
8260
|
+
`INSERT INTO org_cross_repo_edges
|
|
8261
|
+
(id, org, source_repo, source_path, target_repo, target_path, relationship, evidence_json, confidence, created_at)
|
|
8262
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
8263
|
+
);
|
|
8264
|
+
for (const edge of edges) {
|
|
8265
|
+
insertEdge.run(
|
|
8266
|
+
`oge_${stableId([edge.org, edge.sourceRepo, edge.sourcePath, edge.targetRepo, edge.targetPath ?? "", edge.relationship])}`,
|
|
8267
|
+
edge.org,
|
|
8268
|
+
edge.sourceRepo,
|
|
8269
|
+
edge.sourcePath,
|
|
8270
|
+
edge.targetRepo,
|
|
8271
|
+
edge.targetPath ?? null,
|
|
8272
|
+
edge.relationship,
|
|
8273
|
+
evidenceJson(edge.evidence),
|
|
8274
|
+
edge.confidence,
|
|
8275
|
+
now
|
|
8276
|
+
);
|
|
8277
|
+
}
|
|
8278
|
+
const insertContract = db.prepare(
|
|
8279
|
+
`INSERT INTO org_api_contracts
|
|
8280
|
+
(id, org, repo, file_path, contract, evidence_json, confidence, created_at)
|
|
8281
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
8282
|
+
);
|
|
8283
|
+
for (const contract of apiContracts) {
|
|
8284
|
+
insertContract.run(
|
|
8285
|
+
`oac_${stableId([config.org, contract.repo, contract.filePath, contract.contract])}`,
|
|
8286
|
+
config.org,
|
|
8287
|
+
contract.repo,
|
|
8288
|
+
contract.filePath,
|
|
8289
|
+
sanitizeHistoricalText(contract.contract),
|
|
8290
|
+
evidenceJson(contract.evidence),
|
|
8291
|
+
contract.confidence,
|
|
8292
|
+
now
|
|
8293
|
+
);
|
|
8294
|
+
}
|
|
8295
|
+
const insertConsumer = db.prepare(
|
|
8296
|
+
`INSERT INTO org_api_consumers
|
|
8297
|
+
(id, org, provider_repo, provider_path, consumer_repo, consumer_path, contract, evidence_json, confidence, created_at)
|
|
8298
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
8299
|
+
);
|
|
8300
|
+
for (const consumer of apiConsumers) {
|
|
8301
|
+
insertConsumer.run(
|
|
8302
|
+
`oap_${stableId([
|
|
8303
|
+
consumer.org,
|
|
8304
|
+
consumer.providerRepo,
|
|
8305
|
+
consumer.providerPath ?? "",
|
|
8306
|
+
consumer.consumerRepo,
|
|
8307
|
+
consumer.consumerPath,
|
|
8308
|
+
consumer.contract
|
|
8309
|
+
])}`,
|
|
8130
8310
|
consumer.org,
|
|
8131
8311
|
consumer.providerRepo,
|
|
8132
|
-
consumer.providerPath ??
|
|
8312
|
+
consumer.providerPath ?? null,
|
|
8133
8313
|
consumer.consumerRepo,
|
|
8134
8314
|
consumer.consumerPath,
|
|
8135
|
-
consumer.contract
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
8154
|
-
|
|
8315
|
+
sanitizeHistoricalText(consumer.contract),
|
|
8316
|
+
evidenceJson(consumer.evidence),
|
|
8317
|
+
consumer.confidence,
|
|
8318
|
+
now
|
|
8319
|
+
);
|
|
8320
|
+
}
|
|
8321
|
+
});
|
|
8322
|
+
transaction();
|
|
8323
|
+
const durationMs = Date.now() - startedAt;
|
|
8324
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8325
|
+
recordOrgGraphState(db, {
|
|
8326
|
+
org: config.org,
|
|
8327
|
+
status: "success",
|
|
8328
|
+
builtAt: finishedAt,
|
|
8329
|
+
durationMs,
|
|
8330
|
+
edgeCount: edges.length,
|
|
8331
|
+
apiContractCount: apiContracts.length,
|
|
8332
|
+
apiConsumerCount: apiConsumers.length
|
|
8333
|
+
});
|
|
8334
|
+
options.onProgress?.({
|
|
8335
|
+
stage: "completed_org_graph",
|
|
8336
|
+
org: config.org,
|
|
8337
|
+
edges: edges.length,
|
|
8338
|
+
apiContracts: apiContracts.length,
|
|
8339
|
+
apiConsumers: apiConsumers.length,
|
|
8340
|
+
durationMs
|
|
8341
|
+
});
|
|
8342
|
+
return {
|
|
8343
|
+
edges,
|
|
8344
|
+
apiConsumers,
|
|
8345
|
+
apiContracts,
|
|
8346
|
+
durationMs
|
|
8347
|
+
};
|
|
8348
|
+
} catch (error) {
|
|
8349
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8350
|
+
recordOrgGraphState(db, {
|
|
8351
|
+
org: config.org,
|
|
8352
|
+
status: "failed",
|
|
8353
|
+
durationMs: Date.now() - startedAt,
|
|
8354
|
+
error: message
|
|
8355
|
+
});
|
|
8356
|
+
throw error;
|
|
8357
|
+
}
|
|
8155
8358
|
}
|
|
8156
8359
|
|
|
8157
8360
|
// src/org/index.ts
|
|
@@ -8298,25 +8501,48 @@ async function indexOrgRepos(db, config, options = {}) {
|
|
|
8298
8501
|
});
|
|
8299
8502
|
}
|
|
8300
8503
|
}
|
|
8301
|
-
|
|
8504
|
+
let graph;
|
|
8505
|
+
if (options.noGraph) {
|
|
8506
|
+
const counts = getOrgGraphCounts(db, config.org);
|
|
8507
|
+
recordOrgGraphState(db, {
|
|
8508
|
+
org: config.org,
|
|
8509
|
+
status: "skipped",
|
|
8510
|
+
edgeCount: counts.edges,
|
|
8511
|
+
apiContractCount: counts.apiContracts,
|
|
8512
|
+
apiConsumerCount: counts.apiConsumers
|
|
8513
|
+
});
|
|
8514
|
+
graph = { ...counts, skipped: true };
|
|
8515
|
+
} else {
|
|
8516
|
+
try {
|
|
8517
|
+
const rebuiltGraph = rebuildOrgGraph(db, config, {
|
|
8518
|
+
baseDir: options.baseDir,
|
|
8519
|
+
onProgress: options.onGraphProgress
|
|
8520
|
+
});
|
|
8521
|
+
graph = {
|
|
8522
|
+
edges: rebuiltGraph.edges.length,
|
|
8523
|
+
apiConsumers: rebuiltGraph.apiConsumers.length,
|
|
8524
|
+
apiContracts: rebuiltGraph.apiContracts.length
|
|
8525
|
+
};
|
|
8526
|
+
} catch (error) {
|
|
8527
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8528
|
+
const counts = getOrgGraphCounts(db, config.org);
|
|
8529
|
+
graph = { ...counts, error: message };
|
|
8530
|
+
}
|
|
8531
|
+
}
|
|
8302
8532
|
recordOrgIndexRun(db, {
|
|
8303
8533
|
org: config.org,
|
|
8304
8534
|
command: options.command ?? "org index",
|
|
8305
8535
|
startedAt,
|
|
8306
8536
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8307
|
-
status: results.some((result) => result.error) ? "partial" : "success",
|
|
8537
|
+
status: results.some((result) => result.error) || graph.error ? "partial" : "success",
|
|
8308
8538
|
prsIndexed: results.reduce((sum, result) => sum + (result.history?.indexedPrs ?? 0), 0),
|
|
8309
8539
|
codeFilesIndexed: results.reduce((sum, result) => sum + (result.code?.indexedFiles ?? 0), 0),
|
|
8310
|
-
failures: results.map((result) => result.error).filter((error) => Boolean(error))
|
|
8540
|
+
failures: results.map((result) => result.error).concat(graph.error ? [graph.error] : []).filter((error) => Boolean(error))
|
|
8311
8541
|
});
|
|
8312
8542
|
return {
|
|
8313
8543
|
org: config.org,
|
|
8314
8544
|
repos: results.sort((a, b) => a.repo.localeCompare(b.repo)),
|
|
8315
|
-
graph
|
|
8316
|
-
edges: graph.edges.length,
|
|
8317
|
-
apiConsumers: graph.apiConsumers.length,
|
|
8318
|
-
apiContracts: graph.apiContracts.length
|
|
8319
|
-
}
|
|
8545
|
+
graph
|
|
8320
8546
|
};
|
|
8321
8547
|
}
|
|
8322
8548
|
|
|
@@ -9251,6 +9477,7 @@ export {
|
|
|
9251
9477
|
getIndexStatus,
|
|
9252
9478
|
getLastSyncTime,
|
|
9253
9479
|
getOrgArchitectureMap,
|
|
9480
|
+
getOrgGraphCounts,
|
|
9254
9481
|
getOrgRepoState,
|
|
9255
9482
|
getOrgStatus,
|
|
9256
9483
|
getPlaybook,
|
|
@@ -9303,6 +9530,7 @@ export {
|
|
|
9303
9530
|
rebuildOrgGraph,
|
|
9304
9531
|
recordFeedback,
|
|
9305
9532
|
recordIndexRun,
|
|
9533
|
+
recordOrgGraphState,
|
|
9306
9534
|
recordOrgIndexRun,
|
|
9307
9535
|
redactSecrets,
|
|
9308
9536
|
redactedHistoricalText,
|