@isaacriehm/cairn-core 0.25.0 → 0.31.0

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.
Files changed (97) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/gc/config-drift.d.ts +60 -0
  3. package/dist/gc/config-drift.js +262 -0
  4. package/dist/gc/config-drift.js.map +1 -0
  5. package/dist/gc/index.d.ts +2 -0
  6. package/dist/gc/index.js +1 -0
  7. package/dist/gc/index.js.map +1 -1
  8. package/dist/gc/sweep.js +11 -0
  9. package/dist/gc/sweep.js.map +1 -1
  10. package/dist/gc/types.d.ts +2 -2
  11. package/dist/hooks/post-tool-use/sot-align.d.ts +4 -0
  12. package/dist/hooks/post-tool-use/sot-align.js +26 -3
  13. package/dist/hooks/post-tool-use/sot-align.js.map +1 -1
  14. package/dist/hooks/sot-align-common.d.ts +10 -0
  15. package/dist/hooks/sot-align-common.js +32 -0
  16. package/dist/hooks/sot-align-common.js.map +1 -1
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.js +1 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/init/brand-setup.js +8 -3
  21. package/dist/init/brand-setup.js.map +1 -1
  22. package/dist/init/curator/emit.d.ts +10 -0
  23. package/dist/init/curator/emit.js +29 -17
  24. package/dist/init/curator/emit.js.map +1 -1
  25. package/dist/init/curator/index.d.ts +2 -0
  26. package/dist/init/curator/index.js +2 -0
  27. package/dist/init/curator/index.js.map +1 -1
  28. package/dist/init/curator/walker.d.ts +7 -0
  29. package/dist/init/curator/walker.js +17 -4
  30. package/dist/init/curator/walker.js.map +1 -1
  31. package/dist/init/index.d.ts +1 -1
  32. package/dist/init/index.js +1 -1
  33. package/dist/init/index.js.map +1 -1
  34. package/dist/init/phases/4-seed.js +7 -2
  35. package/dist/init/phases/4-seed.js.map +1 -1
  36. package/dist/init/seed.d.ts +7 -0
  37. package/dist/init/seed.js +24 -16
  38. package/dist/init/seed.js.map +1 -1
  39. package/dist/init/topic-index/index.d.ts +8 -0
  40. package/dist/init/topic-index/index.js +17 -8
  41. package/dist/init/topic-index/index.js.map +1 -1
  42. package/dist/invariants/prune.d.ts +6 -4
  43. package/dist/invariants/prune.js +40 -7
  44. package/dist/invariants/prune.js.map +1 -1
  45. package/dist/mcp/schemas.d.ts +26 -0
  46. package/dist/mcp/schemas.js +23 -0
  47. package/dist/mcp/schemas.js.map +1 -1
  48. package/dist/mcp/tools/in-scope.js +7 -1
  49. package/dist/mcp/tools/in-scope.js.map +1 -1
  50. package/dist/mcp/tools/index.js +3 -0
  51. package/dist/mcp/tools/index.js.map +1 -1
  52. package/dist/mcp/tools/invariant-get.js +37 -28
  53. package/dist/mcp/tools/invariant-get.js.map +1 -1
  54. package/dist/mcp/tools/resolve-attention.d.ts +1 -1
  55. package/dist/mcp/tools/resolve-attention.js +92 -0
  56. package/dist/mcp/tools/resolve-attention.js.map +1 -1
  57. package/dist/mcp/tools/resync.d.ts +19 -0
  58. package/dist/mcp/tools/resync.js +109 -0
  59. package/dist/mcp/tools/resync.js.map +1 -0
  60. package/dist/migrate/config-io.d.ts +10 -0
  61. package/dist/migrate/config-io.js +16 -0
  62. package/dist/migrate/config-io.js.map +1 -1
  63. package/dist/migrate/migrations/0005-demote-autofilled-brand.d.ts +34 -0
  64. package/dist/migrate/migrations/0005-demote-autofilled-brand.js +199 -0
  65. package/dist/migrate/migrations/0005-demote-autofilled-brand.js.map +1 -0
  66. package/dist/migrate/migrations/0006-prune-sot-align-invariants.d.ts +27 -0
  67. package/dist/migrate/migrations/0006-prune-sot-align-invariants.js +55 -0
  68. package/dist/migrate/migrations/0006-prune-sot-align-invariants.js.map +1 -0
  69. package/dist/migrate/migrations/0007-collapse-component-dirs.d.ts +24 -0
  70. package/dist/migrate/migrations/0007-collapse-component-dirs.js +131 -0
  71. package/dist/migrate/migrations/0007-collapse-component-dirs.js.map +1 -0
  72. package/dist/migrate/migrations/0008-clean-adoption-scaffolding.d.ts +39 -0
  73. package/dist/migrate/migrations/0008-clean-adoption-scaffolding.js +206 -0
  74. package/dist/migrate/migrations/0008-clean-adoption-scaffolding.js.map +1 -0
  75. package/dist/migrate/registry.js +8 -0
  76. package/dist/migrate/registry.js.map +1 -1
  77. package/dist/resync/archive.d.ts +12 -0
  78. package/dist/resync/archive.js +29 -0
  79. package/dist/resync/archive.js.map +1 -0
  80. package/dist/resync/index.d.ts +73 -0
  81. package/dist/resync/index.js +389 -0
  82. package/dist/resync/index.js.map +1 -0
  83. package/dist/resync/recluster.d.ts +59 -0
  84. package/dist/resync/recluster.js +66 -0
  85. package/dist/resync/recluster.js.map +1 -0
  86. package/dist/session-start/build.js +47 -30
  87. package/dist/session-start/build.js.map +1 -1
  88. package/dist/session-start/templates.js +4 -4
  89. package/dist/session-start/templates.js.map +1 -1
  90. package/package.json +2 -2
  91. package/templates/.cairn/config/sensors.yaml +1 -1
  92. package/templates/.cairn/config/workflow.md +5 -12
  93. package/templates/.cairn/git-hooks/commit-msg +0 -2
  94. package/templates/.cairn/git-hooks/post-commit +0 -2
  95. package/templates/.cairn/ground/brand/overview.md +2 -2
  96. package/templates/.cairn/ground/brand/voice.md +2 -2
  97. package/templates/.cairn/ground/product/positioning.md +2 -2
