@isaacriehm/cairn-core 0.23.0 → 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.
- package/dist/.tsbuildinfo +1 -1
- package/dist/context/index.d.ts +2 -0
- package/dist/context/index.js +1 -0
- package/dist/context/index.js.map +1 -1
- package/dist/context/task-summary.d.ts +18 -0
- package/dist/context/task-summary.js +94 -23
- package/dist/context/task-summary.js.map +1 -1
- package/dist/context/working-header.d.ts +29 -0
- package/dist/context/working-header.js +125 -0
- package/dist/context/working-header.js.map +1 -0
- package/dist/hooks/post-tool-use/post-write.js +16 -0
- package/dist/hooks/post-tool-use/post-write.js.map +1 -1
- package/dist/hooks/post-tool-use/read-enricher.js +96 -4
- package/dist/hooks/post-tool-use/read-enricher.js.map +1 -1
- package/dist/hooks/runners/annotate-surface.d.ts +41 -0
- package/dist/hooks/runners/annotate-surface.js +152 -0
- package/dist/hooks/runners/annotate-surface.js.map +1 -0
- package/dist/hooks/runners/stop.js +26 -0
- package/dist/hooks/runners/stop.js.map +1 -1
- package/dist/hooks/runners/user-prompt-submit.js +59 -9
- package/dist/hooks/runners/user-prompt-submit.js.map +1 -1
- package/dist/mcp/schemas.d.ts +27 -1
- package/dist/mcp/schemas.js +22 -0
- package/dist/mcp/schemas.js.map +1 -1
- package/dist/mcp/tools/component-annotate.d.ts +33 -0
- package/dist/mcp/tools/component-annotate.js +189 -0
- package/dist/mcp/tools/component-annotate.js.map +1 -0
- package/dist/mcp/tools/index.js +3 -1
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/mcp/tools/resume.js +10 -34
- package/dist/mcp/tools/resume.js.map +1 -1
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +2 -0
- package/dist/session/index.js.map +1 -1
- package/dist/session/seen.d.ts +37 -0
- package/dist/session/seen.js +110 -0
- package/dist/session/seen.js.map +1 -0
- package/dist/session/touched.d.ts +17 -0
- package/dist/session/touched.js +57 -0
- package/dist/session/touched.js.map +1 -0
- package/dist/tasks/spec-reader.d.ts +27 -0
- package/dist/tasks/spec-reader.js +60 -0
- package/dist/tasks/spec-reader.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cairn_component_annotate` — the context engine's stage-3 write path.
|
|
3
|
+
*
|
|
4
|
+
* The agent supplies JUDGMENT (category, purpose, aliases, which props
|
|
5
|
+
* are public); the server holds the MECHANICS + truth: it validates the
|
|
6
|
+
* export symbol against the code, validates the category against the
|
|
7
|
+
* project enum, formats the canonical `@cairn` header (block form for
|
|
8
|
+
* C-style langs, hash form for the rest), writes it above the export,
|
|
9
|
+
* and rebuilds the index + singleton §INV. The agent never sees the
|
|
10
|
+
* header grammar — the format is structurally impossible to get wrong.
|
|
11
|
+
*
|
|
12
|
+
* Committed mode writes the in-file header (the SoT). Ghost mode has no
|
|
13
|
+
* in-file SoT, so it delegates to `cairn_component_register` (registry
|
|
14
|
+
* write, no source edit).
|
|
15
|
+
*
|
|
16
|
+
* Spec: docs/CONTEXT_ENGINE.md ("semantics in, server writes"),
|
|
17
|
+
* CAIRN_REBUILD §8d / D7–D9.
|
|
18
|
+
*/
|
|
19
|
+
import type { ToolDef } from "./types.js";
|
|
20
|
+
interface Input {
|
|
21
|
+
file: string;
|
|
22
|
+
export_name: string;
|
|
23
|
+
category: string;
|
|
24
|
+
purpose: string;
|
|
25
|
+
aliases: string[];
|
|
26
|
+
public_props?: string[];
|
|
27
|
+
uses?: string[];
|
|
28
|
+
status?: "stable" | "wip" | "deprecated";
|
|
29
|
+
singleton?: boolean;
|
|
30
|
+
workspace?: string;
|
|
31
|
+
}
|
|
32
|
+
export declare const componentAnnotateTool: ToolDef<Input>;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cairn_component_annotate` — the context engine's stage-3 write path.
|
|
3
|
+
*
|
|
4
|
+
* The agent supplies JUDGMENT (category, purpose, aliases, which props
|
|
5
|
+
* are public); the server holds the MECHANICS + truth: it validates the
|
|
6
|
+
* export symbol against the code, validates the category against the
|
|
7
|
+
* project enum, formats the canonical `@cairn` header (block form for
|
|
8
|
+
* C-style langs, hash form for the rest), writes it above the export,
|
|
9
|
+
* and rebuilds the index + singleton §INV. The agent never sees the
|
|
10
|
+
* header grammar — the format is structurally impossible to get wrong.
|
|
11
|
+
*
|
|
12
|
+
* Committed mode writes the in-file header (the SoT). Ghost mode has no
|
|
13
|
+
* in-file SoT, so it delegates to `cairn_component_register` (registry
|
|
14
|
+
* write, no source edit).
|
|
15
|
+
*
|
|
16
|
+
* Spec: docs/CONTEXT_ENGINE.md ("semantics in, server writes"),
|
|
17
|
+
* CAIRN_REBUILD §8d / D7–D9.
|
|
18
|
+
*/
|
|
19
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import { extractExportNames, hasComponentConfig, isGhost, loadComponentsConfig, parseComponentHeader, profileForFile, } from "@isaacriehm/cairn-state";
|
|
22
|
+
import { emitComponentStore } from "../../components/emit.js";
|
|
23
|
+
import { mcpError } from "../errors.js";
|
|
24
|
+
import { componentAnnotateInput } from "../schemas.js";
|
|
25
|
+
import { componentRegisterTool } from "./component-register.js";
|
|
26
|
+
/** Allowed categories for the workspace owning `rel` (longest-prefix). */
|
|
27
|
+
function categoriesForFile(config, rel) {
|
|
28
|
+
let best = null;
|
|
29
|
+
let bestLen = -1;
|
|
30
|
+
for (const ws of config.workspaces) {
|
|
31
|
+
for (const dir of ws.componentDirs) {
|
|
32
|
+
if ((rel === dir || rel.startsWith(`${dir}/`)) && dir.length > bestLen) {
|
|
33
|
+
bestLen = dir.length;
|
|
34
|
+
best = ws.categories;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return best ?? config.workspaces[0]?.categories ?? [];
|
|
39
|
+
}
|
|
40
|
+
/** Pick the header comment form from the file's language profile. */
|
|
41
|
+
function commentFormFor(file) {
|
|
42
|
+
const forms = profileForFile(file)?.commentForms ?? [];
|
|
43
|
+
if (forms.includes("block"))
|
|
44
|
+
return "block";
|
|
45
|
+
if (forms.includes("hash"))
|
|
46
|
+
return "hash";
|
|
47
|
+
if (forms.includes("dash"))
|
|
48
|
+
return "dash";
|
|
49
|
+
return "block";
|
|
50
|
+
}
|
|
51
|
+
/** Format the canonical `@cairn` header in the given comment form. */
|
|
52
|
+
function formatHeader(input, form) {
|
|
53
|
+
const tags = [
|
|
54
|
+
`@cairn ${input.export_name}`,
|
|
55
|
+
`@category ${input.category}`,
|
|
56
|
+
`@purpose ${input.purpose}`,
|
|
57
|
+
`@aliases ${input.aliases.join(", ")}`,
|
|
58
|
+
];
|
|
59
|
+
if (input.public_props !== undefined && input.public_props.length > 0) {
|
|
60
|
+
tags.push(`@props ${input.public_props.join(", ")}`);
|
|
61
|
+
}
|
|
62
|
+
if (input.uses !== undefined && input.uses.length > 0) {
|
|
63
|
+
tags.push(`@uses ${input.uses.join(", ")}`);
|
|
64
|
+
}
|
|
65
|
+
if (input.status !== undefined)
|
|
66
|
+
tags.push(`@status ${input.status}`);
|
|
67
|
+
if (input.singleton === true)
|
|
68
|
+
tags.push("@singleton");
|
|
69
|
+
if (form === "block") {
|
|
70
|
+
return ["/**", ...tags.map((t) => ` * ${t}`), " */"].join("\n");
|
|
71
|
+
}
|
|
72
|
+
const marker = form === "dash" ? "--" : "#";
|
|
73
|
+
return tags.map((t) => `${marker} ${t}`).join("\n");
|
|
74
|
+
}
|
|
75
|
+
// A leading directive-prologue statement — `"use client"`, `"use server"`,
|
|
76
|
+
// `"use strict"`. These MUST stay the file's first statement (React Server
|
|
77
|
+
// Components / strict mode break if a comment-bearing header pushes them
|
|
78
|
+
// down in some bundlers), so the header is inserted AFTER them.
|
|
79
|
+
const DIRECTIVE_RE = /^\s*['"]use [\w-]+['"]\s*;?\s*$/;
|
|
80
|
+
/**
|
|
81
|
+
* Insert `header` above the code, but below any shebang and leading
|
|
82
|
+
* directive prologue (`"use client"` etc.) — those must remain the
|
|
83
|
+
* file's first line / first statement.
|
|
84
|
+
*/
|
|
85
|
+
function insertHeader(source, header) {
|
|
86
|
+
const lines = source.split("\n");
|
|
87
|
+
let insertAt = 0;
|
|
88
|
+
if (lines.length > 0 && lines[0].startsWith("#!"))
|
|
89
|
+
insertAt = 1; // shebang
|
|
90
|
+
while (insertAt < lines.length) {
|
|
91
|
+
const line = lines[insertAt];
|
|
92
|
+
if (line.trim() === "" || DIRECTIVE_RE.test(line)) {
|
|
93
|
+
insertAt += 1;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
const before = lines.slice(0, insertAt);
|
|
99
|
+
const after = lines.slice(insertAt);
|
|
100
|
+
return [...before, header, ...after].join("\n");
|
|
101
|
+
}
|
|
102
|
+
async function handler(ctx, input) {
|
|
103
|
+
const abs = join(ctx.repoRoot, input.file);
|
|
104
|
+
if (!existsSync(abs)) {
|
|
105
|
+
return mcpError("FILE_NOT_FOUND", `${input.file} does not exist under the repo root — annotate an existing component file.`);
|
|
106
|
+
}
|
|
107
|
+
// Ghost mode has no in-file SoT — delegate to the registry write path.
|
|
108
|
+
if (isGhost(ctx.repoRoot)) {
|
|
109
|
+
return componentRegisterTool.handler(ctx, {
|
|
110
|
+
file: input.file,
|
|
111
|
+
export_name: input.export_name,
|
|
112
|
+
name: input.export_name,
|
|
113
|
+
category: input.category,
|
|
114
|
+
purpose: input.purpose,
|
|
115
|
+
aliases: input.aliases,
|
|
116
|
+
...(input.workspace !== undefined ? { workspace: input.workspace } : {}),
|
|
117
|
+
...(input.singleton !== undefined ? { singleton: input.singleton } : {}),
|
|
118
|
+
...(input.status !== undefined ? { status: input.status } : {}),
|
|
119
|
+
...(input.uses !== undefined ? { uses: input.uses } : {}),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const config = loadComponentsConfig(ctx.repoRoot);
|
|
123
|
+
if (!hasComponentConfig(config)) {
|
|
124
|
+
return mcpError("NOT_ALLOWED", "this repo has no components: config — nothing to annotate.");
|
|
125
|
+
}
|
|
126
|
+
// D9 — category must be in the owning workspace's enum (server owns it).
|
|
127
|
+
const categories = categoriesForFile(config, input.file);
|
|
128
|
+
if (categories.length > 0 && !categories.includes(input.category)) {
|
|
129
|
+
return mcpError("VALIDATION_FAILED", `category "${input.category}" is not one of this workspace's categories: ${categories.join(", ")}.`);
|
|
130
|
+
}
|
|
131
|
+
let source;
|
|
132
|
+
try {
|
|
133
|
+
source = readFileSync(abs, "utf8");
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
return mcpError("INTERNAL_ERROR", `could not read ${input.file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
137
|
+
}
|
|
138
|
+
// Idempotent: a file that already carries a `@cairn` header is left
|
|
139
|
+
// untouched — re-writing would create a second header block.
|
|
140
|
+
if (parseComponentHeader(source) !== null) {
|
|
141
|
+
return {
|
|
142
|
+
ok: true,
|
|
143
|
+
file: input.file,
|
|
144
|
+
header_written: false,
|
|
145
|
+
already_headered: true,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// D7 — export_name must be a real export. When the language profile
|
|
149
|
+
// can detect exports and the claimed name isn't among them, reject;
|
|
150
|
+
// when detection yields nothing (unknown extension) accept best-effort.
|
|
151
|
+
const exports = extractExportNames(source, input.file);
|
|
152
|
+
if (exports.length > 0 && !exports.includes(input.export_name)) {
|
|
153
|
+
return mcpError("VALIDATION_FAILED", `export_name "${input.export_name}" is not an export of ${input.file}. Real exports: ${exports.join(", ")}.`);
|
|
154
|
+
}
|
|
155
|
+
const form = commentFormFor(input.file);
|
|
156
|
+
const header = formatHeader(input, form);
|
|
157
|
+
const next = insertHeader(source, header);
|
|
158
|
+
try {
|
|
159
|
+
writeFileSync(abs, next, "utf8");
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
return mcpError("INTERNAL_ERROR", `could not write ${input.file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
163
|
+
}
|
|
164
|
+
// Rebuild the derived index + promote any @singleton to a hard §INV.
|
|
165
|
+
let indexed = 0;
|
|
166
|
+
try {
|
|
167
|
+
const emit = emitComponentStore(ctx.repoRoot);
|
|
168
|
+
indexed = emit.indexed;
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// header is written + valid; index rebuild is best-effort here
|
|
172
|
+
// (the pre-commit sweep rebuilds deterministically anyway).
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
ok: true,
|
|
176
|
+
name: input.export_name,
|
|
177
|
+
category: input.category,
|
|
178
|
+
file: input.file,
|
|
179
|
+
header_written: true,
|
|
180
|
+
indexed,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
export const componentAnnotateTool = {
|
|
184
|
+
name: "cairn_component_annotate",
|
|
185
|
+
description: "Register a component by writing its canonical `@cairn` header. Supply judgment only — file, export_name, category (one of the workspace's categories), a one-line purpose, and ≥2 aliases; optional public_props/uses/status/singleton. The server validates the export + category against the code, formats + inserts the header, and rebuilds the registry. Committed projects write the in-file header; ghost projects route to the registry. This is the server-driven replacement for hand-writing `@cairn` headers.",
|
|
186
|
+
inputSchema: componentAnnotateInput,
|
|
187
|
+
handler,
|
|
188
|
+
};
|
|
189
|
+
//# sourceMappingURL=component-annotate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"component-annotate.js","sourceRoot":"","sources":["../../../src/mcp/tools/component-annotate.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp/tools/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { attentionDedupTool } from "./attention-dedup.js";
|
|
2
2
|
import { bootstrapRetryTool } from "./bootstrap-retry.js";
|
|
3
3
|
import { canonicalForTopicTool } from "./canonical-for-topic.js";
|
|
4
|
+
import { componentAnnotateTool } from "./component-annotate.js";
|
|
4
5
|
import { componentGetTool } from "./component-get.js";
|
|
5
6
|
import { componentRegisterTool } from "./component-register.js";
|
|
6
7
|
import { componentReconfirmTool } from "./component-reconfirm.js";
|
|
@@ -46,7 +47,8 @@ export const allTools = [
|
|
|
46
47
|
retireInvariantTool,
|
|
47
48
|
// Write — apply pending review-class migrations inline
|
|
48
49
|
migrateTool,
|
|
49
|
-
// Write —
|
|
50
|
+
// Write — component registry (committed header + ghost §3.8.1)
|
|
51
|
+
componentAnnotateTool,
|
|
50
52
|
componentRegisterTool,
|
|
51
53
|
componentReconfirmTool,
|
|
52
54
|
taskCreateTool,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/mcp/tools/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EACL,cAAc,EACd,WAAW,GACZ,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,MAAM,CAAC,MAAM,QAAQ,GAAqB;IACxC,yBAAyB;IACzB,eAAe;IACf,qBAAqB;IACrB,gBAAgB;IAChB,WAAW;IACX,4BAA4B;IAC5B,qBAAqB;IACrB,gBAAgB;IAChB,4BAA4B;IAC5B,UAAU;IACV,QAAQ;IACR,kBAAkB;IAClB,2CAA2C;IAC3C,kBAAkB;IAClB,mBAAmB;IACnB,uDAAuD;IACvD,WAAW;IACX,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/mcp/tools/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EACL,cAAc,EACd,WAAW,GACZ,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,MAAM,CAAC,MAAM,QAAQ,GAAqB;IACxC,yBAAyB;IACzB,eAAe;IACf,qBAAqB;IACrB,gBAAgB;IAChB,WAAW;IACX,4BAA4B;IAC5B,qBAAqB;IACrB,gBAAgB;IAChB,4BAA4B;IAC5B,UAAU;IACV,QAAQ;IACR,kBAAkB;IAClB,2CAA2C;IAC3C,kBAAkB;IAClB,mBAAmB;IACnB,uDAAuD;IACvD,WAAW;IACX,+DAA+D;IAC/D,qBAAqB;IACrB,qBAAqB;IACrB,sBAAsB;IACtB,cAAc;IACd,gBAAgB;IAChB,cAAc;IACd,qBAAqB;IACrB,sBAAsB;IACtB,UAAU;IACV,qCAAqC;IACrC,oBAAoB;IACpB,kBAAkB;IAClB,2EAA2E;IAC3E,kBAAkB;IAClB,yDAAyD;IACzD,cAAc;IACd,WAAW;IACX,oCAAoC;IACpC,gBAAgB;IAChB,sBAAsB;IACtB,cAAc;IACd,oBAAoB;IACpB,kBAAkB;IAClB,iBAAiB;IACjB,iBAAiB;IACjB,uBAAuB;IACvB,sBAAsB;CACvB,CAAC"}
|
package/dist/mcp/tools/resume.js
CHANGED
|
@@ -17,6 +17,7 @@ import { parse as parseYaml } from "yaml";
|
|
|
17
17
|
import { mcpError } from "../errors.js";
|
|
18
18
|
import { resumeInput } from "../schemas.js";
|
|
19
19
|
import { findCurrentActiveTask, readTaskJournal, } from "../../tasks/index.js";
|
|
20
|
+
import { readTaskSpec } from "../../tasks/spec-reader.js";
|
|
20
21
|
import { cairnDir } from "@isaacriehm/cairn-state";
|
|
21
22
|
async function handler(ctx, input) {
|
|
22
23
|
const taskId = input.task_id ?? findCurrentActiveTask(ctx.repoRoot);
|
|
@@ -44,40 +45,15 @@ async function handler(ctx, input) {
|
|
|
44
45
|
else {
|
|
45
46
|
return mcpError("TASK_NOT_FOUND", `task directory missing in active/ and done/: ${taskId}`);
|
|
46
47
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (fmMatch) {
|
|
57
|
-
try {
|
|
58
|
-
const fm = parseYaml(fmMatch[1] ?? "");
|
|
59
|
-
if (typeof fm.title === "string")
|
|
60
|
-
title = fm.title;
|
|
61
|
-
if (Array.isArray(fm.in_scope_decisions)) {
|
|
62
|
-
inScopeDecisions = fm.in_scope_decisions.filter((x) => typeof x === "string");
|
|
63
|
-
}
|
|
64
|
-
if (Array.isArray(fm.in_scope_invariants)) {
|
|
65
|
-
inScopeInvariants = fm.in_scope_invariants.filter((x) => typeof x === "string");
|
|
66
|
-
}
|
|
67
|
-
if (Array.isArray(fm.target_path_globs)) {
|
|
68
|
-
targetPathGlobs = fm.target_path_globs.filter((x) => typeof x === "string");
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
// malformed frontmatter — fall through with defaults
|
|
73
|
-
}
|
|
74
|
-
const body = fmMatch[2] ?? "";
|
|
75
|
-
const goalMatch = body.match(/##\s+Goal\s*\r?\n+([\s\S]*?)(?:\r?\n##\s+|$)/);
|
|
76
|
-
if (goalMatch && goalMatch[1] !== undefined) {
|
|
77
|
-
goal = goalMatch[1].trim();
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
48
|
+
// Shared spec reader (tasks/spec-reader.ts) — same parse the
|
|
49
|
+
// UserPromptSubmit working-header uses. Preserve resume's historical
|
|
50
|
+
// fallbacks: title → taskId, goal → "(spec not found)" when absent.
|
|
51
|
+
const spec = readTaskSpec(taskDir);
|
|
52
|
+
const title = spec !== null && spec.title.length > 0 ? spec.title : taskId;
|
|
53
|
+
const goal = spec !== null && spec.goal.length > 0 ? spec.goal : "(spec not found)";
|
|
54
|
+
const inScopeDecisions = spec?.inScopeDecisions ?? [];
|
|
55
|
+
const inScopeInvariants = spec?.inScopeInvariants ?? [];
|
|
56
|
+
const targetPathGlobs = spec?.targetPathGlobs ?? [];
|
|
81
57
|
const journal = readTaskJournal(ctx.repoRoot, taskId, scope);
|
|
82
58
|
const cap = Math.max(1, Math.min(50, input.max_entries ?? 7));
|
|
83
59
|
const recent = journal.slice(-cap);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resume.js","sourceRoot":"","sources":["../../../src/mcp/tools/resume.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EACL,qBAAqB,EACrB,eAAe,GAEhB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"resume.js","sourceRoot":"","sources":["../../../src/mcp/tools/resume.ts"],"names":[],"mappings":"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"}
|
package/dist/session/index.d.ts
CHANGED
|
@@ -12,3 +12,5 @@ export { cleanupSession, ensureSessionDir, gcStaleSessions, resolveSessionId, }
|
|
|
12
12
|
export type { EnsureSessionDirArgs, EnsureSessionDirResult, GcStaleSessionsArgs, GcStaleSessionsResult, SessionIdSource, SessionMeta, } from "./id.js";
|
|
13
13
|
export { eventsMarkerPath, readEventsMarker, seedEventsMarker, stampEventsPoll, } from "./events-marker.js";
|
|
14
14
|
export type { EventsMarker } from "./events-marker.js";
|
|
15
|
+
export { fingerprintText, filterUnshownIds, getSeenFingerprint, hasShownId, markShownIds, setSeenFingerprint, } from "./seen.js";
|
|
16
|
+
export { appendTouched, readTouched } from "./touched.js";
|
package/dist/session/index.js
CHANGED
|
@@ -10,4 +10,6 @@
|
|
|
10
10
|
*/
|
|
11
11
|
export { cleanupSession, ensureSessionDir, gcStaleSessions, resolveSessionId, } from "./id.js";
|
|
12
12
|
export { eventsMarkerPath, readEventsMarker, seedEventsMarker, stampEventsPoll, } from "./events-marker.js";
|
|
13
|
+
export { fingerprintText, filterUnshownIds, getSeenFingerprint, hasShownId, markShownIds, setSeenFingerprint, } from "./seen.js";
|
|
14
|
+
export { appendTouched, readTouched } from "./touched.js";
|
|
13
15
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/session/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,gBAAgB,GACjB,MAAM,SAAS,CAAC;AASjB,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,GAChB,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/session/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,gBAAgB,GACjB,MAAM,SAAS,CAAC;AASjB,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,GAChB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,kBAAkB,GACnB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-session dedup store — `.cairn/sessions/<id>/seen.json`.
|
|
3
|
+
*
|
|
4
|
+
* The context engine injects scoped ground state as the agent works
|
|
5
|
+
* (working header, DEC/INV legends, component slices). Without dedup
|
|
6
|
+
* the injectors re-send unchanged context every turn and *become* the
|
|
7
|
+
* context-bloat problem they exist to solve. This module is the
|
|
8
|
+
* single source of "already shown this session":
|
|
9
|
+
*
|
|
10
|
+
* - `fingerprints` — keyed text hashes (e.g. `working-header`). An
|
|
11
|
+
* injector re-emits only when the hash changed.
|
|
12
|
+
* - `shownIds` — flat set of opaque ids already surfaced once
|
|
13
|
+
* (DEC-/INV- ids, component names, `annotate:<file>` debounce keys).
|
|
14
|
+
*
|
|
15
|
+
* Pure-FS read/modify/write. A missing or malformed file is treated as
|
|
16
|
+
* empty — callers run inside hooks that must never throw. The path is
|
|
17
|
+
* routed through `cairnDir` so `CAIRN_HOME` redirection holds
|
|
18
|
+
* (smoke-cairn-home).
|
|
19
|
+
*
|
|
20
|
+
* GC: the per-session dir (and this file with it) is removed by
|
|
21
|
+
* `gcStaleSessions` / `cleanupSession` (session/id.ts).
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Short content fingerprint for change-detection. sha256 hex sliced to
|
|
25
|
+
* 12 chars — collision-irrelevant for "did this exact text change".
|
|
26
|
+
*/
|
|
27
|
+
export declare function fingerprintText(text: string): string;
|
|
28
|
+
/** Last fingerprint stored under `key`, or null when none. */
|
|
29
|
+
export declare function getSeenFingerprint(repoRoot: string, sessionId: string, key: string): string | null;
|
|
30
|
+
/** Record `fp` as the fingerprint for `key` (overwrites any prior). */
|
|
31
|
+
export declare function setSeenFingerprint(repoRoot: string, sessionId: string, key: string, fp: string): void;
|
|
32
|
+
/** Subset of `ids` not yet marked shown this session (order preserved). */
|
|
33
|
+
export declare function filterUnshownIds(repoRoot: string, sessionId: string, ids: string[]): string[];
|
|
34
|
+
/** Mark each id in `ids` as shown this session (idempotent, deduped). */
|
|
35
|
+
export declare function markShownIds(repoRoot: string, sessionId: string, ids: string[]): void;
|
|
36
|
+
/** True when `id` has already been surfaced this session. */
|
|
37
|
+
export declare function hasShownId(repoRoot: string, sessionId: string, id: string): boolean;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-session dedup store — `.cairn/sessions/<id>/seen.json`.
|
|
3
|
+
*
|
|
4
|
+
* The context engine injects scoped ground state as the agent works
|
|
5
|
+
* (working header, DEC/INV legends, component slices). Without dedup
|
|
6
|
+
* the injectors re-send unchanged context every turn and *become* the
|
|
7
|
+
* context-bloat problem they exist to solve. This module is the
|
|
8
|
+
* single source of "already shown this session":
|
|
9
|
+
*
|
|
10
|
+
* - `fingerprints` — keyed text hashes (e.g. `working-header`). An
|
|
11
|
+
* injector re-emits only when the hash changed.
|
|
12
|
+
* - `shownIds` — flat set of opaque ids already surfaced once
|
|
13
|
+
* (DEC-/INV- ids, component names, `annotate:<file>` debounce keys).
|
|
14
|
+
*
|
|
15
|
+
* Pure-FS read/modify/write. A missing or malformed file is treated as
|
|
16
|
+
* empty — callers run inside hooks that must never throw. The path is
|
|
17
|
+
* routed through `cairnDir` so `CAIRN_HOME` redirection holds
|
|
18
|
+
* (smoke-cairn-home).
|
|
19
|
+
*
|
|
20
|
+
* GC: the per-session dir (and this file with it) is removed by
|
|
21
|
+
* `gcStaleSessions` / `cleanupSession` (session/id.ts).
|
|
22
|
+
*/
|
|
23
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
24
|
+
import { dirname } from "node:path";
|
|
25
|
+
import { bodyContentHash, cairnDir } from "@isaacriehm/cairn-state";
|
|
26
|
+
function seenPath(repoRoot, sessionId) {
|
|
27
|
+
return cairnDir(repoRoot, "sessions", sessionId, "seen.json");
|
|
28
|
+
}
|
|
29
|
+
function readSeen(repoRoot, sessionId) {
|
|
30
|
+
const empty = { fingerprints: {}, shownIds: [] };
|
|
31
|
+
const path = seenPath(repoRoot, sessionId);
|
|
32
|
+
if (!existsSync(path))
|
|
33
|
+
return empty;
|
|
34
|
+
let parsed;
|
|
35
|
+
try {
|
|
36
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return empty;
|
|
40
|
+
}
|
|
41
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
42
|
+
return empty;
|
|
43
|
+
const p = parsed;
|
|
44
|
+
const fingerprints = typeof p.fingerprints === "object" && p.fingerprints !== null
|
|
45
|
+
? p.fingerprints
|
|
46
|
+
: {};
|
|
47
|
+
const shownIds = Array.isArray(p.shownIds)
|
|
48
|
+
? p.shownIds.filter((x) => typeof x === "string")
|
|
49
|
+
: [];
|
|
50
|
+
return { fingerprints, shownIds };
|
|
51
|
+
}
|
|
52
|
+
function writeSeen(repoRoot, sessionId, data) {
|
|
53
|
+
const path = seenPath(repoRoot, sessionId);
|
|
54
|
+
try {
|
|
55
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
56
|
+
writeFileSync(path, `${JSON.stringify(data, null, 2)}\n`, "utf8");
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// best-effort — never throw inside a hook
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Short content fingerprint for change-detection. sha256 hex sliced to
|
|
64
|
+
* 12 chars — collision-irrelevant for "did this exact text change".
|
|
65
|
+
*/
|
|
66
|
+
export function fingerprintText(text) {
|
|
67
|
+
return bodyContentHash(text).slice(0, 12);
|
|
68
|
+
}
|
|
69
|
+
/** Last fingerprint stored under `key`, or null when none. */
|
|
70
|
+
export function getSeenFingerprint(repoRoot, sessionId, key) {
|
|
71
|
+
const seen = readSeen(repoRoot, sessionId);
|
|
72
|
+
return seen.fingerprints[key] ?? null;
|
|
73
|
+
}
|
|
74
|
+
/** Record `fp` as the fingerprint for `key` (overwrites any prior). */
|
|
75
|
+
export function setSeenFingerprint(repoRoot, sessionId, key, fp) {
|
|
76
|
+
const seen = readSeen(repoRoot, sessionId);
|
|
77
|
+
seen.fingerprints[key] = fp;
|
|
78
|
+
writeSeen(repoRoot, sessionId, seen);
|
|
79
|
+
}
|
|
80
|
+
/** Subset of `ids` not yet marked shown this session (order preserved). */
|
|
81
|
+
export function filterUnshownIds(repoRoot, sessionId, ids) {
|
|
82
|
+
const seen = readSeen(repoRoot, sessionId);
|
|
83
|
+
const shown = new Set(seen.shownIds);
|
|
84
|
+
const out = [];
|
|
85
|
+
const local = new Set();
|
|
86
|
+
for (const id of ids) {
|
|
87
|
+
if (shown.has(id) || local.has(id))
|
|
88
|
+
continue;
|
|
89
|
+
local.add(id);
|
|
90
|
+
out.push(id);
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
/** Mark each id in `ids` as shown this session (idempotent, deduped). */
|
|
95
|
+
export function markShownIds(repoRoot, sessionId, ids) {
|
|
96
|
+
if (ids.length === 0)
|
|
97
|
+
return;
|
|
98
|
+
const seen = readSeen(repoRoot, sessionId);
|
|
99
|
+
const set = new Set(seen.shownIds);
|
|
100
|
+
for (const id of ids)
|
|
101
|
+
set.add(id);
|
|
102
|
+
seen.shownIds = [...set];
|
|
103
|
+
writeSeen(repoRoot, sessionId, seen);
|
|
104
|
+
}
|
|
105
|
+
/** True when `id` has already been surfaced this session. */
|
|
106
|
+
export function hasShownId(repoRoot, sessionId, id) {
|
|
107
|
+
const seen = readSeen(repoRoot, sessionId);
|
|
108
|
+
return seen.shownIds.includes(id);
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=seen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seen.js","sourceRoot":"","sources":["../../src/session/seen.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-session touched-file set — `.cairn/sessions/<id>/touched.json`.
|
|
3
|
+
*
|
|
4
|
+
* The PostToolUse(Write|Edit) hook appends every repo-relative path the
|
|
5
|
+
* agent writes this session (D6). The Stop capture-gate reads the set,
|
|
6
|
+
* filters to component-dir files missing a `@cairn` header, and surfaces
|
|
7
|
+
* a fully-specified `cairn_component_annotate` ask. This is the source
|
|
8
|
+
* for "what did this session touch" — NOT the task journal (which only
|
|
9
|
+
* carries explicitly-journaled work).
|
|
10
|
+
*
|
|
11
|
+
* Pure-FS dedup set. Missing/malformed file → empty. Routed through
|
|
12
|
+
* `cairnDir` (smoke-cairn-home). GC'd with the per-session dir.
|
|
13
|
+
*/
|
|
14
|
+
/** Repo-relative POSIX paths the agent has written this session. */
|
|
15
|
+
export declare function readTouched(repoRoot: string, sessionId: string): string[];
|
|
16
|
+
/** Add `relPath` (repo-relative POSIX) to the session's touched set. */
|
|
17
|
+
export declare function appendTouched(repoRoot: string, sessionId: string, relPath: string): void;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-session touched-file set — `.cairn/sessions/<id>/touched.json`.
|
|
3
|
+
*
|
|
4
|
+
* The PostToolUse(Write|Edit) hook appends every repo-relative path the
|
|
5
|
+
* agent writes this session (D6). The Stop capture-gate reads the set,
|
|
6
|
+
* filters to component-dir files missing a `@cairn` header, and surfaces
|
|
7
|
+
* a fully-specified `cairn_component_annotate` ask. This is the source
|
|
8
|
+
* for "what did this session touch" — NOT the task journal (which only
|
|
9
|
+
* carries explicitly-journaled work).
|
|
10
|
+
*
|
|
11
|
+
* Pure-FS dedup set. Missing/malformed file → empty. Routed through
|
|
12
|
+
* `cairnDir` (smoke-cairn-home). GC'd with the per-session dir.
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { dirname } from "node:path";
|
|
16
|
+
import { cairnDir } from "@isaacriehm/cairn-state";
|
|
17
|
+
function touchedPath(repoRoot, sessionId) {
|
|
18
|
+
return cairnDir(repoRoot, "sessions", sessionId, "touched.json");
|
|
19
|
+
}
|
|
20
|
+
/** Repo-relative POSIX paths the agent has written this session. */
|
|
21
|
+
export function readTouched(repoRoot, sessionId) {
|
|
22
|
+
const path = touchedPath(repoRoot, sessionId);
|
|
23
|
+
if (!existsSync(path))
|
|
24
|
+
return [];
|
|
25
|
+
let parsed;
|
|
26
|
+
try {
|
|
27
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
33
|
+
return [];
|
|
34
|
+
const p = parsed;
|
|
35
|
+
return Array.isArray(p.paths)
|
|
36
|
+
? p.paths.filter((x) => typeof x === "string")
|
|
37
|
+
: [];
|
|
38
|
+
}
|
|
39
|
+
/** Add `relPath` (repo-relative POSIX) to the session's touched set. */
|
|
40
|
+
export function appendTouched(repoRoot, sessionId, relPath) {
|
|
41
|
+
if (relPath.length === 0)
|
|
42
|
+
return;
|
|
43
|
+
const norm = relPath.replace(/\\/g, "/");
|
|
44
|
+
const existing = readTouched(repoRoot, sessionId);
|
|
45
|
+
if (existing.includes(norm))
|
|
46
|
+
return;
|
|
47
|
+
existing.push(norm);
|
|
48
|
+
const path = touchedPath(repoRoot, sessionId);
|
|
49
|
+
try {
|
|
50
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
51
|
+
writeFileSync(path, `${JSON.stringify({ paths: existing }, null, 2)}\n`, "utf8");
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// best-effort — never throw inside a hook
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=touched.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"touched.js","sourceRoot":"","sources":["../../src/session/touched.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAMnD,SAAS,WAAW,CAAC,QAAgB,EAAE,SAAiB;IACtD,OAAO,QAAQ,CAAC,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC;AACnE,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,SAAiB;IAC7D,MAAM,IAAI,GAAG,WAAW,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAC9C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IAC7D,MAAM,CAAC,GAAG,MAA8B,CAAC;IACzC,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;QAC3B,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;QAC3D,CAAC,CAAC,EAAE,CAAC;AACT,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,aAAa,CAC3B,QAAgB,EAChB,SAAiB,EACjB,OAAe;IAEf,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IACjC,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,WAAW,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAClD,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO;IACpC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpB,MAAM,IAAI,GAAG,WAAW,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAC9C,IAAI,CAAC;QACH,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,aAAa,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnF,CAAC;IAAC,MAAM,CAAC;QACP,0CAA0C;IAC5C,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared `spec.tightened.md` frontmatter + `## Goal` reader.
|
|
3
|
+
*
|
|
4
|
+
* Factored out of `cairn_resume` (mcp/tools/resume.ts) so both the
|
|
5
|
+
* resume payload AND the UserPromptSubmit working-header (context
|
|
6
|
+
* engine, stage 1) read the spec the same way — one parse, no drift.
|
|
7
|
+
* The parse is verbatim from the original resume.ts implementation.
|
|
8
|
+
*
|
|
9
|
+
* Returns null when the task has no `spec.tightened.md` yet (e.g. a
|
|
10
|
+
* task still in `queued`/`tightening`). Malformed frontmatter degrades
|
|
11
|
+
* to defaults rather than throwing — callers run inside hooks that must
|
|
12
|
+
* never crash the agent loop.
|
|
13
|
+
*/
|
|
14
|
+
export interface TaskSpec {
|
|
15
|
+
title: string;
|
|
16
|
+
goal: string;
|
|
17
|
+
inScopeDecisions: string[];
|
|
18
|
+
inScopeInvariants: string[];
|
|
19
|
+
targetPathGlobs: string[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Read `<taskDir>/spec.tightened.md`'s frontmatter (`title`,
|
|
23
|
+
* `in_scope_decisions[]`, `in_scope_invariants[]`, `target_path_globs[]`)
|
|
24
|
+
* and the body's `## Goal` section. `taskDir` is the absolute task
|
|
25
|
+
* directory (active or done). Returns null when the spec file is absent.
|
|
26
|
+
*/
|
|
27
|
+
export declare function readTaskSpec(taskDir: string): TaskSpec | null;
|