@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.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +11 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/alignment-pending.d.ts +28 -0
  5. package/dist/alignment-pending.js +83 -0
  6. package/dist/alignment-pending.js.map +1 -0
  7. package/dist/anchor-map.d.ts +14 -0
  8. package/dist/anchor-map.js +56 -0
  9. package/dist/anchor-map.js.map +1 -0
  10. package/dist/archive.d.ts +48 -0
  11. package/dist/archive.js +96 -0
  12. package/dist/archive.js.map +1 -0
  13. package/dist/cache.d.ts +48 -0
  14. package/dist/cache.js +241 -0
  15. package/dist/cache.js.map +1 -0
  16. package/dist/component-registry.d.ts +93 -0
  17. package/dist/component-registry.js +0 -0
  18. package/dist/component-registry.js.map +1 -0
  19. package/dist/components.d.ts +192 -0
  20. package/dist/components.js +603 -0
  21. package/dist/components.js.map +1 -0
  22. package/dist/config.d.ts +9 -0
  23. package/dist/config.js +26 -0
  24. package/dist/config.js.map +1 -0
  25. package/dist/drift.d.ts +8 -0
  26. package/dist/drift.js +23 -0
  27. package/dist/drift.js.map +1 -0
  28. package/dist/file-candidates-map.d.ts +23 -0
  29. package/dist/file-candidates-map.js +76 -0
  30. package/dist/file-candidates-map.js.map +1 -0
  31. package/dist/frontmatter.d.ts +32 -0
  32. package/dist/frontmatter.js +77 -0
  33. package/dist/frontmatter.js.map +1 -0
  34. package/dist/fs.d.ts +36 -0
  35. package/dist/fs.js +47 -0
  36. package/dist/fs.js.map +1 -0
  37. package/dist/glob.d.ts +10 -0
  38. package/dist/glob.js +46 -0
  39. package/dist/glob.js.map +1 -0
  40. package/dist/home.d.ts +69 -0
  41. package/dist/home.js +168 -0
  42. package/dist/home.js.map +1 -0
  43. package/dist/index.d.ts +29 -0
  44. package/dist/index.js +30 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/languages.d.ts +113 -0
  47. package/dist/languages.js +512 -0
  48. package/dist/languages.js.map +1 -0
  49. package/dist/ledgers.d.ts +14 -0
  50. package/dist/ledgers.js +105 -0
  51. package/dist/ledgers.js.map +1 -0
  52. package/dist/logger.d.ts +13 -0
  53. package/dist/logger.js +17 -0
  54. package/dist/logger.js.map +1 -0
  55. package/dist/manifest.d.ts +10 -0
  56. package/dist/manifest.js +84 -0
  57. package/dist/manifest.js.map +1 -0
  58. package/dist/missions.d.ts +119 -0
  59. package/dist/missions.js +414 -0
  60. package/dist/missions.js.map +1 -0
  61. package/dist/paths.d.ts +117 -0
  62. package/dist/paths.js +241 -0
  63. package/dist/paths.js.map +1 -0
  64. package/dist/quality-grades.d.ts +11 -0
  65. package/dist/quality-grades.js +100 -0
  66. package/dist/quality-grades.js.map +1 -0
  67. package/dist/rejected.d.ts +42 -0
  68. package/dist/rejected.js +100 -0
  69. package/dist/rejected.js.map +1 -0
  70. package/dist/schemas.d.ts +789 -0
  71. package/dist/schemas.js +506 -0
  72. package/dist/schemas.js.map +1 -0
  73. package/dist/scope-index.d.ts +96 -0
  74. package/dist/scope-index.js +299 -0
  75. package/dist/scope-index.js.map +1 -0
  76. package/dist/slug.d.ts +81 -0
  77. package/dist/slug.js +138 -0
  78. package/dist/slug.js.map +1 -0
  79. package/dist/sot-bindings.d.ts +14 -0
  80. package/dist/sot-bindings.js +79 -0
  81. package/dist/sot-bindings.js.map +1 -0
  82. package/dist/sot-cache.d.ts +18 -0
  83. package/dist/sot-cache.js +62 -0
  84. package/dist/sot-cache.js.map +1 -0
  85. package/dist/text.d.ts +27 -0
  86. package/dist/text.js +63 -0
  87. package/dist/text.js.map +1 -0
  88. package/dist/topic-index.d.ts +27 -0
  89. package/dist/topic-index.js +82 -0
  90. package/dist/topic-index.js.map +1 -0
  91. package/dist/walk.d.ts +7 -0
  92. package/dist/walk.js +34 -0
  93. package/dist/walk.js.map +1 -0
  94. package/package.json +35 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive.js","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Per-process LRU cache (max 1 entry) for the invariants ledger and