@@ -0,0 +1,109 @@
1
+ /**
2
+ * `cairn_resync` — operator-initiated re-discovery that resolves config drift.
3
+ *
4
+ * The config-drift sensor (24h GC) surfaces the gap between declared config and
5
+ * the grown tree as `baseline_finding` nudges; this tool is the verb that turns
6
+ * them into concrete `config.yaml` edits. It writes COMMITTED config, so it is
7
+ * review-class: preview first (default), summarize the proposed edits, get the
8
+ * operator's OK, then call again with `apply: true`. Apply archives the
9
+ * pre-resync config to `.cairn/ground/.archive/` and is idempotent.
10
+ */
11
+ import { cairnDir } from "@isaacriehm/cairn-state";
12
+ import { runResync, runResyncRecluster } from "../../resync/index.js";
13
+ import { runCuratorEmit, runCuratorWalker } from "../../init/index.js";
14
+ import { requireBootstrap } from "../bootstrap-guard.js";
15
+ import { resyncInput } from "../schemas.js";
16
+ const RECURATE_CAPTURE_SOURCE = "resync-curator";
17
+ export const resyncTool = {
18
+ name: "cairn_resync",
19
+ description: "Resolve surfaced config drift (the config-drift baseline findings) by proposing concrete .cairn/config.yaml edits: add a grown dir to componentDirs, add a new file type to extensions, add an ignored path to off_limits, or drop a dead componentDir. Default previews the edits (mutates nothing) — summarize them and get the operator's OK, then call with apply:true. Apply archives the pre-resync config to .cairn/ground/.archive/ and is idempotent. Pass `area` to scope to one subtree. Pass recluster:true to instead run the LLM re-cluster pass (re-walk prose, Haiku-judge new semantic collisions, rebuild topic-index + anchor-map) — opt-in, spends Haiku on new prose only; preview first, then apply:true to overwrite the (archived) maps. Pass recurate:'walk' (with `area`) to build the curator corpus for re-curation, then — after the cairn-resync skill dispatches curator-map/reduce subagents — recurate:'emit' to write the resulting DEC/INV drafts to _inbox/ (drained via cairn-attention). recurate is skill-driven; don't call it directly.",
20
+ inputSchema: resyncInput,
21
+ handler: async (ctx, input) => {
22
+ const block = requireBootstrap(ctx.repoRoot);
23
+ if (block !== null)
24
+ return block;
25
+ const apply = input.apply === true;
26
+ if (input.recurate === "walk") {
27
+ const w = await runCuratorWalker({
28
+ repoRoot: ctx.repoRoot,
29
+ ...(input.area !== undefined ? { area: input.area } : {}),
30
+ });
31
+ return {
32
+ ok: true,
33
+ mode: "recurate-walk",
34
+ area: input.area ?? null,
35
+ curator_dir: cairnDir(ctx.repoRoot, "init", "curator"),
36
+ shards_path: w.shards_path,
37
+ corpus_path: w.corpus_path,
38
+ records_total: w.records_total,
39
+ records_by_kind: w.records_by_kind,
40
+ shards: w.shards,
41
+ note: w.shards === 0
42
+ ? "no curatable prose in this area — nothing to dispatch; skip emit"
43
+ : "dispatch curator-map (rounds of 4) + curator-reduce over the shard plan, then call again with recurate:'emit'",
44
+ };
45
+ }
46
+ if (input.recurate === "emit") {
47
+ const e = await runCuratorEmit({
48
+ repoRoot: ctx.repoRoot,
49
+ draft: true,
50
+ captureSource: RECURATE_CAPTURE_SOURCE,
51
+ });
52
+ return {
53
+ ok: true,
54
+ mode: "recurate-emit",
55
+ dec_drafts: e.decsWritten.map((d) => ({ id: d.id, path: d.path, title: d.title })),
56
+ inv_drafts: e.invsWritten.map((d) => ({ id: d.id, path: d.path, title: d.title })),
57
+ dropped: e.dropped,
58
+ drop_reasons: e.dropReasons,
59
+ note: "DEC/INV drafts written to _inbox/ — drain via cairn-attention (accept graduates; reject archives)",
60
+ };
61
+ }
62
+ if (input.recluster === true) {
63
+ const r = await runResyncRecluster({ repoRoot: ctx.repoRoot, dryRun: !apply });
64
+ return {
65
+ ok: true,
66
+ mode: "recluster",
67
+ dry_run: r.dryRun,
68
+ applied: r.applied,
69
+ topics_before: r.topicsBefore,
70
+ topics_after: r.topicsAfter,
71
+ block_count: r.blockCount,
72
+ judge_calls: r.judgeCalls,
73
+ judge_calls_fresh: r.judgeFresh,
74
+ judge_calls_cached: r.judgeCached,
75
+ judge_calls_errors: r.judgeErrors,
76
+ archived_maps: r.archivedMaps,
77
+ note: r.dryRun
78
+ ? "preview only — re-walked + judged but wrote no map; call again with apply:true (and recluster:true) to overwrite the archived maps"
79
+ : undefined,
80
+ };
81
+ }
82
+ const result = runResync({
83
+ repoRoot: ctx.repoRoot,
84
+ dryRun: !apply,
85
+ ...(input.area !== undefined ? { area: input.area } : {}),
86
+ });
87
+ return {
88
+ ok: true,
89
+ dry_run: result.dryRun,
90
+ applied: result.applied,
91
+ proposals: result.proposals.map((p) => ({
92
+ kind: p.kind,
93
+ workspace: p.workspace,
94
+ value: p.value,
95
+ from: p.from,
96
+ detail: p.detail,
97
+ })),
98
+ skipped: result.skipped,
99
+ archived_config: result.archivedConfig,
100
+ archived_entities: result.archivedEntities,
101
+ note: result.proposals.length === 0
102
+ ? "no config drift to resolve — config is in sync with the tree"
103
+ : result.dryRun
104
+ ? "preview only — call again with apply:true to write these edits"
105
+ : undefined,
106
+ };
107
+ },
108
+ };
109
+ //# sourceMappingURL=resync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resync.js","sourceRoot":"","sources":["../../../src/mcp/tools/resync.ts"],"names":[],"mappings":"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"}
@@ -18,6 +18,16 @@ export declare function loadConfigDoc(repoRoot: string): Document | null;
18
18
  export declare function readConfigPin(repoRoot: string, doc?: Document | null): string | null;
