@isaacriehm/cairn-core 0.18.2 → 0.19.1
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/.tsbuildinfo +1 -1
- package/dist/attention/dedup.d.ts +24 -0
- package/dist/attention/dedup.js +42 -1
- package/dist/attention/dedup.js.map +1 -1
- package/dist/components/audit.d.ts +13 -2
- package/dist/components/audit.js +111 -14
- package/dist/components/audit.js.map +1 -1
- package/dist/init/detect-components.d.ts +33 -27
- package/dist/init/detect-components.js +231 -130
- package/dist/init/detect-components.js.map +1 -1
- package/dist/init/phases/4-seed.js +5 -5
- package/dist/init/phases/4-seed.js.map +1 -1
- package/dist/mcp/tools/record-decision.js +43 -5
- package/dist/mcp/tools/record-decision.js.map +1 -1
- package/package.json +2 -2
|
@@ -66,3 +66,27 @@ export declare function findDuplicateClusters(args: {
|
|
|
66
66
|
thresholdFloor?: number;
|
|
67
67
|
thresholdDefinite?: number;
|
|
68
68
|
}): DedupResult;
|
|
69
|
+
export interface AcceptedDuplicate {
|
|
70
|
+
dup: boolean;
|
|
71
|
+
/** Matched accepted DEC id, present only when `dup`. */
|
|
72
|
+
matchId?: string;
|
|
73
|
+
/** Matched accepted DEC title, present only when `dup`. */
|
|
74
|
+
matchTitle?: string;
|
|
75
|
+
/** Title-Jaccard similarity of the match, present only when `dup`. */
|
|
76
|
+
similarity?: number;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Is a candidate decision a near-duplicate of one ALREADY accepted in the
|
|
80
|
+
* ledger? This is the dedup half of the auto-accept verify gate: a fresh
|
|
81
|
+
* DEC that merely restates an accepted one should NOT silently re-land —
|
|
82
|
+
* it falls back to an `_inbox/` draft for human eyes instead.
|
|
83
|
+
*
|
|
84
|
+
* Title-token Jaccard against the accepted (non-superseded) ledger, at the
|
|
85
|
+
* same `definite` threshold the inbox clusterer uses. Title-vs-title keeps
|
|
86
|
+
* it symmetric (the ledger only carries titles); deterministic, no LLM.
|
|
87
|
+
*/
|
|
88
|
+
export declare function isDuplicateOfAccepted(args: {
|
|
89
|
+
repoRoot: string;
|
|
90
|
+
title: string;
|
|
91
|
+
threshold?: number;
|
|
92
|
+
}): AcceptedDuplicate;
|
package/dist/attention/dedup.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
21
21
|
import { join } from "node:path";
|
|
22
|
-
import { decisionsDir } from "@isaacriehm/cairn-state";
|
|
22
|
+
import { buildDecisionsLedger, decisionsDir } from "@isaacriehm/cairn-state";
|
|
23
23
|
import { jaccard, tokenize } from "../text/jaccard.js";
|
|
24
24
|
/** Default char window of body to fold into the token bag. */
|
|
25
25
|
const BODY_CHAR_WINDOW = 500;
|
|
@@ -214,4 +214,45 @@ export function findDuplicateClusters(args) {
|
|
|
214
214
|
thresholdDefinite,
|
|
215
215
|
};
|
|
216
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* Is a candidate decision a near-duplicate of one ALREADY accepted in the
|
|
219
|
+
* ledger? This is the dedup half of the auto-accept verify gate: a fresh
|
|
220
|
+
* DEC that merely restates an accepted one should NOT silently re-land —
|
|
221
|
+
* it falls back to an `_inbox/` draft for human eyes instead.
|
|
222
|
+
*
|
|
223
|
+
* Title-token Jaccard against the accepted (non-superseded) ledger, at the
|
|
224
|
+
* same `definite` threshold the inbox clusterer uses. Title-vs-title keeps
|
|
225
|
+
* it symmetric (the ledger only carries titles); deterministic, no LLM.
|
|
226
|
+
*/
|
|
227
|
+
export function isDuplicateOfAccepted(args) {
|
|
228
|
+
const threshold = args.threshold ?? DEFAULT_THRESHOLD_DEFINITE;
|
|
229
|
+
const candidate = tokenize(args.title);
|
|
230
|
+
if (candidate.size === 0)
|
|
231
|
+
return { dup: false };
|
|
232
|
+
let entries;
|
|
233
|
+
try {
|
|
234
|
+
entries = buildDecisionsLedger({ repoRoot: args.repoRoot });
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
return { dup: false };
|
|
238
|
+
}
|
|
239
|
+
let best = { sim: 0, id: "", title: "" };
|
|
240
|
+
for (const e of entries) {
|
|
241
|
+
const t = tokenize(e.title);
|
|
242
|
+
if (t.size === 0)
|
|
243
|
+
continue;
|
|
244
|
+
const sim = jaccard(candidate, t);
|
|
245
|
+
if (sim > best.sim)
|
|
246
|
+
best = { sim, id: e.id, title: e.title };
|
|
247
|
+
}
|
|
248
|
+
if (best.sim >= threshold) {
|
|
249
|
+
return {
|
|
250
|
+
dup: true,
|
|
251
|
+
matchId: best.id,
|
|
252
|
+
matchTitle: best.title,
|
|
253
|
+
similarity: Math.round(best.sim * 100) / 100,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
return { dup: false };
|
|
257
|
+
}
|
|
217
258
|
//# sourceMappingURL=dedup.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dedup.js","sourceRoot":"","sources":["../../src/attention/dedup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC1E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"dedup.js","sourceRoot":"","sources":["../../src/attention/dedup.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,12 +5,23 @@
|
|
|
5
5
|
* 1. Probable inline rebuilds — `className` lists in non-component code
|
|
6
6
|
* whose Tailwind utility ROOTS closely match an indexed component's
|
|
7
7
|
* (max-w-2xl ≈ max-w-4xl, so value-tweaked copies still match).
|
|
8
|
+
* Similarity is IDF-weighted over the component corpus: a utility root
|
|
9
|
+
* that nearly every component uses (`flex`, `gap`, `mx`, `px`) carries
|
|
10
|
+
* almost no weight, so a page sharing only generic layout scaffolding
|
|
11
|
+
* does NOT match — only overlap on DISTINCTIVE class roots counts. This
|
|
12
|
+
* is self-tuning to the project's own CSS; no hardcoded utility list.
|
|
8
13
|
* 2. Name collisions — `@cairn` names colliding with a type/interface name.
|
|
14
|
+
* 3. Unregistered components — a component-shaped file (PascalCase
|
|
15
|
+
* basename, real exports, JSX markup) sitting OUTSIDE the declared
|
|
16
|
+
* component dirs. These are co-located components the registry can't
|
|
17
|
+
* see; route entry files (page.tsx/layout.tsx — lowercase) are
|
|
18
|
+
* naturally excluded, so no framework convention list is hardcoded.
|
|
19
|
+
* Surfaced as an offer to relocate or register the dir — never moved.
|
|
9
20
|
*
|
|
10
21
|
* Findings are triage input surfaced to the attention queue with EXTEND /
|
|
11
|
-
* rename recommendations; never auto-fixed.
|
|
22
|
+
* rename / register recommendations; never auto-fixed.
|
|
12
23
|
*/
|
|
13
|
-
export type ComponentAuditKind = "inline-rebuild" | "name-collision";
|
|
24
|
+
export type ComponentAuditKind = "inline-rebuild" | "name-collision" | "unregistered-component";
|
|
14
25
|
export interface ComponentAuditFinding {
|
|
15
26
|
kind: ComponentAuditKind;
|
|
16
27
|
/** Where the probable violation lives (repo-relative POSIX). */
|
package/dist/components/audit.js
CHANGED
|
@@ -5,18 +5,34 @@
|
|
|
5
5
|
* 1. Probable inline rebuilds — `className` lists in non-component code
|
|
6
6
|
* whose Tailwind utility ROOTS closely match an indexed component's
|
|
7
7
|
* (max-w-2xl ≈ max-w-4xl, so value-tweaked copies still match).
|
|
8
|
+
* Similarity is IDF-weighted over the component corpus: a utility root
|
|
9
|
+
* that nearly every component uses (`flex`, `gap`, `mx`, `px`) carries
|
|
10
|
+
* almost no weight, so a page sharing only generic layout scaffolding
|
|
11
|
+
* does NOT match — only overlap on DISTINCTIVE class roots counts. This
|
|
12
|
+
* is self-tuning to the project's own CSS; no hardcoded utility list.
|
|
8
13
|
* 2. Name collisions — `@cairn` names colliding with a type/interface name.
|
|
14
|
+
* 3. Unregistered components — a component-shaped file (PascalCase
|
|
15
|
+
* basename, real exports, JSX markup) sitting OUTSIDE the declared
|
|
16
|
+
* component dirs. These are co-located components the registry can't
|
|
17
|
+
* see; route entry files (page.tsx/layout.tsx — lowercase) are
|
|
18
|
+
* naturally excluded, so no framework convention list is hardcoded.
|
|
19
|
+
* Surfaced as an offer to relocate or register the dir — never moved.
|
|
9
20
|
*
|
|
10
21
|
* Findings are triage input surfaced to the attention queue with EXTEND /
|
|
11
|
-
* rename recommendations; never auto-fixed.
|
|
22
|
+
* rename / register recommendations; never auto-fixed.
|
|
12
23
|
*/
|
|
13
24
|
import { readFileSync } from "node:fs";
|
|
14
25
|
import { join } from "node:path";
|
|
15
|
-
import { collectComponents, hasComponentConfig, loadComponentsConfig, walkFs, } from "@isaacriehm/cairn-state";
|
|
16
|
-
import { jaccard } from "../text/jaccard.js";
|
|
26
|
+
import { collectComponents, extractExportName, extractExportNames, hasComponentConfig, loadComponentsConfig, walkFs, } from "@isaacriehm/cairn-state";
|
|
17
27
|
const SIMILARITY_THRESHOLD = 0.7;
|
|
18
28
|
const MIN_SHARED_ROOTS = 3;
|
|
19
29
|
const MIN_CLASSES = 3;
|
|
30
|
+
/**
|
|
31
|
+
* A shared root only counts toward MIN_SHARED_ROOTS if it is distinctive —
|
|
32
|
+
* idf ≥ ln(2), i.e. the root appears in fewer than ~half of all components.
|
|
33
|
+
* Ubiquitous layout utilities fall below this and never satisfy the gate.
|
|
34
|
+
*/
|
|
35
|
+
const IDF_DISTINCTIVE_FLOOR = Math.log(2);
|
|
20
36
|
const CLASS_ATTR_RE = /className\s*=\s*(?:"([^"]+)"|'([^']+)'|\{`([^`]+)`\})/g;
|
|
21
37
|
const VALUE_SUFFIX = /-(?:\d[^-]*|xs|sm|md|lg|xl|\dxl|auto|full|none|screen|px)$/;
|
|
22
38
|
const SKIP_DIRS = new Set([".git", ".cairn", "node_modules", "dist", ".next", "build", "coverage"]);
|
|
@@ -43,18 +59,38 @@ function classLists(source) {
|
|
|
43
59
|
}
|
|
44
60
|
return lists;
|
|
45
61
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
let shared = 0;
|
|
50
|
-
for (const r of ra)
|
|
51
|
-
if (rb.has(r))
|
|
52
|
-
shared += 1;
|
|
53
|
-
return { score: jaccard(ra, rb), shared };
|
|
62
|
+
/** Distinct utility roots of a class list. */
|
|
63
|
+
function rootSet(classes) {
|
|
64
|
+
return new Set(classes.map(utilityRoot));
|
|
54
65
|
}
|
|
55
66
|
function inComponentDirs(rel, config) {
|
|
56
67
|
return config.workspaces.some((ws) => ws.componentDirs.some((d) => rel === d || rel.startsWith(`${d}/`)));
|
|
57
68
|
}
|
|
69
|
+
/** JSX markup signal — a tag or a className attribute. */
|
|
70
|
+
const JSX_RE = /<[A-Za-z][^>]*\/?>|className\s*=/;
|
|
71
|
+
/**
|
|
72
|
+
* If a file outside the component dirs looks like a misplaced COMPONENT
|
|
73
|
+
* (rather than a hook/context/util, or markup duplicated inside
|
|
74
|
+
* non-component code), return its exported names; otherwise null. Three
|
|
75
|
+
* convention-based signals, no framework list:
|
|
76
|
+
* 1. PascalCase basename — named component files are `FeaturedShell.tsx`;
|
|
77
|
+
* framework route entry files are lowercase (`page.tsx`, `layout.tsx`).
|
|
78
|
+
* 2. Exports a PascalCase symbol — a component-like export exists (a file
|
|
79
|
+
* exporting only `useThing` / consts is a hook/util, not a component).
|
|
80
|
+
* 3. Renders JSX — excludes type-only / pure-logic files.
|
|
81
|
+
*/
|
|
82
|
+
function misplacedComponentExports(rel, source) {
|
|
83
|
+
const base = rel.slice(rel.lastIndexOf("/") + 1);
|
|
84
|
+
const stem = base.includes(".") ? base.slice(0, base.indexOf(".")) : base;
|
|
85
|
+
if (!/^[A-Z][a-z]/.test(stem))
|
|
86
|
+
return null;
|
|
87
|
+
const names = extractExportNames(source);
|
|
88
|
+
if (!names.some((n) => /^[A-Z][a-z]/.test(n)))
|
|
89
|
+
return null;
|
|
90
|
+
if (!JSX_RE.test(source))
|
|
91
|
+
return null;
|
|
92
|
+
return names;
|
|
93
|
+
}
|
|
58
94
|
export function runComponentAudit(repoRoot) {
|
|
59
95
|
const config = loadComponentsConfig(repoRoot);
|
|
60
96
|
if (!hasComponentConfig(config))
|
|
@@ -66,6 +102,7 @@ export function runComponentAudit(repoRoot) {
|
|
|
66
102
|
const findings = [];
|
|
67
103
|
let scanned = 0;
|
|
68
104
|
// ── 1. Inline-rebuild scan ────────────────────────────────────────────
|
|
105
|
+
// Each component's className lists, with utility roots precomputed.
|
|
69
106
|
const signatures = components
|
|
70
107
|
.map((c) => {
|
|
71
108
|
let src = "";
|
|
@@ -75,9 +112,48 @@ export function runComponentAudit(repoRoot) {
|
|
|
75
112
|
catch {
|
|
76
113
|
/* skip unreadable */
|
|
77
114
|
}
|
|
78
|
-
|
|
115
|
+
const lists = classLists(src).map((l) => ({
|
|
116
|
+
line: l.line,
|
|
117
|
+
roots: rootSet(l.classes),
|
|
118
|
+
}));
|
|
119
|
+
return { name: c.tags.cairn ?? "?", file: c.file, lists };
|
|
79
120
|
})
|
|
80
121
|
.filter((c) => c.lists.length > 0);
|
|
122
|
+
// Corpus IDF: how distinctive each utility root is. A root present in many
|
|
123
|
+
// components (df → N) gets idf → 0; a rare root gets a high weight. Document
|
|
124
|
+
// = one component (union of its lists' roots), so repetition within a file
|
|
125
|
+
// doesn't inflate frequency.
|
|
126
|
+
const corpusN = signatures.length;
|
|
127
|
+
const docFreq = new Map();
|
|
128
|
+
for (const c of signatures) {
|
|
129
|
+
const compRoots = new Set();
|
|
130
|
+
for (const l of c.lists)
|
|
131
|
+
for (const r of l.roots)
|
|
132
|
+
compRoots.add(r);
|
|
133
|
+
for (const r of compRoots)
|
|
134
|
+
docFreq.set(r, (docFreq.get(r) ?? 0) + 1);
|
|
135
|
+
}
|
|
136
|
+
const idf = (root) => Math.log((corpusN + 1) / ((docFreq.get(root) ?? 0) + 1));
|
|
137
|
+
// IDF-weighted Jaccard between two root sets, plus the count of shared
|
|
138
|
+
// DISTINCTIVE roots (ubiquitous roots don't count toward the gate).
|
|
139
|
+
const weightedSimilarity = (target, comp) => {
|
|
140
|
+
let inter = 0;
|
|
141
|
+
let union = 0;
|
|
142
|
+
let shared = 0;
|
|
143
|
+
for (const r of target) {
|
|
144
|
+
const w = idf(r);
|
|
145
|
+
union += w;
|
|
146
|
+
if (comp.has(r)) {
|
|
147
|
+
inter += w;
|
|
148
|
+
if (w >= IDF_DISTINCTIVE_FLOOR)
|
|
149
|
+
shared += 1;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (const r of comp)
|
|
153
|
+
if (!target.has(r))
|
|
154
|
+
union += idf(r);
|
|
155
|
+
return { score: union > 0 ? inter / union : 0, shared };
|
|
156
|
+
};
|
|
81
157
|
// ── 2. Name-collision prep ────────────────────────────────────────────
|
|
82
158
|
const nameToComponent = new Map();
|
|
83
159
|
for (const c of components)
|
|
@@ -105,14 +181,35 @@ export function runComponentAudit(repoRoot) {
|
|
|
105
181
|
scanned += 1;
|
|
106
182
|
// Inline rebuild — only in non-component code with a UI extension.
|
|
107
183
|
if (extensions.has(ext) && !inComponentDirs(rel, config)) {
|
|
184
|
+
// A component-shaped file outside the component dirs is not a
|
|
185
|
+
// rebuild — it is a co-located component the registry can't see.
|
|
186
|
+
// Surface it (with its export names + file) as an offer to
|
|
187
|
+
// relocate/register; skip the rebuild scan for it.
|
|
188
|
+
const exports = misplacedComponentExports(rel, source);
|
|
189
|
+
if (exports !== null) {
|
|
190
|
+
const primary = extractExportName(source) ?? exports[0];
|
|
191
|
+
const others = exports.filter((n) => n !== primary);
|
|
192
|
+
const dirs = config.workspaces.flatMap((w) => w.componentDirs).join(", ");
|
|
193
|
+
findings.push({
|
|
194
|
+
kind: "unregistered-component",
|
|
195
|
+
file: rel,
|
|
196
|
+
component: primary,
|
|
197
|
+
componentFile: rel,
|
|
198
|
+
message: `${primary} (${rel})${others.length > 0 ? ` — also exports ${others.join(", ")}` : ""} ` +
|
|
199
|
+
`looks like a component but lives outside the declared component dir(s) (${dirs}); the registry can't see it`,
|
|
200
|
+
recommendation: `move ${rel} into a component dir, or add its directory to the workspace's componentDirs — then header + index ${primary}`,
|
|
201
|
+
});
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
108
204
|
for (const target of classLists(source)) {
|
|
205
|
+
const troots = rootSet(target.classes);
|
|
109
206
|
let best = null;
|
|
110
207
|
for (const comp of signatures) {
|
|
111
208
|
for (const list of comp.lists) {
|
|
112
|
-
const { score, shared } =
|
|
209
|
+
const { score, shared } = weightedSimilarity(troots, list.roots);
|
|
113
210
|
if (score >= SIMILARITY_THRESHOLD && shared >= MIN_SHARED_ROOTS) {
|
|
114
211
|
if (best === null || score > best.score) {
|
|
115
|
-
best = { name: comp.name, file: comp.file,
|
|
212
|
+
best = { name: comp.name, file: comp.file, score };
|
|
116
213
|
}
|
|
117
214
|
}
|
|
118
215
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/components/audit.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/components/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,MAAM,GAGP,MAAM,yBAAyB,CAAC;AAEjC,MAAM,oBAAoB,GAAG,GAAG,CAAC;AACjC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAC3B,MAAM,WAAW,GAAG,CAAC,CAAC;AACtB;;;;GAIG;AACH,MAAM,qBAAqB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAE1C,MAAM,aAAa,GAAG,wDAAwD,CAAC;AAC/E,MAAM,YAAY,GAAG,4DAA4D,CAAC;AAElF,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;AAyBpG,wEAAwE;AACxE,SAAS,WAAW,CAAC,GAAW;IAC9B,IAAI,IAAY,CAAC;IACjB,IAAI,GAAG,GAAG,GAAG,CAAC;IACd,GAAG,CAAC;QACF,IAAI,GAAG,GAAG,CAAC;QACX,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IACtC,CAAC,QAAQ,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;IAC5C,OAAO,GAAG,IAAI,IAAI,CAAC;AACrB,CAAC;AAOD,SAAS,UAAU,CAAC,MAAc;IAChC,MAAM,KAAK,GAAgB,EAAE,CAAC;IAC9B,aAAa,CAAC,SAAS,GAAG,CAAC,CAAC;IAC5B,IAAI,CAAyB,CAAC;IAC9B,OAAO,CAAC,CAAC,GAAG,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACjD,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;QACtE,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACjD,IAAI,OAAO,CAAC,MAAM,IAAI,WAAW,EAAE,CAAC;YAClC,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QAC7E,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,8CAA8C;AAC9C,SAAS,OAAO,CAAC,OAAiB;IAChC,OAAO,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,eAAe,CAAC,GAAW,EAAE,MAAkC;IACtE,OAAO,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CACnC,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CACnE,CAAC;AACJ,CAAC;AAED,0DAA0D;AAC1D,MAAM,MAAM,GAAG,kCAAkC,CAAC;AAElD;;;;;;;;;;GAUG;AACH,SAAS,yBAAyB,CAAC,GAAW,EAAE,MAAc;IAC5D,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1E,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3C,MAAM,KAAK,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3D,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,MAAM,MAAM,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAC9C,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAErE,MAAM,EAAE,UAAU,EAAE,GAAG,iBAAiB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC3D,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;IAC3E,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,SAAS,EAAE,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAEzF,MAAM,QAAQ,GAA4B,EAAE,CAAC;IAC7C,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,yEAAyE;IACzE,oEAAoE;IACpE,MAAM,UAAU,GAAG,UAAU;SAC1B,GAAG,CAAC,CAAC,CAAkB,EAAE,EAAE;QAC1B,IAAI,GAAG,GAAG,EAAE,CAAC;QACb,IAAI,CAAC;YACH,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;QACrD,CAAC;QAAC,MAAM,CAAC;YACP,qBAAqB;QACvB,CAAC;QACD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACxC,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;SAC1B,CAAC,CAAC,CAAC;QACJ,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;IAC5D,CAAC,CAAC;SACD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAErC,2EAA2E;IAC3E,6EAA6E;IAC7E,2EAA2E;IAC3E,6BAA6B;IAC7B,MAAM,OAAO,GAAG,UAAU,CAAC,MAAM,CAAC;IAClC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC1C,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;QACpC,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK;YAAE,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK;gBAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACnE,KAAK,MAAM,CAAC,IAAI,SAAS;YAAE,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACvE,CAAC;IACD,MAAM,GAAG,GAAG,CAAC,IAAY,EAAU,EAAE,CACnC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAE3D,uEAAuE;IACvE,oEAAoE;IACpE,MAAM,kBAAkB,GAAG,CACzB,MAAmB,EACnB,IAAiB,EACkB,EAAE;QACrC,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;YACvB,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;YACjB,KAAK,IAAI,CAAC,CAAC;YACX,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChB,KAAK,IAAI,CAAC,CAAC;gBACX,IAAI,CAAC,IAAI,qBAAqB;oBAAE,MAAM,IAAI,CAAC,CAAC;YAC9C,CAAC;QACH,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,IAAI;YAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;gBAAE,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;QAC1D,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1D,CAAC,CAAC;IAEF,yEAAyE;IACzE,MAAM,eAAe,GAAG,IAAI,GAAG,EAA2B,CAAC;IAC3D,KAAK,MAAM,CAAC,IAAI,UAAU;QAAE,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK;YAAE,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAEnF,MAAM,CAAC;QACL,GAAG,EAAE,QAAQ;QACb,QAAQ;QACR,QAAQ;QACR,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YACnB,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;YAC5C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,OAAO;YAC/B,IAAI,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC;gBACjC,kEAAkE;gBAClE,yDAAyD;YAC3D,CAAC;YACD,IAAI,MAAc,CAAC;YACnB,IAAI,CAAC;gBACH,MAAM,GAAG,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACrC,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO;YACT,CAAC;YACD,OAAO,IAAI,CAAC,CAAC;YAEb,mEAAmE;YACnE,IAAI,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC;gBACzD,8DAA8D;gBAC9D,iEAAiE;gBACjE,2DAA2D;gBAC3D,mDAAmD;gBACnD,MAAM,OAAO,GAAG,yBAAyB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;gBACvD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;oBACrB,MAAM,OAAO,GAAG,iBAAiB,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,CAAC,CAAE,CAAC;oBACzD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC;oBACpD,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBAC1E,QAAQ,CAAC,IAAI,CAAC;wBACZ,IAAI,EAAE,wBAAwB;wBAC9B,IAAI,EAAE,GAAG;wBACT,SAAS,EAAE,OAAO;wBAClB,aAAa,EAAE,GAAG;wBAClB,OAAO,EACL,GAAG,OAAO,KAAK,GAAG,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,mBAAmB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG;4BACxF,2EAA2E,IAAI,8BAA8B;wBAC/G,cAAc,EAAE,QAAQ,GAAG,sGAAsG,OAAO,EAAE;qBAC3I,CAAC,CAAC;oBACH,OAAO;gBACT,CAAC;gBACD,KAAK,MAAM,MAAM,IAAI,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;oBACxC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;oBACvC,IAAI,IAAI,GAAyD,IAAI,CAAC;oBACtE,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;wBAC9B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;4BAC9B,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,kBAAkB,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;4BACjE,IAAI,KAAK,IAAI,oBAAoB,IAAI,MAAM,IAAI,gBAAgB,EAAE,CAAC;gCAChE,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;oCACxC,IAAI,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;gCACrD,CAAC;4BACH,CAAC;wBACH,CAAC;oBACH,CAAC;oBACD,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;wBAClB,QAAQ,CAAC,IAAI,CAAC;4BACZ,IAAI,EAAE,gBAAgB;4BACtB,IAAI,EAAE,GAAG;4BACT,IAAI,EAAE,MAAM,CAAC,IAAI;4BACjB,SAAS,EAAE,IAAI,CAAC,IAAI;4BACpB,aAAa,EAAE,IAAI,CAAC,IAAI;4BACxB,KAAK,EAAE,IAAI,CAAC,KAAK;4BACjB,OAAO,EAAE,GAAG,GAAG,IAAI,MAAM,CAAC,IAAI,YAAY,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,sBAAsB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;4BAC9G,cAAc,EAAE,UAAU,IAAI,CAAC,IAAI,kDAAkD;yBACtF,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC;YAED,2DAA2D;YAC3D,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,eAAe,EAAE,CAAC;gBAC3C,IAAI,IAAI,CAAC,IAAI,KAAK,GAAG;oBAAE,SAAS;gBAChC,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,4BAA4B,IAAI,KAAK,CAAC,CAAC;gBAC7D,IAAI,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;oBACpB,QAAQ,CAAC,IAAI,CAAC;wBACZ,IAAI,EAAE,gBAAgB;wBACtB,IAAI,EAAE,GAAG;wBACT,SAAS,EAAE,IAAI;wBACf,aAAa,EAAE,IAAI,CAAC,IAAI;wBACxB,OAAO,EAAE,WAAW,IAAI,MAAM,IAAI,CAAC,IAAI,uCAAuC,GAAG,EAAE;wBACnF,cAAc,EAAE,wCAAwC;qBACzD,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;KACF,CAAC,CAAC;IAEH,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;AAC/B,CAAC"}
|
|
@@ -1,36 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* LLM-driven, convention-agnostic component-layout detection.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Cairn adoption ALWAYS runs inside an LLM coding agent, so detection
|
|
5
|
+
* leans on a model rather than a hardcoded convention list — there is no
|
|
6
|
+
* `src/components` / `packages/*` assumption baked in. A Sonnet call reads
|
|
7
|
+
* the repo's structural digest (per-directory file-extension histogram,
|
|
8
|
+
* the dirs that hold a `package.json`, and any workspace-manifest files)
|
|
9
|
+
* and returns the `components:` config: which workspaces carry reusable
|
|
10
|
+
* UI, where their component dirs live, the extensions in play, and a
|
|
11
|
+
* taxonomy that fits THAT workspace. A non-UI repo (a backend with no
|
|
12
|
+
* components) returns null and is left untouched.
|
|
8
13
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
14
|
+
* Only mechanical, repo-agnostic facts stay deterministic: the file walk
|
|
15
|
+
* and the universal build-output exclude list. Everything that requires
|
|
16
|
+
* understanding "what is this directory for" is the model's job — naming,
|
|
17
|
+
* monorepo tooling, framework, and taxonomy are all inferred, never
|
|
18
|
+
* assumed.
|
|
19
|
+
*
|
|
20
|
+
* Isolation invariant (port invariant 3): a workspace is NEVER emitted as
|
|
21
|
+
* `shared`. The flag is omitted (it normalizes to isolated); the operator
|
|
22
|
+
* opts in afterward (the skill asks).
|
|
13
23
|
*/
|
|
14
24
|
import { type ComponentsConfig } from "@isaacriehm/cairn-state";
|
|
15
|
-
import type { DetectionResult } from "./types.js";
|
|
16
25
|
/**
|
|
17
|
-
*
|
|
18
|
-
* config block (raw yaml shape), or `null` when
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* `detection` is reserved for future stack-aware tuning; extension
|
|
24
|
-
* detection is presence-driven so the result holds even when stack
|
|
25
|
-
* signatures are absent (e.g. a Vue repo with no tsconfig).
|
|
26
|
+
* Detect the repo's component layout via the model and return a
|
|
27
|
+
* `components:` config block (raw yaml shape), or `null` when the repo
|
|
28
|
+
* carries no reusable UI components. Retries the model call once before
|
|
29
|
+
* giving up. There is no deterministic fallback by design: detection only
|
|
30
|
+
* ever runs inside an LLM coding agent, so "no model" means adoption is
|
|
31
|
+
* not happening at all.
|
|
26
32
|
*/
|
|
27
|
-
export declare function detectComponentsConfig(repoRoot: string
|
|
33
|
+
export declare function detectComponentsConfig(repoRoot: string): Promise<ComponentsConfig | null>;
|
|
28
34
|
export type EnsureComponentsStatus =
|
|
29
35
|
/** No `.cairn/config.yaml` — the repo isn't adopted; run `/cairn-adopt` first. */
|
|
30
36
|
"not-adopted"
|
|
31
37
|
/** A `components:` block already exists — left untouched (idempotent). */
|
|
32
38
|
| "exists"
|
|
33
|
-
/** No
|
|
39
|
+
/** No reusable UI components on disk — nothing written (non-UI repo). */
|
|
34
40
|
| "none"
|
|
35
41
|
/** A `components:` block was detected and merged into the config. */
|
|
36
42
|
| "written";
|
|
@@ -43,9 +49,9 @@ export interface EnsureComponentsConfigResult {
|
|
|
43
49
|
}
|
|
44
50
|
/**
|
|
45
51
|
* Backfill a `components:` block into an already-adopted repo's
|
|
46
|
-
* `.cairn/config.yaml`.
|
|
47
|
-
*
|
|
48
|
-
*
|
|
52
|
+
* `.cairn/config.yaml`. Runs the same LLM detection adoption Phase 4-seed
|
|
53
|
+
* runs, but MERGES the key into a config that already exists (preserving
|
|
54
|
+
* every other key) rather than writing a fresh file.
|
|
49
55
|
*
|
|
50
56
|
* Idempotent: a repo that already carries a `components:` block is left
|
|
51
57
|
* untouched ("exists"). The standalone backfill path
|
|
@@ -53,7 +59,7 @@ export interface EnsureComponentsConfigResult {
|
|
|
53
59
|
* is the only caller; adoption keeps using `detectComponentsConfig`
|
|
54
60
|
* directly inside 4-seed.
|
|
55
61
|
*
|
|
56
|
-
* Isolation invariant (port invariant 3):
|
|
57
|
-
*
|
|
62
|
+
* Isolation invariant (port invariant 3): workspaces are never emitted as
|
|
63
|
+
* `shared` — the operator opts in afterward (the skill asks).
|
|
58
64
|
*/
|
|
59
|
-
export declare function ensureComponentsConfig(repoRoot: string): EnsureComponentsConfigResult
|
|
65
|
+
export declare function ensureComponentsConfig(repoRoot: string): Promise<EnsureComponentsConfigResult>;
|