@isaacriehm/cairn-state 0.22.5
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/LICENSE +21 -0
- package/README.md +11 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/alignment-pending.d.ts +28 -0
- package/dist/alignment-pending.js +83 -0
- package/dist/alignment-pending.js.map +1 -0
- package/dist/anchor-map.d.ts +14 -0
- package/dist/anchor-map.js +56 -0
- package/dist/anchor-map.js.map +1 -0
- package/dist/archive.d.ts +48 -0
- package/dist/archive.js +96 -0
- package/dist/archive.js.map +1 -0
- package/dist/cache.d.ts +48 -0
- package/dist/cache.js +241 -0
- package/dist/cache.js.map +1 -0
- package/dist/component-registry.d.ts +93 -0
- package/dist/component-registry.js +0 -0
- package/dist/component-registry.js.map +1 -0
- package/dist/components.d.ts +192 -0
- package/dist/components.js +603 -0
- package/dist/components.js.map +1 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +26 -0
- package/dist/config.js.map +1 -0
- package/dist/drift.d.ts +8 -0
- package/dist/drift.js +23 -0
- package/dist/drift.js.map +1 -0
- package/dist/file-candidates-map.d.ts +23 -0
- package/dist/file-candidates-map.js +76 -0
- package/dist/file-candidates-map.js.map +1 -0
- package/dist/frontmatter.d.ts +32 -0
- package/dist/frontmatter.js +77 -0
- package/dist/frontmatter.js.map +1 -0
- package/dist/fs.d.ts +36 -0
- package/dist/fs.js +47 -0
- package/dist/fs.js.map +1 -0
- package/dist/glob.d.ts +10 -0
- package/dist/glob.js +46 -0
- package/dist/glob.js.map +1 -0
- package/dist/home.d.ts +69 -0
- package/dist/home.js +168 -0
- package/dist/home.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/languages.d.ts +113 -0
- package/dist/languages.js +512 -0
- package/dist/languages.js.map +1 -0
- package/dist/ledgers.d.ts +14 -0
- package/dist/ledgers.js +105 -0
- package/dist/ledgers.js.map +1 -0
- package/dist/logger.d.ts +13 -0
- package/dist/logger.js +17 -0
- package/dist/logger.js.map +1 -0
- package/dist/manifest.d.ts +10 -0
- package/dist/manifest.js +84 -0
- package/dist/manifest.js.map +1 -0
- package/dist/missions.d.ts +119 -0
- package/dist/missions.js +414 -0
- package/dist/missions.js.map +1 -0
- package/dist/paths.d.ts +117 -0
- package/dist/paths.js +241 -0
- package/dist/paths.js.map +1 -0
- package/dist/quality-grades.d.ts +11 -0
- package/dist/quality-grades.js +100 -0
- package/dist/quality-grades.js.map +1 -0
- package/dist/rejected.d.ts +42 -0
- package/dist/rejected.js +100 -0
- package/dist/rejected.js.map +1 -0
- package/dist/schemas.d.ts +789 -0
- package/dist/schemas.js +506 -0
- package/dist/schemas.js.map +1 -0
- package/dist/scope-index.d.ts +96 -0
- package/dist/scope-index.js +299 -0
- package/dist/scope-index.js.map +1 -0
- package/dist/slug.d.ts +81 -0
- package/dist/slug.js +138 -0
- package/dist/slug.js.map +1 -0
- package/dist/sot-bindings.d.ts +14 -0
- package/dist/sot-bindings.js +79 -0
- package/dist/sot-bindings.js.map +1 -0
- package/dist/sot-cache.d.ts +18 -0
- package/dist/sot-cache.js +62 -0
- package/dist/sot-cache.js.map +1 -0
- package/dist/text.d.ts +27 -0
- package/dist/text.js +63 -0
- package/dist/text.js.map +1 -0
- package/dist/topic-index.d.ts +27 -0
- package/dist/topic-index.js +82 -0
- package/dist/topic-index.js.map +1 -0
- package/dist/walk.d.ts +7 -0
- package/dist/walk.js +34 -0
- package/dist/walk.js.map +1 -0
- package/package.json +35 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { writeFileSafe } from "./fs.js";
|
|
3
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
4
|
+
import { getLogger } from "./logger.js";
|
|
5
|
+
import { sotCachePath } from "./paths.js";
|
|
6
|
+
import { SotCache } from "./schemas.js";
|
|
7
|
+
const log = getLogger();
|
|
8
|
+
/**
|
|
9
|
+
* Sot-cache holds pre-tokenized DEC body content for the Layer A Jaccard
|
|
10
|
+
* pre-filter. Rebuilt at SessionStart, incremental on PostToolUse Write
|
|
11
|
+
* events that touch a DEC body or sot-path file. Mtime-keyed so a stale
|
|
12
|
+
* entry is detected without re-tokenizing every body on every Write.
|
|
13
|
+
*/
|
|
14
|
+
export function emptySotCache() {
|
|
15
|
+
return { version: 1, generated: new Date().toISOString(), entries: {} };
|
|
16
|
+
}
|
|
17
|
+
export function readSotCache(repoRoot) {
|
|
18
|
+
const path = sotCachePath(repoRoot);
|
|
19
|
+
if (!existsSync(path))
|
|
20
|
+
return emptySotCache();
|
|
21
|
+
try {
|
|
22
|
+
const raw = readFileSync(path, "utf8");
|
|
23
|
+
const parsed = SotCache.safeParse(parseYaml(raw));
|
|
24
|
+
if (!parsed.success) {
|
|
25
|
+
log.warn({ path, error: parsed.error.message }, "sot-cache invalid; treating as empty");
|
|
26
|
+
return emptySotCache();
|
|
27
|
+
}
|
|
28
|
+
return parsed.data;
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
log.warn({ path, err }, "sot-cache read failed; treating as empty");
|
|
32
|
+
return emptySotCache();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function writeSotCache(repoRoot, cache) {
|
|
36
|
+
const path = sotCachePath(repoRoot);
|
|
37
|
+
const next = { ...cache, generated: new Date().toISOString() };
|
|
38
|
+
writeFileSafe(path, stringifyYaml(next));
|
|
39
|
+
log.debug({ path, entries: Object.keys(next.entries).length }, "wrote sot-cache");
|
|
40
|
+
return path;
|
|
41
|
+
}
|
|
42
|
+
export function setSotCacheEntry(cache, decId, entry) {
|
|
43
|
+
return { ...cache, entries: { ...cache.entries, [decId]: entry } };
|
|
44
|
+
}
|
|
45
|
+
export function getSotCacheEntry(cache, decId) {
|
|
46
|
+
return cache.entries[decId] ?? null;
|
|
47
|
+
}
|
|
48
|
+
export function deleteSotCacheEntry(cache, decId) {
|
|
49
|
+
if (cache.entries[decId] === undefined)
|
|
50
|
+
return cache;
|
|
51
|
+
const entries = { ...cache.entries };
|
|
52
|
+
delete entries[decId];
|
|
53
|
+
return { ...cache, entries };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Iterate all entries. Layer A's pre-filter pass calls this on each
|
|
57
|
+
* Write to compute Jaccard against every cached DEC body.
|
|
58
|
+
*/
|
|
59
|
+
export function sotCacheEntries(cache) {
|
|
60
|
+
return Object.values(cache.entries);
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=sot-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sot-cache.js","sourceRoot":"","sources":["../src/sot-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,MAAM,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAsB,MAAM,cAAc,CAAC;AAE5D,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC;AAExB;;;;;GAKG;AAEH,MAAM,UAAU,aAAa;IAC3B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AAC1E,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,QAAgB;IAC3C,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACpC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,aAAa,EAAE,CAAC;IAC9C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACvC,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;QAClD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,sCAAsC,CAAC,CAAC;YACxF,OAAO,aAAa,EAAE,CAAC;QACzB,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,0CAA0C,CAAC,CAAC;QACpE,OAAO,aAAa,EAAE,CAAC;IACzB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,QAAgB,EAAE,KAAe;IAC7D,MAAM,IAAI,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACpC,MAAM,IAAI,GAAa,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;IACzE,aAAa,CAAC,IAAI,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;IACzC,GAAG,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,iBAAiB,CAAC,CAAC;IAClF,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAe,EAAE,KAAa,EAAE,KAAoB;IACnF,OAAO,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAe,EAAE,KAAa;IAC7D,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAe,EAAE,KAAa;IAChE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACrD,MAAM,OAAO,GAAG,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;IACrC,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC;IACtB,OAAO,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,CAAC;AAC/B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,KAAe;IAC7C,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;AACtC,CAAC"}
|
package/dist/text.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get the 1-based line number for a given character index in a text string.
|
|
3
|
+
*/
|
|
4
|
+
export declare function lineOf(text: string, charIndex: number): number;
|
|
5
|
+
/**
|
|
6
|
+
* Escape a string for literal use inside a `RegExp`. The single canonical
|
|
7
|
+
* implementation — `components.ts`, the assertion engine, and the audit all
|
|
8
|
+
* route through this instead of re-spelling the character class.
|
|
9
|
+
*/
|
|
10
|
+
export declare function escapeRegExp(s: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Split a comma-separated tag value (`@aliases`, `@uses`, …) into trimmed,
|
|
13
|
+
* non-empty parts. Replaces the `.split(",").map(trim).filter(Boolean)`
|
|
14
|
+
* triple that was repeated at every tag-list read site.
|
|
15
|
+
*/
|
|
16
|
+
export declare function splitCsv(value: string | undefined | null): string[];
|
|
17
|
+
/** True when `name` is PascalCase by the {@link PASCAL_CASE_RE} signal. */
|
|
18
|
+
export declare function isPascalCase(name: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* The `@cairn` registry-header signal: `@cairn` then whitespace then an
|
|
21
|
+
* identifier start. Single source of truth — deliberately disjoint from the
|
|
22
|
+
* colon-form `@cairn:decision` / `@cairn:rule` SoT markers (those can never
|
|
23
|
+
* be whitespace-then-identifier), so a marker is never misread as a header.
|
|
24
|
+
*/
|
|
25
|
+
export declare const HEADER_SIGNAL_RE: RegExp;
|
|
26
|
+
/** Strip a file's extension, returning the basename stem (`Button.vue` → `Button`). */
|
|
27
|
+
export declare function stemOf(basename: string): string;
|
package/dist/text.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get the 1-based line number for a given character index in a text string.
|
|
3
|
+
*/
|
|
4
|
+
export function lineOf(text, charIndex) {
|
|
5
|
+
let line = 1;
|
|
6
|
+
for (let i = 0; i < charIndex && i < text.length; i++) {
|
|
7
|
+
if (text.charCodeAt(i) === 10 /* \n */) {
|
|
8
|
+
line += 1;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return line;
|
|
12
|
+
}
|
|
13
|
+
/* -------------------------------------------------------------------------- */
|
|
14
|
+
/* Shared regex + string primitives */
|
|
15
|
+
/* */
|
|
16
|
+
/* These collapse identifiers that were re-derived inline across the */
|
|
17
|
+
/* component store, audit, sensors, and assertion engine. One definition, */
|
|
18
|
+
/* imported everywhere — never re-spell a regex literal in a call site. */
|
|
19
|
+
/* -------------------------------------------------------------------------- */
|
|
20
|
+
/**
|
|
21
|
+
* Escape a string for literal use inside a `RegExp`. The single canonical
|
|
22
|
+
* implementation — `components.ts`, the assertion engine, and the audit all
|
|
23
|
+
* route through this instead of re-spelling the character class.
|
|
24
|
+
*/
|
|
25
|
+
export function escapeRegExp(s) {
|
|
26
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Split a comma-separated tag value (`@aliases`, `@uses`, …) into trimmed,
|
|
30
|
+
* non-empty parts. Replaces the `.split(",").map(trim).filter(Boolean)`
|
|
31
|
+
* triple that was repeated at every tag-list read site.
|
|
32
|
+
*/
|
|
33
|
+
export function splitCsv(value) {
|
|
34
|
+
return (value ?? "")
|
|
35
|
+
.split(",")
|
|
36
|
+
.map((s) => s.trim())
|
|
37
|
+
.filter(Boolean);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* PascalCase signal: a leading uppercase letter followed by a lowercase one.
|
|
41
|
+
* Deliberately excludes SCREAMING_CASE constants (`FEATURED_TABS`) and bare
|
|
42
|
+
* single-letter / acronym idents. The component convention for a unit name.
|
|
43
|
+
*/
|
|
44
|
+
const PASCAL_CASE_RE = /^[A-Z][a-z]/;
|
|
45
|
+
/** True when `name` is PascalCase by the {@link PASCAL_CASE_RE} signal. */
|
|
46
|
+
export function isPascalCase(name) {
|
|
47
|
+
return PASCAL_CASE_RE.test(name);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The `@cairn` registry-header signal: `@cairn` then whitespace then an
|
|
51
|
+
* identifier start. Single source of truth — deliberately disjoint from the
|
|
52
|
+
* colon-form `@cairn:decision` / `@cairn:rule` SoT markers (those can never
|
|
53
|
+
* be whitespace-then-identifier), so a marker is never misread as a header.
|
|
54
|
+
*/
|
|
55
|
+
export const HEADER_SIGNAL_RE = /@cairn[ \t]+[A-Za-z_$]/;
|
|
56
|
+
/** Strip a file's extension, returning the basename stem (`Button.vue` → `Button`). */
|
|
57
|
+
export function stemOf(basename) {
|
|
58
|
+
const slash = Math.max(basename.lastIndexOf("/"), basename.lastIndexOf("\\"));
|
|
59
|
+
const base = slash === -1 ? basename : basename.slice(slash + 1);
|
|
60
|
+
const dot = base.indexOf(".");
|
|
61
|
+
return dot === -1 ? base : base.slice(0, dot);
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=text.js.map
|
package/dist/text.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,MAAM,CAAC,IAAY,EAAE,SAAiB;IACpD,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtD,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,CAAC;YACvC,IAAI,IAAI,CAAC,CAAC;QACZ,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAEhF;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,CAAS;IACpC,OAAO,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AAClD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAgC;IACvD,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;SACjB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC;AAED;;;;GAIG;AACH,MAAM,cAAc,GAAG,aAAa,CAAC;AAErC,2EAA2E;AAC3E,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,wBAAwB,CAAC;AAEzD,uFAAuF;AACvF,MAAM,UAAU,MAAM,CAAC,QAAgB;IACrC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9E,MAAM,IAAI,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IACjE,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC9B,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { TopicIndex, type TopicIndexEntry } from "./schemas.js";
|
|
2
|
+
/**
|
|
3
|
+
* Topic-index is the ground-state file that maps content-fingerprint
|
|
4
|
+
* slugs to the DECs they belong to. Phase 5b builds it before any
|
|
5
|
+
* extractor runs so phases 6 / 7b / 7c can dedup-by-topic instead of
|
|
6
|
+
* emitting one DEC per source. Layer A's PostToolUse hook reads it on
|
|
7
|
+
* every Write to know whether a freshly typed prose block is the first
|
|
8
|
+
* sighting of its content or a repeat of an existing topic.
|
|
9
|
+
*/
|
|
10
|
+
export declare function emptyTopicIndex(): TopicIndex;
|
|
11
|
+
export declare function readTopicIndex(repoRoot: string): TopicIndex;
|
|
12
|
+
export declare function writeTopicIndex(repoRoot: string, index: TopicIndex): string;
|
|
13
|
+
/**
|
|
14
|
+
* Insert or replace a topic entry. Returns the updated index.
|
|
15
|
+
*/
|
|
16
|
+
export declare function setTopic(index: TopicIndex, slug: string, entry: TopicIndexEntry): TopicIndex;
|
|
17
|
+
/**
|
|
18
|
+
* Look up a topic by slug. Returns null if absent.
|
|
19
|
+
*/
|
|
20
|
+
export declare function getTopic(index: TopicIndex, slug: string): TopicIndexEntry | null;
|
|
21
|
+
/**
|
|
22
|
+
* Clear `dec_id` from any topic entry that references this DEC. Used
|
|
23
|
+
* by `cairn attention undo` for tier3-creation reversal so the topic
|
|
24
|
+
* stays in the index (next phase 5b walk can re-emit the topic) but
|
|
25
|
+
* no longer points at the now-deleted DEC.
|
|
26
|
+
*/
|
|
27
|
+
export declare function clearDecFromTopicIndex(index: TopicIndex, decId: string): TopicIndex;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { writeFileSafe } from "./fs.js";
|
|
3
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
4
|
+
import { getLogger } from "./logger.js";
|
|
5
|
+
import { topicIndexPath } from "./paths.js";
|
|
6
|
+
import { TopicIndex } from "./schemas.js";
|
|
7
|
+
const log = getLogger();
|
|
8
|
+
/**
|
|
9
|
+
* Topic-index is the ground-state file that maps content-fingerprint
|
|
10
|
+
* slugs to the DECs they belong to. Phase 5b builds it before any
|
|
11
|
+
* extractor runs so phases 6 / 7b / 7c can dedup-by-topic instead of
|
|
12
|
+
* emitting one DEC per source. Layer A's PostToolUse hook reads it on
|
|
13
|
+
* every Write to know whether a freshly typed prose block is the first
|
|
14
|
+
* sighting of its content or a repeat of an existing topic.
|
|
15
|
+
*/
|
|
16
|
+
export function emptyTopicIndex() {
|
|
17
|
+
return { version: 1, generated: new Date().toISOString(), topics: {} };
|
|
18
|
+
}
|
|
19
|
+
export function readTopicIndex(repoRoot) {
|
|
20
|
+
const path = topicIndexPath(repoRoot);
|
|
21
|
+
if (!existsSync(path))
|
|
22
|
+
return emptyTopicIndex();
|
|
23
|
+
try {
|
|
24
|
+
const raw = readFileSync(path, "utf8");
|
|
25
|
+
const parsed = TopicIndex.safeParse(parseYaml(raw));
|
|
26
|
+
if (!parsed.success) {
|
|
27
|
+
log.warn({ path, error: parsed.error.message }, "topic-index invalid; treating as empty");
|
|
28
|
+
return emptyTopicIndex();
|
|
29
|
+
}
|
|
30
|
+
return parsed.data;
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
log.warn({ path, err }, "topic-index read failed; treating as empty");
|
|
34
|
+
return emptyTopicIndex();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function writeTopicIndex(repoRoot, index) {
|
|
38
|
+
const path = topicIndexPath(repoRoot);
|
|
39
|
+
const next = { ...index, generated: new Date().toISOString() };
|
|
40
|
+
writeFileSafe(path, stringifyYaml(next));
|
|
41
|
+
log.debug({ path, topics: Object.keys(next.topics).length }, "wrote topic-index");
|
|
42
|
+
return path;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Insert or replace a topic entry. Returns the updated index.
|
|
46
|
+
*/
|
|
47
|
+
export function setTopic(index, slug, entry) {
|
|
48
|
+
return {
|
|
49
|
+
...index,
|
|
50
|
+
topics: { ...index.topics, [slug]: entry },
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Look up a topic by slug. Returns null if absent.
|
|
55
|
+
*/
|
|
56
|
+
export function getTopic(index, slug) {
|
|
57
|
+
return index.topics[slug] ?? null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Clear `dec_id` from any topic entry that references this DEC. Used
|
|
61
|
+
* by `cairn attention undo` for tier3-creation reversal so the topic
|
|
62
|
+
* stays in the index (next phase 5b walk can re-emit the topic) but
|
|
63
|
+
* no longer points at the now-deleted DEC.
|
|
64
|
+
*/
|
|
65
|
+
export function clearDecFromTopicIndex(index, decId) {
|
|
66
|
+
let mutated = false;
|
|
67
|
+
const topics = {};
|
|
68
|
+
for (const [slug, entry] of Object.entries(index.topics)) {
|
|
69
|
+
if (entry.dec_id === decId) {
|
|
70
|
+
const { dec_id: _omitted, ...rest } = entry;
|
|
71
|
+
topics[slug] = rest;
|
|
72
|
+
mutated = true;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
topics[slug] = entry;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!mutated)
|
|
79
|
+
return index;
|
|
80
|
+
return { ...index, topics };
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=topic-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"topic-index.js","sourceRoot":"","sources":["../src/topic-index.ts"],"names":[],"mappings":"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"}
|
package/dist/walk.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Walks the repo from root and returns every file (relative to root) that
|
|
3
|
+
* matches CANONICAL_GLOBS and is not excluded by CANONICAL_EXCLUDES.
|
|
4
|
+
*
|
|
5
|
+
* Skips heavyweight noise dirs (.git, node_modules, dist, etc.) eagerly.
|
|
6
|
+
*/
|
|
7
|
+
export declare function walkCanonical(repoRoot: string): string[];
|
package/dist/walk.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { CANONICAL_EXCLUDES, CANONICAL_GLOBS } from "./paths.js";
|
|
2
|
+
import { matchAnyGlob } from "./glob.js";
|
|
3
|
+
import { walkFs } from "./fs.js";
|
|
4
|
+
const SKIP_DIRS = new Set([
|
|
5
|
+
".git",
|
|
6
|
+
"node_modules",
|
|
7
|
+
".pnpm-store",
|
|
8
|
+
"dist",
|
|
9
|
+
".next",
|
|
10
|
+
".turbo",
|
|
11
|
+
".cache",
|
|
12
|
+
".archive",
|
|
13
|
+
]);
|
|
14
|
+
/**
|
|
15
|
+
* Walks the repo from root and returns every file (relative to root) that
|
|
16
|
+
* matches CANONICAL_GLOBS and is not excluded by CANONICAL_EXCLUDES.
|
|
17
|
+
*
|
|
18
|
+
* Skips heavyweight noise dirs (.git, node_modules, dist, etc.) eagerly.
|
|
19
|
+
*/
|
|
20
|
+
export function walkCanonical(repoRoot) {
|
|
21
|
+
const out = [];
|
|
22
|
+
walkFs({
|
|
23
|
+
dir: repoRoot,
|
|
24
|
+
skipDirs: SKIP_DIRS,
|
|
25
|
+
onFile: (rel) => {
|
|
26
|
+
if (matchAnyGlob(rel, CANONICAL_GLOBS) && !matchAnyGlob(rel, CANONICAL_EXCLUDES)) {
|
|
27
|
+
out.push(rel);
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
out.sort();
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=walk.js.map
|
package/dist/walk.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"walk.js","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC;IACxB,MAAM;IACN,cAAc;IACd,aAAa;IACb,MAAM;IACN,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,UAAU;CACX,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,MAAM,CAAC;QACL,GAAG,EAAE,QAAQ;QACb,QAAQ,EAAE,SAAS;QACnB,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE;YACd,IAAI,YAAY,CAAC,GAAG,EAAE,eAAe,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,kBAAkB,CAAC,EAAE,CAAC;gBACjF,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAChB,CAAC;QACH,CAAC;KACF,CAAC,CAAC;IACH,GAAG,CAAC,IAAI,EAAE,CAAC;IACX,OAAO,GAAG,CAAC;AACb,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@isaacriehm/cairn-state",
|
|
3
|
+
"version": "0.22.5",
|
|
4
|
+
"description": "Cairn state — lightweight schemas and read-only I/O.",
|
|
5
|
+
"author": "Isaac Riehm",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=22"
|
|
10
|
+
},
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"yaml": "^2.9.0",
|
|
24
|
+
"zod": "^4.4.3"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^25.9.2",
|
|
28
|
+
"typescript": "^6.0.3"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc -b",
|
|
32
|
+
"typecheck": "tsc -b",
|
|
33
|
+
"clean": "rm -rf dist *.tsbuildinfo"
|
|
34
|
+
}
|
|
35
|
+
}
|