@slowcook-ai/cli 0.19.1 → 0.19.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.
@@ -62,6 +62,227 @@ function pruneTokenList(items, pathPrefix, findings) {
62
62
  }
63
63
  return out;
64
64
  }
65
+ /**
66
+ * 0.19.x+ — catch hallucinated entity.field references in the spec.
67
+ *
68
+ * Refine sometimes invents fields that don't exist on the consumer's
69
+ * actual entities (e.g. story-005 in delgoosh referenced
70
+ * `user.timezone.label` but Timezone has only `name`/`offset`/`offsetStr`).
71
+ * Brew + testgen downstream silently grounded against the wrong field,
72
+ * leading to a runtime error that took a human to spot.
73
+ *
74
+ * This lint parses `.brewing/repo-knowledge/auto/backend-entities.md`,
75
+ * walks every string-typed field of the spec, and for each
76
+ * `lowercaseName.fieldName` pair where `lowercaseName` matches a known
77
+ * entity, checks whether `fieldName` is in that entity's field set.
78
+ * Misses are flagged as `"flagged"` (kept as-is — the spec text isn't
79
+ * auto-repairable since we don't know what the author meant).
80
+ *
81
+ * Chained references like `user.timezone.label` are decomposed into
82
+ * pairs (`user.timezone`, `timezone.label`); each pair is checked
83
+ * independently. Relation traversal works because relation field
84
+ * values are themselves listed in the entity catalog with the
85
+ * other-entity name as their type.
86
+ *
87
+ * Exported for testing.
88
+ */
89
+ export function parseEntityCatalog(md) {
90
+ const out = new Map();
91
+ let current = null;
92
+ let currentFields = null;
93
+ for (const line of md.split(/\r?\n/)) {
94
+ const headerMatch = line.match(/^##\s+([A-Z]\w+)\b/);
95
+ if (headerMatch) {
96
+ if (current && currentFields)
97
+ out.set(current, currentFields);
98
+ // Map by LOWERCASED entity name so spec text like `user.firstName`
99
+ // resolves to the User entity.
100
+ current = headerMatch[1].toLowerCase();
101
+ currentFields = new Set();
102
+ continue;
103
+ }
104
+ if (!currentFields)
105
+ continue;
106
+ const fieldMatch = line.match(/^-\s+(\w+)\??:/);
107
+ if (fieldMatch)
108
+ currentFields.add(fieldMatch[1]);
109
+ }
110
+ if (current && currentFields)
111
+ out.set(current, currentFields);
112
+ return out;
113
+ }
114
+ /**
115
+ * Walk every string in a value (recursing into arrays/objects).
116
+ * Yields each string with a dotted path for diagnostic context.
117
+ */
118
+ function* walkStrings(value, path = "") {
119
+ if (typeof value === "string") {
120
+ yield { path, text: value };
121
+ return;
122
+ }
123
+ if (Array.isArray(value)) {
124
+ for (let i = 0; i < value.length; i++) {
125
+ yield* walkStrings(value[i], `${path}[${i}]`);
126
+ }
127
+ return;
128
+ }
129
+ if (value && typeof value === "object") {
130
+ for (const [k, v] of Object.entries(value)) {
131
+ yield* walkStrings(v, path ? `${path}.${k}` : k);
132
+ }
133
+ }
134
+ }
135
+ export function validateEntityFieldReferences(spec, entityCatalogMd) {
136
+ const findings = [];
137
+ const catalog = parseEntityCatalog(entityCatalogMd);
138
+ if (catalog.size === 0)
139
+ return findings; // No catalog → can't lint.
140
+ // Sections worth walking. We deliberately skip free-form metadata
141
+ // (title, notes, rationale text under proposals) to keep false-
142
+ // positives low — only fields where field references are LOAD-BEARING.
143
+ const sections = [
144
+ { key: "invariants", value: spec.invariants },
145
+ { key: "preconditions", value: spec.preconditions },
146
+ { key: "acceptance_scenarios", value: spec.acceptance_scenarios },
147
+ { key: "api_contract", value: spec.api_contract },
148
+ { key: "ui_behavior", value: spec.ui_behavior },
149
+ ];
150
+ // Per pair, only flag the FIRST occurrence so a single typo
151
+ // doesn't generate noise across 20 invariants.
152
+ const seen = new Set();
153
+ for (const section of sections) {
154
+ if (section.value == null)
155
+ continue;
156
+ for (const { path, text } of walkStrings(section.value, String(section.key))) {
157
+ // Match every dotted chain (>=2 segments) and split into adjacent
158
+ // pairs. `regex.exec` with /g advances past each full match, so we
159
+ // can't catch overlapping pairs (`user.timezone` and
160
+ // `timezone.label` inside `user.timezone.label`) with a single
161
+ // pair-regex — extract the whole chain and decompose.
162
+ const chainRe = /\b([a-z][a-zA-Z0-9_]*(?:\.[a-zA-Z][a-zA-Z0-9_]*)+)\b/g;
163
+ let m;
164
+ while ((m = chainRe.exec(text)) !== null) {
165
+ const segments = m[1].split(".");
166
+ for (let i = 0; i < segments.length - 1; i++) {
167
+ const lhs = segments[i].toLowerCase();
168
+ const rhs = segments[i + 1];
169
+ const fields = catalog.get(lhs);
170
+ if (!fields)
171
+ continue; // LHS isn't a known entity — skip.
172
+ if (fields.has(rhs))
173
+ continue; // Field exists.
174
+ const key = `${lhs}.${rhs}`;
175
+ if (seen.has(key))
176
+ continue;
177
+ seen.add(key);
178
+ findings.push({
179
+ path,
180
+ message: `Spec references \`${segments[i]}.${rhs}\` but the ${lhs} entity has no \`${rhs}\` field. Known fields: ${[...fields].sort().join(", ") || "(none)"}.`,
181
+ action: "flagged",
182
+ });
183
+ }
184
+ }
185
+ }
186
+ }
187
+ return findings;
188
+ }
189
+ /**
190
+ * 0.19.x+ — check whether the mock files refine listed in
191
+ * `components_to_reuse` actually render the spec's data fields.
192
+ *
193
+ * Refine derives `components_to_reuse` from spec prose mentions of
194
+ * `src/components/...` paths (proposals-synth.ts:198). It doesn't
195
+ * verify the mock at that path actually mentions the fields the
196
+ * spec cares about. A reuse listing of the WRONG mock misleads
197
+ * brew into lifting unrelated UI.
198
+ *
199
+ * Concrete repro (delgoosh story-005):
200
+ * spec.invariants reference user.firstName / user.lastName /
201
+ * user.email / userLocation.city / userLocation.country / etc.
202
+ * refine's components_to_reuse listed mock/src/app/patient/profile/page.tsx
203
+ * which actually renders therapy PREFERENCES (topics/approach/gender/belief)
204
+ * — none of the spec's fields appear in that mock at all.
205
+ *
206
+ * Heuristic: extract the set of unique field names mentioned in the
207
+ * spec's load-bearing sections (RHS of every dotted reference like
208
+ * `user.firstName`). For each `components_to_reuse` entry that names
209
+ * a real path, count how many of those field names appear in the
210
+ * file body. If overlap is < 25% of the spec's field set AND the
211
+ * file body is non-trivial (>500 chars — empty mocks aren't false
212
+ * positives), flag the entry.
213
+ *
214
+ * Action is "flagged" — the spec text isn't auto-repairable; the
215
+ * author needs to either drop the reuse listing or rebuild the
216
+ * mock at that path. Surfaced in run logs alongside other findings.
217
+ *
218
+ * Exported for testing.
219
+ */
220
+ export function validateComponentReuseShape(spec, mockReader) {
221
+ const findings = [];
222
+ const reuse = spec.proposals?.ui_layout?.components_to_reuse;
223
+ if (!reuse || reuse.length === 0)
224
+ return findings;
225
+ // Extract every field-name (RHS-and-after of dotted refs) from
226
+ // load-bearing sections — same chain shape as
227
+ // validateEntityFieldReferences.
228
+ const sections = [
229
+ spec.invariants,
230
+ spec.preconditions,
231
+ spec.acceptance_scenarios,
232
+ spec.api_contract,
233
+ spec.ui_behavior,
234
+ ];
235
+ const specFields = new Set();
236
+ const chainRe = /\b[a-z][a-zA-Z0-9_]*(?:\.[a-zA-Z][a-zA-Z0-9_]*)+\b/g;
237
+ for (const section of sections) {
238
+ if (section == null)
239
+ continue;
240
+ for (const { text } of walkStrings(section)) {
241
+ let m;
242
+ while ((m = chainRe.exec(text)) !== null) {
243
+ const segments = m[0].split(".");
244
+ for (let i = 1; i < segments.length; i++) {
245
+ specFields.add(segments[i]);
246
+ }
247
+ }
248
+ }
249
+ }
250
+ if (specFields.size === 0)
251
+ return findings;
252
+ for (let i = 0; i < reuse.length; i++) {
253
+ const entry = reuse[i];
254
+ // Skip backtick-name entries (`Component-name (path TBD)`) — no path to read.
255
+ const pathMatch = entry.match(/^(src\/[^\s(]+|mock\/[^\s(]+|apps\/[^\s(]+)/);
256
+ if (!pathMatch)
257
+ continue;
258
+ const body = mockReader(pathMatch[1]);
259
+ if (body == null) {
260
+ findings.push({
261
+ path: `proposals.ui_layout.components_to_reuse[${i}]`,
262
+ message: `Reuse target ${JSON.stringify(pathMatch[1])} does not exist on disk — path is wrong, or the mock hasn't been authored yet.`,
263
+ action: "flagged",
264
+ });
265
+ continue;
266
+ }
267
+ if (body.length < 500)
268
+ continue; // Empty / placeholder mock — not a false positive.
269
+ let hits = 0;
270
+ for (const f of specFields) {
271
+ const re = new RegExp(`\\b${f.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
272
+ if (re.test(body))
273
+ hits++;
274
+ }
275
+ const ratio = hits / specFields.size;
276
+ if (ratio < 0.25) {
277
+ findings.push({
278
+ path: `proposals.ui_layout.components_to_reuse[${i}]`,
279
+ message: `Reuse target ${JSON.stringify(pathMatch[1])} mentions only ${hits}/${specFields.size} of the spec's data fields (${Math.round(ratio * 100)}%). The mock likely renders a different surface — verify the listing or update the mock before brew lifts it.`,
280
+ action: "flagged",
281
+ });
282
+ }
283
+ }
284
+ return findings;
285
+ }
65
286
  function pruneStringList(items, pathPrefix, findings) {
66
287
  const out = [];
67
288
  for (let i = 0; i < items.length; i++) {
@@ -1 +1 @@
1
- {"version":3,"file":"spec-validate.js","sourceRoot":"","sources":["../../../src/commands/refine/spec-validate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"spec-validate.js","sourceRoot":"","sources":["../../../src/commands/refine/spec-validate.ts"],"names":[],"mappings":"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"}
@@ -38,6 +38,7 @@
38
38
  * ├── frontend-contexts.md (mock/src/contexts/*-context.tsx hooks)
39
39
  * ├── tokens.md (Tailwind brand-token vocabulary)
40
40
  * ├── config.md (tsconfig paths + workspace + scripts)
41
+ * ├── aliases.md (every tsconfig + vite/vitest path alias)
41
42
  * ├── migrations.md (migration file timestamps + table names)
42
43
  * └── routes-inventory.md (filesystem-derived route URLs)
43
44
  *
@@ -102,6 +103,49 @@ export declare function buildConfigDigest(repoRoot: string): {
102
103
  body: string;
103
104
  built: boolean;
104
105
  };
106
+ /**
107
+ * Parse a tsconfig.json body's `compilerOptions.paths` map.
108
+ * Strips JSON-with-comments before parsing (tsconfig allows
109
+ * `//` and `/* … *​/` per spec). Returns `{}` on parse failure.
110
+ *
111
+ * Exported for testing.
112
+ */
113
+ export declare function parseTsconfigPaths(body: string): Record<string, string[]>;
114
+ /**
115
+ * Regex-extract `resolve.alias` keys + (best-effort) target hints from
116
+ * a vite / vitest config body. The config is TS code and not safely
117
+ * parseable here; we capture the common literal-object form:
118
+ *
119
+ * resolve: { alias: { "@": path.join(root, "src"), … } }
120
+ *
121
+ * Returns `Array<{ alias: string; targetHint: string }>` where
122
+ * `targetHint` is the raw RHS text (e.g., `path.join(root, "src")`).
123
+ * Agents reading the digest can interpret the hint contextually.
124
+ *
125
+ * Exported for testing.
126
+ */
127
+ export declare function parseViteAliases(body: string): Array<{
128
+ alias: string;
129
+ targetHint: string;
130
+ }>;
131
+ /**
132
+ * Walks the workspace for tsconfig + vite/vitest configs and emits a
133
+ * single `aliases.md` listing every `@whatever`-style alias and what
134
+ * it resolves to in EACH context.
135
+ *
136
+ * Motivation: in a monorepo the same alias (e.g. `@/`) often means
137
+ * different things in different directories — root vitest config may
138
+ * map `@/` to root `src/`, mock workspace's tsconfig may map `@/` to
139
+ * `mock/src/`, and each Next.js app's tsconfig maps `@/` to its own
140
+ * `apps/<role>/src/`. Without a digest, agents see `@/foo/bar` in a
141
+ * file and can't tell which root it resolves against — they read the
142
+ * config file by hand on every cold start. (Surfaced in
143
+ * delgoosh/monorepo as a brew-vs-testgen path divergence.)
144
+ */
145
+ export declare function buildAliasesDigest(repoRoot: string): {
146
+ body: string;
147
+ built: boolean;
148
+ };
105
149
  export declare function buildMigrationsDigest(repoRoot: string): {
106
150
  body: string;
107
151
  built: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"refresh-knowledge.d.ts","sourceRoot":"","sources":["../../src/commands/refresh-knowledge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AA+CH;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,MAAM,CAAC;CACrC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAMnC;AAiID,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM;UAvI/C,MAAM;WAAS,OAAO;EA6JjC;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM;UA/J7C,MAAM;WAAS,OAAO;EAsLjC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAS1D;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM;UA/M5C,MAAM;WAAS,OAAO;EAyOjC;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM;UA3O7C,MAAM;WAAS,OAAO;EA0QjC;AAED,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,MAAM;UA5QlD,MAAM;WAAS,OAAO;EA2SjC;AAED,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,MAAM;UA7ShD,MAAM;WAAS,OAAO;EAiUjC;AAED,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM;UAnU9C,MAAM;WAAS,OAAO;EA8VjC;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM;UAhWtC,MAAM;WAAS,OAAO;EA4ZjC;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM;UA9Z1C,MAAM;WAAS,OAAO;EA+bjC;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM;UAjc/C,MAAM;WAAS,OAAO;EA4djC;AAID,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,sBAAsB,CAuB3G;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBpE;AAuTD,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,iBAAiB,CAsCnI"}
1
+ {"version":3,"file":"refresh-knowledge.d.ts","sourceRoot":"","sources":["../../src/commands/refresh-knowledge.ts"],"names":[],"mappings":"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"}
@@ -38,6 +38,7 @@
38
38
  * ├── frontend-contexts.md (mock/src/contexts/*-context.tsx hooks)
39
39
  * ├── tokens.md (Tailwind brand-token vocabulary)
40
40
  * ├── config.md (tsconfig paths + workspace + scripts)
41
+ * ├── aliases.md (every tsconfig + vite/vitest path alias)
41
42
  * ├── migrations.md (migration file timestamps + table names)
42
43
  * └── routes-inventory.md (filesystem-derived route URLs)
43
44
  *
@@ -547,6 +548,194 @@ export function buildConfigDigest(repoRoot) {
547
548
  },
548
549
  });
549
550
  }
551
+ /**
552
+ * Parse a tsconfig.json body's `compilerOptions.paths` map.
553
+ * Strips JSON-with-comments before parsing (tsconfig allows
554
+ * `//` and `/* … *​/` per spec). Returns `{}` on parse failure.
555
+ *
556
+ * Exported for testing.
557
+ */
558
+ export function parseTsconfigPaths(body) {
559
+ try {
560
+ const json = JSON.parse(body.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, ""));
561
+ return json.compilerOptions?.paths ?? {};
562
+ }
563
+ catch {
564
+ return {};
565
+ }
566
+ }
567
+ /**
568
+ * Regex-extract `resolve.alias` keys + (best-effort) target hints from
569
+ * a vite / vitest config body. The config is TS code and not safely
570
+ * parseable here; we capture the common literal-object form:
571
+ *
572
+ * resolve: { alias: { "@": path.join(root, "src"), … } }
573
+ *
574
+ * Returns `Array<{ alias: string; targetHint: string }>` where
575
+ * `targetHint` is the raw RHS text (e.g., `path.join(root, "src")`).
576
+ * Agents reading the digest can interpret the hint contextually.
577
+ *
578
+ * Exported for testing.
579
+ */
580
+ export function parseViteAliases(body) {
581
+ // Find the `alias: {` opener, then walk char-by-char to find its
582
+ // matching close brace (brace-balanced — naive regex can't do this
583
+ // because the values may themselves contain `{}`).
584
+ const opener = body.match(/\balias\s*:\s*\{/);
585
+ if (!opener || opener.index === undefined)
586
+ return [];
587
+ let start = opener.index + opener[0].length;
588
+ let depth = 1;
589
+ let end = start;
590
+ let qChar2 = null;
591
+ while (end < body.length && depth > 0) {
592
+ const c = body[end];
593
+ if (qChar2) {
594
+ if (c === qChar2 && body[end - 1] !== "\\")
595
+ qChar2 = null;
596
+ }
597
+ else if (c === '"' || c === "'" || c === "`") {
598
+ qChar2 = c;
599
+ }
600
+ else if (c === "{")
601
+ depth++;
602
+ else if (c === "}")
603
+ depth--;
604
+ if (depth === 0)
605
+ break;
606
+ end++;
607
+ }
608
+ const block = body.slice(start, end);
609
+ const out = [];
610
+ // Walk character-by-character so the value's parens / quotes don't
611
+ // confuse a naive regex (e.g., `path.join(root, "src")` contains a
612
+ // comma but is one value).
613
+ let i = 0;
614
+ while (i < block.length) {
615
+ // Skip whitespace + commas.
616
+ while (i < block.length && /[\s,]/.test(block[i]))
617
+ i++;
618
+ if (i >= block.length)
619
+ break;
620
+ // Expect a quote for the alias key.
621
+ const q = block[i];
622
+ if (q !== '"' && q !== "'") {
623
+ // Not at a key — advance to next char.
624
+ i++;
625
+ continue;
626
+ }
627
+ i++; // past opening quote
628
+ const keyStart = i;
629
+ while (i < block.length && block[i] !== q)
630
+ i++;
631
+ const alias = block.slice(keyStart, i);
632
+ i++; // past closing quote
633
+ // Skip to colon.
634
+ while (i < block.length && /\s/.test(block[i]))
635
+ i++;
636
+ if (block[i] !== ":")
637
+ continue;
638
+ i++; // past colon
639
+ while (i < block.length && /\s/.test(block[i]))
640
+ i++;
641
+ // Capture value until top-level comma or newline (paren-aware).
642
+ const valStart = i;
643
+ let depth = 0;
644
+ let qChar = null;
645
+ while (i < block.length) {
646
+ const c = block[i];
647
+ if (qChar) {
648
+ if (c === qChar && block[i - 1] !== "\\")
649
+ qChar = null;
650
+ i++;
651
+ continue;
652
+ }
653
+ if (c === '"' || c === "'" || c === "`") {
654
+ qChar = c;
655
+ i++;
656
+ continue;
657
+ }
658
+ if (c === "(" || c === "[" || c === "{")
659
+ depth++;
660
+ else if (c === ")" || c === "]" || c === "}")
661
+ depth--;
662
+ else if (depth === 0 && (c === "," || c === "\n"))
663
+ break;
664
+ i++;
665
+ }
666
+ const targetHint = block.slice(valStart, i).trim();
667
+ if (alias && targetHint)
668
+ out.push({ alias, targetHint });
669
+ }
670
+ return out;
671
+ }
672
+ /**
673
+ * Walks the workspace for tsconfig + vite/vitest configs and emits a
674
+ * single `aliases.md` listing every `@whatever`-style alias and what
675
+ * it resolves to in EACH context.
676
+ *
677
+ * Motivation: in a monorepo the same alias (e.g. `@/`) often means
678
+ * different things in different directories — root vitest config may
679
+ * map `@/` to root `src/`, mock workspace's tsconfig may map `@/` to
680
+ * `mock/src/`, and each Next.js app's tsconfig maps `@/` to its own
681
+ * `apps/<role>/src/`. Without a digest, agents see `@/foo/bar` in a
682
+ * file and can't tell which root it resolves against — they read the
683
+ * config file by hand on every cold start. (Surfaced in
684
+ * delgoosh/monorepo as a brew-vs-testgen path divergence.)
685
+ */
686
+ export function buildAliasesDigest(repoRoot) {
687
+ // Walk for tsconfigs (at root + per-workspace).
688
+ const tsconfigs = findFilesByGlob(repoRoot, /(?:^|\/)tsconfig\.json$/, {
689
+ maxDepth: 5,
690
+ });
691
+ // Walk for vite + vitest configs (TS/JS/MJS).
692
+ const viteConfigs = findFilesByGlob(repoRoot, /(?:^|\/)vite(?:st)?\.config\.(?:ts|js|mjs)$/, { maxDepth: 5 });
693
+ return buildDigest({
694
+ repoRoot,
695
+ name: "aliases",
696
+ inputFiles: [...tsconfigs, ...viteConfigs],
697
+ build: (inputs) => {
698
+ const rows = [];
699
+ for (const rel of inputs) {
700
+ const body = safeRead(repoRoot, rel);
701
+ if (!body)
702
+ continue;
703
+ if (rel.endsWith("tsconfig.json")) {
704
+ const paths = parseTsconfigPaths(body);
705
+ for (const [alias, targets] of Object.entries(paths)) {
706
+ rows.push({
707
+ alias,
708
+ source: rel,
709
+ target: targets.join(" | "),
710
+ });
711
+ }
712
+ }
713
+ else {
714
+ for (const e of parseViteAliases(body)) {
715
+ rows.push({
716
+ alias: e.alias,
717
+ source: rel,
718
+ target: e.targetHint,
719
+ });
720
+ }
721
+ }
722
+ }
723
+ const lines = [];
724
+ lines.push("# Path aliases\n");
725
+ lines.push("Auto-extracted from every `tsconfig.json` + `vite(st).config.{ts,js,mjs}` in the workspace. In a monorepo the SAME alias (e.g. `@/`) often resolves differently from different directories — agents should consult this digest before assuming where an `@/foo/bar` import lands.\n");
726
+ if (rows.length === 0) {
727
+ lines.push("_(No path aliases detected.)_");
728
+ return lines.join("\n");
729
+ }
730
+ lines.push("| Alias | Source | Target |");
731
+ lines.push("|---|---|---|");
732
+ for (const r of rows) {
733
+ lines.push(`| \`${r.alias}\` | \`${r.source}\` | \`${r.target}\` |`);
734
+ }
735
+ return lines.join("\n");
736
+ },
737
+ });
738
+ }
550
739
  export function buildMigrationsDigest(repoRoot) {
551
740
  const files = findFilesByGlob(repoRoot, /\/migrations\/\d+[^/]*\.ts$/);
552
741
  return buildDigest({
@@ -624,6 +813,7 @@ export function refreshKnowledgeAuto(repoRoot, opts = {}) {
624
813
  { name: "frontend-contexts", fn: () => buildFrontendContextsDigest(repoRoot) },
625
814
  { name: "tokens", fn: () => buildTailwindTokensDigest(repoRoot) },
626
815
  { name: "config", fn: () => buildConfigDigest(repoRoot) },
816
+ { name: "aliases", fn: () => buildAliasesDigest(repoRoot) },
627
817
  { name: "migrations", fn: () => buildMigrationsDigest(repoRoot) },
628
818
  { name: "routes-inventory", fn: () => buildRoutesInventoryDigest(repoRoot) },
629
819
  ];