19
19
  /** Set the `cairn_version` pin. No-op (returns false) when already equal. */
20
20
  export declare function writeConfigPin(repoRoot: string, version: string): boolean;
21
+ /**
22
+ * Write a mutated config `Document` back to disk. The migration system was
23
+ * built for top-level key edits (drop a dead key, set the pin); content
24
+ * migrations that need to repair a NESTED structure (e.g. collapse a
25
+ * workspace's `componentDirs`) mutate the `Document` via `loadConfigDoc` +
26
+ * `doc.getIn` / `doc.setIn` and persist with this. Whole-file rewrite under
27
+ * the caller's migrate lock; key order + comments are preserved by the yaml
28
+ * Document API. Returns false when the config is absent.
29
+ */
30
+ export declare function writeConfigDoc(repoRoot: string, doc: Document): boolean;
21
31
  /** Which of `keys` are present as top-level config keys. */
22
32
  export declare function configHasKeys(repoRoot: string, keys: readonly string[], doc?: Document | null): string[];
23
33
  /** Delete top-level `keys`; returns the keys actually removed. */
@@ -46,6 +46,22 @@ export function writeConfigPin(repoRoot, version) {
46
46
  writeFileSync(p, doc.toString(), "utf8");
47
47
  return true;
48
48
  }
