@isaacriehm/cairn-core 0.22.6 → 0.23.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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/cites/expand.d.ts +75 -0
- package/dist/cites/expand.js +197 -0
- package/dist/cites/expand.js.map +1 -0
- package/dist/doctor/index.d.ts +10 -4
- package/dist/doctor/index.js +24 -11
- package/dist/doctor/index.js.map +1 -1
- package/dist/gc/completion-integrity.d.ts +0 -1
- package/dist/gc/completion-integrity.js +0 -6
- package/dist/gc/completion-integrity.js.map +1 -1
- package/dist/hooks/post-tool-use/index.d.ts +1 -1
- package/dist/hooks/post-tool-use/index.js +1 -1
- package/dist/hooks/post-tool-use/index.js.map +1 -1
- package/dist/hooks/post-tool-use/sot-align.js +52 -21
- package/dist/hooks/post-tool-use/sot-align.js.map +1 -1
- package/dist/hooks/sot-align-common.d.ts +13 -0
- package/dist/hooks/sot-align-common.js +69 -0
- package/dist/hooks/sot-align-common.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/init/claude-rule.d.ts +1 -0
- package/dist/init/claude-rule.js +1 -1
- package/dist/init/claude-rule.js.map +1 -1
- package/dist/init/source-comments/ingest.js +11 -8
- package/dist/init/source-comments/ingest.js.map +1 -1
- package/dist/invariants/prune.d.ts +50 -0
- package/dist/invariants/prune.js +113 -0
- package/dist/invariants/prune.js.map +1 -0
- package/dist/uninstall/index.d.ts +44 -0
- package/dist/uninstall/index.js +185 -0
- package/dist/uninstall/index.js.map +1 -0
- package/package.json +2 -2
- package/templates/.cairn/config/sensors.yaml +10 -3
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cairn invariants prune` — retire junk invariants the Layer A sot-align
|
|
3
|
+
* hook minted before the creation gate landed.
|
|
4
|
+
*
|
|
5
|
+
* Pre-gate, the runtime hook ran a Haiku "creation judge" on every prose
|
|
6
|
+
* block and over-labeled descriptions as `constraint`, so section banners,
|
|
7
|
+
* box-drawing separators, class/endpoint descriptions and test-fixture
|
|
8
|
+
* notes all became "active invariants". This sweep archives them.
|
|
9
|
+
*
|
|
10
|
+
* Scope is deliberately narrow: ONLY invariants stamped
|
|
11
|
+
* `capture_source: layer-a-sot-align` are eligible. Curated invariants
|
|
12
|
+
* (init, curator, manual `record`/`propose`) are never touched.
|
|
13
|
+
*
|
|
14
|
+
* surgical (default) — archive a sot-align invariant whose statement
|
|
15
|
+
* carries no constraint shape (no modal/marker).
|
|
16
|
+
* This mirrors the new creation gate: anything that
|
|
17
|
+
* couldn't be minted today gets retired.
|
|
18
|
+
* all — archive every sot-align invariant (full reset).
|
|
19
|
+
*
|
|
20
|
+
* Retirement reuses `archiveEntity` (move to `.cairn/ground/.archive/`,
|
|
21
|
+
* flip status, drop from the SoT cache) but defers the per-entity ledger
|
|
22
|
+
* rebuild so a 700-entity sweep rebuilds once instead of O(n²).
|
|
23
|
+
*/
|
|
24
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
import { archiveEntity, deleteSotCacheEntry, invariantsDir, parseFrontmatterRecord, readSotCache, writeInvariantsLedger, writeSotCache, } from "@isaacriehm/cairn-state";
|
|
27
|
+
import { hasConstraintShape } from "../hooks/sot-align-common.js";
|
|
28
|
+
/** The runtime hook's stamp — the only capture_source this sweep retires. */
|
|
29
|
+
const SOT_ALIGN_SOURCE = "layer-a-sot-align";
|
|
30
|
+
export function pruneInvariants(opts) {
|
|
31
|
+
const mode = opts.mode ?? "surgical";
|
|
32
|
+
const dryRun = opts.dryRun ?? false;
|
|
33
|
+
const dir = invariantsDir(opts.repoRoot);
|
|
34
|
+
const result = {
|
|
35
|
+
scanned: 0,
|
|
36
|
+
sotAlignTotal: 0,
|
|
37
|
+
pruned: [],
|
|
38
|
+
kept: 0,
|
|
39
|
+
dryRun,
|
|
40
|
+
};
|
|
41
|
+
if (!existsSync(dir))
|
|
42
|
+
return result;
|
|
43
|
+
const files = readdirSync(dir).filter((n) => n.endsWith(".md"));
|
|
44
|
+
let archivedAny = false;
|
|
45
|
+
for (const file of files) {
|
|
46
|
+
result.scanned += 1;
|
|
47
|
+
const id = file.replace(/\.md$/, "");
|
|
48
|
+
let fm;
|
|
49
|
+
let body;
|
|
50
|
+
try {
|
|
51
|
+
({ fm, body } = parseFrontmatterRecord(readFileSync(join(dir, file), "utf8")));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
continue; // unreadable / malformed — leave it for the doctor to flag
|
|
55
|
+
}
|
|
56
|
+
const capture = typeof fm["capture_source"] === "string" ? fm["capture_source"] : "";
|
|
57
|
+
if (capture !== SOT_ALIGN_SOURCE)
|
|
58
|
+
continue; // never touch curated invariants
|
|
59
|
+
result.sotAlignTotal += 1;
|
|
60
|
+
const title = typeof fm["title"] === "string" ? fm["title"] : "";
|
|
61
|
+
let prune = false;
|
|
62
|
+
let reason = "";
|
|
63
|
+
if (mode === "all") {
|
|
64
|
+
prune = true;
|
|
65
|
+
reason = "full reset of sot-align invariants";
|
|
66
|
+
}
|
|
67
|
+
else if (!hasConstraintShape(`${title}\n${body}`)) {
|
|
68
|
+
prune = true;
|
|
69
|
+
reason = "no constraint shape — not a real invariant under the creation gate";
|
|
70
|
+
}
|
|
71
|
+
if (!prune) {
|
|
72
|
+
result.kept += 1;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (!dryRun) {
|
|
76
|
+
const r = archiveEntity({
|
|
77
|
+
repoRoot: opts.repoRoot,
|
|
78
|
+
id,
|
|
79
|
+
reason: `cairn invariants prune — ${reason}`,
|
|
80
|
+
deferDerivedRebuild: true,
|
|
81
|
+
...(opts.now !== undefined ? { now: opts.now } : {}),
|
|
82
|
+
});
|
|
83
|
+
if (!r.ok) {
|
|
84
|
+
// Couldn't move it (already gone / unreadable) — don't claim a prune.
|
|
85
|
+
result.kept += 1;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
archivedAny = true;
|
|
89
|
+
}
|
|
90
|
+
result.pruned.push({ id, title: title.slice(0, 100), reason });
|
|
91
|
+
}
|
|
92
|
+
// One derived-state rebuild for the whole batch (archiveEntity deferred it).
|
|
93
|
+
if (archivedAny && !dryRun) {
|
|
94
|
+
const prunedIds = new Set(result.pruned.map((p) => p.id));
|
|
95
|
+
try {
|
|
96
|
+
let cache = readSotCache(opts.repoRoot);
|
|
97
|
+
for (const id of prunedIds)
|
|
98
|
+
cache = deleteSotCacheEntry(cache, id);
|
|
99
|
+
writeSotCache(opts.repoRoot, cache);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
/* best-effort — cache self-heals on next sweep */
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
writeInvariantsLedger({ repoRoot: opts.repoRoot });
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
/* best-effort — `cairn fix` rebuilds it */
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=prune.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prune.js","sourceRoot":"","sources":["../../src/invariants/prune.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cairn uninstall` — remove Cairn from a repo, cleanly.
|
|
3
|
+
*
|
|
4
|
+
* The inverse of adoption (`cairn init`) + per-clone bootstrap
|
|
5
|
+
* (`cairn join`). The install footprint is:
|
|
6
|
+
*
|
|
7
|
+
* - `.cairn/` the ground state + config + hooks
|
|
8
|
+
* - `.claude/rules/cairn.md` the plugin-absent onboarding notice
|
|
9
|
+
* - an `@.claude/rules/cairn.md` import block in CLAUDE.md / AGENTS.md
|
|
10
|
+
* - `git config core.hooksPath` pointed at `.cairn/git-hooks`
|
|
11
|
+
* - in-source `// §DEC-/§INV-` citations from sot-align strip-replace
|
|
12
|
+
*
|
|
13
|
+
* Uninstall reverses these in dependency order. Cites are expanded FIRST
|
|
14
|
+
* (while `.cairn/ground/` still exists to resolve them), so removing
|
|
15
|
+
* `.cairn/` doesn't leave dangling `§` references — the source ends up
|
|
16
|
+
* self-documenting. `core.hooksPath` is only unset when it is Cairn's own
|
|
17
|
+
* value (a foreign husky/lefthook path is never clobbered).
|
|
18
|
+
*
|
|
19
|
+
* Every step reports a status; nothing throws on a recoverable issue. The
|
|
20
|
+
* machine-level Claude Code plugin (`/plugin install`) is user-scoped, not
|
|
21
|
+
* repo-scoped — uninstall can't remove it and says so.
|
|
22
|
+
*/
|
|
23
|
+
export type UninstallStepStatus = "ok" | "skipped" | "warn";
|
|
24
|
+
export interface UninstallStep {
|
|
25
|
+
step: string;
|
|
26
|
+
status: UninstallStepStatus;
|
|
27
|
+
detail: string;
|
|
28
|
+
}
|
|
29
|
+
export interface UninstallOptions {
|
|
30
|
+
repoRoot: string;
|
|
31
|
+
/** Expand DEC/INV cites to inline comments first. Default true. */
|
|
32
|
+
expandCites?: boolean;
|
|
33
|
+
/** Report what would happen; change nothing. */
|
|
34
|
+
dryRun?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface UninstallResult {
|
|
37
|
+
repoRoot: string;
|
|
38
|
+
steps: UninstallStep[];
|
|
39
|
+
/** True when `.cairn/` was (or would be) removed. */
|
|
40
|
+
removed: boolean;
|
|
41
|
+
}
|
|
42
|
+
export declare function uninstallCairn(opts: UninstallOptions): UninstallResult;
|
|
43
|
+
/** Remove the marker + import lines, then collapse the blank-line run they left. */
|
|
44
|
+
export declare function stripImportBlock(content: string): string;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cairn uninstall` — remove Cairn from a repo, cleanly.
|
|
3
|
+
*
|
|
4
|
+
* The inverse of adoption (`cairn init`) + per-clone bootstrap
|
|
5
|
+
* (`cairn join`). The install footprint is:
|
|
6
|
+
*
|
|
7
|
+
* - `.cairn/` the ground state + config + hooks
|
|
8
|
+
* - `.claude/rules/cairn.md` the plugin-absent onboarding notice
|
|
9
|
+
* - an `@.claude/rules/cairn.md` import block in CLAUDE.md / AGENTS.md
|
|
10
|
+
* - `git config core.hooksPath` pointed at `.cairn/git-hooks`
|
|
11
|
+
* - in-source `// §DEC-/§INV-` citations from sot-align strip-replace
|
|
12
|
+
*
|
|
13
|
+
* Uninstall reverses these in dependency order. Cites are expanded FIRST
|
|
14
|
+
* (while `.cairn/ground/` still exists to resolve them), so removing
|
|
15
|
+
* `.cairn/` doesn't leave dangling `§` references — the source ends up
|
|
16
|
+
* self-documenting. `core.hooksPath` is only unset when it is Cairn's own
|
|
17
|
+
* value (a foreign husky/lefthook path is never clobbered).
|
|
18
|
+
*
|
|
19
|
+
* Every step reports a status; nothing throws on a recoverable issue. The
|
|
20
|
+
* machine-level Claude Code plugin (`/plugin install`) is user-scoped, not
|
|
21
|
+
* repo-scoped — uninstall can't remove it and says so.
|
|
22
|
+
*/
|
|
23
|
+
import { execFileSync } from "node:child_process";
|
|
24
|
+
import { existsSync, readFileSync, readdirSync, rmSync, writeFileSync, } from "node:fs";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
import { cairnDir, COMMITTED_HOOKS_PATH } from "@isaacriehm/cairn-state";
|
|
27
|
+
import { expandCitesInRepo } from "../cites/expand.js";
|
|
28
|
+
import { CAIRN_RULE_IMPORT, IMPORT_MARKER } from "../init/claude-rule.js";
|
|
29
|
+
export function uninstallCairn(opts) {
|
|
30
|
+
const root = opts.repoRoot;
|
|
31
|
+
const dryRun = opts.dryRun ?? false;
|
|
32
|
+
const doExpand = opts.expandCites ?? true;
|
|
33
|
+
const steps = [];
|
|
34
|
+
// 1. Expand cites FIRST — needs `.cairn/ground/` to resolve bodies.
|
|
35
|
+
if (doExpand) {
|
|
36
|
+
const r = expandCitesInRepo({ repoRoot: root, dryRun });
|
|
37
|
+
const bits = [`${r.expanded} citation(s) inlined across ${r.filesChanged} file(s)`];
|
|
38
|
+
if (r.danglingSkipped > 0)
|
|
39
|
+
bits.push(`${r.danglingSkipped} dangling left in place`);
|
|
40
|
+
if (r.inlineSkipped > 0)
|
|
41
|
+
bits.push(`${r.inlineSkipped} inline left in place`);
|
|
42
|
+
steps.push({ step: "expand-cites", status: "ok", detail: bits.join("; ") });
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
steps.push({
|
|
46
|
+
step: "expand-cites",
|
|
47
|
+
status: "skipped",
|
|
48
|
+
detail: "--keep-cites: in-source §DEC-/§INV- tokens will dangle after removal",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
// 2. Remove the `@.claude/rules/cairn.md` import block from the memory file.
|
|
52
|
+
steps.push(unwireRuleImport(root, dryRun));
|
|
53
|
+
// 3. Remove `.claude/rules/cairn.md` and prune the dirs if they empty out.
|
|
54
|
+
steps.push(removeRuleFile(root, dryRun));
|
|
55
|
+
// 4. Unset `core.hooksPath` — only when it is Cairn's own value.
|
|
56
|
+
steps.push(unsetHooksPath(root, dryRun));
|
|
57
|
+
// 5. Remove `.cairn/`.
|
|
58
|
+
const cairnPath = cairnDir(root);
|
|
59
|
+
let removed = false;
|
|
60
|
+
if (existsSync(cairnPath)) {
|
|
61
|
+
removed = true;
|
|
62
|
+
if (!dryRun)
|
|
63
|
+
rmSync(cairnPath, { recursive: true, force: true });
|
|
64
|
+
steps.push({ step: "remove-cairn-dir", status: "ok", detail: `${dryRun ? "would remove" : "removed"} .cairn/` });
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
steps.push({ step: "remove-cairn-dir", status: "skipped", detail: ".cairn/ not present" });
|
|
68
|
+
}
|
|
69
|
+
// 6. Advisory — the machine-level plugin is user-scoped.
|
|
70
|
+
steps.push({
|
|
71
|
+
step: "plugin",
|
|
72
|
+
status: "skipped",
|
|
73
|
+
detail: "Claude Code plugin is machine-scoped — remove with `/plugin uninstall cairn` if no other repo uses it",
|
|
74
|
+
});
|
|
75
|
+
return { repoRoot: root, steps, removed };
|
|
76
|
+
}
|
|
77
|
+
/* -------------------------------------------------------------------------- */
|
|
78
|
+
function unwireRuleImport(root, dryRun) {
|
|
79
|
+
for (const rel of ["CLAUDE.md", "AGENTS.md"]) {
|
|
80
|
+
const abs = join(root, rel);
|
|
81
|
+
if (!existsSync(abs))
|
|
82
|
+
continue;
|
|
83
|
+
let content;
|
|
84
|
+
try {
|
|
85
|
+
content = readFileSync(abs, "utf8");
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (!content.includes(CAIRN_RULE_IMPORT))
|
|
91
|
+
continue;
|
|
92
|
+
const next = stripImportBlock(content);
|
|
93
|
+
if (!dryRun)
|
|
94
|
+
writeFileSync(abs, next, "utf8");
|
|
95
|
+
return {
|
|
96
|
+
step: "unwire-import",
|
|
97
|
+
status: "ok",
|
|
98
|
+
detail: `${dryRun ? "would remove" : "removed"} the cairn rule import from ${rel}`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return { step: "unwire-import", status: "skipped", detail: "no cairn rule import found in CLAUDE.md / AGENTS.md" };
|
|
102
|
+
}
|
|
103
|
+
/** Remove the marker + import lines, then collapse the blank-line run they left. */
|
|
104
|
+
export function stripImportBlock(content) {
|
|
105
|
+
const lines = content.split(/\r?\n/);
|
|
106
|
+
const kept = lines.filter((l) => l.trim() !== IMPORT_MARKER && l.trim() !== CAIRN_RULE_IMPORT);
|
|
107
|
+
// Collapse 3+ consecutive blank lines (the import block was fenced by blanks)
|
|
108
|
+
// down to a single blank, then normalize the trailing newline.
|
|
109
|
+
const collapsed = [];
|
|
110
|
+
let blanks = 0;
|
|
111
|
+
for (const l of kept) {
|
|
112
|
+
if (l.trim() === "") {
|
|
113
|
+
blanks += 1;
|
|
114
|
+
if (blanks >= 2)
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
blanks = 0;
|
|
119
|
+
}
|
|
120
|
+
collapsed.push(l);
|
|
121
|
+
}
|
|
122
|
+
return `${collapsed.join("\n").replace(/\s*$/, "")}\n`;
|
|
123
|
+
}
|
|
124
|
+
function removeRuleFile(root, dryRun) {
|
|
125
|
+
const ruleAbs = join(root, ".claude", "rules", "cairn.md");
|
|
126
|
+
if (!existsSync(ruleAbs)) {
|
|
127
|
+
return { step: "remove-rule", status: "skipped", detail: ".claude/rules/cairn.md not present" };
|
|
128
|
+
}
|
|
129
|
+
if (!dryRun) {
|
|
130
|
+
rmSync(ruleAbs, { force: true });
|
|
131
|
+
pruneIfEmpty(join(root, ".claude", "rules"));
|
|
132
|
+
pruneIfEmpty(join(root, ".claude"));
|
|
133
|
+
}
|
|
134
|
+
return { step: "remove-rule", status: "ok", detail: `${dryRun ? "would remove" : "removed"} .claude/rules/cairn.md` };
|
|
135
|
+
}
|
|
136
|
+
function pruneIfEmpty(dir) {
|
|
137
|
+
try {
|
|
138
|
+
if (existsSync(dir) && readdirSync(dir).length === 0)
|
|
139
|
+
rmSync(dir, { recursive: true, force: true });
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
/* best-effort */
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function unsetHooksPath(root, dryRun) {
|
|
146
|
+
if (!existsSync(join(root, ".git"))) {
|
|
147
|
+
return { step: "unset-hooks", status: "skipped", detail: "not a git repo" };
|
|
148
|
+
}
|
|
149
|
+
const current = readGitConfig(root, "core.hooksPath");
|
|
150
|
+
if (current === null) {
|
|
151
|
+
return { step: "unset-hooks", status: "skipped", detail: "core.hooksPath not set" };
|
|
152
|
+
}
|
|
153
|
+
const ours = current === COMMITTED_HOOKS_PATH || current === cairnDir(root, "git-hooks");
|
|
154
|
+
if (!ours) {
|
|
155
|
+
return {
|
|
156
|
+
step: "unset-hooks",
|
|
157
|
+
status: "warn",
|
|
158
|
+
detail: `core.hooksPath is '${current}' (not Cairn's) — left untouched`,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
if (!dryRun) {
|
|
162
|
+
try {
|
|
163
|
+
execFileSync("git", ["config", "--unset", "core.hooksPath"], { cwd: root, stdio: "ignore" });
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return { step: "unset-hooks", status: "warn", detail: "failed to unset core.hooksPath" };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return { step: "unset-hooks", status: "ok", detail: `${dryRun ? "would unset" : "unset"} core.hooksPath` };
|
|
170
|
+
}
|
|
171
|
+
function readGitConfig(root, key) {
|
|
172
|
+
try {
|
|
173
|
+
const out = execFileSync("git", ["config", "--get", key], {
|
|
174
|
+
cwd: root,
|
|
175
|
+
encoding: "utf8",
|
|
176
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
177
|
+
});
|
|
178
|
+
const trimmed = out.trim();
|
|
179
|
+
return trimmed.length === 0 ? null : trimmed;
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/uninstall/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@isaacriehm/cairn-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "Cairn core — state + context layer. Curated `.cairn/ground/` (decisions, §INV invariants, canonical-map, brand, quality-grades), MCP server, init wizard, hook runners, sensors, GC drift sweep.",
|
|
5
5
|
"author": "Isaac Riehm",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"simple-git": "^3.36.0",
|
|
40
40
|
"yaml": "^2.9.0",
|
|
41
41
|
"zod": "^4.4.3",
|
|
42
|
-
"@isaacriehm/cairn-state": "0.
|
|
42
|
+
"@isaacriehm/cairn-state": "0.23.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/cli-progress": "^3.11.6",
|
|
@@ -36,16 +36,23 @@ sensors:
|
|
|
36
36
|
- every_run
|
|
37
37
|
fail_severity: hard
|
|
38
38
|
|
|
39
|
-
# ── §INV invariant sensors
|
|
39
|
+
# ── §INV invariant sensors ───────────────────────────────────────────────
|
|
40
|
+
# PLANNED — not yet wired. No runner reads this entry: the pre-commit sweep
|
|
41
|
+
# executes only stub-pattern-catalog + decision-assertions. Invariants are
|
|
42
|
+
# curated (§INV pillar) but enforcement (an assertions[] array on each
|
|
43
|
+
# invariant routed through the assertion evaluator, like decisions) is a
|
|
44
|
+
# gap, not a live gate. Kept `soft` so it reads as a roadmap item, NOT a
|
|
45
|
+
# hard gate that silently passes because nothing runs it.
|
|
40
46
|
- id: invariant-suite
|
|
41
47
|
layer: invariants
|
|
42
48
|
kind: invariant_runner
|
|
43
|
-
|
|
49
|
+
status: planned
|
|
50
|
+
description: "PLANNED (not wired): would run every active §INV invariant's linked assertion. Today no runner reads this; invariant enforcement is unimplemented."
|
|
44
51
|
triggers:
|
|
45
52
|
- every_run
|
|
46
53
|
sources:
|
|
47
54
|
- .cairn/ground/invariants/
|
|
48
|
-
fail_severity:
|
|
55
|
+
fail_severity: soft
|
|
49
56
|
|
|
50
57
|
# ── Frontmatter freshness (nightly GC pass) ──────────────────────────────
|
|
51
58
|
- id: frontmatter-freshness
|