@specferret/core 0.9.2 → 0.9.3
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/context/index.d.ts.map +1 -1
- package/dist/context/index.js +25 -0
- package/dist/context/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/reconciler/dependents.d.ts +25 -0
- package/dist/reconciler/dependents.d.ts.map +1 -0
- package/dist/reconciler/dependents.js +55 -0
- package/dist/reconciler/dependents.js.map +1 -0
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +8 -1
- package/dist/store/sqlite.js.map +1 -1
- package/package.json +1 -1
- package/src/context/index.test.ts +99 -0
- package/src/context/index.ts +27 -0
- package/src/index.ts +1 -0
- package/src/reconciler/dependents.test.ts +96 -0
- package/src/reconciler/dependents.ts +76 -0
- package/src/store/sqlite.ts +8 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/context/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,eAAO,MAAM,eAAe,EAAG,KAAc,CAAC;AAC9C,eAAO,MAAM,sBAAsB,EAAG,OAAgB,CAAC;AAUvD,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,eAAe,CAAC;IAChC,aAAa,EAAE,OAAO,sBAAsB,CAAC;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAkED,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,CAalE;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,KAAK,EAAE,OAAO,EACd,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/context/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,eAAO,MAAM,eAAe,EAAG,KAAc,CAAC;AAC9C,eAAO,MAAM,sBAAsB,EAAG,OAAgB,CAAC;AAUvD,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,eAAe,CAAC;IAChC,aAAa,EAAE,OAAO,sBAAsB,CAAC;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAkED,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,CAalE;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,KAAK,EAAE,OAAO,EACd,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAuFf"}
|
package/dist/context/index.js
CHANGED
|
@@ -131,10 +131,35 @@ export async function writeContext(store, projectRoot) {
|
|
|
131
131
|
if (!fs.existsSync(ferretDir)) {
|
|
132
132
|
fs.mkdirSync(ferretDir, { recursive: true });
|
|
133
133
|
}
|
|
134
|
+
// Leave the file alone when only `generated` would change. context.json is
|
|
135
|
+
// meant to be committed, so rewriting it on every run would dirty the
|
|
136
|
+
// user's repository after every lint even when nothing about the graph
|
|
137
|
+
// moved. Both sides are produced by this same function, so key order is
|
|
138
|
+
// stable and a string comparison is sufficient.
|
|
139
|
+
if (fs.existsSync(contextPath) && !hasContextChanged(contextPath, context)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
134
142
|
fs.writeFileSync(contextPath, JSON.stringify(context, null, 2), "utf-8");
|
|
135
143
|
}
|
|
136
144
|
catch (err) {
|
|
137
145
|
process.stderr.write(`⚠ Could not write context.json: ${err}\n`);
|
|
138
146
|
}
|
|
139
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* True when the graph content differs from what is already on disk, ignoring
|
|
150
|
+
* the `generated` timestamp. An unreadable or malformed existing file counts
|
|
151
|
+
* as changed, so a corrupt context.json is always replaced.
|
|
152
|
+
*/
|
|
153
|
+
function hasContextChanged(contextPath, next) {
|
|
154
|
+
try {
|
|
155
|
+
const existingRaw = fs.readFileSync(contextPath, "utf-8");
|
|
156
|
+
const existing = JSON.parse(existingRaw);
|
|
157
|
+
const { generated: _existingGenerated, ...existingRest } = existing;
|
|
158
|
+
const { generated: _nextGenerated, ...nextRest } = next;
|
|
159
|
+
return JSON.stringify(existingRest) !== JSON.stringify(nextRest);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
140
165
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/context/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/context/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export * from './store/sqlite.js';
|
|
|
11
11
|
export * from './store/factory.js';
|
|
12
12
|
export * from './reconciler/index.js';
|
|
13
13
|
export * from './reconciler/import-suggestions.js';
|
|
14
|
+
export * from './reconciler/dependents.js';
|
|
14
15
|
export * from './config.js';
|
|
15
16
|
export * from './utils/paths.js';
|
|
16
17
|
export * from './contract.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oCAAoC,CAAC;AACnD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qBAAqB,CAAC;AACpC,cAAc,kCAAkC,CAAC;AACjD,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oCAAoC,CAAC;AACnD,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oCAAoC,CAAC;AACnD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qBAAqB,CAAC;AACpC,cAAc,kCAAkC,CAAC;AACjD,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oCAAoC,CAAC;AACnD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ export * from './store/sqlite.js';
|
|
|
13
13
|
export * from './store/factory.js';
|
|
14
14
|
export * from './reconciler/index.js';
|
|
15
15
|
export * from './reconciler/import-suggestions.js';
|
|
16
|
+
export * from './reconciler/dependents.js';
|
|
16
17
|
export * from './config.js';
|
|
17
18
|
export * from './utils/paths.js';
|
|
18
19
|
export * from './contract.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,4EAA4E;AAE5E,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oCAAoC,CAAC;AACnD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qBAAqB,CAAC;AACpC,cAAc,kCAAkC,CAAC;AACjD,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oCAAoC,CAAC;AACnD,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,4EAA4E;AAE5E,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oCAAoC,CAAC;AACnD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qBAAqB,CAAC;AACpC,cAAc,kCAAkC,CAAC;AACjD,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oCAAoC,CAAC;AACnD,cAAc,4BAA4B,CAAC;AAC3C,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { FerretContract, FerretDependency, FerretNode } from '../store/types.js';
|
|
2
|
+
export interface DependentContract {
|
|
3
|
+
contractId: string;
|
|
4
|
+
filePath: string;
|
|
5
|
+
/** 1 = imports the drifted contract directly; >1 = reached through other contracts. */
|
|
6
|
+
depth: number;
|
|
7
|
+
impact: 'direct' | 'transitive';
|
|
8
|
+
}
|
|
9
|
+
export interface DependentsInput {
|
|
10
|
+
dependencies: FerretDependency[];
|
|
11
|
+
nodes: FerretNode[];
|
|
12
|
+
contracts: FerretContract[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Breadth-first walk over dependency edges to find every contract that depends
|
|
16
|
+
* on `contractId`, directly or transitively.
|
|
17
|
+
*
|
|
18
|
+
* Mirrors the Reconciler's traversal semantics deliberately: edges point
|
|
19
|
+
* dependent -> target, depth 1 means direct, and the walk is capped at 10 hops
|
|
20
|
+
* (S011). Unlike the Reconciler it never filters by node status and never
|
|
21
|
+
* writes — a contract's dependents are worth reporting whatever state they
|
|
22
|
+
* are in.
|
|
23
|
+
*/
|
|
24
|
+
export declare function findDependents(contractId: string, input: DependentsInput): DependentContract[];
|
|
25
|
+
//# sourceMappingURL=dependents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dependents.d.ts","sourceRoot":"","sources":["../../src/reconciler/dependents.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEtF,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,uFAAuF;IACvF,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,QAAQ,GAAG,YAAY,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,gBAAgB,EAAE,CAAC;IACjC,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,SAAS,EAAE,cAAc,EAAE,CAAC;CAC7B;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,iBAAiB,EAAE,CAwC9F"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Pure function. No I/O. No side effects. Ever.
|
|
2
|
+
// Answers "who depends on this contract?" without mutating graph state.
|
|
3
|
+
//
|
|
4
|
+
// The Reconciler walks the same edges but only from nodes it has already
|
|
5
|
+
// flagged needs-review, and it writes status as it goes. Upward drift (code
|
|
6
|
+
// diverged from its declared contract) never flags the contract's node, so
|
|
7
|
+
// that walk never runs — leaving the user told that a shape broke but not who
|
|
8
|
+
// relies on it. This read-only walk fills that gap.
|
|
9
|
+
/**
|
|
10
|
+
* Breadth-first walk over dependency edges to find every contract that depends
|
|
11
|
+
* on `contractId`, directly or transitively.
|
|
12
|
+
*
|
|
13
|
+
* Mirrors the Reconciler's traversal semantics deliberately: edges point
|
|
14
|
+
* dependent -> target, depth 1 means direct, and the walk is capped at 10 hops
|
|
15
|
+
* (S011). Unlike the Reconciler it never filters by node status and never
|
|
16
|
+
* writes — a contract's dependents are worth reporting whatever state they
|
|
17
|
+
* are in.
|
|
18
|
+
*/
|
|
19
|
+
export function findDependents(contractId, input) {
|
|
20
|
+
const { dependencies, nodes, contracts } = input;
|
|
21
|
+
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
22
|
+
const contractsByNodeId = new Map();
|
|
23
|
+
for (const contract of contracts) {
|
|
24
|
+
const existing = contractsByNodeId.get(contract.node_id) ?? [];
|
|
25
|
+
existing.push(contract);
|
|
26
|
+
contractsByNodeId.set(contract.node_id, existing);
|
|
27
|
+
}
|
|
28
|
+
const found = [];
|
|
29
|
+
const seenContracts = new Set([contractId]);
|
|
30
|
+
const queue = [[contractId, 1]];
|
|
31
|
+
while (queue.length > 0) {
|
|
32
|
+
const [currentId, depth] = queue.shift();
|
|
33
|
+
if (depth > 10)
|
|
34
|
+
continue;
|
|
35
|
+
for (const edge of dependencies.filter((d) => d.target_contract_id === currentId)) {
|
|
36
|
+
const dependentNode = nodeById.get(edge.source_node_id);
|
|
37
|
+
if (!dependentNode)
|
|
38
|
+
continue;
|
|
39
|
+
for (const dependentContract of contractsByNodeId.get(dependentNode.id) ?? []) {
|
|
40
|
+
if (seenContracts.has(dependentContract.id))
|
|
41
|
+
continue;
|
|
42
|
+
seenContracts.add(dependentContract.id);
|
|
43
|
+
found.push({
|
|
44
|
+
contractId: dependentContract.id,
|
|
45
|
+
filePath: dependentNode.file_path.replace(/\\/g, '/'),
|
|
46
|
+
depth,
|
|
47
|
+
impact: depth === 1 ? 'direct' : 'transitive',
|
|
48
|
+
});
|
|
49
|
+
queue.push([dependentContract.id, depth + 1]);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return found.sort((a, b) => (a.depth === b.depth ? a.contractId.localeCompare(b.contractId) : a.depth - b.depth));
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=dependents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dependents.js","sourceRoot":"","sources":["../../src/reconciler/dependents.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,wEAAwE;AACxE,EAAE;AACF,yEAAyE;AACzE,4EAA4E;AAC5E,2EAA2E;AAC3E,8EAA8E;AAC9E,oDAAoD;AAkBpD;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAC,UAAkB,EAAE,KAAsB;IACvE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC;IAEjD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAC/D,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAA4B,CAAC;IAC9D,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QAC/D,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACxB,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,KAAK,GAAwB,EAAE,CAAC;IACtC,MAAM,aAAa,GAAG,IAAI,GAAG,CAAS,CAAC,UAAU,CAAC,CAAC,CAAC;IACpD,MAAM,KAAK,GAA4B,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;IAEzD,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG,KAAK,CAAC,KAAK,EAAG,CAAC;QAC1C,IAAI,KAAK,GAAG,EAAE;YAAE,SAAS;QAEzB,KAAK,MAAM,IAAI,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,kBAAkB,KAAK,SAAS,CAAC,EAAE,CAAC;YAClF,MAAM,aAAa,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YACxD,IAAI,CAAC,aAAa;gBAAE,SAAS;YAE7B,KAAK,MAAM,iBAAiB,IAAI,iBAAiB,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;gBAC9E,IAAI,aAAa,CAAC,GAAG,CAAC,iBAAiB,CAAC,EAAE,CAAC;oBAAE,SAAS;gBACtD,aAAa,CAAC,GAAG,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC;gBAExC,KAAK,CAAC,IAAI,CAAC;oBACT,UAAU,EAAE,iBAAiB,CAAC,EAAE;oBAChC,QAAQ,EAAE,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;oBACrD,KAAK;oBACL,MAAM,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY;iBAC9C,CAAC,CAAC;gBAEH,KAAK,CAAC,IAAI,CAAC,CAAC,iBAAiB,CAAC,EAAE,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;YAChD,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;AACpH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/store/sqlite.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEtJ,qBAAa,WAAY,YAAW,OAAO;IACzC,OAAO,CAAC,EAAE,CAAyB;IACnC,OAAO,CAAC,MAAM,CAAS;gBAEX,UAAU,CAAC,EAAE,MAAM;IAMzB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/store/sqlite.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEtJ,qBAAa,WAAY,YAAW,OAAO;IACzC,OAAO,CAAC,EAAE,CAAyB;IACnC,OAAO,CAAC,MAAM,CAAS;gBAEX,UAAU,CAAC,EAAE,MAAM;IAMzB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA0FrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAK/D,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAc3C,iBAAiB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAItC,cAAc,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BvD,gBAAgB,CAAC,UAAU,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAU7D,gCAAgC,CAAC,YAAY,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBlG,QAAQ,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAIjC,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAI3D,YAAY,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAIzC,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAKvD,eAAe,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAI9C,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAInE,uBAAuB,CAAC,GAAG,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC;IASpE,qBAAqB,IAAI,OAAO,CAAC,uBAAuB,EAAE,CAAC;IAM3D,uBAAuB,CAAC,QAAQ,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC;IASzE,qBAAqB,IAAI,OAAO,CAAC,uBAAuB,EAAE,CAAC;IAI3D,eAAe,CAAC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;CA4BnE"}
|
package/dist/store/sqlite.js
CHANGED
|
@@ -19,9 +19,16 @@ export class SqliteStore {
|
|
|
19
19
|
if (!fs.existsSync(ferretDir)) {
|
|
20
20
|
fs.mkdirSync(ferretDir, { recursive: true });
|
|
21
21
|
}
|
|
22
|
+
// Only write when the content would actually change. Rewriting on every
|
|
23
|
+
// run leaves the file permanently modified in repositories that track it,
|
|
24
|
+
// because line-ending normalisation alone is enough to dirty it.
|
|
22
25
|
const gitignorePath = path.join(ferretDir, '.gitignore');
|
|
26
|
+
const gitignoreBody = '*\n!.gitignore\n!context.json\n';
|
|
23
27
|
try {
|
|
24
|
-
fs.
|
|
28
|
+
const existing = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf-8') : null;
|
|
29
|
+
if (existing === null || existing.replace(/\r\n/g, '\n') !== gitignoreBody) {
|
|
30
|
+
fs.writeFileSync(gitignorePath, gitignoreBody, 'utf-8');
|
|
31
|
+
}
|
|
25
32
|
}
|
|
26
33
|
catch { }
|
|
27
34
|
}
|
package/dist/store/sqlite.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/store/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGpD,MAAM,OAAO,WAAW;IACd,EAAE,GAAoB,IAAI,CAAC;IAC3B,MAAM,CAAS;IAEvB,YAAY,UAAmB;QAC7B,MAAM,IAAI,GAAG,eAAe,EAAE,CAAC;QAC/B,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;QAC3D,IAAI,CAAC,MAAM,GAAG,UAAU,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;IACnH,CAAC;IAED,KAAK,CAAC,IAAI;QACR,IAAI,IAAI,CAAC,EAAE;YAAE,OAAO;QAEpB,IAAI,IAAI,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YAC/B,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC5C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC9B,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC/C,CAAC;YACD,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;YACzD,IAAI,CAAC;gBACH,EAAE,CAAC,aAAa,CAAC,aAAa,EAAE,
|
|
1
|
+
{"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/store/sqlite.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -301,3 +301,102 @@ describe("writeContext — Task 5", () => {
|
|
|
301
301
|
assert.equal(stableContract?.status, "stable");
|
|
302
302
|
});
|
|
303
303
|
});
|
|
304
|
+
|
|
305
|
+
describe("writeContext — stability (does not churn the repo)", () => {
|
|
306
|
+
const tmps: string[] = [];
|
|
307
|
+
|
|
308
|
+
afterEach(() => {
|
|
309
|
+
for (const dir of tmps) {
|
|
310
|
+
try {
|
|
311
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
312
|
+
} catch {}
|
|
313
|
+
}
|
|
314
|
+
tmps.length = 0;
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("leaves the file byte-identical when only the timestamp would change", async () => {
|
|
318
|
+
const tmpDir = makeTmpDir();
|
|
319
|
+
tmps.push(tmpDir);
|
|
320
|
+
const { store } = await makeStoreWithData(tmpDir);
|
|
321
|
+
|
|
322
|
+
await writeContext(store, tmpDir);
|
|
323
|
+
const contextPath = path.join(tmpDir, ".ferret", "context.json");
|
|
324
|
+
const first = fs.readFileSync(contextPath, "utf-8");
|
|
325
|
+
|
|
326
|
+
// A later run with an unchanged graph must not rewrite the file, or every
|
|
327
|
+
// lint dirties the working tree of any repo that commits context.json.
|
|
328
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
329
|
+
await writeContext(store, tmpDir);
|
|
330
|
+
|
|
331
|
+
assert.equal(fs.readFileSync(contextPath, "utf-8"), first);
|
|
332
|
+
await store.close();
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it("still rewrites when the graph actually changes", async () => {
|
|
336
|
+
const tmpDir = makeTmpDir();
|
|
337
|
+
tmps.push(tmpDir);
|
|
338
|
+
const { store } = await makeStoreWithData(tmpDir);
|
|
339
|
+
|
|
340
|
+
await writeContext(store, tmpDir);
|
|
341
|
+
const contextPath = path.join(tmpDir, ".ferret", "context.json");
|
|
342
|
+
const before = fs.readFileSync(contextPath, "utf-8");
|
|
343
|
+
|
|
344
|
+
// A node alone is invisible in context.json — only contracts and edges are
|
|
345
|
+
// serialised — so the change has to include a contract to be observable.
|
|
346
|
+
const newNodeId = randomUUID();
|
|
347
|
+
await store.upsertNode({
|
|
348
|
+
id: newNodeId,
|
|
349
|
+
file_path: "contracts/new.contract.md",
|
|
350
|
+
hash: "zzz",
|
|
351
|
+
status: "stable",
|
|
352
|
+
});
|
|
353
|
+
await store.upsertContract({
|
|
354
|
+
id: "api.GET/new",
|
|
355
|
+
node_id: newNodeId,
|
|
356
|
+
shape_hash: "newhash",
|
|
357
|
+
shape_schema: JSON.stringify({ type: "object" }),
|
|
358
|
+
type: "api",
|
|
359
|
+
status: "stable",
|
|
360
|
+
});
|
|
361
|
+
await writeContext(store, tmpDir);
|
|
362
|
+
|
|
363
|
+
const after = fs.readFileSync(contextPath, "utf-8");
|
|
364
|
+
assert.notEqual(after, before);
|
|
365
|
+
await store.close();
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it("does not rewrite a CRLF file whose content is otherwise identical", async () => {
|
|
369
|
+
const tmpDir = makeTmpDir();
|
|
370
|
+
tmps.push(tmpDir);
|
|
371
|
+
const { store } = await makeStoreWithData(tmpDir);
|
|
372
|
+
|
|
373
|
+
await writeContext(store, tmpDir);
|
|
374
|
+
const contextPath = path.join(tmpDir, ".ferret", "context.json");
|
|
375
|
+
|
|
376
|
+
// Simulate a Windows checkout with autocrlf: same content, CRLF endings.
|
|
377
|
+
const crlf = fs.readFileSync(contextPath, "utf-8").replace(/\n/g, "\r\n");
|
|
378
|
+
fs.writeFileSync(contextPath, crlf, "utf-8");
|
|
379
|
+
|
|
380
|
+
await writeContext(store, tmpDir);
|
|
381
|
+
|
|
382
|
+
assert.equal(fs.readFileSync(contextPath, "utf-8"), crlf);
|
|
383
|
+
await store.close();
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it("replaces a corrupt context.json instead of leaving it in place", async () => {
|
|
387
|
+
const tmpDir = makeTmpDir();
|
|
388
|
+
tmps.push(tmpDir);
|
|
389
|
+
const { store } = await makeStoreWithData(tmpDir);
|
|
390
|
+
|
|
391
|
+
const ferretDir = path.join(tmpDir, ".ferret");
|
|
392
|
+
fs.mkdirSync(ferretDir, { recursive: true });
|
|
393
|
+
const contextPath = path.join(ferretDir, "context.json");
|
|
394
|
+
fs.writeFileSync(contextPath, "{ not valid json", "utf-8");
|
|
395
|
+
|
|
396
|
+
await writeContext(store, tmpDir);
|
|
397
|
+
|
|
398
|
+
const parsed = JSON.parse(fs.readFileSync(contextPath, "utf-8"));
|
|
399
|
+
assert.equal(parsed.version, "3.0");
|
|
400
|
+
await store.close();
|
|
401
|
+
});
|
|
402
|
+
});
|
package/src/context/index.ts
CHANGED
|
@@ -196,8 +196,35 @@ export async function writeContext(
|
|
|
196
196
|
if (!fs.existsSync(ferretDir)) {
|
|
197
197
|
fs.mkdirSync(ferretDir, { recursive: true });
|
|
198
198
|
}
|
|
199
|
+
|
|
200
|
+
// Leave the file alone when only `generated` would change. context.json is
|
|
201
|
+
// meant to be committed, so rewriting it on every run would dirty the
|
|
202
|
+
// user's repository after every lint even when nothing about the graph
|
|
203
|
+
// moved. Both sides are produced by this same function, so key order is
|
|
204
|
+
// stable and a string comparison is sufficient.
|
|
205
|
+
if (fs.existsSync(contextPath) && !hasContextChanged(contextPath, context)) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
199
209
|
fs.writeFileSync(contextPath, JSON.stringify(context, null, 2), "utf-8");
|
|
200
210
|
} catch (err) {
|
|
201
211
|
process.stderr.write(`⚠ Could not write context.json: ${err}\n`);
|
|
202
212
|
}
|
|
203
213
|
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* True when the graph content differs from what is already on disk, ignoring
|
|
217
|
+
* the `generated` timestamp. An unreadable or malformed existing file counts
|
|
218
|
+
* as changed, so a corrupt context.json is always replaced.
|
|
219
|
+
*/
|
|
220
|
+
function hasContextChanged(contextPath: string, next: FerretContext): boolean {
|
|
221
|
+
try {
|
|
222
|
+
const existingRaw = fs.readFileSync(contextPath, "utf-8");
|
|
223
|
+
const existing = JSON.parse(existingRaw) as Partial<FerretContext>;
|
|
224
|
+
const { generated: _existingGenerated, ...existingRest } = existing;
|
|
225
|
+
const { generated: _nextGenerated, ...nextRest } = next;
|
|
226
|
+
return JSON.stringify(existingRest) !== JSON.stringify(nextRest);
|
|
227
|
+
} catch {
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ export * from './store/sqlite.js';
|
|
|
14
14
|
export * from './store/factory.js';
|
|
15
15
|
export * from './reconciler/index.js';
|
|
16
16
|
export * from './reconciler/import-suggestions.js';
|
|
17
|
+
export * from './reconciler/dependents.js';
|
|
17
18
|
export * from './config.js';
|
|
18
19
|
export * from './utils/paths.js';
|
|
19
20
|
export * from './contract.js';
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { describe, it } from 'bun:test';
|
|
3
|
+
import { findDependents } from './dependents.js';
|
|
4
|
+
import type { FerretContract, FerretDependency, FerretNode } from '../store/types.js';
|
|
5
|
+
|
|
6
|
+
function node(id: string, filePath: string): FerretNode {
|
|
7
|
+
return { id, file_path: filePath, hash: 'h', status: 'stable' };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function contract(id: string, nodeId: string): FerretContract {
|
|
11
|
+
return { id, node_id: nodeId, shape_hash: 'h', shape_schema: '{}', type: 'type', status: 'stable' };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function edge(sourceNodeId: string, targetContractId: string): FerretDependency {
|
|
15
|
+
return { id: `${sourceNodeId}->${targetContractId}`, source_node_id: sourceNodeId, target_contract_id: targetContractId };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe('findDependents', () => {
|
|
19
|
+
it('finds a direct consumer of a contract', () => {
|
|
20
|
+
const input = {
|
|
21
|
+
nodes: [node('n1', 'src/types.ts'), node('n2', 'contracts/client/mobile.contract.md')],
|
|
22
|
+
contracts: [contract('api.order', 'n1'), contract('client.mobile', 'n2')],
|
|
23
|
+
dependencies: [edge('n2', 'api.order')],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const found = findDependents('api.order', input);
|
|
27
|
+
|
|
28
|
+
assert.equal(found.length, 1);
|
|
29
|
+
assert.equal(found[0].contractId, 'client.mobile');
|
|
30
|
+
assert.equal(found[0].filePath, 'contracts/client/mobile.contract.md');
|
|
31
|
+
assert.equal(found[0].impact, 'direct');
|
|
32
|
+
assert.equal(found[0].depth, 1);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('walks transitively and reports increasing depth', () => {
|
|
36
|
+
const input = {
|
|
37
|
+
nodes: [node('n1', 'a.ts'), node('n2', 'b.md'), node('n3', 'c.md')],
|
|
38
|
+
contracts: [contract('a', 'n1'), contract('b', 'n2'), contract('c', 'n3')],
|
|
39
|
+
dependencies: [edge('n2', 'a'), edge('n3', 'b')],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const found = findDependents('a', input);
|
|
43
|
+
|
|
44
|
+
assert.deepEqual(
|
|
45
|
+
found.map((d) => [d.contractId, d.depth, d.impact]),
|
|
46
|
+
[
|
|
47
|
+
['b', 1, 'direct'],
|
|
48
|
+
['c', 2, 'transitive'],
|
|
49
|
+
],
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('returns an empty list when nothing depends on the contract', () => {
|
|
54
|
+
const input = {
|
|
55
|
+
nodes: [node('n1', 'src/types.ts')],
|
|
56
|
+
contracts: [contract('api.order', 'n1')],
|
|
57
|
+
dependencies: [],
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
assert.deepEqual(findDependents('api.order', input), []);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('terminates on a dependency cycle instead of looping forever', () => {
|
|
64
|
+
const input = {
|
|
65
|
+
nodes: [node('n1', 'a.md'), node('n2', 'b.md')],
|
|
66
|
+
contracts: [contract('a', 'n1'), contract('b', 'n2')],
|
|
67
|
+
// a <- b and b <- a
|
|
68
|
+
dependencies: [edge('n2', 'a'), edge('n1', 'b')],
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const found = findDependents('a', input);
|
|
72
|
+
|
|
73
|
+
assert.equal(found.length, 1);
|
|
74
|
+
assert.equal(found[0].contractId, 'b');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('normalises Windows path separators so output is portable', () => {
|
|
78
|
+
const input = {
|
|
79
|
+
nodes: [node('n1', 'src/types.ts'), node('n2', 'contracts\\client\\mobile.contract.md')],
|
|
80
|
+
contracts: [contract('api.order', 'n1'), contract('client.mobile', 'n2')],
|
|
81
|
+
dependencies: [edge('n2', 'api.order')],
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
assert.equal(findDependents('api.order', input)[0].filePath, 'contracts/client/mobile.contract.md');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('never reports the drifted contract as its own dependent', () => {
|
|
88
|
+
const input = {
|
|
89
|
+
nodes: [node('n1', 'a.md')],
|
|
90
|
+
contracts: [contract('a', 'n1')],
|
|
91
|
+
dependencies: [edge('n1', 'a')], // self-import
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
assert.deepEqual(findDependents('a', input), []);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Pure function. No I/O. No side effects. Ever.
|
|
2
|
+
// Answers "who depends on this contract?" without mutating graph state.
|
|
3
|
+
//
|
|
4
|
+
// The Reconciler walks the same edges but only from nodes it has already
|
|
5
|
+
// flagged needs-review, and it writes status as it goes. Upward drift (code
|
|
6
|
+
// diverged from its declared contract) never flags the contract's node, so
|
|
7
|
+
// that walk never runs — leaving the user told that a shape broke but not who
|
|
8
|
+
// relies on it. This read-only walk fills that gap.
|
|
9
|
+
|
|
10
|
+
import type { FerretContract, FerretDependency, FerretNode } from '../store/types.js';
|
|
11
|
+
|
|
12
|
+
export interface DependentContract {
|
|
13
|
+
contractId: string;
|
|
14
|
+
filePath: string;
|
|
15
|
+
/** 1 = imports the drifted contract directly; >1 = reached through other contracts. */
|
|
16
|
+
depth: number;
|
|
17
|
+
impact: 'direct' | 'transitive';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface DependentsInput {
|
|
21
|
+
dependencies: FerretDependency[];
|
|
22
|
+
nodes: FerretNode[];
|
|
23
|
+
contracts: FerretContract[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Breadth-first walk over dependency edges to find every contract that depends
|
|
28
|
+
* on `contractId`, directly or transitively.
|
|
29
|
+
*
|
|
30
|
+
* Mirrors the Reconciler's traversal semantics deliberately: edges point
|
|
31
|
+
* dependent -> target, depth 1 means direct, and the walk is capped at 10 hops
|
|
32
|
+
* (S011). Unlike the Reconciler it never filters by node status and never
|
|
33
|
+
* writes — a contract's dependents are worth reporting whatever state they
|
|
34
|
+
* are in.
|
|
35
|
+
*/
|
|
36
|
+
export function findDependents(contractId: string, input: DependentsInput): DependentContract[] {
|
|
37
|
+
const { dependencies, nodes, contracts } = input;
|
|
38
|
+
|
|
39
|
+
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
40
|
+
const contractsByNodeId = new Map<string, FerretContract[]>();
|
|
41
|
+
for (const contract of contracts) {
|
|
42
|
+
const existing = contractsByNodeId.get(contract.node_id) ?? [];
|
|
43
|
+
existing.push(contract);
|
|
44
|
+
contractsByNodeId.set(contract.node_id, existing);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const found: DependentContract[] = [];
|
|
48
|
+
const seenContracts = new Set<string>([contractId]);
|
|
49
|
+
const queue: Array<[string, number]> = [[contractId, 1]];
|
|
50
|
+
|
|
51
|
+
while (queue.length > 0) {
|
|
52
|
+
const [currentId, depth] = queue.shift()!;
|
|
53
|
+
if (depth > 10) continue;
|
|
54
|
+
|
|
55
|
+
for (const edge of dependencies.filter((d) => d.target_contract_id === currentId)) {
|
|
56
|
+
const dependentNode = nodeById.get(edge.source_node_id);
|
|
57
|
+
if (!dependentNode) continue;
|
|
58
|
+
|
|
59
|
+
for (const dependentContract of contractsByNodeId.get(dependentNode.id) ?? []) {
|
|
60
|
+
if (seenContracts.has(dependentContract.id)) continue;
|
|
61
|
+
seenContracts.add(dependentContract.id);
|
|
62
|
+
|
|
63
|
+
found.push({
|
|
64
|
+
contractId: dependentContract.id,
|
|
65
|
+
filePath: dependentNode.file_path.replace(/\\/g, '/'),
|
|
66
|
+
depth,
|
|
67
|
+
impact: depth === 1 ? 'direct' : 'transitive',
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
queue.push([dependentContract.id, depth + 1]);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return found.sort((a, b) => (a.depth === b.depth ? a.contractId.localeCompare(b.contractId) : a.depth - b.depth));
|
|
76
|
+
}
|
package/src/store/sqlite.ts
CHANGED
|
@@ -23,9 +23,16 @@ export class SqliteStore implements DBStore {
|
|
|
23
23
|
if (!fs.existsSync(ferretDir)) {
|
|
24
24
|
fs.mkdirSync(ferretDir, { recursive: true });
|
|
25
25
|
}
|
|
26
|
+
// Only write when the content would actually change. Rewriting on every
|
|
27
|
+
// run leaves the file permanently modified in repositories that track it,
|
|
28
|
+
// because line-ending normalisation alone is enough to dirty it.
|
|
26
29
|
const gitignorePath = path.join(ferretDir, '.gitignore');
|
|
30
|
+
const gitignoreBody = '*\n!.gitignore\n!context.json\n';
|
|
27
31
|
try {
|
|
28
|
-
fs.
|
|
32
|
+
const existing = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf-8') : null;
|
|
33
|
+
if (existing === null || existing.replace(/\r\n/g, '\n') !== gitignoreBody) {
|
|
34
|
+
fs.writeFileSync(gitignorePath, gitignoreBody, 'utf-8');
|
|
35
|
+
}
|
|
29
36
|
} catch {}
|
|
30
37
|
}
|
|
31
38
|
|