@isaacriehm/cairn-core 0.4.2 → 0.4.3
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/attention/bulk-accept.js +1 -1
- package/dist/attention/bulk-accept.js.map +1 -1
- package/dist/attention/dedup.d.ts +2 -2
- package/dist/attention/dedup.js +15 -4
- package/dist/attention/dedup.js.map +1 -1
- package/dist/attention/index.d.ts +1 -0
- package/dist/attention/index.js +1 -0
- package/dist/attention/index.js.map +1 -1
- package/dist/attention/restore.js +1 -1
- package/dist/attention/restore.js.map +1 -1
- package/dist/attention/serve/api.d.ts +23 -0
- package/dist/attention/serve/api.js +344 -0
- package/dist/attention/serve/api.js.map +1 -0
- package/dist/attention/serve/index.d.ts +62 -0
- package/dist/attention/serve/index.js +205 -0
- package/dist/attention/serve/index.js.map +1 -0
- package/dist/decision-capture/id.d.ts +62 -25
- package/dist/decision-capture/id.js +78 -57
- package/dist/decision-capture/id.js.map +1 -1
- package/dist/decision-capture/index.d.ts +3 -3
- package/dist/decision-capture/index.js +3 -3
- package/dist/decision-capture/index.js.map +1 -1
- package/dist/ground/schemas.js +2 -2
- package/dist/ground/schemas.js.map +1 -1
- package/dist/ground/scope-index.js +2 -2
- package/dist/ground/scope-index.js.map +1 -1
- package/dist/hooks/post-tool-use/citation-scanner.d.ts +1 -1
- package/dist/hooks/post-tool-use/citation-scanner.js +3 -3
- package/dist/hooks/post-tool-use/citation-scanner.js.map +1 -1
- package/dist/hooks/post-tool-use/copy-scanner.js +1 -1
- package/dist/hooks/post-tool-use/copy-scanner.js.map +1 -1
- package/dist/hooks/post-tool-use/legend-builder.d.ts +1 -1
- package/dist/hooks/post-tool-use/legend-builder.js +2 -2
- package/dist/hooks/post-tool-use/legend-builder.js.map +1 -1
- package/dist/init/ingest-docs.js +10 -6
- package/dist/init/ingest-docs.js.map +1 -1
- package/dist/init/mapper-parallel.js +1 -1
- package/dist/init/mapper-parallel.js.map +1 -1
- package/dist/init/rules-merge/ingest.js +9 -2
- package/dist/init/rules-merge/ingest.js.map +1 -1
- package/dist/init/source-comments/ingest.js +16 -4
- package/dist/init/source-comments/ingest.js.map +1 -1
- package/dist/mcp/history/summarizer.js +1 -1
- package/dist/mcp/history/summarizer.js.map +1 -1
- package/dist/mcp/schemas.d.ts +1 -1
- package/dist/mcp/schemas.js +5 -5
- package/dist/mcp/schemas.js.map +1 -1
- package/dist/mcp/tools/archive.js +1 -1
- package/dist/mcp/tools/archive.js.map +1 -1
- package/dist/mcp/tools/attention-restore.js +1 -1
- package/dist/mcp/tools/attention-restore.js.map +1 -1
- package/dist/mcp/tools/attention-serve.d.ts +23 -0
- package/dist/mcp/tools/attention-serve.js +78 -0
- package/dist/mcp/tools/attention-serve.js.map +1 -0
- package/dist/mcp/tools/attention-wait.d.ts +18 -0
- package/dist/mcp/tools/attention-wait.js +74 -0
- package/dist/mcp/tools/attention-wait.js.map +1 -0
- package/dist/mcp/tools/index.js +4 -0
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/mcp/tools/record-decision.js +14 -2
- package/dist/mcp/tools/record-decision.js.map +1 -1
- package/dist/mcp/tools/resolve-attention.js +2 -2
- package/dist/mcp/tools/resolve-attention.js.map +1 -1
- package/package.json +1 -1
- package/templates/attention-ui/app.css +406 -0
- package/templates/attention-ui/app.js +384 -0
- package/templates/attention-ui/index.html +56 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-based DEC-draft triage GUI.
|
|
3
|
+
*
|
|
4
|
+
* Spawned by `cairn attention serve` (CLI) or
|
|
5
|
+
* `cairn_attention_serve` (MCP). Operator drains the inbox in the
|
|
6
|
+
* browser instead of through `AskUserQuestion` round-trips, then
|
|
7
|
+
* clicks "I'm done" — the server writes a sentinel at
|
|
8
|
+
* `.cairn/cache/attention-done.json` that the caller polls (via
|
|
9
|
+
* `cairn_attention_wait` or by tailing the file).
|
|
10
|
+
*
|
|
11
|
+
* Why a GUI: at >15 drafts the inline `AskUserQuestion` flow burns
|
|
12
|
+
* `cairn_decision_get` calls per draft and 4-cap-per-question batches
|
|
13
|
+
* the operator through dozens of MCP turns. The browser does all the
|
|
14
|
+
* I/O directly against `.cairn/`, dropping per-triage round-trips to
|
|
15
|
+
* zero.
|
|
16
|
+
*
|
|
17
|
+
* Mechanics:
|
|
18
|
+
* - HTTP server on a free port (or operator-supplied), bound to
|
|
19
|
+
* 127.0.0.1 so the surface is local-only.
|
|
20
|
+
* - JSON API mirrors the existing attention handlers (bulk-accept,
|
|
21
|
+
* dedup, resolve, restore) so all writes funnel through the same
|
|
22
|
+
* `withWriteLock` path the MCP tools use.
|
|
23
|
+
* - Static SPA bundle (vanilla HTML+JS+CSS) under
|
|
24
|
+
* `cairn-core/templates/attention-ui/` — no build step.
|
|
25
|
+
* - Idle timeout: 10 min default, reset by `/api/heartbeat`. Server
|
|
26
|
+
* shuts down when idle exceeds the timeout or on `/api/done`.
|
|
27
|
+
*/
|
|
28
|
+
/** Read-only accessor for `cairn_attention_wait` and tests. */
|
|
29
|
+
export declare function getActiveAttentionServer(repoRoot: string): AttentionServeHandle | undefined;
|
|
30
|
+
export interface AttentionServeOptions {
|
|
31
|
+
repoRoot: string;
|
|
32
|
+
/** Listen port. Pass 0 to let the OS pick. */
|
|
33
|
+
port: number;
|
|
34
|
+
/** Idle (no heartbeat / no API activity) before auto-shutdown. */
|
|
35
|
+
idleTimeoutMs?: number;
|
|
36
|
+
/** Optional caller-supplied abort signal. */
|
|
37
|
+
signal?: AbortSignal;
|
|
38
|
+
}
|
|
39
|
+
export interface AttentionServeHandle {
|
|
40
|
+
port: number;
|
|
41
|
+
url: string;
|
|
42
|
+
sentinelPath: string;
|
|
43
|
+
/** Resolves once the server has shut down (operator clicked Done or idled out). */
|
|
44
|
+
done: Promise<DoneState>;
|
|
45
|
+
/** Force shutdown — typically wired to SIGINT / SIGTERM in CLI. */
|
|
46
|
+
close: () => Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
export interface DoneState {
|
|
49
|
+
reason: "done" | "idle" | "abort";
|
|
50
|
+
accepted: number;
|
|
51
|
+
rejected: number;
|
|
52
|
+
merged: number;
|
|
53
|
+
edited: number;
|
|
54
|
+
startedAt: string;
|
|
55
|
+
endedAt: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Boot the triage server. Returns once the listener is ready;
|
|
59
|
+
* `handle.done` resolves when the operator finishes or the server
|
|
60
|
+
* idles out.
|
|
61
|
+
*/
|
|
62
|
+
export declare function startAttentionServer(opts: AttentionServeOptions): Promise<AttentionServeHandle>;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-based DEC-draft triage GUI.
|
|
3
|
+
*
|
|
4
|
+
* Spawned by `cairn attention serve` (CLI) or
|
|
5
|
+
* `cairn_attention_serve` (MCP). Operator drains the inbox in the
|
|
6
|
+
* browser instead of through `AskUserQuestion` round-trips, then
|
|
7
|
+
* clicks "I'm done" — the server writes a sentinel at
|
|
8
|
+
* `.cairn/cache/attention-done.json` that the caller polls (via
|
|
9
|
+
* `cairn_attention_wait` or by tailing the file).
|
|
10
|
+
*
|
|
11
|
+
* Why a GUI: at >15 drafts the inline `AskUserQuestion` flow burns
|
|
12
|
+
* `cairn_decision_get` calls per draft and 4-cap-per-question batches
|
|
13
|
+
* the operator through dozens of MCP turns. The browser does all the
|
|
14
|
+
* I/O directly against `.cairn/`, dropping per-triage round-trips to
|
|
15
|
+
* zero.
|
|
16
|
+
*
|
|
17
|
+
* Mechanics:
|
|
18
|
+
* - HTTP server on a free port (or operator-supplied), bound to
|
|
19
|
+
* 127.0.0.1 so the surface is local-only.
|
|
20
|
+
* - JSON API mirrors the existing attention handlers (bulk-accept,
|
|
21
|
+
* dedup, resolve, restore) so all writes funnel through the same
|
|
22
|
+
* `withWriteLock` path the MCP tools use.
|
|
23
|
+
* - Static SPA bundle (vanilla HTML+JS+CSS) under
|
|
24
|
+
* `cairn-core/templates/attention-ui/` — no build step.
|
|
25
|
+
* - Idle timeout: 10 min default, reset by `/api/heartbeat`. Server
|
|
26
|
+
* shuts down when idle exceeds the timeout or on `/api/done`.
|
|
27
|
+
*/
|
|
28
|
+
import { createServer } from "node:http";
|
|
29
|
+
import { dirname, join } from "node:path";
|
|
30
|
+
import { fileURLToPath } from "node:url";
|
|
31
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
|
|
32
|
+
import { logger } from "../../logger.js";
|
|
33
|
+
import { handleApi } from "./api.js";
|
|
34
|
+
const log = logger("attention.serve");
|
|
35
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
/**
|
|
37
|
+
* dist/attention/serve/index.js → walk up to package root, then into
|
|
38
|
+
* templates/attention-ui/. Bundled layout co-locates templates as a
|
|
39
|
+
* sibling of dist/cli.mjs (mirrors the seed.ts pattern).
|
|
40
|
+
*/
|
|
41
|
+
const TEMPLATES_ROOT = typeof __CAIRN_BUNDLED__ !== "undefined" && __CAIRN_BUNDLED__
|
|
42
|
+
? join(HERE, "templates", "attention-ui")
|
|
43
|
+
: join(HERE, "..", "..", "..", "templates", "attention-ui");
|
|
44
|
+
const DEFAULT_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
|
|
45
|
+
const DONE_TIMEOUT_GRACE_MS = 500;
|
|
46
|
+
/**
|
|
47
|
+
* Per-repoRoot live-server registry. The MCP server is a single
|
|
48
|
+
* long-lived process; both `cairn_attention_serve` and
|
|
49
|
+
* `cairn_attention_wait` share this map so wait can await the live
|
|
50
|
+
* `done` promise instead of polling.
|
|
51
|
+
*/
|
|
52
|
+
const liveServers = new Map();
|
|
53
|
+
/** Read-only accessor for `cairn_attention_wait` and tests. */
|
|
54
|
+
export function getActiveAttentionServer(repoRoot) {
|
|
55
|
+
return liveServers.get(repoRoot);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Boot the triage server. Returns once the listener is ready;
|
|
59
|
+
* `handle.done` resolves when the operator finishes or the server
|
|
60
|
+
* idles out.
|
|
61
|
+
*/
|
|
62
|
+
export async function startAttentionServer(opts) {
|
|
63
|
+
const idleTimeoutMs = opts.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
64
|
+
const sentinelPath = join(opts.repoRoot, ".cairn", "cache", "attention-done.json");
|
|
65
|
+
// Pre-clear any stale sentinel so the caller's wait loop isn't
|
|
66
|
+
// tricked by a previous run's payload.
|
|
67
|
+
try {
|
|
68
|
+
rmSync(sentinelPath, { force: true });
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
/* best-effort */
|
|
72
|
+
}
|
|
73
|
+
const counters = { accepted: 0, rejected: 0, merged: 0, edited: 0 };
|
|
74
|
+
const startedAt = new Date().toISOString();
|
|
75
|
+
let lastActivity = Date.now();
|
|
76
|
+
const touch = () => {
|
|
77
|
+
lastActivity = Date.now();
|
|
78
|
+
};
|
|
79
|
+
let resolveDone;
|
|
80
|
+
const donePromise = new Promise((resolve) => {
|
|
81
|
+
resolveDone = resolve;
|
|
82
|
+
});
|
|
83
|
+
let server;
|
|
84
|
+
let idleTimer;
|
|
85
|
+
let shutdownStarted = false;
|
|
86
|
+
const writeSentinel = (reason) => {
|
|
87
|
+
const state = {
|
|
88
|
+
reason,
|
|
89
|
+
...counters,
|
|
90
|
+
startedAt,
|
|
91
|
+
endedAt: new Date().toISOString(),
|
|
92
|
+
};
|
|
93
|
+
try {
|
|
94
|
+
mkdirSync(dirname(sentinelPath), { recursive: true });
|
|
95
|
+
writeFileSync(sentinelPath, JSON.stringify(state, null, 2), "utf8");
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
log.warn({ err: err instanceof Error ? err.message : String(err) }, "failed to write sentinel");
|
|
99
|
+
}
|
|
100
|
+
return state;
|
|
101
|
+
};
|
|
102
|
+
const beginShutdown = (reason) => {
|
|
103
|
+
if (shutdownStarted)
|
|
104
|
+
return;
|
|
105
|
+
shutdownStarted = true;
|
|
106
|
+
clearInterval(idleTimer);
|
|
107
|
+
const state = writeSentinel(reason);
|
|
108
|
+
setTimeout(() => {
|
|
109
|
+
server.close(() => {
|
|
110
|
+
resolveDone(state);
|
|
111
|
+
});
|
|
112
|
+
}, DONE_TIMEOUT_GRACE_MS);
|
|
113
|
+
};
|
|
114
|
+
server = createServer((req, res) => {
|
|
115
|
+
void handleRequest(req, res, {
|
|
116
|
+
repoRoot: opts.repoRoot,
|
|
117
|
+
counters,
|
|
118
|
+
touch,
|
|
119
|
+
onDone: () => beginShutdown("done"),
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
await new Promise((resolve, reject) => {
|
|
123
|
+
server.once("error", reject);
|
|
124
|
+
server.listen(opts.port, "127.0.0.1", () => {
|
|
125
|
+
server.off("error", reject);
|
|
126
|
+
resolve();
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
const addr = server.address();
|
|
130
|
+
const port = typeof addr === "object" && addr !== null ? addr.port : opts.port;
|
|
131
|
+
const url = `http://127.0.0.1:${port}/`;
|
|
132
|
+
idleTimer = setInterval(() => {
|
|
133
|
+
if (Date.now() - lastActivity >= idleTimeoutMs) {
|
|
134
|
+
log.info({ idleTimeoutMs }, "attention server idle — shutting down");
|
|
135
|
+
beginShutdown("idle");
|
|
136
|
+
}
|
|
137
|
+
}, 30_000);
|
|
138
|
+
if (opts.signal !== undefined) {
|
|
139
|
+
if (opts.signal.aborted) {
|
|
140
|
+
beginShutdown("abort");
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
opts.signal.addEventListener("abort", () => beginShutdown("abort"), {
|
|
144
|
+
once: true,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
log.info({ port, url, sentinelPath }, "attention server listening");
|
|
149
|
+
const handle = {
|
|
150
|
+
port,
|
|
151
|
+
url,
|
|
152
|
+
sentinelPath,
|
|
153
|
+
done: donePromise,
|
|
154
|
+
close: async () => {
|
|
155
|
+
beginShutdown("abort");
|
|
156
|
+
await donePromise;
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
liveServers.set(opts.repoRoot, handle);
|
|
160
|
+
void donePromise.finally(() => liveServers.delete(opts.repoRoot));
|
|
161
|
+
return handle;
|
|
162
|
+
}
|
|
163
|
+
async function handleRequest(req, res, ctx) {
|
|
164
|
+
ctx.touch();
|
|
165
|
+
const url = req.url ?? "/";
|
|
166
|
+
if (url === "/" || url === "/index.html") {
|
|
167
|
+
return serveStatic(res, "index.html", "text/html; charset=utf-8");
|
|
168
|
+
}
|
|
169
|
+
if (url === "/static/app.js") {
|
|
170
|
+
return serveStatic(res, "app.js", "application/javascript; charset=utf-8");
|
|
171
|
+
}
|
|
172
|
+
if (url === "/static/app.css") {
|
|
173
|
+
return serveStatic(res, "app.css", "text/css; charset=utf-8");
|
|
174
|
+
}
|
|
175
|
+
if (url.startsWith("/api/")) {
|
|
176
|
+
return handleApi(req, res, ctx);
|
|
177
|
+
}
|
|
178
|
+
res.statusCode = 404;
|
|
179
|
+
res.setHeader("content-type", "text/plain");
|
|
180
|
+
res.end("not found");
|
|
181
|
+
}
|
|
182
|
+
function serveStatic(res, filename, contentType) {
|
|
183
|
+
const path = join(TEMPLATES_ROOT, filename);
|
|
184
|
+
if (!existsSync(path)) {
|
|
185
|
+
res.statusCode = 500;
|
|
186
|
+
res.setHeader("content-type", "text/plain");
|
|
187
|
+
res.end(`attention-ui template missing: ${filename}`);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
let body;
|
|
191
|
+
try {
|
|
192
|
+
body = readFileSync(path, "utf8");
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
res.statusCode = 500;
|
|
196
|
+
res.setHeader("content-type", "text/plain");
|
|
197
|
+
res.end(`attention-ui template read failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
res.statusCode = 200;
|
|
201
|
+
res.setHeader("content-type", contentType);
|
|
202
|
+
res.setHeader("cache-control", "no-store");
|
|
203
|
+
res.end(body);
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/attention/serve/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,38 +1,75 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Content-addressed id derivation for decisions and invariants.
|
|
3
3
|
*
|
|
4
|
-
* Decisions:
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Decisions: `DEC-<hash>` where `<hash>` is the first 7 hex chars of
|
|
5
|
+
* sha256(canonicalized input). Stable across clones — two devs
|
|
6
|
+
* that capture the same source comment in the same file produce
|
|
7
|
+
* the same id, so concurrent adoption runs do not collide on merge.
|
|
8
8
|
*
|
|
9
|
-
* Invariants:
|
|
10
|
-
* invariants directly to ground state (no `_inbox/` flow — they auto-promote
|
|
11
|
-
* from the constraint classifier; operator edits / supersedes after the
|
|
12
|
-
* fact).
|
|
9
|
+
* Invariants: same shape, `INV-<hash>`.
|
|
13
10
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* On the rare hash collision against an existing on-disk id with
|
|
12
|
+
* different content, the new id extends to 8 chars. Same fallback git
|
|
13
|
+
* uses for short SHAs.
|
|
14
|
+
*
|
|
15
|
+
* Ids are never recycled — rejecting a draft renames the file to
|
|
16
|
+
* `<id>.rejected.md` so the same hash never re-allocates to a
|
|
17
|
+
* different decision.
|
|
16
18
|
*/
|
|
19
|
+
export interface DecisionIdInput {
|
|
20
|
+
/** Title — required. Lowercased + trimmed in the hash input. */
|
|
21
|
+
title: string;
|
|
22
|
+
/** Free-text rationale or summary. */
|
|
23
|
+
rationale?: string;
|
|
24
|
+
/** Provenance source (e.g. `init-source-comments`, `init-rules-merge`, `user-record`). */
|
|
25
|
+
capture_source?: string;
|
|
26
|
+
/** Source file the decision was extracted from. */
|
|
27
|
+
source_file?: string;
|
|
28
|
+
/** Line / offset within the source file. */
|
|
29
|
+
source_offset?: number;
|
|
30
|
+
/** Original raw comment / section text. */
|
|
31
|
+
raw?: string;
|
|
32
|
+
/** Scope globs (sorted before hashing for stability). */
|
|
33
|
+
scope_globs?: string[];
|
|
34
|
+
/** Full body markdown (for manual `cairn_record_decision` calls). */
|
|
35
|
+
body_markdown?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Millisecond timestamp — only set for manual user-record paths
|
|
38
|
+
* where there is no stable provenance. Source-comment / rules-merge
|
|
39
|
+
* derived ids omit this so re-running the pipeline is idempotent.
|
|
40
|
+
*/
|
|
41
|
+
timestamp_ms?: number;
|
|
42
|
+
}
|
|
43
|
+
export interface InvariantIdInput {
|
|
44
|
+
/** Title — required. Lowercased + trimmed. */
|
|
45
|
+
title: string;
|
|
46
|
+
/** Source file the constraint was extracted from. */
|
|
47
|
+
source_file?: string;
|
|
48
|
+
/** Line / offset within the source file. */
|
|
49
|
+
source_offset?: number;
|
|
50
|
+
/** Original raw comment text. */
|
|
51
|
+
raw?: string;
|
|
52
|
+
/** Millisecond timestamp — manual writes only. */
|
|
53
|
+
timestamp_ms?: number;
|
|
54
|
+
}
|
|
17
55
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
56
|
+
* Compute a stable `DEC-<hash>` id from the canonical input. When
|
|
57
|
+
* `existing` is supplied and the 7-char prefix collides with an id
|
|
58
|
+
* already in that set whose content differs, the id extends to 8+
|
|
59
|
+
* chars until unique.
|
|
20
60
|
*/
|
|
21
|
-
export declare function
|
|
61
|
+
export declare function computeDecisionId(input: DecisionIdInput, existing?: Set<string>): string;
|
|
22
62
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
63
|
+
* Compute a stable `INV-<hash>` id from the canonical input. Same
|
|
64
|
+
* collision-extension behavior as `computeDecisionId`.
|
|
25
65
|
*/
|
|
26
|
-
export declare function
|
|
66
|
+
export declare function computeInvariantId(input: InvariantIdInput, existing?: Set<string>): string;
|
|
27
67
|
/**
|
|
28
|
-
* Scan
|
|
29
|
-
* set of ids found.
|
|
68
|
+
* Scan both the canonical decisions dir and `_inbox/` for
|
|
69
|
+
* `DEC-<hash>` filenames; return the set of ids found.
|
|
30
70
|
*/
|
|
31
|
-
export declare function
|
|
71
|
+
export declare function scanExistingDecisionIds(repoRoot: string): Set<string>;
|
|
32
72
|
/**
|
|
33
|
-
*
|
|
34
|
-
* at packages/cairn-core/src/ground/schemas.ts. Optionally factor in
|
|
35
|
-
* a caller-supplied set so a batch of allocations doesn't collide on
|
|
36
|
-
* disk before any are written.
|
|
73
|
+
* Scan `.cairn/ground/invariants/` for `INV-<hash>` filenames.
|
|
37
74
|
*/
|
|
38
|
-
export declare function
|
|
75
|
+
export declare function scanExistingInvariantIds(repoRoot: string): Set<string>;
|
|
@@ -1,29 +1,87 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Content-addressed id derivation for decisions and invariants.
|
|
3
3
|
*
|
|
4
|
-
* Decisions:
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Decisions: `DEC-<hash>` where `<hash>` is the first 7 hex chars of
|
|
5
|
+
* sha256(canonicalized input). Stable across clones — two devs
|
|
6
|
+
* that capture the same source comment in the same file produce
|
|
7
|
+
* the same id, so concurrent adoption runs do not collide on merge.
|
|
8
8
|
*
|
|
9
|
-
* Invariants:
|
|
10
|
-
* invariants directly to ground state (no `_inbox/` flow — they auto-promote
|
|
11
|
-
* from the constraint classifier; operator edits / supersedes after the
|
|
12
|
-
* fact).
|
|
9
|
+
* Invariants: same shape, `INV-<hash>`.
|
|
13
10
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* On the rare hash collision against an existing on-disk id with
|
|
12
|
+
* different content, the new id extends to 8 chars. Same fallback git
|
|
13
|
+
* uses for short SHAs.
|
|
14
|
+
*
|
|
15
|
+
* Ids are never recycled — rejecting a draft renames the file to
|
|
16
|
+
* `<id>.rejected.md` so the same hash never re-allocates to a
|
|
17
|
+
* different decision.
|
|
16
18
|
*/
|
|
19
|
+
import { createHash } from "node:crypto";
|
|
17
20
|
import { existsSync, readdirSync } from "node:fs";
|
|
18
21
|
import { join } from "node:path";
|
|
19
22
|
import { decisionsDir, invariantsDir } from "../ground/paths.js";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
23
|
+
/** Default short-hash length (matches git short-SHA convention). */
|
|
24
|
+
const HASH_LEN = 7;
|
|
25
|
+
/** DEC filename: `DEC-<hex>.md`, optionally `.draft` or `.rejected`. */
|
|
26
|
+
const FILENAME_RE = /^DEC-([0-9a-f]{7,})(?:\.draft|\.rejected)?\.md$/;
|
|
27
|
+
/** INV filename: `INV-<hex>.md`. Matches the schema id regex in `ground/schemas.ts`. */
|
|
28
|
+
const INVARIANT_FILENAME_RE = /^INV-([0-9a-f]{7,})\.md$/;
|
|
29
|
+
function canonicalDecision(input) {
|
|
30
|
+
return JSON.stringify({
|
|
31
|
+
title: input.title.trim().toLowerCase(),
|
|
32
|
+
rationale: input.rationale ?? null,
|
|
33
|
+
capture_source: input.capture_source ?? null,
|
|
34
|
+
source_file: input.source_file ?? null,
|
|
35
|
+
source_offset: input.source_offset ?? null,
|
|
36
|
+
raw: input.raw ?? null,
|
|
37
|
+
scope_globs: input.scope_globs !== undefined ? [...input.scope_globs].sort() : null,
|
|
38
|
+
body_markdown: input.body_markdown ?? null,
|
|
39
|
+
timestamp_ms: input.timestamp_ms ?? null,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function canonicalInvariant(input) {
|
|
43
|
+
return JSON.stringify({
|
|
44
|
+
title: input.title.trim().toLowerCase(),
|
|
45
|
+
source_file: input.source_file ?? null,
|
|
46
|
+
source_offset: input.source_offset ?? null,
|
|
47
|
+
raw: input.raw ?? null,
|
|
48
|
+
timestamp_ms: input.timestamp_ms ?? null,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Compute a stable `DEC-<hash>` id from the canonical input. When
|
|
53
|
+
* `existing` is supplied and the 7-char prefix collides with an id
|
|
54
|
+
* already in that set whose content differs, the id extends to 8+
|
|
55
|
+
* chars until unique.
|
|
56
|
+
*/
|
|
57
|
+
export function computeDecisionId(input, existing) {
|
|
58
|
+
const digest = createHash("sha256").update(canonicalDecision(input)).digest("hex");
|
|
59
|
+
for (let len = HASH_LEN; len <= digest.length; len++) {
|
|
60
|
+
const candidate = `DEC-${digest.slice(0, len)}`;
|
|
61
|
+
if (existing === undefined || !existing.has(candidate))
|
|
62
|
+
return candidate;
|
|
63
|
+
}
|
|
64
|
+
throw new Error("computeDecisionId: hash exhaustion (impossible at sha256)");
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Compute a stable `INV-<hash>` id from the canonical input. Same
|
|
68
|
+
* collision-extension behavior as `computeDecisionId`.
|
|
69
|
+
*/
|
|
70
|
+
export function computeInvariantId(input, existing) {
|
|
71
|
+
const digest = createHash("sha256").update(canonicalInvariant(input)).digest("hex");
|
|
72
|
+
for (let len = HASH_LEN; len <= digest.length; len++) {
|
|
73
|
+
const candidate = `INV-${digest.slice(0, len)}`;
|
|
74
|
+
if (existing === undefined || !existing.has(candidate))
|
|
75
|
+
return candidate;
|
|
76
|
+
}
|
|
77
|
+
throw new Error("computeInvariantId: hash exhaustion (impossible at sha256)");
|
|
78
|
+
}
|
|
79
|
+
/* -------------------------------------------------------------------------- */
|
|
80
|
+
/* On-disk scans (used for collision check + auxiliary lookups) */
|
|
81
|
+
/* -------------------------------------------------------------------------- */
|
|
24
82
|
/**
|
|
25
|
-
* Scan both the canonical decisions dir and
|
|
26
|
-
* DEC
|
|
83
|
+
* Scan both the canonical decisions dir and `_inbox/` for
|
|
84
|
+
* `DEC-<hash>` filenames; return the set of ids found.
|
|
27
85
|
*/
|
|
28
86
|
export function scanExistingDecisionIds(repoRoot) {
|
|
29
87
|
const dir = decisionsDir(repoRoot);
|
|
@@ -43,31 +101,13 @@ export function scanExistingDecisionIds(repoRoot) {
|
|
|
43
101
|
const match = name.match(FILENAME_RE);
|
|
44
102
|
if (!match || !match[1])
|
|
45
103
|
continue;
|
|
46
|
-
ids.add(`DEC-${match[1]
|
|
104
|
+
ids.add(`DEC-${match[1]}`);
|
|
47
105
|
}
|
|
48
106
|
}
|
|
49
107
|
return ids;
|
|
50
108
|
}
|
|
51
109
|
/**
|
|
52
|
-
*
|
|
53
|
-
* caller-supplied set (e.g. ids the MCP tool just validated against).
|
|
54
|
-
*/
|
|
55
|
-
export function allocateDecisionId(repoRoot, existing) {
|
|
56
|
-
const ids = existing ?? scanExistingDecisionIds(repoRoot);
|
|
57
|
-
let max = 0;
|
|
58
|
-
for (const id of ids) {
|
|
59
|
-
const m = id.match(/^DEC-(\d+)$/);
|
|
60
|
-
if (!m?.[1])
|
|
61
|
-
continue;
|
|
62
|
-
const n = Number.parseInt(m[1], 10);
|
|
63
|
-
if (Number.isFinite(n) && n > max)
|
|
64
|
-
max = n;
|
|
65
|
-
}
|
|
66
|
-
return `DEC-${(max + 1).toString().padStart(4, "0")}`;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Scan `.cairn/ground/invariants/` for INV-<NNNN>-prefixed files; return the
|
|
70
|
-
* set of ids found.
|
|
110
|
+
* Scan `.cairn/ground/invariants/` for `INV-<hash>` filenames.
|
|
71
111
|
*/
|
|
72
112
|
export function scanExistingInvariantIds(repoRoot) {
|
|
73
113
|
const dir = invariantsDir(repoRoot);
|
|
@@ -85,27 +125,8 @@ export function scanExistingInvariantIds(repoRoot) {
|
|
|
85
125
|
const match = name.match(INVARIANT_FILENAME_RE);
|
|
86
126
|
if (!match || !match[1])
|
|
87
127
|
continue;
|
|
88
|
-
ids.add(`INV-${match[1]
|
|
128
|
+
ids.add(`INV-${match[1]}`);
|
|
89
129
|
}
|
|
90
130
|
return ids;
|
|
91
131
|
}
|
|
92
|
-
/**
|
|
93
|
-
* Return the next free `INV-<NNNN>` id — matches the schema regex
|
|
94
|
-
* at packages/cairn-core/src/ground/schemas.ts. Optionally factor in
|
|
95
|
-
* a caller-supplied set so a batch of allocations doesn't collide on
|
|
96
|
-
* disk before any are written.
|
|
97
|
-
*/
|
|
98
|
-
export function allocateInvariantId(repoRoot, existing) {
|
|
99
|
-
const ids = existing ?? scanExistingInvariantIds(repoRoot);
|
|
100
|
-
let max = 0;
|
|
101
|
-
for (const id of ids) {
|
|
102
|
-
const m = id.match(/^INV-(\d+)$/);
|
|
103
|
-
if (!m?.[1])
|
|
104
|
-
continue;
|
|
105
|
-
const n = Number.parseInt(m[1], 10);
|
|
106
|
-
if (Number.isFinite(n) && n > max)
|
|
107
|
-
max = n;
|
|
108
|
-
}
|
|
109
|
-
return `INV-${(max + 1).toString().padStart(4, "0")}`;
|
|
110
|
-
}
|
|
111
132
|
//# sourceMappingURL=id.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"id.js","sourceRoot":"","sources":["../../src/decision-capture/id.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"id.js","sourceRoot":"","sources":["../../src/decision-capture/id.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Decision-capture surface.
|
|
3
3
|
*
|
|
4
|
-
* Only the
|
|
5
|
-
* refinement pipeline was orchestrator-era code (auto-extract DECs from
|
|
4
|
+
* Only the content-addressed id helpers remain — the Tier-1 LLM extractor
|
|
5
|
+
* + refinement pipeline was orchestrator-era code (auto-extract DECs from
|
|
6
6
|
* sessions) that is no longer wired into the plugin flow. Operator-driven
|
|
7
7
|
* DEC creation lives in the `cairn-direction` skill + the
|
|
8
8
|
* `cairn_record_decision` MCP tool now.
|
|
9
9
|
*/
|
|
10
|
-
export {
|
|
10
|
+
export { computeDecisionId, computeInvariantId, scanExistingDecisionIds, scanExistingInvariantIds, type DecisionIdInput, type InvariantIdInput, } from "./id.js";
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Decision-capture surface.
|
|
3
3
|
*
|
|
4
|
-
* Only the
|
|
5
|
-
* refinement pipeline was orchestrator-era code (auto-extract DECs from
|
|
4
|
+
* Only the content-addressed id helpers remain — the Tier-1 LLM extractor
|
|
5
|
+
* + refinement pipeline was orchestrator-era code (auto-extract DECs from
|
|
6
6
|
* sessions) that is no longer wired into the plugin flow. Operator-driven
|
|
7
7
|
* DEC creation lives in the `cairn-direction` skill + the
|
|
8
8
|
* `cairn_record_decision` MCP tool now.
|
|
9
9
|
*/
|
|
10
|
-
export {
|
|
10
|
+
export { computeDecisionId, computeInvariantId, scanExistingDecisionIds, scanExistingInvariantIds, } from "./id.js";
|
|
11
11
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/decision-capture/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,kBAAkB,EAClB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/decision-capture/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,uBAAuB,EACvB,wBAAwB,GAGzB,MAAM,SAAS,CAAC"}
|
package/dist/ground/schemas.js
CHANGED
|
@@ -108,7 +108,7 @@ export const DecisionAssertion = z.discriminatedUnion("kind", [
|
|
|
108
108
|
]);
|
|
109
109
|
export const DecisionFrontmatter = z
|
|
110
110
|
.object({
|
|
111
|
-
id: z.string().regex(/^DEC
|
|
111
|
+
id: z.string().regex(/^DEC-[0-9a-f]{7,}$/, "decision id must match DEC-<hash7>"),
|
|
112
112
|
title: z.string(),
|
|
113
113
|
type: z.literal("adr").optional(),
|
|
114
114
|
status: z
|
|
@@ -133,7 +133,7 @@ export const DecisionFrontmatter = z
|
|
|
133
133
|
.passthrough();
|
|
134
134
|
export const InvariantFrontmatter = z
|
|
135
135
|
.object({
|
|
136
|
-
id: z.string().regex(/^INV
|
|
136
|
+
id: z.string().regex(/^INV-[0-9a-f]{7,}$/, "invariant id must match INV-<hash7>"),
|
|
137
137
|
title: z.string(),
|
|
138
138
|
type: z.literal("invariant").optional(),
|
|
139
139
|
status: z.enum(["active", "superseded"]).optional(),
|