ai-spector 0.5.7 → 0.5.8
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/README.md +11 -0
- package/dist/graph/InMemoryGraph.d.ts +1 -19
- package/dist/graph/InMemoryGraph.d.ts.map +1 -1
- package/dist/graph/InMemoryGraph.js +1 -192
- package/dist/graph/InMemoryGraph.js.map +1 -1
- package/dist/graph/impact.d.ts +3 -49
- package/dist/graph/impact.d.ts.map +1 -1
- package/dist/graph/impact.js +3 -197
- package/dist/graph/impact.js.map +1 -1
- package/dist/graph/knowledge.d.ts +3 -45
- package/dist/graph/knowledge.d.ts.map +1 -1
- package/dist/graph/knowledge.js +1 -18
- package/dist/graph/knowledge.js.map +1 -1
- package/dist/graph/layer-audit.d.ts +2 -45
- package/dist/graph/layer-audit.d.ts.map +1 -1
- package/dist/graph/layer-audit.js +6 -209
- package/dist/graph/layer-audit.js.map +1 -1
- package/dist/graph/path-target-edges.d.ts +1 -4
- package/dist/graph/path-target-edges.d.ts.map +1 -1
- package/dist/graph/path-target-edges.js +1 -8
- package/dist/graph/path-target-edges.js.map +1 -1
- package/dist/graph/query.d.ts +2 -18
- package/dist/graph/query.d.ts.map +1 -1
- package/dist/graph/query.js +1 -146
- package/dist/graph/query.js.map +1 -1
- package/dist/graph/resolve.d.ts +3 -30
- package/dist/graph/resolve.d.ts.map +1 -1
- package/dist/graph/resolve.js +4 -244
- package/dist/graph/resolve.js.map +1 -1
- package/dist/prototype/build-manifest.d.ts.map +1 -1
- package/dist/prototype/build-manifest.js +12 -10
- package/dist/prototype/build-manifest.js.map +1 -1
- package/dist/prototype/types.d.ts +4 -3
- package/dist/prototype/types.d.ts.map +1 -1
- package/dist/visualize/stats.d.ts +4 -17
- package/dist/visualize/stats.d.ts.map +1 -1
- package/dist/visualize/stats.js +4 -40
- package/dist/visualize/stats.js.map +1 -1
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -87,6 +87,17 @@ For scripts or debugging: `npx ai-spector index`, `graph validate`, `graph visua
|
|
|
87
87
|
| Validate errors after edits | `/index` |
|
|
88
88
|
| Old slash commands | `npx ai-spector sync-cursor` |
|
|
89
89
|
|
|
90
|
+
## Web / graph SDK
|
|
91
|
+
|
|
92
|
+
For **browser or custom dashboards** (not the Cursor CLI), use the read-only npm package **`ai-spector-graph`**. Your backend serves repo JSON; the frontend loads it into `ProjectSession`.
|
|
93
|
+
|
|
94
|
+
- **[Integration guide](docs/ai-spector-graph-integration-guide.md)** — architecture, API examples, React, recipes
|
|
95
|
+
- **[API reference](docs/ai-spector-graph.md)** — types and exports
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npm install ai-spector-graph
|
|
99
|
+
```
|
|
100
|
+
|
|
90
101
|
## Develop
|
|
91
102
|
|
|
92
103
|
```bash
|
|
@@ -1,20 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export declare class InMemoryGraph {
|
|
3
|
-
readonly nodesById: Map<string, GraphNode>;
|
|
4
|
-
readonly outEdges: Map<string, GraphEdge[]>;
|
|
5
|
-
readonly inEdges: Map<string, GraphEdge[]>;
|
|
6
|
-
private readonly edgeKeys;
|
|
7
|
-
static from(data: TraceabilityGraph): InMemoryGraph;
|
|
8
|
-
/** Dedupe edges when exporting (each edge stored once in outEdges). */
|
|
9
|
-
toTraceabilityGraph(): TraceabilityGraph;
|
|
10
|
-
addNode(node: GraphNode): void;
|
|
11
|
-
/** Update an existing node or insert. Same `type` required when updating. */
|
|
12
|
-
upsertNode(node: GraphNode): "created" | "updated";
|
|
13
|
-
edgeKey(edge: GraphEdge): string;
|
|
14
|
-
hasEdge(edge: GraphEdge): boolean;
|
|
15
|
-
addEdge(edge: GraphEdge): void;
|
|
16
|
-
addEdgeIfAbsent(edge: GraphEdge): boolean;
|
|
17
|
-
neighbors(id: string, direction: "out" | "in" | "both", edgeTypes?: Set<string>, depth?: number): Set<string>;
|
|
18
|
-
validateStructure(): ValidationIssue[];
|
|
19
|
-
}
|
|
1
|
+
export { InMemoryGraph } from "ai-spector-graph";
|
|
20
2
|
//# sourceMappingURL=InMemoryGraph.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InMemoryGraph.d.ts","sourceRoot":"","sources":["../../src/graph/InMemoryGraph.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"InMemoryGraph.d.ts","sourceRoot":"","sources":["../../src/graph/InMemoryGraph.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -1,193 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export class InMemoryGraph {
|
|
3
|
-
nodesById = new Map();
|
|
4
|
-
outEdges = new Map();
|
|
5
|
-
inEdges = new Map();
|
|
6
|
-
edgeKeys = new Set();
|
|
7
|
-
static from(data) {
|
|
8
|
-
const g = new InMemoryGraph();
|
|
9
|
-
for (const node of data.nodes) {
|
|
10
|
-
g.addNode(node);
|
|
11
|
-
}
|
|
12
|
-
for (const edge of data.edges) {
|
|
13
|
-
if (!g.nodesById.has(edge.from)) {
|
|
14
|
-
throw new Error(`Edge references missing node: ${edge.from} -> ${edge.to}`);
|
|
15
|
-
}
|
|
16
|
-
if (!isPathTargetEdge(edge.type) && !g.nodesById.has(edge.to)) {
|
|
17
|
-
throw new Error(`Edge references missing node: ${edge.from} -> ${edge.to}`);
|
|
18
|
-
}
|
|
19
|
-
g.addEdge(edge);
|
|
20
|
-
}
|
|
21
|
-
return g;
|
|
22
|
-
}
|
|
23
|
-
/** Dedupe edges when exporting (each edge stored once in outEdges). */
|
|
24
|
-
toTraceabilityGraph() {
|
|
25
|
-
const edgeSet = new Set();
|
|
26
|
-
const edges = [];
|
|
27
|
-
for (const list of this.outEdges.values()) {
|
|
28
|
-
for (const e of list) {
|
|
29
|
-
const key = `${e.type}|${e.from}|${e.to}|${e.role ?? ""}`;
|
|
30
|
-
if (!edgeSet.has(key)) {
|
|
31
|
-
edgeSet.add(key);
|
|
32
|
-
edges.push(e);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return {
|
|
37
|
-
version: 1,
|
|
38
|
-
nodes: [...this.nodesById.values()],
|
|
39
|
-
edges,
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
addNode(node) {
|
|
43
|
-
if (this.nodesById.has(node.id)) {
|
|
44
|
-
throw new Error(`Duplicate node id: ${node.id}`);
|
|
45
|
-
}
|
|
46
|
-
this.nodesById.set(node.id, node);
|
|
47
|
-
this.outEdges.set(node.id, []);
|
|
48
|
-
this.inEdges.set(node.id, []);
|
|
49
|
-
}
|
|
50
|
-
/** Update an existing node or insert. Same `type` required when updating. */
|
|
51
|
-
upsertNode(node) {
|
|
52
|
-
const existing = this.nodesById.get(node.id);
|
|
53
|
-
if (existing) {
|
|
54
|
-
if (existing.type !== node.type) {
|
|
55
|
-
throw new Error(`Cannot change node type for ${node.id}: ${existing.type} → ${node.type}`);
|
|
56
|
-
}
|
|
57
|
-
this.nodesById.set(node.id, { ...existing, ...node });
|
|
58
|
-
return "updated";
|
|
59
|
-
}
|
|
60
|
-
this.addNode(node);
|
|
61
|
-
return "created";
|
|
62
|
-
}
|
|
63
|
-
edgeKey(edge) {
|
|
64
|
-
return `${edge.type}|${edge.from}|${edge.to}|${edge.role ?? ""}`;
|
|
65
|
-
}
|
|
66
|
-
hasEdge(edge) {
|
|
67
|
-
return this.edgeKeys.has(this.edgeKey(edge));
|
|
68
|
-
}
|
|
69
|
-
addEdge(edge) {
|
|
70
|
-
this.edgeKeys.add(this.edgeKey(edge));
|
|
71
|
-
this.outEdges.get(edge.from).push(edge);
|
|
72
|
-
const inbound = this.inEdges.get(edge.to);
|
|
73
|
-
if (inbound) {
|
|
74
|
-
inbound.push(edge);
|
|
75
|
-
}
|
|
76
|
-
else if (!isPathTargetEdge(edge.type)) {
|
|
77
|
-
throw new Error(`Edge references missing target node: ${edge.from} -> ${edge.to}`);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
addEdgeIfAbsent(edge) {
|
|
81
|
-
if (this.hasEdge(edge)) {
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
if (!this.nodesById.has(edge.from)) {
|
|
85
|
-
throw new Error(`Edge references missing source node: ${edge.from}`);
|
|
86
|
-
}
|
|
87
|
-
if (!isPathTargetEdge(edge.type) && !this.nodesById.has(edge.to)) {
|
|
88
|
-
throw new Error(`Edge references missing target node: ${edge.from} -> ${edge.to}`);
|
|
89
|
-
}
|
|
90
|
-
this.addEdge(edge);
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
neighbors(id, direction, edgeTypes, depth = 1) {
|
|
94
|
-
const seen = new Set();
|
|
95
|
-
const queue = [{ id, d: 0 }];
|
|
96
|
-
while (queue.length > 0) {
|
|
97
|
-
const { id: current, d } = queue.shift();
|
|
98
|
-
if (d > depth) {
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
if (d > 0) {
|
|
102
|
-
seen.add(current);
|
|
103
|
-
}
|
|
104
|
-
if (d === depth) {
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
const expand = (next) => {
|
|
108
|
-
if (!seen.has(next) && next !== id) {
|
|
109
|
-
queue.push({ id: next, d: d + 1 });
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
if (direction === "out" || direction === "both") {
|
|
113
|
-
for (const e of this.outEdges.get(current) ?? []) {
|
|
114
|
-
if (!edgeTypes || edgeTypes.has(e.type)) {
|
|
115
|
-
expand(e.to);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
if (direction === "in" || direction === "both") {
|
|
120
|
-
for (const e of this.inEdges.get(current) ?? []) {
|
|
121
|
-
if (!edgeTypes || edgeTypes.has(e.type)) {
|
|
122
|
-
expand(e.from);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
seen.delete(id);
|
|
128
|
-
return seen;
|
|
129
|
-
}
|
|
130
|
-
validateStructure() {
|
|
131
|
-
const issues = [];
|
|
132
|
-
const domainTypes = new Set([
|
|
133
|
-
"actor",
|
|
134
|
-
"useCase",
|
|
135
|
-
"feature",
|
|
136
|
-
"requirement",
|
|
137
|
-
"nfr",
|
|
138
|
-
"dataEntity",
|
|
139
|
-
]);
|
|
140
|
-
for (const node of this.nodesById.values()) {
|
|
141
|
-
if (node.type === "section") {
|
|
142
|
-
const partOf = (this.outEdges.get(node.id) ?? []).filter((e) => e.type === "partOf");
|
|
143
|
-
if (partOf.length !== 1) {
|
|
144
|
-
issues.push({
|
|
145
|
-
ruleId: "SECTION-TREE",
|
|
146
|
-
severity: "error",
|
|
147
|
-
message: `Section ${node.id} must have exactly one partOf parent (found ${partOf.length})`,
|
|
148
|
-
nodeId: node.id,
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
if (domainTypes.has(node.type)) {
|
|
153
|
-
const anchored = (this.outEdges.get(node.id) ?? []).some((e) => ["listedIn", "definedIn", "describedIn"].includes(e.type));
|
|
154
|
-
const anchoredIn = (this.inEdges.get(node.id) ?? []).some((e) => ["listedIn", "definedIn", "describedIn"].includes(e.type));
|
|
155
|
-
if (!anchored && !anchoredIn) {
|
|
156
|
-
issues.push({
|
|
157
|
-
ruleId: "DOMAIN-ANCHORED",
|
|
158
|
-
severity: "error",
|
|
159
|
-
message: `Domain node ${node.id} has no listedIn, definedIn, or describedIn edge`,
|
|
160
|
-
nodeId: node.id,
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
for (const node of this.nodesById.values()) {
|
|
166
|
-
if (node.type === "document") {
|
|
167
|
-
if (node.outputPattern) {
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
const isPerDomainInstance = typeof node.output === "string" &&
|
|
171
|
-
(node.perDomain === "useCase" ||
|
|
172
|
-
node.perDomain === "feature" ||
|
|
173
|
-
node.perDomain === "apiDetail" ||
|
|
174
|
-
node.perDomain === "screenDetail");
|
|
175
|
-
if (isPerDomainInstance) {
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
const hasSection = [...this.outEdges.get(node.id) ?? []].some((e) => e.type === "contains" &&
|
|
179
|
-
this.nodesById.get(e.to)?.type === "section");
|
|
180
|
-
if (!hasSection) {
|
|
181
|
-
issues.push({
|
|
182
|
-
ruleId: "DOC-SECTION-COVERAGE",
|
|
183
|
-
severity: "error",
|
|
184
|
-
message: `Document ${node.id} has no child sections`,
|
|
185
|
-
nodeId: node.id,
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return issues;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
1
|
+
export { InMemoryGraph } from "ai-spector-graph";
|
|
193
2
|
//# sourceMappingURL=InMemoryGraph.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InMemoryGraph.js","sourceRoot":"","sources":["../../src/graph/InMemoryGraph.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"InMemoryGraph.js","sourceRoot":"","sources":["../../src/graph/InMemoryGraph.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/graph/impact.d.ts
CHANGED
|
@@ -1,51 +1,5 @@
|
|
|
1
|
-
import type
|
|
2
|
-
|
|
3
|
-
type
|
|
4
|
-
direction: "in" | "out";
|
|
5
|
-
depth: number | "unbounded";
|
|
6
|
-
};
|
|
7
|
-
export interface ImpactRulesFile {
|
|
8
|
-
version: 2;
|
|
9
|
-
pass1_expand: Record<string, EdgeRule>;
|
|
10
|
-
pass2_downstream: Record<string, EdgeRule>;
|
|
11
|
-
buckets: {
|
|
12
|
-
regenerate: NodeType[];
|
|
13
|
-
review: NodeType[];
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
export interface ImpactEntry {
|
|
17
|
-
id: string;
|
|
18
|
-
type: string;
|
|
19
|
-
reason: string;
|
|
20
|
-
projectionPath?: string;
|
|
21
|
-
}
|
|
22
|
-
export interface ImpactResult {
|
|
23
|
-
origin: {
|
|
24
|
-
id: string;
|
|
25
|
-
type: string;
|
|
26
|
-
change: string;
|
|
27
|
-
};
|
|
28
|
-
regenerate: ImpactEntry[];
|
|
29
|
-
review: ImpactEntry[];
|
|
30
|
-
affectedOutputPaths: string[];
|
|
31
|
-
staleTranslations?: ImpactEntry[];
|
|
32
|
-
resolvedFrom?: {
|
|
33
|
-
id: string;
|
|
34
|
-
type: string;
|
|
35
|
-
reason: string;
|
|
36
|
-
};
|
|
37
|
-
truncated?: boolean;
|
|
38
|
-
noTraceabilityImpact?: boolean;
|
|
39
|
-
gitSeeds?: Array<{
|
|
40
|
-
id: string;
|
|
41
|
-
type: string;
|
|
42
|
-
reason: string;
|
|
43
|
-
file?: string;
|
|
44
|
-
heading?: string;
|
|
45
|
-
}>;
|
|
46
|
-
}
|
|
47
|
-
export declare function computeImpact(g: InMemoryGraph, originId: string, change: string, rules: ImpactRulesFile): ImpactResult;
|
|
48
|
-
export declare function mergeImpactResults(results: ImpactResult[], gitSeeds?: ImpactResult["gitSeeds"]): ImpactResult;
|
|
1
|
+
import { type ImpactRulesFile } from "ai-spector-graph";
|
|
2
|
+
export { computeImpact, mergeImpactResults, parseImpactRules, DEFAULT_IMPACT_RULES, } from "ai-spector-graph";
|
|
3
|
+
export type { ImpactRulesFile, ImpactEntry, ImpactResult } from "ai-spector-graph";
|
|
49
4
|
export declare function loadImpactRules(path: string): Promise<ImpactRulesFile>;
|
|
50
|
-
export {};
|
|
51
5
|
//# sourceMappingURL=impact.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"impact.d.ts","sourceRoot":"","sources":["../../src/graph/impact.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"impact.d.ts","sourceRoot":"","sources":["../../src/graph/impact.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEnF,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAE5E"}
|
package/dist/graph/impact.js
CHANGED
|
@@ -1,201 +1,7 @@
|
|
|
1
1
|
import { readJson } from "../util/fs.js";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
return d === "unbounded" ? 10_000 : d;
|
|
5
|
-
}
|
|
6
|
-
function runBfs(g, seeds, rules, globalSeen) {
|
|
7
|
-
const provenance = new Map();
|
|
8
|
-
const queue = seeds.map((id) => ({ id, depth: 0 }));
|
|
9
|
-
const cap = 500;
|
|
10
|
-
let capped = false;
|
|
11
|
-
while (queue.length > 0) {
|
|
12
|
-
const { id: current, depth } = queue.shift();
|
|
13
|
-
if (depth >= cap) {
|
|
14
|
-
capped = true;
|
|
15
|
-
continue;
|
|
16
|
-
}
|
|
17
|
-
for (const [edgeType, rule] of Object.entries(rules)) {
|
|
18
|
-
const limit = maxDepth(rule.depth);
|
|
19
|
-
if (depth >= limit)
|
|
20
|
-
continue;
|
|
21
|
-
if (rule.direction === "out") {
|
|
22
|
-
for (const e of g.outEdges.get(current) ?? []) {
|
|
23
|
-
if (e.type !== edgeType || globalSeen.has(e.to))
|
|
24
|
-
continue;
|
|
25
|
-
globalSeen.add(e.to);
|
|
26
|
-
provenance.set(e.to, { fromId: current, edgeType });
|
|
27
|
-
queue.push({ id: e.to, depth: depth + 1 });
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
for (const e of g.inEdges.get(current) ?? []) {
|
|
32
|
-
if (e.type !== edgeType || globalSeen.has(e.from))
|
|
33
|
-
continue;
|
|
34
|
-
globalSeen.add(e.from);
|
|
35
|
-
provenance.set(e.from, { fromId: current, edgeType });
|
|
36
|
-
queue.push({ id: e.from, depth: depth + 1 });
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return { provenance, capped };
|
|
42
|
-
}
|
|
43
|
-
function makeEntry(g, id, prov) {
|
|
44
|
-
const node = g.nodesById.get(id);
|
|
45
|
-
return {
|
|
46
|
-
id,
|
|
47
|
-
type: node.type,
|
|
48
|
-
reason: `${prov.edgeType} from ${prov.fromId}`,
|
|
49
|
-
projectionPath: projectionPathForNode(g, id),
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
export function computeImpact(g, originId, change, rules) {
|
|
53
|
-
const origin = g.nodesById.get(originId);
|
|
54
|
-
if (!origin)
|
|
55
|
-
throw new Error(`Unknown node: ${originId}`);
|
|
56
|
-
const result = {
|
|
57
|
-
origin: { id: originId, type: origin.type, change },
|
|
58
|
-
regenerate: [],
|
|
59
|
-
review: [],
|
|
60
|
-
affectedOutputPaths: [],
|
|
61
|
-
};
|
|
62
|
-
const regenerateTypes = new Set(rules.buckets.regenerate);
|
|
63
|
-
const reviewTypes = new Set(rules.buckets.review);
|
|
64
|
-
const globalSeen = new Set([originId]);
|
|
65
|
-
let truncated = false;
|
|
66
|
-
// Pass 1 — expand upward from origin
|
|
67
|
-
const { provenance: p1, capped: c1 } = runBfs(g, [originId], rules.pass1_expand, globalSeen);
|
|
68
|
-
if (c1)
|
|
69
|
-
truncated = true;
|
|
70
|
-
const domainSeeds = [];
|
|
71
|
-
for (const [id, prov] of p1) {
|
|
72
|
-
const node = g.nodesById.get(id);
|
|
73
|
-
if (!node)
|
|
74
|
-
continue;
|
|
75
|
-
if (regenerateTypes.has(node.type)) {
|
|
76
|
-
result.regenerate.push(makeEntry(g, id, prov));
|
|
77
|
-
}
|
|
78
|
-
else if (reviewTypes.has(node.type)) {
|
|
79
|
-
result.review.push(makeEntry(g, id, prov));
|
|
80
|
-
domainSeeds.push(id);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
// Also seed pass 2 from origin itself if it's a domain node
|
|
84
|
-
if (reviewTypes.has(origin.type)) {
|
|
85
|
-
domainSeeds.push(originId);
|
|
86
|
-
}
|
|
87
|
-
// Pass 2 — propagate downstream from domain nodes
|
|
88
|
-
if (domainSeeds.length > 0) {
|
|
89
|
-
const { provenance: p2, capped: c2 } = runBfs(g, domainSeeds, rules.pass2_downstream, globalSeen);
|
|
90
|
-
if (c2)
|
|
91
|
-
truncated = true;
|
|
92
|
-
for (const [id, prov] of p2) {
|
|
93
|
-
const node = g.nodesById.get(id);
|
|
94
|
-
if (!node)
|
|
95
|
-
continue;
|
|
96
|
-
if (regenerateTypes.has(node.type)) {
|
|
97
|
-
result.regenerate.push(makeEntry(g, id, prov));
|
|
98
|
-
}
|
|
99
|
-
else if (reviewTypes.has(node.type)) {
|
|
100
|
-
result.review.push(makeEntry(g, id, prov));
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
if (truncated)
|
|
105
|
-
result.truncated = true;
|
|
106
|
-
result.regenerate.sort((a, b) => a.id.localeCompare(b.id));
|
|
107
|
-
result.review.sort((a, b) => a.id.localeCompare(b.id));
|
|
108
|
-
// Pass 3 — collect output paths from rendersTo edges on regenerate nodes
|
|
109
|
-
for (const entry of result.regenerate) {
|
|
110
|
-
for (const e of g.outEdges.get(entry.id) ?? []) {
|
|
111
|
-
if (e.type === "rendersTo")
|
|
112
|
-
result.affectedOutputPaths.push(e.to);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
result.affectedOutputPaths.sort();
|
|
116
|
-
// Collect stale translation nodes
|
|
117
|
-
const affectedDocIds = new Set(result.regenerate.filter((e) => g.nodesById.get(e.id)?.type === "document").map((e) => e.id));
|
|
118
|
-
if (origin.type === "document")
|
|
119
|
-
affectedDocIds.add(originId);
|
|
120
|
-
const staleTranslations = [];
|
|
121
|
-
for (const docId of affectedDocIds) {
|
|
122
|
-
for (const e of g.inEdges.get(docId) ?? []) {
|
|
123
|
-
if (e.type !== "translationOf")
|
|
124
|
-
continue;
|
|
125
|
-
const n = g.nodesById.get(e.from);
|
|
126
|
-
if (!n)
|
|
127
|
-
continue;
|
|
128
|
-
staleTranslations.push({
|
|
129
|
-
id: e.from,
|
|
130
|
-
type: n.type,
|
|
131
|
-
reason: `translationOf ${docId}`,
|
|
132
|
-
projectionPath: projectionPathForNode(g, e.from),
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
if (staleTranslations.length > 0) {
|
|
137
|
-
staleTranslations.sort((a, b) => a.id.localeCompare(b.id));
|
|
138
|
-
result.staleTranslations = staleTranslations;
|
|
139
|
-
}
|
|
140
|
-
return result;
|
|
141
|
-
}
|
|
142
|
-
export function mergeImpactResults(results, gitSeeds) {
|
|
143
|
-
if (results.length === 0)
|
|
144
|
-
throw new Error("mergeImpactResults requires at least one result");
|
|
145
|
-
if (results.length === 1) {
|
|
146
|
-
const single = { ...results[0] };
|
|
147
|
-
if (gitSeeds?.length)
|
|
148
|
-
single.gitSeeds = gitSeeds;
|
|
149
|
-
return single;
|
|
150
|
-
}
|
|
151
|
-
const merged = {
|
|
152
|
-
origin: {
|
|
153
|
-
id: results.map((r) => r.origin.id).join(","),
|
|
154
|
-
type: "multi",
|
|
155
|
-
change: results[0].origin.change,
|
|
156
|
-
},
|
|
157
|
-
regenerate: [],
|
|
158
|
-
review: [],
|
|
159
|
-
affectedOutputPaths: [],
|
|
160
|
-
gitSeeds: gitSeeds ??
|
|
161
|
-
results.map((r) => ({
|
|
162
|
-
id: r.origin.id,
|
|
163
|
-
type: r.origin.type,
|
|
164
|
-
reason: r.resolvedFrom?.reason ?? "git diff seed",
|
|
165
|
-
})),
|
|
166
|
-
};
|
|
167
|
-
if (results.some((r) => r.truncated))
|
|
168
|
-
merged.truncated = true;
|
|
169
|
-
const seen = new Set();
|
|
170
|
-
for (const key of ["regenerate", "review"]) {
|
|
171
|
-
for (const r of results) {
|
|
172
|
-
for (const e of r[key]) {
|
|
173
|
-
const dk = `${key}:${e.id}`;
|
|
174
|
-
if (seen.has(dk))
|
|
175
|
-
continue;
|
|
176
|
-
seen.add(dk);
|
|
177
|
-
merged[key].push(e);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
merged[key].sort((a, b) => a.id.localeCompare(b.id));
|
|
181
|
-
}
|
|
182
|
-
const pathSeen = new Set();
|
|
183
|
-
for (const r of results) {
|
|
184
|
-
for (const p of r.affectedOutputPaths) {
|
|
185
|
-
if (!pathSeen.has(p)) {
|
|
186
|
-
pathSeen.add(p);
|
|
187
|
-
merged.affectedOutputPaths.push(p);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
merged.affectedOutputPaths.sort();
|
|
192
|
-
return merged;
|
|
193
|
-
}
|
|
2
|
+
import { parseImpactRules, } from "ai-spector-graph";
|
|
3
|
+
export { computeImpact, mergeImpactResults, parseImpactRules, DEFAULT_IMPACT_RULES, } from "ai-spector-graph";
|
|
194
4
|
export async function loadImpactRules(path) {
|
|
195
|
-
|
|
196
|
-
if (raw.version !== 2) {
|
|
197
|
-
throw new Error(`rules.impact.json version ${raw.version} is not supported. Expected version 2. Update your rules file.`);
|
|
198
|
-
}
|
|
199
|
-
return raw;
|
|
5
|
+
return parseImpactRules(await readJson(path));
|
|
200
6
|
}
|
|
201
7
|
//# sourceMappingURL=impact.js.map
|
package/dist/graph/impact.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"impact.js","sourceRoot":"","sources":["../../src/graph/impact.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"impact.js","sourceRoot":"","sources":["../../src/graph/impact.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EACL,gBAAgB,GAEjB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAG1B,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAY;IAChD,OAAO,gBAAgB,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;AAChD,CAAC"}
|
|
@@ -1,53 +1,11 @@
|
|
|
1
|
+
import type { AnalysisKnowledge } from "ai-spector-graph";
|
|
1
2
|
import type { GraphEdge, GraphNode } from "../types.js";
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
actors?: KnowledgeActor[];
|
|
5
|
-
useCases?: KnowledgeUseCase[];
|
|
6
|
-
features?: KnowledgeFeature[];
|
|
7
|
-
functionalRequirements?: KnowledgeRequirement[];
|
|
8
|
-
nfrs?: KnowledgeRequirement[];
|
|
9
|
-
entities?: KnowledgeEntity[];
|
|
10
|
-
}
|
|
11
|
-
export interface KnowledgeActor {
|
|
12
|
-
id: string;
|
|
13
|
-
name?: string;
|
|
14
|
-
title?: string;
|
|
15
|
-
description?: string;
|
|
16
|
-
listedInSection?: string;
|
|
17
|
-
}
|
|
18
|
-
export interface KnowledgeUseCase {
|
|
19
|
-
id: string;
|
|
20
|
-
title: string;
|
|
21
|
-
priority?: string;
|
|
22
|
-
description?: string;
|
|
23
|
-
listedInSection?: string;
|
|
24
|
-
}
|
|
25
|
-
export interface KnowledgeFeature {
|
|
26
|
-
id: string;
|
|
27
|
-
title: string;
|
|
28
|
-
description?: string;
|
|
29
|
-
listedInSection?: string;
|
|
30
|
-
satisfies?: string[];
|
|
31
|
-
}
|
|
32
|
-
export interface KnowledgeRequirement {
|
|
33
|
-
id: string;
|
|
34
|
-
title: string;
|
|
35
|
-
description?: string;
|
|
36
|
-
listedInSection?: string;
|
|
37
|
-
tracesTo?: string[];
|
|
38
|
-
}
|
|
39
|
-
export interface KnowledgeEntity {
|
|
40
|
-
id: string;
|
|
41
|
-
name: string;
|
|
42
|
-
description?: string;
|
|
43
|
-
listedInSection?: string;
|
|
44
|
-
}
|
|
3
|
+
export type { AnalysisKnowledge, KnowledgeActor, KnowledgeUseCase, KnowledgeFeature, KnowledgeRequirement, KnowledgeEntity, } from "ai-spector-graph";
|
|
4
|
+
export { isKnowledgePayload, knowledgeHasDomainEntries, } from "ai-spector-graph";
|
|
45
5
|
export interface ExtractPatch {
|
|
46
6
|
version: 1;
|
|
47
7
|
nodes: GraphNode[];
|
|
48
8
|
edges: GraphEdge[];
|
|
49
9
|
}
|
|
50
10
|
export declare function knowledgeToPatch(knowledge: AnalysisKnowledge): ExtractPatch;
|
|
51
|
-
export declare function isKnowledgePayload(data: unknown): data is AnalysisKnowledge;
|
|
52
|
-
export declare function knowledgeHasDomainEntries(knowledge: AnalysisKnowledge): boolean;
|
|
53
11
|
//# sourceMappingURL=knowledge.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"knowledge.d.ts","sourceRoot":"","sources":["../../src/graph/knowledge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"knowledge.d.ts","sourceRoot":"","sources":["../../src/graph/knowledge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EAMlB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGxD,YAAY,EACV,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EACpB,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,GAC1B,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,CAAC,CAAC;IACX,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,SAAS,EAAE,CAAC;CACpB;AAsBD,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,iBAAiB,GAAG,YAAY,CA8D3E"}
|
package/dist/graph/knowledge.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DEFAULT_LISTED_IN } from "./defaults.js";
|
|
2
|
+
export { isKnowledgePayload, knowledgeHasDomainEntries, } from "ai-spector-graph";
|
|
2
3
|
function pickNodeFields(item, id, type) {
|
|
3
4
|
const node = { id, type };
|
|
4
5
|
for (const [key, value] of Object.entries(item)) {
|
|
@@ -58,22 +59,4 @@ export function knowledgeToPatch(knowledge) {
|
|
|
58
59
|
}
|
|
59
60
|
return { version: 1, nodes, edges };
|
|
60
61
|
}
|
|
61
|
-
export function isKnowledgePayload(data) {
|
|
62
|
-
if (!data || typeof data !== "object") {
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
const k = data;
|
|
66
|
-
return ("knowledgeVersion" in k ||
|
|
67
|
-
Array.isArray(k.useCases) ||
|
|
68
|
-
Array.isArray(k.features) ||
|
|
69
|
-
Array.isArray(k.actors));
|
|
70
|
-
}
|
|
71
|
-
export function knowledgeHasDomainEntries(knowledge) {
|
|
72
|
-
return ((knowledge.useCases?.length ?? 0) > 0 ||
|
|
73
|
-
(knowledge.features?.length ?? 0) > 0 ||
|
|
74
|
-
(knowledge.actors?.length ?? 0) > 0 ||
|
|
75
|
-
(knowledge.functionalRequirements?.length ?? 0) > 0 ||
|
|
76
|
-
(knowledge.nfrs?.length ?? 0) > 0 ||
|
|
77
|
-
(knowledge.entities?.length ?? 0) > 0);
|
|
78
|
-
}
|
|
79
62
|
//# sourceMappingURL=knowledge.js.map
|