@isaacriehm/cairn-core 0.22.6 → 0.24.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 (76) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/cites/expand.d.ts +75 -0
  3. package/dist/cites/expand.js +197 -0
  4. package/dist/cites/expand.js.map +1 -0
  5. package/dist/context/index.d.ts +2 -0
  6. package/dist/context/index.js +1 -0
  7. package/dist/context/index.js.map +1 -1
  8. package/dist/context/task-summary.d.ts +18 -0
  9. package/dist/context/task-summary.js +94 -23
  10. package/dist/context/task-summary.js.map +1 -1
  11. package/dist/context/working-header.d.ts +29 -0
  12. package/dist/context/working-header.js +125 -0
  13. package/dist/context/working-header.js.map +1 -0
  14. package/dist/doctor/index.d.ts +10 -4
  15. package/dist/doctor/index.js +24 -11
  16. package/dist/doctor/index.js.map +1 -1
  17. package/dist/gc/completion-integrity.d.ts +0 -1
  18. package/dist/gc/completion-integrity.js +0 -6
  19. package/dist/gc/completion-integrity.js.map +1 -1
  20. package/dist/hooks/post-tool-use/index.d.ts +1 -1
  21. package/dist/hooks/post-tool-use/index.js +1 -1
  22. package/dist/hooks/post-tool-use/index.js.map +1 -1
  23. package/dist/hooks/post-tool-use/post-write.js +16 -0
  24. package/dist/hooks/post-tool-use/post-write.js.map +1 -1
  25. package/dist/hooks/post-tool-use/read-enricher.js +96 -4
  26. package/dist/hooks/post-tool-use/read-enricher.js.map +1 -1
  27. package/dist/hooks/post-tool-use/sot-align.js +52 -21
  28. package/dist/hooks/post-tool-use/sot-align.js.map +1 -1
  29. package/dist/hooks/runners/annotate-surface.d.ts +41 -0
  30. package/dist/hooks/runners/annotate-surface.js +152 -0
  31. package/dist/hooks/runners/annotate-surface.js.map +1 -0
  32. package/dist/hooks/runners/stop.js +26 -0
  33. package/dist/hooks/runners/stop.js.map +1 -1
  34. package/dist/hooks/runners/user-prompt-submit.js +59 -9
  35. package/dist/hooks/runners/user-prompt-submit.js.map +1 -1
  36. package/dist/hooks/sot-align-common.d.ts +13 -0
  37. package/dist/hooks/sot-align-common.js +69 -0
  38. package/dist/hooks/sot-align-common.js.map +1 -1
  39. package/dist/index.d.ts +3 -0
  40. package/dist/index.js +3 -0
  41. package/dist/index.js.map +1 -1
  42. package/dist/init/claude-rule.d.ts +1 -0
  43. package/dist/init/claude-rule.js +1 -1
  44. package/dist/init/claude-rule.js.map +1 -1
  45. package/dist/init/source-comments/ingest.js +11 -8
  46. package/dist/init/source-comments/ingest.js.map +1 -1
  47. package/dist/invariants/prune.d.ts +50 -0
  48. package/dist/invariants/prune.js +113 -0
  49. package/dist/invariants/prune.js.map +1 -0
  50. package/dist/mcp/schemas.d.ts +27 -1
  51. package/dist/mcp/schemas.js +22 -0
  52. package/dist/mcp/schemas.js.map +1 -1
  53. package/dist/mcp/tools/component-annotate.d.ts +33 -0
  54. package/dist/mcp/tools/component-annotate.js +189 -0
  55. package/dist/mcp/tools/component-annotate.js.map +1 -0
  56. package/dist/mcp/tools/index.js +3 -1
  57. package/dist/mcp/tools/index.js.map +1 -1
  58. package/dist/mcp/tools/resume.js +10 -34
  59. package/dist/mcp/tools/resume.js.map +1 -1
  60. package/dist/session/index.d.ts +2 -0
  61. package/dist/session/index.js +2 -0
  62. package/dist/session/index.js.map +1 -1
  63. package/dist/session/seen.d.ts +37 -0
  64. package/dist/session/seen.js +110 -0
  65. package/dist/session/seen.js.map +1 -0
  66. package/dist/session/touched.d.ts +17 -0
  67. package/dist/session/touched.js +57 -0
  68. package/dist/session/touched.js.map +1 -0
  69. package/dist/tasks/spec-reader.d.ts +27 -0
  70. package/dist/tasks/spec-reader.js +60 -0
  71. package/dist/tasks/spec-reader.js.map +1 -0
  72. package/dist/uninstall/index.d.ts +44 -0
  73. package/dist/uninstall/index.js +185 -0
  74. package/dist/uninstall/index.js.map +1 -0
  75. package/package.json +2 -2
  76. package/templates/.cairn/config/sensors.yaml +10 -3
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Inline cite expander — the inverse of sot-align's strip-replace.
3
+ *
4
+ * sot-align replaces a prose block with a bare `// §DEC-<hash>` /
5
+ * `// §INV-<hash>` citation. This expands such a citation back into the
6
+ * entity's body, rendered as a plain comment in the file's own comment
7
+ * style. Two uses:
8
+ *
9
+ * 1. Uninstall — un-cite a repo so removing `.cairn/` leaves the source
10
+ * self-documenting, with no dangling `§DEC-/§INV-` references.
11
+ * 2. General "expand cites" tooling.
12
+ *
13
+ * Only a PURE cite line — one whose entire content (after the comment
14
+ * leader) is citation tokens — is expanded. A citation that shares a line
15
+ * with code is left untouched (counted as `inlineSkipped`); a citation
16
+ * whose entity is missing on disk is left as-is (`danglingSkipped`).
17
+ */
18
+ export interface ExpandResult {
19
+ text: string;
20
+ /** Citations replaced with their entity body. */
21
+ expanded: number;
22
+ /** Citation ids with no entity on disk — left in place. */
23
+ danglingSkipped: number;
24
+ /** Citations sharing a line with code — left in place. */
25
+ inlineSkipped: number;
26
+ }
27
+ /**
28
+ * Pure transform: expand every pure-cite line in `text`. `resolve` returns
29
+ * an entity body (post-frontmatter) by id, or null when it doesn't exist.
30
+ */
31
+ export declare function expandCitesInText(text: string, resolve: (id: string) => string | null): ExpandResult;
32
+ export interface ExpandCitesFileOptions {
33
+ repoRoot: string;
34
+ /** Repo-relative path. */
35
+ filePath: string;
36
+ /** When true, compute the result but don't write. */
37
+ dryRun?: boolean;
38
+ }
39
+ export interface ExpandCitesFileResult extends ExpandResult {
40
+ filePath: string;
41
+ changed: boolean;
42
+ }
43
+ /**
44
+ * Expand every pure-cite line in one source file, resolving ids against the
45
+ * live `.cairn/ground/{decisions,invariants}/` store. Writes in place unless
46
+ * `dryRun`.
47
+ */
48
+ export declare function expandCitesInFile(opts: ExpandCitesFileOptions): ExpandCitesFileResult;
49
+ /**
50
+ * Walk the working tree for files that contain a `§DEC-/§INV-` token. The
51
+ * source of truth for un-citing is the source itself, NOT the scope-index —
52
+ * a stale/missing scope-index would silently leave dangling cites behind.
53
+ */
54
+ export declare function findCitedFiles(repoRoot: string): string[];
55
+ export interface ExpandCitesRepoOptions {
56
+ repoRoot: string;
57
+ /**
58
+ * Explicit repo-relative files to expand. When omitted, the working tree
59
+ * is scanned for every file carrying a `§DEC-/§INV-` token.
60
+ */
61
+ files?: string[];
62
+ dryRun?: boolean;
63
+ }
64
+ export interface ExpandCitesRepoResult {
65
+ files: ExpandCitesFileResult[];
66
+ filesChanged: number;
67
+ expanded: number;
68
+ danglingSkipped: number;
69
+ inlineSkipped: number;
70
+ }
71
+ /**
72
+ * Expand cites across many files. Defaults to the scope-index's cited-file
73
+ * set; pass `files` to target a subset (or when the scope-index is absent).
74
+ */
75
+ export declare function expandCitesInRepo(opts: ExpandCitesRepoOptions): ExpandCitesRepoResult;
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Inline cite expander — the inverse of sot-align's strip-replace.
3
+ *
4
+ * sot-align replaces a prose block with a bare `// §DEC-<hash>` /
5
+ * `// §INV-<hash>` citation. This expands such a citation back into the
6
+ * entity's body, rendered as a plain comment in the file's own comment
7
+ * style. Two uses:
8
+ *
9
+ * 1. Uninstall — un-cite a repo so removing `.cairn/` leaves the source
10
+ * self-documenting, with no dangling `§DEC-/§INV-` references.
11
+ * 2. General "expand cites" tooling.
12
+ *
13
+ * Only a PURE cite line — one whose entire content (after the comment
14
+ * leader) is citation tokens — is expanded. A citation that shares a line
15
+ * with code is left untouched (counted as `inlineSkipped`); a citation
16
+ * whose entity is missing on disk is left as-is (`danglingSkipped`).
17
+ */
18
+ import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
19
+ import { join } from "node:path";
20
+ import { readEntityBody } from "../hooks/sot-align-common.js";
21
+ const CITE_RE = /§(DEC|INV)-([0-9a-f]{7,})/g;
22
+ /** A line that, with all cite tokens removed, is only indent + comment leader. */
23
+ const PURE_CITE_LEADER_RE = /^(\s*)(\/\/+|#+|;+|--+|\*)?\s*$/;
24
+ /**
25
+ * Pure transform: expand every pure-cite line in `text`. `resolve` returns
26
+ * an entity body (post-frontmatter) by id, or null when it doesn't exist.
27
+ */
28
+ export function expandCitesInText(text, resolve) {
29
+ let expanded = 0;
30
+ let danglingSkipped = 0;
31
+ let inlineSkipped = 0;
32
+ const eol = text.includes("\r\n") ? "\r\n" : "\n";
33
+ const lines = text.split(/\r?\n/);
34
+ const out = [];
35
+ for (const line of lines) {
36
+ const cites = [...line.matchAll(CITE_RE)];
37
+ if (cites.length === 0) {
38
+ out.push(line);
39
+ continue;
40
+ }
41
+ const stripped = line.replace(CITE_RE, "").replace(/\s+$/, "");
42
+ const leaderMatch = stripped.match(PURE_CITE_LEADER_RE);
43
+ if (leaderMatch === null) {
44
+ // Citation shares the line with code — don't touch it.
45
+ inlineSkipped += cites.length;
46
+ out.push(line);
47
+ continue;
48
+ }
49
+ const indent = leaderMatch[1] ?? "";
50
+ const leader = leaderMatch[2] ?? "//";
51
+ const replacement = [];
52
+ let expandedAny = false;
53
+ cites.forEach((m, idx) => {
54
+ const id = `${m[1]}-${m[2]}`;
55
+ const body = resolve(id);
56
+ if (body === null) {
57
+ danglingSkipped += 1;
58
+ replacement.push(`${indent}${leader} §${id}`); // keep dangling cite verbatim
59
+ return;
60
+ }
61
+ if (expandedAny)
62
+ replacement.push(`${indent}${leader}`); // separator between bodies
63
+ for (const bl of body.split(/\r?\n/)) {
64
+ replacement.push(bl.length > 0 ? `${indent}${leader} ${bl}` : `${indent}${leader}`);
65
+ }
66
+ expanded += 1;
67
+ expandedAny = true;
68
+ void idx;
69
+ });
70
+ out.push(...replacement);
71
+ }
72
+ return { text: out.join(eol), expanded, danglingSkipped, inlineSkipped };
73
+ }
74
+ /**
75
+ * Expand every pure-cite line in one source file, resolving ids against the
76
+ * live `.cairn/ground/{decisions,invariants}/` store. Writes in place unless
77
+ * `dryRun`.
78
+ */
79
+ export function expandCitesInFile(opts) {
80
+ const abs = join(opts.repoRoot, opts.filePath);
81
+ const empty = {
82
+ filePath: opts.filePath,
83
+ text: "",
84
+ expanded: 0,
85
+ danglingSkipped: 0,
86
+ inlineSkipped: 0,
87
+ changed: false,
88
+ };
89
+ if (!existsSync(abs))
90
+ return empty;
91
+ let source;
92
+ try {
93
+ source = readFileSync(abs, "utf8");
94
+ }
95
+ catch {
96
+ return empty;
97
+ }
98
+ const result = expandCitesInText(source, (id) => readEntityBody(opts.repoRoot, id));
99
+ const changed = result.text !== source;
100
+ if (changed && opts.dryRun !== true) {
101
+ writeFileSync(abs, result.text, "utf8");
102
+ }
103
+ return { ...result, filePath: opts.filePath, changed };
104
+ }
105
+ /** Dirs the cited-file scan never descends into. */
106
+ const SCAN_SKIP_DIRS = new Set([
107
+ ".git",
108
+ ".cairn",
109
+ "node_modules",
110
+ "dist",
111
+ "build",
112
+ "out",
113
+ "coverage",
114
+ ".next",
115
+ ".turbo",
116
+ ".vercel",
117
+ ]);
118
+ const CITE_SCAN_RE = /§(?:DEC|INV)-[0-9a-f]{7,}/;
119
+ const SCAN_SKIP_EXT = new Set([
120
+ ".png", ".jpg", ".jpeg", ".gif", ".webp", ".ico", ".pdf", ".zip", ".gz",
121
+ ".woff", ".woff2", ".ttf", ".eot", ".mp4", ".mov", ".wasm", ".lock", ".map",
122
+ ]);
123
+ const SCAN_MAX_BYTES = 2_000_000;
124
+ /**
125
+ * Walk the working tree for files that contain a `§DEC-/§INV-` token. The
126
+ * source of truth for un-citing is the source itself, NOT the scope-index —
127
+ * a stale/missing scope-index would silently leave dangling cites behind.
128
+ */
129
+ export function findCitedFiles(repoRoot) {
130
+ const out = [];
131
+ const walk = (absDir, relDir) => {
132
+ let entries;
133
+ try {
134
+ entries = readdirSync(absDir, { withFileTypes: true });
135
+ }
136
+ catch {
137
+ return;
138
+ }
139
+ for (const e of entries) {
140
+ const rel = relDir.length > 0 ? `${relDir}/${e.name}` : e.name;
141
+ if (e.isDirectory()) {
142
+ if (SCAN_SKIP_DIRS.has(e.name))
143
+ continue;
144
+ walk(join(absDir, e.name), rel);
145
+ }
146
+ else if (e.isFile()) {
147
+ const dot = e.name.lastIndexOf(".");
148
+ if (dot !== -1 && SCAN_SKIP_EXT.has(e.name.slice(dot).toLowerCase()))
149
+ continue;
150
+ const abs = join(absDir, e.name);
151
+ try {
152
+ if (statSync(abs).size > SCAN_MAX_BYTES)
153
+ continue;
154
+ if (CITE_SCAN_RE.test(readFileSync(abs, "utf8")))
155
+ out.push(rel);
156
+ }
157
+ catch {
158
+ /* unreadable — skip */
159
+ }
160
+ }
161
+ }
162
+ };
163
+ walk(repoRoot, "");
164
+ return out;
165
+ }
166
+ /**
167
+ * Expand cites across many files. Defaults to the scope-index's cited-file
168
+ * set; pass `files` to target a subset (or when the scope-index is absent).
169
+ */
170
+ export function expandCitesInRepo(opts) {
171
+ const targets = opts.files ?? findCitedFiles(opts.repoRoot);
172
+ const out = {
173
+ files: [],
174
+ filesChanged: 0,
175
+ expanded: 0,
176
+ danglingSkipped: 0,
177
+ inlineSkipped: 0,
178
+ };
179
+ for (const filePath of targets) {
180
+ const r = expandCitesInFile({
181
+ repoRoot: opts.repoRoot,
182
+ filePath,
183
+ ...(opts.dryRun !== undefined ? { dryRun: opts.dryRun } : {}),
184
+ });
185
+ if (r.expanded === 0 && r.danglingSkipped === 0 && r.inlineSkipped === 0) {
186
+ continue; // no cites in this file
187
+ }
188
+ out.files.push(r);
189
+ if (r.changed)
190
+ out.filesChanged += 1;
191
+ out.expanded += r.expanded;
192
+ out.danglingSkipped += r.danglingSkipped;
193
+ out.inlineSkipped += r.inlineSkipped;
194
+ }
195
+ return out;
196
+ }
197
+ //# sourceMappingURL=expand.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expand.js","sourceRoot":"","sources":["../../src/cites/expand.ts"],"names":[],"mappings":"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"}
@@ -9,3 +9,5 @@ export { buildSpecDelta } from "./spec-delta.js";
9
9
  export type { SpecDelta } from "./spec-delta.js";
10
10
  export { readActiveTaskSummary } from "./task-summary.js";
11
11
  export type { ActiveTaskSummary } from "./task-summary.js";
12
+ export { buildWorkingHeader } from "./working-header.js";
13
+ export type { WorkingHeader } from "./working-header.js";
@@ -7,4 +7,5 @@
7
7
  export { buildHandoffBlock } from "./handoff-builder.js";
8
8
  export { buildSpecDelta } from "./spec-delta.js";
9
9
  export { readActiveTaskSummary } from "./task-summary.js";
10
+ export { buildWorkingHeader } from "./working-header.js";
10
11
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/context/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/context/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -16,3 +16,21 @@ export interface ActiveTaskSummary {
16
16
  taskModule: string;
17
17
  }
18
18
  export declare function readActiveTaskSummary(repoRoot: string): ActiveTaskSummary | null;
19
+ /**
20
+ * Summary for one specific active task id, or null when it is not
21
+ * in-flight. Used by the working-context header to render the task this
22
+ * session is actually on (resolved by session affinity) rather than the
23
+ * first/arbitrary active task.
24
+ */
25
+ export declare function readTaskSummaryById(repoRoot: string, taskId: string): ActiveTaskSummary | null;
26
+ /**
27
+ * Resolve which active task THIS session is on, for multi-task /
28
+ * multi-window correctness. Among in-flight tasks, prefer one this
29
+ * session created or last journaled (`created_by_session` /
30
+ * `last_journal_session` on status.yaml); fall back to the most-recently
31
+ * touched active task. Returns null when nothing is in flight.
32
+ *
33
+ * Without this, two Claude windows on one checkout would both surface
34
+ * whichever task was globally first/most-recent — wrong frame.
35
+ */
36
+ export declare function resolveSessionTaskId(repoRoot: string, sessionId: string | null): string | null;
@@ -8,7 +8,7 @@
8
8
  * unrecognized values collapse to `idle` so the surface stays quiet
9
9
  * when nothing is actually in flight.
10
10
  */
11
- import { existsSync, readFileSync, readdirSync } from "node:fs";
11
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
12
12
  import { join } from "node:path";
13
13
  import { parse as parseYaml } from "yaml";
14
14
  import { cairnDir, parseFrontmatter } from "@isaacriehm/cairn-state";
@@ -52,8 +52,48 @@ export function readActiveTaskSummary(repoRoot) {
52
52
  for (const e of dirents) {
53
53
  if (!e.isDirectory())
54
54
  continue;
55
- const taskDir = join(activeDir, e.name);
56
- const statusPath = join(taskDir, "status.yaml");
55
+ const summary = summarizeActiveTask(join(activeDir, e.name), e.name);
56
+ if (summary !== null)
57
+ return summary;
58
+ }
59
+ return null;
60
+ }
61
+ /**
62
+ * Summary for one specific active task id, or null when it is not
63
+ * in-flight. Used by the working-context header to render the task this
64
+ * session is actually on (resolved by session affinity) rather than the
65
+ * first/arbitrary active task.
66
+ */
67
+ export function readTaskSummaryById(repoRoot, taskId) {
68
+ return summarizeActiveTask(cairnDir(repoRoot, "tasks", "active", taskId), taskId);
69
+ }
70
+ /**
71
+ * Resolve which active task THIS session is on, for multi-task /
72
+ * multi-window correctness. Among in-flight tasks, prefer one this
73
+ * session created or last journaled (`created_by_session` /
74
+ * `last_journal_session` on status.yaml); fall back to the most-recently
75
+ * touched active task. Returns null when nothing is in flight.
76
+ *
77
+ * Without this, two Claude windows on one checkout would both surface
78
+ * whichever task was globally first/most-recent — wrong frame.
79
+ */
80
+ export function resolveSessionTaskId(repoRoot, sessionId) {
81
+ const activeDir = cairnDir(repoRoot, "tasks", "active");
82
+ if (!existsSync(activeDir))
83
+ return null;
84
+ let dirents;
85
+ try {
86
+ dirents = readdirSync(activeDir, { withFileTypes: true, encoding: "utf8" });
87
+ }
88
+ catch {
89
+ return null;
90
+ }
91
+ const sid = sessionId !== null && sessionId.length > 0 ? sessionId : null;
92
+ const candidates = [];
93
+ for (const e of dirents) {
94
+ if (!e.isDirectory())
95
+ continue;
96
+ const statusPath = join(activeDir, e.name, "status.yaml");
57
97
  if (!existsSync(statusPath))
58
98
  continue;
59
99
  let parsed;
@@ -65,29 +105,60 @@ export function readActiveTaskSummary(repoRoot) {
65
105
  }
66
106
  if (typeof parsed !== "object" || parsed === null)
67
107
  continue;
68
- const phase = parsed.phase;
108
+ const o = parsed;
109
+ const phase = o["phase"];
69
110
  if (typeof phase !== "string" || !ACTIVE_PHASES.has(phase))
70
111
  continue;
71
- let title = e.name;
72
- const specPath = join(taskDir, "spec.tightened.md");
73
- if (existsSync(specPath)) {
74
- try {
75
- const specText = readFileSync(specPath, "utf8");
76
- const body = parseFrontmatter(specText).body;
77
- const m = body.match(/^#\s+(.+)$/m);
78
- if (m && m[1])
79
- title = m[1].trim();
80
- }
81
- catch {
82
- // fall through to taskId
83
- }
112
+ let mtimeMs = 0;
113
+ try {
114
+ mtimeMs = statSync(statusPath).mtimeMs;
84
115
  }
85
- return {
86
- taskId: e.name,
87
- taskState: mapPhase(phase),
88
- taskModule: title,
89
- };
116
+ catch {
117
+ // keep 0 — undated tasks sort last
118
+ }
119
+ const owned = sid !== null &&
120
+ (o["last_journal_session"] === sid || o["created_by_session"] === sid);
121
+ candidates.push({ taskId: e.name, mtimeMs, owned });
90
122
  }
91
- return null;
123
+ if (candidates.length === 0)
124
+ return null;
125
+ const byRecent = (a, b) => b.mtimeMs - a.mtimeMs;
126
+ const owned = candidates.filter((c) => c.owned).sort(byRecent);
127
+ if (owned.length > 0)
128
+ return owned[0].taskId;
129
+ return candidates.sort(byRecent)[0].taskId;
130
+ }
131
+ /** Read one active task dir into a summary, or null when not in-flight. */
132
+ function summarizeActiveTask(taskDir, taskId) {
133
+ const statusPath = join(taskDir, "status.yaml");
134
+ if (!existsSync(statusPath))
135
+ return null;
136
+ let parsed;
137
+ try {
138
+ parsed = parseYaml(readFileSync(statusPath, "utf8"));
139
+ }
140
+ catch {
141
+ return null;
142
+ }
143
+ if (typeof parsed !== "object" || parsed === null)
144
+ return null;
145
+ const phase = parsed.phase;
146
+ if (typeof phase !== "string" || !ACTIVE_PHASES.has(phase))
147
+ return null;
148
+ let title = taskId;
149
+ const specPath = join(taskDir, "spec.tightened.md");
150
+ if (existsSync(specPath)) {
151
+ try {
152
+ const specText = readFileSync(specPath, "utf8");
153
+ const body = parseFrontmatter(specText).body;
154
+ const m = body.match(/^#\s+(.+)$/m);
155
+ if (m && m[1])
156
+ title = m[1].trim();
157
+ }
158
+ catch {
159
+ // fall through to taskId
160
+ }
161
+ }
162
+ return { taskId, taskState: mapPhase(phase), taskModule: title };
92
163
  }
93
164
  //# sourceMappingURL=task-summary.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"task-summary.js","sourceRoot":"","sources":["../../src/context/task-summary.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"task-summary.js","sourceRoot":"","sources":["../../src/context/task-summary.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Working-context header — the context engine's stage-1 surface.
3
+ *
4
+ * Per prompt (UserPromptSubmit), the server injects a compact "what am
5
+ * I working on" frame so the agent never has to call `cairn_in_scope` /
6
+ * `cairn_mission_get` to know its frame. This module builds that text +
7
+ * a fingerprint; the runner injects it only when the fingerprint
8
+ * changed (per-session dedup via session/seen.ts).
9
+ *
10
+ * The header is the PERSISTENT in-scope id INDEX (every prompt) — ids
11
+ * only, no bodies. The stage-2 read-enricher shows each DEC/INV/
12
+ * component BODY once per session. So the agent always knows WHAT is in
13
+ * scope (this header) even after a body scrolled off (D13).
14
+ *
15
+ * Pure-FS, zero LLM — safe to run on every prompt inside a hook.
16
+ *
17
+ * Spec: docs/CONTEXT_ENGINE.md (stage 1), CAIRN_REBUILD §6 / D11–D13.
18
+ */
19
+ export interface WorkingHeader {
20
+ text: string;
21
+ fingerprint: string;
22
+ }
23
+ /**
24
+ * Build the working-context header for the repo's current frame, or
25
+ * null when there is no active task AND no active mission (D11 — inject
26
+ * nothing extra). When a task is active but has no in-scope ids, the
27
+ * "In scope" line is omitted (active-task line only).
28
+ */
29
+ export declare function buildWorkingHeader(repoRoot: string, sessionId: string | null): WorkingHeader | null;
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Working-context header — the context engine's stage-1 surface.
3
+ *
4
+ * Per prompt (UserPromptSubmit), the server injects a compact "what am
5
+ * I working on" frame so the agent never has to call `cairn_in_scope` /
6
+ * `cairn_mission_get` to know its frame. This module builds that text +
7
+ * a fingerprint; the runner injects it only when the fingerprint
8
+ * changed (per-session dedup via session/seen.ts).
9
+ *
10
+ * The header is the PERSISTENT in-scope id INDEX (every prompt) — ids
11
+ * only, no bodies. The stage-2 read-enricher shows each DEC/INV/
12
+ * component BODY once per session. So the agent always knows WHAT is in
13
+ * scope (this header) even after a body scrolled off (D13).
14
+ *
15
+ * Pure-FS, zero LLM — safe to run on every prompt inside a hook.
16
+ *
17
+ * Spec: docs/CONTEXT_ENGINE.md (stage 1), CAIRN_REBUILD §6 / D11–D13.
18
+ */
19
+ import { cairnDir, findActiveMission, readMissionState, readRoadmap, } from "@isaacriehm/cairn-state";
20
+ import { resolveSessionTaskId, readTaskSummaryById } from "./task-summary.js";
21
+ import { readTaskMissionAnchor } from "../missions/index.js";
22
+ import { readTaskSpec } from "../tasks/spec-reader.js";
23
+ import { fingerprintText } from "../session/seen.js";
24
+ const GOAL_MAX = 120;
25
+ const ID_LIST_CAP = 12;
26
+ /** Truncate at a word boundary, appending `…` when cut. */
27
+ function truncateAtWord(s, max) {
28
+ const flat = s.replace(/\s+/g, " ").trim();
29
+ if (flat.length <= max)
30
+ return flat;
31
+ const slice = flat.slice(0, max);
32
+ const lastSpace = slice.lastIndexOf(" ");
33
+ const cut = lastSpace > max * 0.6 ? slice.slice(0, lastSpace) : slice;
34
+ return `${cut.trimEnd()}…`;
35
+ }
36
+ /** Render up to `cap` ids, then `(+N more)`. Empty → "". */
37
+ function renderIdList(ids, cap) {
38
+ if (ids.length === 0)
39
+ return "";
40
+ const head = ids.slice(0, cap);
41
+ const extra = ids.length - head.length;
42
+ const base = head.join(", ");
43
+ return extra > 0 ? `${base} (+${extra} more)` : base;
44
+ }
45
+ /**
46
+ * Build the working-context header for the repo's current frame, or
47
+ * null when there is no active task AND no active mission (D11 — inject
48
+ * nothing extra). When a task is active but has no in-scope ids, the
49
+ * "In scope" line is omitted (active-task line only).
50
+ */
51
+ export function buildWorkingHeader(repoRoot, sessionId) {
52
+ // Resolve the task THIS session is on (affinity), not a global pick —
53
+ // correct when multiple tasks are active across windows.
54
+ const taskId = resolveSessionTaskId(repoRoot, sessionId);
55
+ const active = taskId !== null ? readTaskSummaryById(repoRoot, taskId) : null;
56
+ // Mission: prefer the resolved task's own anchor (the mission + phase
57
+ // this session is working) over a global scan — correct when several
58
+ // missions are active. Fall back to the single active mission only
59
+ // when there's no task to anchor on.
60
+ const anchor = active !== null ? readTaskMissionAnchor(repoRoot, active.taskId) : null;
61
+ const missionId = anchor?.mission_id ?? findActiveMission(repoRoot);
62
+ const preferredPhaseId = anchor?.phase_id ?? null;
63
+ if (active === null && missionId === null)
64
+ return null;
65
+ const lines = ["## Cairn — working context"];
66
+ let hasInScope = false;
67
+ if (active !== null) {
68
+ const spec = readTaskSpec(cairnDir(repoRoot, "tasks", "active", active.taskId));
69
+ const goal = spec !== null && spec.goal.length > 0 ? spec.goal : "";
70
+ const parts = [`Active: ${active.taskId} (${active.taskState})`];
71
+ if (goal.length > 0)
72
+ parts.push(`— ${truncateAtWord(goal, GOAL_MAX)}`);
73
+ if (active.taskModule.length > 0 && active.taskModule !== active.taskId) {
74
+ parts.push(`· module ${active.taskModule}`);
75
+ }
76
+ lines.push(parts.join(" "));
77
+ const decs = spec?.inScopeDecisions ?? [];
78
+ const invs = spec?.inScopeInvariants ?? [];
79
+ if (decs.length > 0 || invs.length > 0) {
80
+ hasInScope = true;
81
+ const segs = [];
82
+ if (decs.length > 0)
83
+ segs.push(renderIdList(decs, ID_LIST_CAP));
84
+ if (invs.length > 0)
85
+ segs.push(renderIdList(invs, ID_LIST_CAP));
86
+ lines.push(`In scope: ${segs.join(" · ")}`);
87
+ }
88
+ }
89
+ if (missionId !== null) {
90
+ const missionLine = renderMissionLine(repoRoot, missionId, preferredPhaseId);
91
+ if (missionLine !== null)
92
+ lines.push(missionLine);
93
+ }
94
+ if (hasInScope) {
95
+ lines.push("Bodies on demand: cairn_decision_get / cairn_invariant_get.");
96
+ }
97
+ const text = lines.join("\n");
98
+ return { text, fingerprint: fingerprintText(text) };
99
+ }
100
+ /**
101
+ * `Mission: <title> — phase <i>/<n> "<phase title>"`. Degrades to a
102
+ * title-only line when the cursor phase can't be located, and to null
103
+ * when the mission state/roadmap is unreadable (D-graceful: never throw).
104
+ */
105
+ function renderMissionLine(repoRoot, missionId, preferredPhaseId) {
106
+ const roadmap = readRoadmap(repoRoot, missionId);
107
+ const state = readMissionState(repoRoot, missionId);
108
+ if (roadmap === null)
109
+ return null;
110
+ const title = roadmap.frontmatter.title;
111
+ const phases = roadmap.frontmatter.phases;
112
+ // The task anchor's phase wins (the phase THIS session is working);
113
+ // fall back to the mission cursor when there's no task anchor.
114
+ const activePhase = preferredPhaseId ?? state?.cursor.active_phase ?? null;
115
+ if (activePhase !== null) {
116
+ const idx = phases.findIndex((p) => p.id === activePhase);
117
+ if (idx >= 0) {
118
+ const phase = phases[idx];
119
+ const phaseTitle = phase !== undefined ? phase.title : activePhase;
120
+ return `Mission: ${title} — phase ${idx + 1}/${phases.length} "${phaseTitle}"`;
121
+ }
122
+ }
123
+ return `Mission: ${title}`;
124
+ }
125
+ //# sourceMappingURL=working-header.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"working-header.js","sourceRoot":"","sources":["../../src/context/working-header.ts"],"names":[],"mappings":"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"}