3
+ * task-status lookups consumed by the read-enricher hook. All disk
4
+ * reads here are best-effort: any failure returns null/not_found so
5
+ * the hook stays a no-op.
6
+ */
7
+ import { type ScopeIndexEntry } from "./scope-index.js";
8
+ export type { ScopeIndexEntry } from "./scope-index.js";
9
+ export interface LedgerSnapshot {
10
+ invariantsByid: Map<string, {
11
+ title: string;
12
+ status: string;
13
+ superseded_by?: string;
14
+ }>;
15
+ }
16
+ export interface DecisionsLedgerSnapshot {
17
+ decisionsByid: Map<string, {
18
+ title: string;
19
+ status: string;
20
+ superseded_by?: string;
21
+ }>;
22
+ }
23
+ export declare function getInvariantsLedger(repoRoot: string): LedgerSnapshot | null;
24
+ export declare function getDecisionsLedger(repoRoot: string): DecisionsLedgerSnapshot | null;
25
+ /**
26
+ * Cached scope-index reader. Content-keyed (sha256 of first 512 bytes) so
27
+ * back-to-back hook invocations in the same process don't re-parse the file
28
+ * AND so concurrent ledger writes that happen within the same mtime tick
29
+ * are not silently masked by a stale cache hit (Gap 6 in BUILD_REPORT).
30
+ *
31
+ * Returns null when the file is missing, when no entry matches the path,
32
+ * when the entry is explicitly `unscoped: true`, or when the entry has no
33
+ * decisions/invariants — i.e., when there is nothing useful to surface.
34
+ */
35
+ export declare function getScopeIndexEntry(repoRoot: string, repoRelativePath: string): ScopeIndexEntry | null;
36
+ /**
37
+ * Cached `file-candidates-map.yaml` reader. mtime-keyed; the read-enrich
38
+ * hook hits this once per Read so the cache pays off after the second
39
+ * read of any file in the same session. Returns 0 when the file is
40
+ * missing (no walks have happened yet — typical for a fresh clone) so
41
+ * the candidate-count hint stays silent on un-adopted projects.
42
+ *
43
+ * The map is regenerated every time
44
+ * phase 5b walks the repo and every time phase 6 / `cairn_propose_decision`
45
+ * stamps a new `dec_id` on a topic-index entry. The mtime invalidation
46
+ * picks up both refresh paths.
47
+ */
48
+ export declare function getFileCandidateCount(repoRoot: string, repoRelativePath: string): number;
package/dist/cache.js ADDED
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Per-process LRU cache (max 1 entry) for the invariants ledger and
3
+ * task-status lookups consumed by the read-enricher hook. All disk
4
+ * reads here are best-effort: any failure returns null/not_found so
5
+ * the hook stays a no-op.
6
+ */
7
+ import { createHash } from "node:crypto";
8
+ import { existsSync, openSync, readFileSync, readSync, closeSync, statSync } from "node:fs";
9
+ import { parse as parseYaml } from "yaml";
10
+ import { z } from "zod";
11
+ import { cairnDir } from "./home.js";
12
+ import { fileCandidatesMapPath } from "./paths.js";
13
+ import { FileCandidatesMap } from "./schemas.js";
14
+ import { lookupScope, readScopeIndex, scopeIndexPath, } from "./scope-index.js";
15
+ const DecisionLedgerEntrySchema = z.object({
16
+ id: z.string(),
17
+ title: z.string(),
18
+ status: z.string().optional(),
19
+ superseded_by: z.string().optional(),
20
+ }).passthrough();
21
+ const InvariantLedgerEntrySchema = z.object({
22
+ id: z.string(),
23
+ title: z.string(),
24
+ status: z.string().optional(),
25
+ superseded_by: z.string().optional(),
26
+ }).passthrough();
27
+ const SCOPE_INDEX_HASH_BYTES = 512;
28
+ /**
29
+ * Hash the first N bytes of a file. Returns null when the file can't be read.
30
+ * Used as the cache key for scope-index — mtime is unreliable under clock
31
+ * skew (concurrent ledger writes can land with the same mtime as the
32
+ * previous read).
33
+ */
34
+ function hashFilePrefix(path, bytes) {
35
+ let fd;
36
+ try {
37
+ fd = openSync(path, "r");
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ try {
43
+ const buf = Buffer.alloc(bytes);
44
+ const read = readSync(fd, buf, 0, bytes, 0);
45
+ return createHash("sha256")
46
+ .update(buf.subarray(0, read))
47
+ .digest("hex");
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ finally {
53
+ try {
54
+ closeSync(fd);
55
+ }
56
+ catch {
57
+ // ignore — best-effort
58
+ }
59
+ }
60
+ }
61
+ let invariantsCache = null;
62
+ let decisionsCache = null;
63
+ let scopeIndexCache = null;
64
+ let fileCandidatesCache = null;
65
+ function invariantsLedgerFile(repoRoot) {
66
+ return cairnDir(repoRoot, "ground", "invariants", "invariants.ledger.yaml");
67
+ }
68
+ export function getInvariantsLedger(repoRoot) {
69
+ const path = invariantsLedgerFile(repoRoot);
70
+ if (!existsSync(path))
71
+ return null;
72
+ let mtimeMs;
73
+ try {
74
+ mtimeMs = statSync(path).mtimeMs;
75
+ }
76
+ catch {
77
+ return null;
78
+ }
79
+ if (invariantsCache !== null &&
80
+ invariantsCache.repoRoot === repoRoot &&
81
+ invariantsCache.mtimeMs === mtimeMs) {
82
+ return invariantsCache.snapshot;
83
+ }
84
+ let parsed;
85
+ try {
86
+ const raw = readFileSync(path, "utf8");
87
+ parsed = parseYaml(raw);
88
+ }
89
+ catch {
90
+ return null;
91
+ }
92
+ const result = z.array(InvariantLedgerEntrySchema).safeParse(parsed);
93
+ if (!result.success)
94
+ return null;
95
+ const map = new Map();
96
+ for (const entry of result.data) {
97
+ const id = entry.id;
98
+ const title = entry.title;
99
+ const status = entry.status ?? "active";
100
+ const supersededBy = entry.superseded_by;
101
+ map.set(id, {
102
+ title,
103
+ status,
104
+ ...(typeof supersededBy === "string" && supersededBy.length > 0 ? { superseded_by: supersededBy } : {}),
105
+ });
106
+ }
107
+ const snapshot = { invariantsByid: map };
108
+ invariantsCache = { repoRoot, mtimeMs, snapshot };
109
+ return snapshot;
110
+ }
111
+ function decisionsLedgerFile(repoRoot) {
112
+ return cairnDir(repoRoot, "ground", "decisions", "decisions.ledger.yaml");
113
+ }
114
+ export function getDecisionsLedger(repoRoot) {
115
+ const path = decisionsLedgerFile(repoRoot);
116
+ if (!existsSync(path))
117
+ return null;
118
+ let mtimeMs;
119
+ try {
120
+ mtimeMs = statSync(path).mtimeMs;
121
+ }
122
+ catch {
123
+ return null;
124
+ }
125
+ if (decisionsCache !== null &&
126
+ decisionsCache.repoRoot === repoRoot &&
127
+ decisionsCache.mtimeMs === mtimeMs) {
128
+ return decisionsCache.snapshot;
129
+ }
130
+ let parsed;
131
+ try {
132
+ const raw = readFileSync(path, "utf8");
133
+ parsed = parseYaml(raw);
134
+ }
135
+ catch {
136
+ return null;
137
+ }
138
+ const result = z.array(DecisionLedgerEntrySchema).safeParse(parsed);
139
+ if (!result.success)
140
+ return null;
141
+ const map = new Map();
142
+ for (const entry of result.data) {
143
+ const id = entry.id;
144
+ const title = entry.title;
145
+ const status = entry.status ?? "accepted";
146
+ const supersededBy = entry.superseded_by;
147
+ map.set(id, {
148
+ title,
149
+ status,
150
+ ...(typeof supersededBy === "string" && supersededBy.length > 0 ? { superseded_by: supersededBy } : {}),
151
+ });
152
+ }
153
+ const snapshot = { decisionsByid: map };
154
+ decisionsCache = { repoRoot, mtimeMs, snapshot };
155
+ return snapshot;
156
+ }
157
+ /**
158
+ * Cached scope-index reader. Content-keyed (sha256 of first 512 bytes) so
159
+ * back-to-back hook invocations in the same process don't re-parse the file
160
+ * AND so concurrent ledger writes that happen within the same mtime tick
161
+ * are not silently masked by a stale cache hit (Gap 6 in BUILD_REPORT).
162
+ *
163
+ * Returns null when the file is missing, when no entry matches the path,
164
+ * when the entry is explicitly `unscoped: true`, or when the entry has no
165
+ * decisions/invariants — i.e., when there is nothing useful to surface.
166
+ */
167
+ export function getScopeIndexEntry(repoRoot, repoRelativePath) {
168
+ const path = scopeIndexPath(repoRoot);
169
+ if (!existsSync(path))
170
+ return null;
171
+ const contentHash = hashFilePrefix(path, SCOPE_INDEX_HASH_BYTES);
172
+ if (contentHash === null)
173
+ return null;
174
+ let index;
175
+ if (scopeIndexCache !== null &&
176
+ scopeIndexCache.repoRoot === repoRoot &&
177
+ scopeIndexCache.contentHash === contentHash) {
178
+ index = scopeIndexCache.index;
179
+ }
180
+ else {
181
+ const fresh = readScopeIndex(repoRoot);
182
+ if (fresh === null)
183
+ return null;
184
+ scopeIndexCache = { repoRoot, contentHash, index: fresh };
185
+ index = fresh;
186
+ }
187
+ const entry = lookupScope(index, repoRelativePath);
188
+ if (entry === null)
189
+ return null;
190
+ if (entry.unscoped === true)
191
+ return null;
192
+ if (entry.decisions.length === 0 && entry.invariants.length === 0)
193
+ return null;
194
+ return entry;
195
+ }
196
+ /**
197
+ * Cached `file-candidates-map.yaml` reader. mtime-keyed; the read-enrich
198
+ * hook hits this once per Read so the cache pays off after the second
199
+ * read of any file in the same session. Returns 0 when the file is
200
+ * missing (no walks have happened yet — typical for a fresh clone) so
201
+ * the candidate-count hint stays silent on un-adopted projects.
202
+ *
203
+ * The map is regenerated every time
204
+ * phase 5b walks the repo and every time phase 6 / `cairn_propose_decision`
205
+ * stamps a new `dec_id` on a topic-index entry. The mtime invalidation
206
+ * picks up both refresh paths.
207
+ */
208
+ export function getFileCandidateCount(repoRoot, repoRelativePath) {
209
+ const path = fileCandidatesMapPath(repoRoot);
210
+ if (!existsSync(path))
211
+ return 0;
212
+ let mtimeMs;
213
+ try {
214
+ mtimeMs = statSync(path).mtimeMs;
215
+ }
216
+ catch {
217
+ return 0;
218
+ }
219
+ if (fileCandidatesCache === null ||
220
+ fileCandidatesCache.repoRoot !== repoRoot ||
221
+ fileCandidatesCache.mtimeMs !== mtimeMs) {
222
+ let parsed;
223
+ try {
224
+ parsed = parseYaml(readFileSync(path, "utf8"));
225
+ }
226
+ catch {
227
+ return 0;
228
+ }
229
+ const result = FileCandidatesMap.safeParse(parsed);
230
+ if (!result.success)
231
+ return 0;
232
+ const counts = new Map();
233
+ for (const [file, count] of Object.entries(result.data.file_candidates)) {
234
+ if (typeof count === "number" && count > 0)
235
+ counts.set(file, count);
236
+ }
237
+ fileCandidatesCache = { repoRoot, mtimeMs, counts };
238
+ }
239
+ return fileCandidatesCache.counts.get(repoRelativePath) ?? 0;
240
+ }
241
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Headerless component registry — the ghost-mode source of truth for the
3
+ * component store (GHOST_MODE_PLAN.md §3.8.1).
4
+ *
5
+ * In committed mode the `@cairn <Name>` header in source IS the source of
6
+ * truth (`collectComponents` reads it with `parseComponentHeader`). Ghost never
7
+ * writes that header into client source, so the same classification
8
+ * (name / category / purpose / aliases) lives out-of-repo here instead, keyed
9
+ * by the natural identity the in-file header never had to encode because it sat
10
+ * physically on the code: **(workspace, file, export)**. A content-hash anchor
11
+ * rides along for the (later) re-anchor-on-read + freshness passes; nothing
12
+ * reads it yet, so v1 stores a whole-file fingerprint.
13
+ *
14
+ * This module is pure state — schema + I/O + upsert + lookup. The MCP write
15
+ * tool, the mode-aware `collectComponents` flip, and the adoption phases live
16
+ * in cairn-core. `ComponentRecord`/`ComponentTags` projection (`entryToTags`)
17
+ * stays in `components.ts` to avoid an import cycle.
18
+ */
19
+ import { z } from "zod";
20
+ export declare const ComponentAnchor: z.ZodObject<{
21
+ content_hash: z.ZodString;
22
+ line_range: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
23
+ }, z.core.$strip>;
24
+ export type ComponentAnchor = z.infer<typeof ComponentAnchor>;
25
+ export declare const ComponentRegistryEntry: z.ZodObject<{
26
+ workspace: z.ZodDefault<z.ZodString>;
27
+ file: z.ZodString;
28
+ export: z.ZodString;
29
+ name: z.ZodString;
30
+ category: z.ZodString;
31
+ purpose: z.ZodString;
32
+ aliases: z.ZodDefault<z.ZodArray<z.ZodString>>;
33
+ singleton: z.ZodOptional<z.ZodBoolean>;
34
+ status: z.ZodOptional<z.ZodString>;
35
+ uses: z.ZodDefault<z.ZodArray<z.ZodString>>;
36
+ anchor: z.ZodObject<{
37
+ content_hash: z.ZodString;
38
+ line_range: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
39
+ }, z.core.$strip>;
40
+ exports: z.ZodDefault<z.ZodArray<z.ZodString>>;
41
+ unit_shaped: z.ZodOptional<z.ZodBoolean>;
42
+ needs_reconfirm: z.ZodOptional<z.ZodBoolean>;
43
+ }, z.core.$strip>;
44
+ export type ComponentRegistryEntry = z.infer<typeof ComponentRegistryEntry>;
45
+ export declare const ComponentRegistry: z.ZodObject<{
46
+ version: z.ZodLiteral<1>;
47
+ generated: z.ZodString;
48
+ entries: z.ZodDefault<z.ZodArray<z.ZodObject<{
49
+ workspace: z.ZodDefault<z.ZodString>;
50
+ file: z.ZodString;
51
+ export: z.ZodString;
52
+ name: z.ZodString;
53
+ category: z.ZodString;
54
+ purpose: z.ZodString;
55
+ aliases: z.ZodDefault<z.ZodArray<z.ZodString>>;
56
+ singleton: z.ZodOptional<z.ZodBoolean>;
57
+ status: z.ZodOptional<z.ZodString>;
58
+ uses: z.ZodDefault<z.ZodArray<z.ZodString>>;
59
+ anchor: z.ZodObject<{
60
+ content_hash: z.ZodString;
61
+ line_range: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
62
+ }, z.core.$strip>;
63
+ exports: z.ZodDefault<z.ZodArray<z.ZodString>>;
64
+ unit_shaped: z.ZodOptional<z.ZodBoolean>;
65
+ needs_reconfirm: z.ZodOptional<z.ZodBoolean>;
66
+ }, z.core.$strip>>>;
67
+ }, z.core.$strip>;
68
+ export type ComponentRegistry = z.infer<typeof ComponentRegistry>;
69
+ export declare function componentRegistryPath(repoRoot: string): string;
70
+ export declare function emptyComponentRegistry(): ComponentRegistry;
71
+ /** Read the registry; empty when absent / unreadable / invalid. */
72
+ export declare function readComponentRegistry(repoRoot: string): ComponentRegistry;
73
+ export declare function writeComponentRegistry(repoRoot: string, reg: ComponentRegistry): string;
74
+ /**
75
+ * Upsert one entry by its natural key (workspace, file, export). Pass
76
+ * `preloaded` when the caller already parsed the registry this tick (e.g. the
77
+ * freshness hot path on PostToolUse Write) to skip a second YAML read+parse.
78
+ */
79
+ export declare function registerComponentEntry(repoRoot: string, entry: ComponentRegistryEntry, preloaded?: ComponentRegistry): ComponentRegistry;
80
+ /**
81
+ * Find the registry entry for a file: same (workspace, file) and an export
82
+ * the file actually still declares. `exports` is the file's current export
83
+ * list — passing it lets a renamed-but-not-moved file miss cleanly (→ the
84
+ * unit reads as unregistered) rather than resolve to a stale symbol.
85
+ */
86
+ export declare function lookupComponentEntry(reg: ComponentRegistry, workspace: string, file: string, exports: readonly string[]): ComponentRegistryEntry | null;
87
+ /**
88
+ * Find the entry that governs a repo-relative file path, ignoring workspace
89
+ * and export — the freshness gate (§3.8.1) only has an edited path in hand, not
90
+ * the workspace/export. A file maps to at most one registered unit, so the
91
+ * first `file` match wins. Null when the path isn't registered.
92
+ */
93
+ export declare function lookupComponentEntryByFile(reg: ComponentRegistry, file: string): ComponentRegistryEntry | null;
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component-registry.js","sourceRoot":"","sources":["../src/component-registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Component registry — Cairn's fourth ground store.
3
+ *
4
+ * Headers in source (`@cairn <ExportName>` comments) are the committed
5
+ * source of truth; `.cairn/ground/components/` holds the *derived* index the
6
+ * agent reads in full before UI work. This module is the pure state layer:
7
+ * config loading, header parsing, repo walking, deterministic index
8
+ * rendering, and validation. Disk writes, the advisory audit, MCP tools, and
9
+ * adoption phases live in cairn-core.
10
+ *
11
+ * The registry header is recognized in EVERY comment form (language-agnostic):
12
+ * block `/* *​/` / JSDoc, `//` line runs, `#` hash runs, `<!-- -->` (Vue /
13
+ * Svelte / Razor / HTML), `--` (Lua / SQL), and Python `"""` docstrings. The
14
+ * earliest comment carrying the `@cairn <Name>` signal wins. Export extraction
15
+ * and unit-shape detection are routed through the shared `languages.ts`
16
+ * profile table, so a Python `def`, a Go capitalized ident, a SwiftUI `View`,
17
+ * or a Vue SFC are all first-class — there is no JS/React default.
18
+ *
19
+ * The `@cairn <Name>` registry header is disjoint from the pre-existing
20
+ * `@cairn:decision` / `@cairn:rule` SoT markers: the header detector requires
21
+ * whitespace then an identifier start, so a colon-form marker can never be
22
+ * misread as a header.
23
+ */
24
+ import { z } from "zod";
25
+ export declare const DEFAULT_CATEGORIES: readonly ["layout", "navigation", "data-display", "forms", "feedback", "overlay", "media", "marketing", "utility"];
26
+ export declare const DEFAULT_EXTENSIONS: readonly string[];
27
+ export declare const DEFAULT_EXCLUDE: readonly ["node_modules", "dist", ".next", "build", "coverage", "__tests__", "__mocks__", "stories"];
28
+ /** Tags a header MUST carry. */
29
+ export declare const COMPONENT_REQUIRED_TAGS: readonly ["cairn", "category", "purpose", "aliases"];
30
+ export declare const ComponentsWorkspaceConfig: z.ZodObject<{
31
+ componentDirs: z.ZodDefault<z.ZodArray<z.ZodString>>;
32
+ extensions: z.ZodOptional<z.ZodArray<z.ZodString>>;
33
+ categories: z.ZodOptional<z.ZodArray<z.ZodString>>;
34
+ exclude: z.ZodOptional<z.ZodArray<z.ZodString>>;
35
+ shared: z.ZodOptional<z.ZodBoolean>;
36
+ }, z.core.$loose>;
37
+ export type ComponentsWorkspaceConfig = z.infer<typeof ComponentsWorkspaceConfig>;
38
+ export declare const ComponentsConfig: z.ZodObject<{
39
+ componentDirs: z.ZodOptional<z.ZodArray<z.ZodString>>;
40
+ extensions: z.ZodOptional<z.ZodArray<z.ZodString>>;
41
+ categories: z.ZodOptional<z.ZodArray<z.ZodString>>;
42
+ exclude: z.ZodOptional<z.ZodArray<z.ZodString>>;
43
+ workspaces: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
44
+ componentDirs: z.ZodDefault<z.ZodArray<z.ZodString>>;
45
+ extensions: z.ZodOptional<z.ZodArray<z.ZodString>>;
46
+ categories: z.ZodOptional<z.ZodArray<z.ZodString>>;
47
+ exclude: z.ZodOptional<z.ZodArray<z.ZodString>>;
48
+ shared: z.ZodOptional<z.ZodBoolean>;
49
+ }, z.core.$loose>>>;
50
+ }, z.core.$loose>;
51
+ export type ComponentsConfig = z.infer<typeof ComponentsConfig>;
52
+ /** A workspace after defaults are folded in. `name: ""` = single-app. */
53
+ export interface ComponentWorkspace {
54
+ name: string;
55
+ componentDirs: string[];
56
+ exclude: string[];
57
+ extensions: string[];
58
+ categories: string[];
59
+ shared: boolean;
60
+ }
61
+ export interface NormalizedComponentsConfig {
62
+ workspaces: ComponentWorkspace[];
63
+ }
64
+ /**
65
+ * Fold the raw `components:` config into a normalized workspace list.
66
+ * Flat (single-app) config becomes one unnamed workspace; a `workspaces`
67
+ * map becomes one entry per key, each inheriting the top-level
68
+ * exclude/extensions/categories unless it overrides them.
69
+ */
70
+ export declare function normalizeComponentsConfig(raw: ComponentsConfig): NormalizedComponentsConfig;
71
+ /** Read + normalize the `components:` config from `.cairn/config.yaml`. */
72
+ export declare function loadComponentsConfig(repoRoot: string): NormalizedComponentsConfig;
73
+ /** True when any workspace declares at least one component dir. */
74
+ export declare function hasComponentConfig(config: NormalizedComponentsConfig): boolean;
75
+ /** True for monorepo configs (more than one workspace). */
76
+ export declare function isMonorepoComponents(config: NormalizedComponentsConfig): boolean;
77
+ export interface ComponentTags {
78
+ cairn?: string;
79
+ category?: string;
80
+ purpose?: string;
81
+ aliases?: string;
82
+ props?: string;
83
+ uses?: string;
84
+ singleton?: string;
85
+ status?: string;
86
+ example?: string;
87
+ [k: string]: string | undefined;
88
+ }
89
+ export interface ComponentRecord {
90
+ /** Repo-relative POSIX path. */
91
+ file: string;
92
+ /** Owning workspace name; "" for single-app. */
93
+ workspace: string;
94
+ tags: ComponentTags;
95
+ /** Best-effort single detected export name, or null when undetectable. */
96
+ exportName: string | null;
97
+ /** Every top-level export name in the file (header must match one). */
98
+ exportNames: string[];
99
+ }
100
+ /**
101
+ * True when a raw comment block is a `@cairn` registry header. Exported so
102
+ * the source-comment + curator walkers can exclude headers from candidate
103
+ * registration (headers must not pollute the topic index or be stripped).
104
+ */
105
+ export declare function isComponentHeaderBlock(rawBlockText: string): boolean;
106
+ /**
107
+ * Parse a file's component header into a tag map, or null if absent. The
108
+ * header is the EARLIEST comment — in any supported form — that carries the
109
+ * `@cairn <Name>` signal. The per-line tag finder is tolerant of leading
110
+ * markers (`*`, `//`, `#`, `--`), so raw comment text parses directly.
111
+ */
112
+ export declare function parseComponentHeader(source: string): ComponentTags | null;
113
+ export interface CollectResult {
114
+ components: ComponentRecord[];
115
+ /**
116
+ * Repo-relative paths of unit-shaped files that are not yet in the store —
117
+ * committed: no `@cairn` header; ghost: not in the external registry. The
118
+ * `ghost` flag below tells `validateComponents` how to phrase the finding.
119
+ */
120
+ missing: string[];
121
+ /** True when the tag source was the out-of-repo registry (ghost). */
122
+ ghost: boolean;
123
+ }
124
+ /**
125
+ * Walk every workspace's component dirs and parse headers. Returns the
126
+ * parsed components plus the files that are missing a header. Output is
127
+ * deterministically sorted (workspace, then component name).
128
+ */
129
+ export declare function collectComponents(repoRoot: string, config: NormalizedComponentsConfig): CollectResult;
130
+ /** Filesystem-safe slice slug for a workspace name. */
131
+ export declare function sliceSlug(name: string): string;
132
+ /** Relative path (under the components ground dir) of a workspace slice. */
133
+ export declare function sliceRelPath(name: string): string;
134
+ /**
135
+ * Render every index artifact as a Map of relative path (under
136
+ * `.cairn/ground/components/`) → content. Single workspace: one flat
137
+ * INDEX.md. Monorepo: INDEX.md manifest + index/<ws>.md slices — never an
138
+ * all-workspace inventory file (the honeypot invariant).
139
+ */
140
+ export declare function renderComponentsIndex(components: ComponentRecord[], config: NormalizedComponentsConfig): Map<string, string>;
141
+ export type ComponentFindingKind = "missing-header" | "unregistered-unit" | "missing-tag" | "invalid-category" | "duplicate-name" | "export-mismatch";
142
+ export interface ComponentFinding {
143
+ kind: ComponentFindingKind;
144
+ severity: "hard" | "soft";
145
+ /** Repo-relative path the finding is about, when applicable. */
146
+ file?: string;
147
+ message: string;
148
+ }
149
+ /**
150
+ * Validate headers across all workspaces. Name uniqueness is scoped per
151
+ * workspace — platform/Button and site/Button may coexist. Hard findings are
152
+ * gate failures; soft findings are warnings.
153
+ *
154
+ * `@aliases` overlap is NOT a finding: aliases are intentionally-overlapping
155
+ * fuzzy search hints (two components both findable by "activity log" is
156
+ * correct), so collision is a non-constraint — flagging it drove edits that
157
+ * deleted valid aliases and degraded search recall.
158
+ */
159
+ export declare function validateComponents(result: CollectResult, config: NormalizedComponentsConfig): ComponentFinding[];
160
+ export interface ComponentLedgerEntry {
161
+ name: string;
162
+ workspace: string;
163
+ file: string;
164
+ category: string;
165
+ purpose: string;
166
+ aliases: string[];
167
+ singleton: boolean;
168
+ status?: string;
169
+ uses: string[];
170
+ }
171
+ /** Collect + project the component registry into ledger entries. */
172
+ export declare function buildComponentsLedger(repoRoot: string): ComponentLedgerEntry[];
173
+ export interface ComponentScope {
174
+ /** Workspaces the requested paths resolved to (own + shared). */
175
+ workspaces: string[];
176
+ /** Isolated workspaces the caller may NOT use (awareness list). */
177
+ offLimits: string[];
178
+ /** The entitled inventory. */
179
+ components: ComponentLedgerEntry[];
180
+ }
181
+ /**
182
+ * Resolve which workspace(s) the given repo-relative path globs touch
183
+ * (longest-prefix match against component dirs), and return the entitled
184
+ * inventory: own workspace(s) + any `[shared]` workspace, with isolated
185
+ * workspaces named in `offLimits`. Single-app → the whole inventory.
186
+ */
187
+ export declare function componentsInScope(repoRoot: string, pathGlobs: string[]): ComponentScope;
188
+ /** Look up a single component by name (optionally scoped to a workspace). */
189
+ export declare function getComponent(repoRoot: string, name: string, workspace?: string): {
190
+ entry: ComponentLedgerEntry;
191
+ record: ComponentRecord;
192
+ } | null;