49
+ /**
50
+ * Write a mutated config `Document` back to disk. The migration system was
51
+ * built for top-level key edits (drop a dead key, set the pin); content
52
+ * migrations that need to repair a NESTED structure (e.g. collapse a
53
+ * workspace's `componentDirs`) mutate the `Document` via `loadConfigDoc` +
54
+ * `doc.getIn` / `doc.setIn` and persist with this. Whole-file rewrite under
55
+ * the caller's migrate lock; key order + comments are preserved by the yaml
56
+ * Document API. Returns false when the config is absent.
57
+ */
58
+ export function writeConfigDoc(repoRoot, doc) {
59
+ const p = configPath(repoRoot);
60
+ if (!existsSync(p))
61
+ return false;
62
+ writeFileSync(p, doc.toString(), "utf8");
63
+ return true;
64
+ }
49
65
  /** Which of `keys` are present as top-level config keys. */
50
66
  export function configHasKeys(repoRoot, keys, doc) {
51
67
  const d = doc !== undefined ? doc : loadConfigDoc(repoRoot);
@@ -1 +1 @@
1
- {"version":3,"file":"config-io.js","sourceRoot":"","sources":["../../src/migrate/config-io.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config-io.js","sourceRoot":"","sources":["../../src/migrate/config-io.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * 0005 — demote auto-filled brand drafts back to `status: draft`.
3
+ *
4
+ * Before 0.25.0, the adoption brand step DEFAULTED to auto-fill and, on
5
+ * apply, flipped each written file `draft → current`. SessionStart then
6
+ * injected every `current` brand file as authoritative voice — so a
7
+ * generic, machine-written first draft (or the mechanical fallback when
8
+ * the Haiku derive timed out) burned context every session as if the
9
+ * operator had confirmed it.
10
+ *
11
+ * 0.25.0 fixed this going forward (auto-fill writes `draft`; SessionStart
12
+ * injects only confirmed brand). This migration repairs EXISTING repos:
13
+ * it finds brand/product docs that are provably auto-generated yet marked
14
+ * `current`/`accepted`, and demotes them to `draft` so they stop being
15
+ * injected. Operator-written brand is left alone; a demoted file is one
16
+ * frontmatter edit (or a re-run of brand setup) away from `current` again.
17
+ *
18
+ * The 0.26.0 detection only caught the mechanical fallback (fixed marker
19
+ * strings) and the byte-identical overview/positioning pair — it missed
20
+ * Haiku-derived auto-fill (voice.md / personas.yaml), which is worded
21
+ * freshly each run. 0.27.0 adds a co-generation cohort channel: once the
22
+ * overview≡positioning identity proves the pass auto-filled brand, every
23
+ * confirmed doc stamped with the same `generated` timestamp is demoted too.
24
+ *
25
+ * `review`-class: it rewrites committed ground state, so it surfaces for
26
+ * the operator and applies via `cairn migrate` — never silently.
27
+ *
28
+ * The cohort channel ships in 0.27.0, so `introducedIn` advances to 0.27.0:
29
+ * a repo that already ran the 0.26.0 pass (demoting only the pair) must
30
+ * re-evaluate to catch the co-generated siblings. `detect()` carries
31
+ * correctness.
32
+ */
33
+ import type { Migration } from "../types.js";
34
+ export declare const demoteAutofilledBrand: Migration;
@@ -0,0 +1,199 @@
1
+ /**
2
+ * 0005 — demote auto-filled brand drafts back to `status: draft`.
3
+ *
4
+ * Before 0.25.0, the adoption brand step DEFAULTED to auto-fill and, on
5
+ * apply, flipped each written file `draft → current`. SessionStart then
6
+ * injected every `current` brand file as authoritative voice — so a
7
+ * generic, machine-written first draft (or the mechanical fallback when
8
+ * the Haiku derive timed out) burned context every session as if the
9
+ * operator had confirmed it.
10
+ *
11
+ * 0.25.0 fixed this going forward (auto-fill writes `draft`; SessionStart
12
+ * injects only confirmed brand). This migration repairs EXISTING repos:
13
+ * it finds brand/product docs that are provably auto-generated yet marked
14
+ * `current`/`accepted`, and demotes them to `draft` so they stop being
15
+ * injected. Operator-written brand is left alone; a demoted file is one
16
+ * frontmatter edit (or a re-run of brand setup) away from `current` again.
17
+ *
18
+ * The 0.26.0 detection only caught the mechanical fallback (fixed marker
19
+ * strings) and the byte-identical overview/positioning pair — it missed
20
+ * Haiku-derived auto-fill (voice.md / personas.yaml), which is worded
21
+ * freshly each run. 0.27.0 adds a co-generation cohort channel: once the
22
+ * overview≡positioning identity proves the pass auto-filled brand, every
23
+ * confirmed doc stamped with the same `generated` timestamp is demoted too.
24
+ *
25
+ * `review`-class: it rewrites committed ground state, so it surfaces for
26
+ * the operator and applies via `cairn migrate` — never silently.
27
+ *
28
+ * The cohort channel ships in 0.27.0, so `introducedIn` advances to 0.27.0:
29
+ * a repo that already ran the 0.26.0 pass (demoting only the pair) must
30
+ * re-evaluate to catch the co-generated siblings. `detect()` carries
31
+ * correctness.
32
+ */
33
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
34
+ import { cairnDir, parseFrontmatter } from "@isaacriehm/cairn-state";
35
+ /** Distinctive substrings only the pre-0.25.0 mechanical fallback emits. */
36
+ const VOICE_MARKER = "Match the existing tone in CLAUDE.md / AGENTS.md if those files set a register";
37
+ const AVOID_MARKER = 'Marketing fluff ("world-class", "revolutionary", "game-changing")';
38
+ const PERSONAS_MARKER = "Refine when adding consumer-facing or external personas";
39
+ function brandDocs(repoRoot) {
40
+ return [
41
+ { rel: "brand/overview.md", abs: cairnDir(repoRoot, "ground", "brand", "overview.md") },
42
+ { rel: "brand/voice.md", abs: cairnDir(repoRoot, "ground", "brand", "voice.md") },
43
+ { rel: "product/positioning.md", abs: cairnDir(repoRoot, "ground", "product", "positioning.md") },
44
+ { rel: "product/personas.yaml", abs: cairnDir(repoRoot, "ground", "product", "personas.yaml") },
45
+ ];
46
+ }
47
+ function readDoc(abs) {
48
+ if (!existsSync(abs))
49
+ return null;
50
+ let raw;
51
+ try {
52
+ raw = readFileSync(abs, "utf8");
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ // personas.yaml is pure YAML (status as a top-level key, no `---` fence);
58
+ // the brand .md files use frontmatter. parseFrontmatter handles the .md;
59
+ // for the .yaml fall back to a status-line scan over the whole text.
60
+ const parsed = parseFrontmatter(raw);
61
+ const fm = (parsed.frontmatter ?? {});
62
+ let status = typeof fm["status"] === "string" ? fm["status"] : null;
63
+ if (status === null) {
64
+ const m = raw.match(/^status:\s*(\S+)\s*$/m);
65
+ status = m?.[1] ?? null;
66
+ }
67
+ let generated = typeof fm["generated"] === "string" ? fm["generated"] : null;
68
+ if (generated === null) {
69
+ const g = raw.match(/^generated:\s*(\S+)\s*$/m);
70
+ generated = g?.[1] ?? null;
71
+ }
72
+ return { status, generated, body: parsed.body, raw };
73
+ }
74
+ const CONFIRMED = new Set(["current", "accepted"]);
75
+ /** Strip headings + collapse whitespace, matching SessionStart's dedup. */
76
+ function normalize(s) {
77
+ return s
78
+ .replace(/^\s*#{1,6}\s+.*$/gm, "")
79
+ .replace(/\s+/g, " ")
80
+ .trim();
81
+ }
82
+ /**
83
+ * Repo-relative labels of confirmed brand docs that are provably the
84
+ * pre-0.25.0 auto-fill output. Two evidence channels:
85
+ *
86
+ * 1. Per-doc markers — voice.md / personas.yaml carrying a mechanical
87
+ * fallback's distinctive string. Precise but narrow: it ONLY catches
88
+ * the timeout fallback, never the Haiku-derived auto-fill (which is
89
+ * worded freshly each run and so carries no fixed marker).
90
+ *
91
+ * 2. Co-generation cohort — overview.md and positioning.md with
92
+ * byte-identical bodies is near-certain auto-fill proof (a hand-author
93
+ * doesn't write the exact same domain summary into two files). When
94
+ * that fires, every confirmed brand doc stamped with the SAME
95
+ * `generated` timestamp came out of the same automated pass and is
96
+ * demoted too. This is what catches a Haiku-derived voice.md the
97
+ * marker channel misses. The identity check is status-independent —
98
+ * an earlier run may have already demoted the pair to draft, yet its
99
+ * still-confirmed siblings must follow.
100
+ *
101
+ * A demoted file is one frontmatter edit from `current` again, so the
102
+ * asymmetry favors demoting: a false demote costs one keystroke, a false
103
+ * keep burns context every session as machine-written "authoritative" voice.
104
+ */
105
+ function autofilledConfirmed(repoRoot) {
106
+ const docs = brandDocs(repoRoot);
107
+ const byRel = new Map();
108
+ for (const d of docs)
109
+ byRel.set(d.rel, readDoc(d.abs));
110
+ const hits = new Set();
111
+ const get = (rel) => byRel.get(rel) ?? null;
112
+ const confirmed = (rel) => {
113
+ const p = get(rel);
114
+ return p !== null && p.status !== null && CONFIRMED.has(p.status) ? p : null;
115
+ };
116
+ // Channel 1 — per-doc mechanical-fallback markers.
117
+ const voice = confirmed("brand/voice.md");
118
+ if (voice && (voice.body.includes(VOICE_MARKER) || voice.body.includes(AVOID_MARKER))) {
119
+ hits.add("brand/voice.md");
120
+ }
121
+ const personas = confirmed("product/personas.yaml");
122
+ if (personas && personas.raw.includes(PERSONAS_MARKER)) {
123
+ hits.add("product/personas.yaml");
124
+ }
125
+ // Channel 2 — co-generation cohort, proven by the overview≡positioning
126
+ // identity (checked regardless of either doc's current status).
127
+ const overviewAny = get("brand/overview.md");
128
+ const positioningAny = get("product/positioning.md");
129
+ const pairAutofilled = overviewAny !== null &&
130
+ positioningAny !== null &&
131
+ normalize(overviewAny.body).length > 0 &&
132
+ normalize(overviewAny.body) === normalize(positioningAny.body);
133
+ if (pairAutofilled) {
134
+ // Cohort timestamp — the pass stamps every co-generated doc identically.
135
+ const cohortGen = overviewAny.generated ?? positioningAny.generated;
136
+ // overview/positioning are themselves cohort members; demote if still confirmed.
137
+ if (confirmed("brand/overview.md"))
138
+ hits.add("brand/overview.md");
139
+ if (confirmed("product/positioning.md"))
140
+ hits.add("product/positioning.md");
141
+ // A confirmed voice.md sharing the cohort timestamp is the same pass's
142
+ // output — the timestamp guard spares a voice the operator hand-wrote later.
143
+ if (voice && cohortGen !== null && voice.generated === cohortGen) {
144
+ hits.add("brand/voice.md");
145
+ }
146
+ // personas.yaml carries no `generated`; it's the lowest-value, most
147
+ // placeholder-prone doc, so the pair-proof alone demotes a confirmed one.
148
+ if (personas)
149
+ hits.add("product/personas.yaml");
150
+ }
151
+ return [...hits];
152
+ }
153
+ /** Rewrite the first `status: current|accepted` line to `status: draft`. */
154
+ function demoteToDraft(abs) {
155
+ let raw;
156
+ try {
157
+ raw = readFileSync(abs, "utf8");
158
+ }
159
+ catch {
160
+ return false;
161
+ }
162
+ const next = raw.replace(/^status:\s*(current|accepted)\s*$/m, "status: draft");
163
+ if (next === raw)
164
+ return false;
165
+ try {
166
+ writeFileSync(abs, next, "utf8");
167
+ }
168
+ catch {
169
+ return false;
170
+ }
171
+ return true;
172
+ }
173
+ export const demoteAutofilledBrand = {
174
+ id: "0005-demote-autofilled-brand",
175
+ introducedIn: "0.27.0",
176
+ describe: "Demote auto-generated brand (marked confirmed before 0.25.0) back to status: draft so machine-written voice/positioning/personas stops being injected every session — now catches the full co-generated cohort, not just the mechanical fallback",
177
+ class: "review",
178
+ detect(repoRoot) {
179
+ return autofilledConfirmed(repoRoot).length > 0;
180
+ },
181
+ apply(repoRoot) {
182
+ const docs = brandDocs(repoRoot);
183
+ const relToAbs = new Map(docs.map((d) => [d.rel, d.abs]));
184
+ const targets = autofilledConfirmed(repoRoot);
185
+ const demoted = [];
186
+ for (const rel of targets) {
187
+ const abs = relToAbs.get(rel);
188
+ if (abs !== undefined && demoteToDraft(abs))
189
+ demoted.push(rel);
190
+ }
191
+ return {
192
+ changed: demoted.length > 0,
193
+ detail: demoted.length > 0
194
+ ? `demoted ${demoted.length} auto-generated brand doc(s) to draft: ${demoted.join(", ")} — review and set status: current on any you actually wrote`
195
+ : "no auto-generated confirmed brand docs found",
196
+ };
197
+ },
198
+ };
199
+ //# sourceMappingURL=0005-demote-autofilled-brand.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"0005-demote-autofilled-brand.js","sourceRoot":"","sources":["../../../src/migrate/migrations/0005-demote-autofilled-brand.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * 0006 — prune junk Layer-A (sot-align) invariants from existing repos.
3
+ *
4
+ * The sot-align hook minted an "invariant" from almost any prose block —
5
+ * banners, separators, class/endpoint descriptions, test notes. The 0.23.0
6
+ * creation gate cut the worst of it, but a modal buried anywhere in a
7
+ * multi-line block still slipped through, so even post-gate repos accreted
8
+ * box-drawing-titled artifacts and test-fixture comments as "active
9
+ * invariants". 0.27.0 sharpens both the gate (skip test files) and this
10
+ * prune (statement-scoped shape + test-source + separator-title rejects).
11
+ *
12
+ * `detect`/`apply` wrap the same `pruneInvariants` surgical core the CLI
13
+ * uses: it touches ONLY `capture_source: layer-a-sot-align` invariants —
14
+ * archiving those captured from a test/fixture file, titled with a
15
+ * separator, or with no constraint shape in their statement — to
16
+ * `.cairn/ground/.archive/` (recoverable), rebuilding the ledger once at
17
+ * the end. Curated DEC/INV are never touched. For a full reset of the
18
+ * legacy corpus the operator can run `cairn invariants prune --all`.
19
+ *
20
+ * `review`-class: it archives committed ground state, so it surfaces for
21
+ * the operator and applies via `cairn migrate`. The sharpened gate ships
22
+ * in 0.27.0, so `introducedIn` advances to 0.27.0 — a repo that already
23
+ * ran the weaker 0.26.0 pass must re-evaluate; `detect()` carries
24
+ * correctness.
25
+ */
26
+ import type { Migration } from "../types.js";
27
+ export declare const pruneSotAlignInvariants: Migration;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * 0006 — prune junk Layer-A (sot-align) invariants from existing repos.
3
+ *
4
+ * The sot-align hook minted an "invariant" from almost any prose block —
5
+ * banners, separators, class/endpoint descriptions, test notes. The 0.23.0
6
+ * creation gate cut the worst of it, but a modal buried anywhere in a
7
+ * multi-line block still slipped through, so even post-gate repos accreted
8
+ * box-drawing-titled artifacts and test-fixture comments as "active
9
+ * invariants". 0.27.0 sharpens both the gate (skip test files) and this
10
+ * prune (statement-scoped shape + test-source + separator-title rejects).
11
+ *
12
+ * `detect`/`apply` wrap the same `pruneInvariants` surgical core the CLI
13
+ * uses: it touches ONLY `capture_source: layer-a-sot-align` invariants —
14
+ * archiving those captured from a test/fixture file, titled with a
15
+ * separator, or with no constraint shape in their statement — to
16
+ * `.cairn/ground/.archive/` (recoverable), rebuilding the ledger once at
17
+ * the end. Curated DEC/INV are never touched. For a full reset of the
18
+ * legacy corpus the operator can run `cairn invariants prune --all`.
19
+ *
20
+ * `review`-class: it archives committed ground state, so it surfaces for
21
+ * the operator and applies via `cairn migrate`. The sharpened gate ships
22
+ * in 0.27.0, so `introducedIn` advances to 0.27.0 — a repo that already
23
+ * ran the weaker 0.26.0 pass must re-evaluate; `detect()` carries
24
+ * correctness.
25
+ */
26
+ import { pruneInvariants } from "../../invariants/prune.js";
27
+ export const pruneSotAlignInvariants = {
28
+ id: "0006-prune-sot-align-invariants",
29
+ // 0.27.0: the surgical gate was sharpened (statement-scoped shape +
30
+ // test-source + separator-title rejects). A repo that already ran the
31
+ // weaker 0.26.0 pass must re-evaluate, so `introducedIn` advances to the
32
+ // ship version — `detect()` (a dry-run prune) stays the correctness floor.
33
+ introducedIn: "0.27.0",
34
+ describe: "Archive junk Layer-A (sot-align) invariants the creation gate would reject today — test/fixture captures, separator-titled artifacts, and entries with no rule in their statement; curated DEC/INV untouched",
35
+ class: "review",
36
+ detect(repoRoot) {
37
+ try {
38
+ return pruneInvariants({ repoRoot, dryRun: true }).pruned.length > 0;
39
+ }
40
+ catch {
41
+ return false;
42
+ }
43
+ },
44
+ apply(repoRoot) {
45
+ const result = pruneInvariants({ repoRoot });
46
+ const n = result.pruned.length;
47
+ return {
48
+ changed: n > 0,
49
+ detail: n > 0
50
+ ? `archived ${n} shapeless sot-align invariant(s) of ${result.sotAlignTotal} eligible (${result.kept} kept) → .cairn/ground/.archive/`
51
+ : "no junk sot-align invariants found",
52
+ };
53
+ },
54
+ };
55
+ //# sourceMappingURL=0006-prune-sot-align-invariants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"0006-prune-sot-align-invariants.js","sourceRoot":"","sources":["../../../src/migrate/migrations/0006-prune-sot-align-invariants.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAG5D,MAAM,CAAC,MAAM,uBAAuB,GAAc;IAChD,EAAE,EAAE,iCAAiC;IACrC,oEAAoE;IACpE,sEAAsE;IACtE,yEAAyE;IACzE,2EAA2E;IAC3E,YAAY,EAAE,QAAQ;IACtB,QAAQ,EACN,8MAA8M;IAChN,KAAK,EAAE,QAAQ;IACf,MAAM,CAAC,QAAgB;QACrB,IAAI,CAAC;YACH,OAAO,eAAe,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;QACvE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,KAAK,CAAC,QAAgB;QACpB,MAAM,MAAM,GAAG,eAAe,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC7C,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;QAC/B,OAAO;YACL,OAAO,EAAE,CAAC,GAAG,CAAC;YACd,MAAM,EACJ,CAAC,GAAG,CAAC;gBACH,CAAC,CAAC,YAAY,CAAC,wCAAwC,MAAM,CAAC,aAAa,cAAc,MAAM,CAAC,IAAI,kCAAkC;gBACtI,CAAC,CAAC,oCAAoC;SAC3C,CAAC;IACJ,CAAC;CACF,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * 0007 — collapse redundant nested `componentDirs`.
3
+ *
4
+ * The pre-0.22.4 component-layout detector could enumerate a directory AND
5
+ * its sub-directories (and even individual leaf component folders) as
6
+ * separate `componentDirs`. On a repo adopted in that era a workspace's
7
+ * `componentDirs` can carry dozens of entries where a directory is already
8
+ * covered by an ancestor that is also listed — e.g. both `a/b` and
9
+ * `a/b/c`, plus `a/b/c/Leaf`.
10
+ *
11
+ * The component walk recurses (`walkFs`) and collection dedups visited
12
+ * files (`collectComponents`'s `seen` set), so the redundant entries change
13
+ * nothing at runtime — they are pure config bloat. This migration removes
14
+ * any `componentDir` that is a descendant (or exact duplicate) of another
15
+ * `componentDir` in the same workspace, keeping only the shallowest
16
+ * ancestors. The collected component set is byte-identical afterward.
17
+ *
18
+ * `review`-class: it rewrites the operator's curated `config.yaml`, so it
19
+ * surfaces the count and applies via `cairn migrate` rather than silently
20
+ * rewriting their list. Ships in 0.26.0 → `introducedIn` 0.26.0; `detect()`
21
+ * carries correctness for any older pin.
22
+ */
23
+ import type { Migration } from "../types.js";
24
+ export declare const collapseComponentDirs: Migration;