@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.
@@ -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,CA6Ef"}
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"}
@@ -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":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAGlC,MAAM,CAAC,MAAM,eAAe,GAAG,KAAc,CAAC;AAC9C,MAAM,CAAC,MAAM,sBAAsB,GAAG,OAAgB,CAAC;AAiCvD,SAAS,gBAAgB,CAAC,GAAY;IACpC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,4FAA4F,CAAC,CAAC;IAChH,CAAC;IAED,MAAM,SAAS,GAAG,GAA8B,CAAC;IACjD,MAAM,cAAc,GAAG,SAAS,CAAC,OAAO,CAAC;IAEzC,6CAA6C;IAC7C,IAAI,cAAc,KAAK,KAAK,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,SAA2C,CAAC;QAC3D,MAAM,iBAAiB,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;aAChF,MAAM,CAAC,CAAC,CAAU,EAAgC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,CAAC;aAClF,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACf,GAAG,KAAK;YACR,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC;SACpE,CAAC,CAAsB,CAAC;QAC3B,OAAO;YACL,OAAO,EAAE,eAAe;YACxB,aAAa,EAAE,sBAAsB;YACrC,SAAS,EACP,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ;gBAClC,CAAC,CAAC,MAAM,CAAC,SAAS;gBAClB,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;YAC/B,SAAS,EAAE,iBAAiB;YAC5B,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;YACtD,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC;gBAC5C,CAAC,CAAC,MAAM,CAAC,WAAW;gBACpB,CAAC,CAAC,EAAE;SACP,CAAC;IACJ,CAAC;IAED,IAAI,cAAc,KAAK,eAAe,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CACb,6CAA6C,MAAM,CAAC,cAAc,CAAC,+EAA+E,CACnJ,CAAC;IACJ,CAAC;IAED,MAAM,aAAa,GAAG,SAAS,CAAC,aAAa,CAAC;IAC9C,IAAI,aAAa,KAAK,SAAS,IAAI,aAAa,KAAK,sBAAsB,EAAE,CAAC;QAC5E,MAAM,IAAI,KAAK,CACb,8CAA8C,MAAM,CAAC,aAAa,CAAC,4EAA4E,CAChJ,CAAC;IACJ,CAAC;IAED,iFAAiF;IACjF,MAAM,MAAM,GAAG,SAA2C,CAAC;IAC3D,OAAO;QACL,OAAO,EAAE,eAAe;QACxB,aAAa,EAAE,sBAAsB;QACrC,SAAS,EACP,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ;YAClC,CAAC,CAAC,MAAM,CAAC,SAAS;YAClB,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;QAC/B,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC;YACxC,CAAC,CAAC,MAAM,CAAC,SAAS;YAClB,CAAC,CAAC,EAAE;QACN,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;QACtD,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC;YAC5C,CAAC,CAAC,MAAM,CAAC,WAAW;YACpB,CAAC,CAAC,EAAE;KACP,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,WAAmB;IACjD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QAClD,OAAO,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,IAAI,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAClC,MAAM,KAAK,CAAC;QACd,CAAC;QACD,MAAM,IAAI,KAAK,CACb,gDAAgD,OAAO,wCAAwC,CAChG,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,KAAc,EACd,WAAmB;IAEnB,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,YAAY,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACzD,KAAK,CAAC,QAAQ,EAAE;QAChB,KAAK,CAAC,YAAY,EAAE;QACpB,KAAK,CAAC,eAAe,EAAE;KACxB,CAAC,CAAC;IAEH,iDAAiD;IACjD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAEtD,iCAAiC;IACjC,MAAM,gBAAgB,GAAsB,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC9D,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAC3C,MAAM,QAAQ,GAAI,UAAkB,EAAE,IAAI,IAAI,MAAM,CAAC,CAAC,mCAAmC;QAEzF,IAAI,KAAK,GAAY,EAAE,CAAC;QACxB,IAAI,CAAC;YACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;QACrC,CAAC;QAAC,MAAM,CAAC;YACP,KAAK,GAAG,EAAE,CAAC;QACb,CAAC;QAED,OAAO;YACL,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,KAAK;YACL,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,QAAQ,EACN,QAAQ,KAAK,MAAM;gBACjB,CAAC,CAAC,CAAC,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC;gBACtD,CAAC,CAAC,IAAI;YACV,QAAQ,EACN,QAAQ,KAAK,MAAM;gBACjB,CAAC,CAAC,CAAC,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC;gBACtD,CAAC,CAAC,IAAI;SACX,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,0DAA0D;IAC1D,MAAM,KAAK,GAAkB,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAClD,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;QAClD,OAAO;YACL,IAAI,EAAE,CAAC,UAAU,EAAE,SAAS,IAAI,CAAC,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;YACrE,EAAE,EAAE,CAAC,CAAC,kBAAkB;SACzB,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,sEAAsE;IACtE,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAChC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,cAAc,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAClE,CAAC;IACF,MAAM,WAAW,GAAG,SAAS;SAC1B,MAAM,CACL,CAAC,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,cAAc,CACxE;SACA,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAEpB,MAAM,OAAO,GAAkB;QAC7B,OAAO,EAAE,eAAe;QACxB,aAAa,EAAE,sBAAsB;QACrC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,SAAS,EAAE,gBAAgB;QAC3B,KAAK;QACL,WAAW;KACZ,CAAC;IAEF,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;IACpD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IAEzD,IAAI,CAAC;QACH,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC9B,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/C,CAAC;QACD,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IAC3E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,mCAAmC,GAAG,IAAI,CAAC,CAAC;IACnE,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/context/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAGlC,MAAM,CAAC,MAAM,eAAe,GAAG,KAAc,CAAC;AAC9C,MAAM,CAAC,MAAM,sBAAsB,GAAG,OAAgB,CAAC;AAiCvD,SAAS,gBAAgB,CAAC,GAAY;IACpC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,4FAA4F,CAAC,CAAC;IAChH,CAAC;IAED,MAAM,SAAS,GAAG,GAA8B,CAAC;IACjD,MAAM,cAAc,GAAG,SAAS,CAAC,OAAO,CAAC;IAEzC,6CAA6C;IAC7C,IAAI,cAAc,KAAK,KAAK,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,SAA2C,CAAC;QAC3D,MAAM,iBAAiB,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;aAChF,MAAM,CAAC,CAAC,CAAU,EAAgC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,CAAC;aAClF,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACf,GAAG,KAAK;YACR,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC;SACpE,CAAC,CAAsB,CAAC;QAC3B,OAAO;YACL,OAAO,EAAE,eAAe;YACxB,aAAa,EAAE,sBAAsB;YACrC,SAAS,EACP,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ;gBAClC,CAAC,CAAC,MAAM,CAAC,SAAS;gBAClB,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;YAC/B,SAAS,EAAE,iBAAiB;YAC5B,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;YACtD,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC;gBAC5C,CAAC,CAAC,MAAM,CAAC,WAAW;gBACpB,CAAC,CAAC,EAAE;SACP,CAAC;IACJ,CAAC;IAED,IAAI,cAAc,KAAK,eAAe,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CACb,6CAA6C,MAAM,CAAC,cAAc,CAAC,+EAA+E,CACnJ,CAAC;IACJ,CAAC;IAED,MAAM,aAAa,GAAG,SAAS,CAAC,aAAa,CAAC;IAC9C,IAAI,aAAa,KAAK,SAAS,IAAI,aAAa,KAAK,sBAAsB,EAAE,CAAC;QAC5E,MAAM,IAAI,KAAK,CACb,8CAA8C,MAAM,CAAC,aAAa,CAAC,4EAA4E,CAChJ,CAAC;IACJ,CAAC;IAED,iFAAiF;IACjF,MAAM,MAAM,GAAG,SAA2C,CAAC;IAC3D,OAAO;QACL,OAAO,EAAE,eAAe;QACxB,aAAa,EAAE,sBAAsB;QACrC,SAAS,EACP,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ;YAClC,CAAC,CAAC,MAAM,CAAC,SAAS;YAClB,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;QAC/B,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC;YACxC,CAAC,CAAC,MAAM,CAAC,SAAS;YAClB,CAAC,CAAC,EAAE;QACN,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;QACtD,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC;YAC5C,CAAC,CAAC,MAAM,CAAC,WAAW;YACpB,CAAC,CAAC,EAAE;KACP,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,WAAmB;IACjD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QAClD,OAAO,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,IAAI,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAClC,MAAM,KAAK,CAAC;QACd,CAAC;QACD,MAAM,IAAI,KAAK,CACb,gDAAgD,OAAO,wCAAwC,CAChG,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,KAAc,EACd,WAAmB;IAEnB,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,YAAY,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACzD,KAAK,CAAC,QAAQ,EAAE;QAChB,KAAK,CAAC,YAAY,EAAE;QACpB,KAAK,CAAC,eAAe,EAAE;KACxB,CAAC,CAAC;IAEH,iDAAiD;IACjD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAEtD,iCAAiC;IACjC,MAAM,gBAAgB,GAAsB,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAC9D,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAC3C,MAAM,QAAQ,GAAI,UAAkB,EAAE,IAAI,IAAI,MAAM,CAAC,CAAC,mCAAmC;QAEzF,IAAI,KAAK,GAAY,EAAE,CAAC;QACxB,IAAI,CAAC;YACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;QACrC,CAAC;QAAC,MAAM,CAAC;YACP,KAAK,GAAG,EAAE,CAAC;QACb,CAAC;QAED,OAAO;YACL,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,KAAK;YACL,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,QAAQ,EACN,QAAQ,KAAK,MAAM;gBACjB,CAAC,CAAC,CAAC,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC;gBACtD,CAAC,CAAC,IAAI;YACV,QAAQ,EACN,QAAQ,KAAK,MAAM;gBACjB,CAAC,CAAC,CAAC,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC;gBACtD,CAAC,CAAC,IAAI;SACX,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,0DAA0D;IAC1D,MAAM,KAAK,GAAkB,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QAClD,MAAM,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;QAClD,OAAO;YACL,IAAI,EAAE,CAAC,UAAU,EAAE,SAAS,IAAI,CAAC,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;YACrE,EAAE,EAAE,CAAC,CAAC,kBAAkB;SACzB,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,sEAAsE;IACtE,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAChC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,cAAc,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAClE,CAAC;IACF,MAAM,WAAW,GAAG,SAAS;SAC1B,MAAM,CACL,CAAC,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,cAAc,CACxE;SACA,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAEpB,MAAM,OAAO,GAAkB;QAC7B,OAAO,EAAE,eAAe;QACxB,aAAa,EAAE,sBAAsB;QACrC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,SAAS,EAAE,gBAAgB;QAC3B,KAAK;QACL,WAAW;KACZ,CAAC;IAEF,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;IACpD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IAEzD,IAAI,CAAC;QACH,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC9B,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/C,CAAC;QAED,2EAA2E;QAC3E,sEAAsE;QACtE,uEAAuE;QACvE,wEAAwE;QACxE,gDAAgD;QAChD,IAAI,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,iBAAiB,CAAC,WAAW,EAAE,OAAO,CAAC,EAAE,CAAC;YAC3E,OAAO;QACT,CAAC;QAED,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IAC3E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,mCAAmC,GAAG,IAAI,CAAC,CAAC;IACnE,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,WAAmB,EAAE,IAAmB;IACjE,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QAC1D,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAA2B,CAAC;QACnE,MAAM,EAAE,SAAS,EAAE,kBAAkB,EAAE,GAAG,YAAY,EAAE,GAAG,QAAQ,CAAC;QACpE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,GAAG,QAAQ,EAAE,GAAG,IAAI,CAAC;QACxD,OAAO,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
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';
@@ -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;IAmFrB,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"}
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"}
@@ -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.writeFileSync(gitignorePath, '*\n!.gitignore\n!context.json\n', 'utf-8');
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
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/store/sqlite.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/store/sqlite.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specferret/core",
3
- "version": "0.9.2",
3
+ "version": "0.9.3",
4
4
  "description": "SpecFerret core engine — spec drift detection.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -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
+ });
@@ -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
+ }
@@ -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.writeFileSync(gitignorePath, '*\n!.gitignore\n!context.json\n', 'utf-8');
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