specweave 3.0.5 → 3.0.6
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/.claude-plugin/marketplace.json +2 -2
- package/README.md +2 -2
- package/bin/specweave.js +5 -5
- package/bin/startup-check.js +1 -1
- package/dist/src/cli/commands/auto-handoff.d.ts +4 -26
- package/dist/src/cli/commands/auto-handoff.d.ts.map +1 -1
- package/dist/src/cli/commands/auto-handoff.js +23 -52
- package/dist/src/cli/commands/auto-handoff.js.map +1 -1
- package/dist/src/core/session/checkpoint-worker.d.ts +2 -0
- package/dist/src/core/session/checkpoint-worker.d.ts.map +1 -0
- package/dist/src/core/session/checkpoint-worker.js +43 -0
- package/dist/src/core/session/checkpoint-worker.js.map +1 -0
- package/dist/src/core/session/handoff-doc-format.d.ts +2 -0
- package/dist/src/core/session/handoff-doc-format.d.ts.map +1 -1
- package/dist/src/core/session/handoff-doc-format.js +16 -4
- package/dist/src/core/session/handoff-doc-format.js.map +1 -1
- package/dist/src/core/session/handoff-git-state.d.ts +3 -1
- package/dist/src/core/session/handoff-git-state.d.ts.map +1 -1
- package/dist/src/core/session/handoff-git-state.js +32 -12
- package/dist/src/core/session/handoff-git-state.js.map +1 -1
- package/dist/src/core/session/session-checkpoint.d.ts +46 -0
- package/dist/src/core/session/session-checkpoint.d.ts.map +1 -0
- package/dist/src/core/session/session-checkpoint.js +262 -0
- package/dist/src/core/session/session-checkpoint.js.map +1 -0
- package/dist/src/core/session/usage-guard.d.ts +6 -69
- package/dist/src/core/session/usage-guard.d.ts.map +1 -1
- package/dist/src/core/session/usage-guard.js +5 -136
- package/dist/src/core/session/usage-guard.js.map +1 -1
- package/dist/src/core/session/work-handoff.d.ts +4 -0
- package/dist/src/core/session/work-handoff.d.ts.map +1 -1
- package/dist/src/core/session/work-handoff.js +31 -10
- package/dist/src/core/session/work-handoff.js.map +1 -1
- package/package.json +1 -1
- package/plugins/specweave/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave/skills/handoff/SKILL.md +21 -13
- package/src/templates/AGENTS.md.template +1 -1
|
@@ -37,17 +37,20 @@ export const BOOKKEEPING_PATHSPECS = [
|
|
|
37
37
|
/** Total Git budget leaves headroom below PreCompact's five-second budget. */
|
|
38
38
|
export const GIT_CAPTURE_BUDGET_MS = 3500;
|
|
39
39
|
/** Capture through a private index; an interrupted hook never stages user files. */
|
|
40
|
-
export function captureGitState(repoRoot, diffOutputPath) {
|
|
40
|
+
export function captureGitState(repoRoot, diffOutputPath, opts = {}) {
|
|
41
41
|
const empty = {
|
|
42
42
|
isGitRepo: false, branch: '', shortSha: '', statusPorcelain: '',
|
|
43
43
|
diffStat: '', hasUncommittedChanges: false,
|
|
44
44
|
};
|
|
45
45
|
const deadline = Date.now() + GIT_CAPTURE_BUDGET_MS;
|
|
46
46
|
const env = { ...process.env, GIT_OPTIONAL_LOCKS: '0' };
|
|
47
|
-
function git(args, trim = true) {
|
|
47
|
+
function git(args, trim = true, required = true) {
|
|
48
48
|
const remaining = deadline - Date.now();
|
|
49
|
-
if (remaining <= 0)
|
|
49
|
+
if (remaining <= 0) {
|
|
50
|
+
if (opts.strict)
|
|
51
|
+
throw new Error('checkpoint Git capture budget exceeded');
|
|
50
52
|
return null;
|
|
53
|
+
}
|
|
51
54
|
try {
|
|
52
55
|
const result = execFileSync('git', args, {
|
|
53
56
|
cwd: repoRoot, env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'],
|
|
@@ -55,19 +58,32 @@ export function captureGitState(repoRoot, diffOutputPath) {
|
|
|
55
58
|
});
|
|
56
59
|
return trim ? result.trim() : result;
|
|
57
60
|
}
|
|
58
|
-
catch {
|
|
61
|
+
catch (error) {
|
|
62
|
+
// Missing HEAD/upstream are normal. Timeouts and required commands are not.
|
|
63
|
+
if (opts.strict && (required || error.status !== 128 ||
|
|
64
|
+
error.signal || typeof error.code === 'string')) {
|
|
65
|
+
throw new Error('checkpoint Git capture failed');
|
|
66
|
+
}
|
|
59
67
|
return null;
|
|
60
68
|
}
|
|
61
69
|
}
|
|
70
|
+
function writeDiff(content) {
|
|
71
|
+
if (!opts.strict) {
|
|
72
|
+
safeWriteDiff(diffOutputPath, content);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
fs.mkdirSync(path.dirname(diffOutputPath), { recursive: true });
|
|
76
|
+
fs.writeFileSync(diffOutputPath, content, 'utf8');
|
|
77
|
+
}
|
|
62
78
|
let temporary;
|
|
63
79
|
try {
|
|
64
|
-
if (git(['rev-parse', '--is-inside-work-tree']) !== 'true') {
|
|
65
|
-
|
|
80
|
+
if (git(['rev-parse', '--is-inside-work-tree'], true, fs.existsSync(path.join(repoRoot, '.git'))) !== 'true') {
|
|
81
|
+
writeDiff('');
|
|
66
82
|
return empty;
|
|
67
83
|
}
|
|
68
84
|
const indexPath = git(['rev-parse', '--path-format=absolute', '--git-path', 'index']);
|
|
69
85
|
if (!indexPath) {
|
|
70
|
-
|
|
86
|
+
writeDiff('');
|
|
71
87
|
return empty;
|
|
72
88
|
}
|
|
73
89
|
temporary = fs.mkdtempSync(path.join(os.tmpdir(), 'specweave-handoff-index-'));
|
|
@@ -75,8 +91,8 @@ export function captureGitState(repoRoot, diffOutputPath) {
|
|
|
75
91
|
if (fs.existsSync(indexPath))
|
|
76
92
|
fs.copyFileSync(indexPath, privateIndex);
|
|
77
93
|
Object.assign(env, { GIT_INDEX_FILE: privateIndex });
|
|
78
|
-
const branch = git(['rev-parse', '--abbrev-ref', 'HEAD']) ?? '';
|
|
79
|
-
const shortSha = git(['rev-parse', '--short', 'HEAD']) ?? '';
|
|
94
|
+
const branch = git(['rev-parse', '--abbrev-ref', 'HEAD'], true, false) ?? '';
|
|
95
|
+
const shortSha = git(['rev-parse', '--short', 'HEAD'], true, false) ?? '';
|
|
80
96
|
const scope = ['--', '.', ...BOOKKEEPING_PATHSPECS];
|
|
81
97
|
const statusPorcelain = git(['status', '--porcelain', ...scope]) ?? '';
|
|
82
98
|
// NUL-delimited paths handle spaces, quotes, Unicode and embedded newlines.
|
|
@@ -91,9 +107,11 @@ export function captureGitState(repoRoot, diffOutputPath) {
|
|
|
91
107
|
const stagedDiff = (hasHead ? git([...staged, ...scope]) : '') ?? '';
|
|
92
108
|
const workingStat = git([...working, '--stat', ...scope]) ?? '';
|
|
93
109
|
const stagedStat = (hasHead ? git([...staged, '--stat', ...scope]) : '') ?? '';
|
|
94
|
-
|
|
95
|
-
const upstream = git(['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}']) ?? '';
|
|
110
|
+
writeDiff([workingDiff, stagedDiff].filter(Boolean).join('\n'));
|
|
111
|
+
const upstream = git(['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'], true, false) ?? '';
|
|
96
112
|
const aheadRaw = upstream ? git(['rev-list', '--count', `${upstream}..HEAD`]) : null;
|
|
113
|
+
if (opts.strict && Date.now() > deadline)
|
|
114
|
+
throw new Error('checkpoint Git capture budget exceeded');
|
|
97
115
|
return {
|
|
98
116
|
isGitRepo: true, branch, shortSha, statusPorcelain,
|
|
99
117
|
diffStat: [workingStat, stagedStat].filter(Boolean).join('\n'),
|
|
@@ -102,7 +120,9 @@ export function captureGitState(repoRoot, diffOutputPath) {
|
|
|
102
120
|
ahead: aheadRaw !== null && aheadRaw !== '' ? Number(aheadRaw) : undefined,
|
|
103
121
|
};
|
|
104
122
|
}
|
|
105
|
-
catch {
|
|
123
|
+
catch (error) {
|
|
124
|
+
if (opts.strict)
|
|
125
|
+
throw error;
|
|
106
126
|
safeWriteDiff(diffOutputPath, '');
|
|
107
127
|
return empty;
|
|
108
128
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handoff-git-state.js","sourceRoot":"","sources":["../../../../src/core/session/handoff-git-state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AAwBzB;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAsB;IACtD,qDAAqD;IACrD,sDAAsD;IACtD,mDAAmD;IACnD,mDAAmD;IACnD,qDAAqD;IACrD,sCAAsC;IACtC,oCAAoC;IACpC,mCAAmC;CACpC,CAAC;AAEF,8EAA8E;AAC9E,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAE1C,oFAAoF;AACpF,MAAM,UAAU,eAAe,CAAC,QAAgB,EAAE,cAAsB;
|
|
1
|
+
{"version":3,"file":"handoff-git-state.js","sourceRoot":"","sources":["../../../../src/core/session/handoff-git-state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export declare const CHECKPOINT_INTERVAL_MS: number;
|
|
2
|
+
export declare const CHECKPOINT_WORKER_TIMEOUT_MS = 8000;
|
|
3
|
+
export declare const CHECKPOINT_LOCK_STALE_MS = 15000;
|
|
4
|
+
export declare const CHECKPOINT_MAX_WORKERS = 4;
|
|
5
|
+
export interface CheckpointInput {
|
|
6
|
+
session_id?: string;
|
|
7
|
+
sessionId?: string;
|
|
8
|
+
cwd?: string;
|
|
9
|
+
workspaceRoot?: string;
|
|
10
|
+
last_assistant_message?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface CheckpointOptions {
|
|
13
|
+
home?: string;
|
|
14
|
+
now?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface CheckpointReceipt {
|
|
17
|
+
version: 1;
|
|
18
|
+
sessionId: string;
|
|
19
|
+
cwd: string;
|
|
20
|
+
savedAt: string;
|
|
21
|
+
docPath: string;
|
|
22
|
+
diffPath: string;
|
|
23
|
+
incrementId?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface CheckpointRequest {
|
|
26
|
+
version: 1;
|
|
27
|
+
sessionId: string;
|
|
28
|
+
cwd: string;
|
|
29
|
+
directory: string;
|
|
30
|
+
generation: string;
|
|
31
|
+
token: string;
|
|
32
|
+
slot: string;
|
|
33
|
+
summary?: string;
|
|
34
|
+
}
|
|
35
|
+
export declare function checkpointsDirectory(home?: string): string;
|
|
36
|
+
export declare function checkpointDirectory(input: CheckpointInput, opts?: CheckpointOptions): string | undefined;
|
|
37
|
+
export declare function readSessionCheckpoint(input: CheckpointInput, opts?: CheckpointOptions): CheckpointReceipt | undefined;
|
|
38
|
+
/** @internal Reserve a bounded worker slot and persist only scrubbed worker input. */
|
|
39
|
+
export declare function prepareSessionCheckpoint(input: CheckpointInput, opts?: CheckpointOptions): string | undefined;
|
|
40
|
+
/** Queue at most one capture per session/worktree and four per user. All errors fail open. */
|
|
41
|
+
export declare function queueSessionCheckpoint(input: CheckpointInput, opts?: CheckpointOptions): void;
|
|
42
|
+
/** Called by the supervisor on success, failure, or its hard deadline. */
|
|
43
|
+
export declare function cleanupCheckpointRequest(requestPath: string): void;
|
|
44
|
+
/** Worker entry: publish the receipt last, so interruption leaves the previous snapshot usable. */
|
|
45
|
+
export declare function runSessionCheckpoint(requestPath: string): Promise<void>;
|
|
46
|
+
//# sourceMappingURL=session-checkpoint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-checkpoint.d.ts","sourceRoot":"","sources":["../../../../src/core/session/session-checkpoint.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,sBAAsB,QAAa,CAAC;AACjD,eAAO,MAAM,4BAA4B,OAAQ,CAAC;AAClD,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAC/C,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,iBAAiB;IAAG,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE;AAElE,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,CAAC,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAGD,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,CAAC,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAuBD,wBAAgB,oBAAoB,CAAC,IAAI,SAAe,GAAG,MAAM,CAEhE;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,eAAe,EAAE,IAAI,GAAE,iBAAsB,GAAG,MAAM,GAAG,SAAS,CAK5G;AAMD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,eAAe,EAAE,IAAI,GAAE,iBAAsB,GAAG,iBAAiB,GAAG,SAAS,CAMzH;AAoDD,sFAAsF;AACtF,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,eAAe,EAAE,IAAI,GAAE,iBAAsB,GAAG,MAAM,GAAG,SAAS,CA0CjH;AAED,8FAA8F;AAC9F,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,eAAe,EAAE,IAAI,GAAE,iBAAsB,GAAG,IAAI,CAUjG;AAED,0EAA0E;AAC1E,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAWlE;AAED,mGAAmG;AACnG,wBAAsB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgC7E"}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/** Local, session-owned recovery snapshots. Scheduling never waits for Git or a model. */
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { scrubSecrets } from './handoff-secret-scrub.js';
|
|
9
|
+
export const CHECKPOINT_INTERVAL_MS = 5 * 60000;
|
|
10
|
+
export const CHECKPOINT_WORKER_TIMEOUT_MS = 8000;
|
|
11
|
+
export const CHECKPOINT_LOCK_STALE_MS = 15000;
|
|
12
|
+
export const CHECKPOINT_MAX_WORKERS = 4;
|
|
13
|
+
/** Resolve symlinks and the nearest Git worktree without invoking Git in the hook. */
|
|
14
|
+
function checkpointIdentity(input) {
|
|
15
|
+
const sessionId = input.session_id ?? input.sessionId;
|
|
16
|
+
if (typeof sessionId !== 'string' || !/^[\w.-]{1,128}$/.test(sessionId) || sessionId.includes('..'))
|
|
17
|
+
return undefined;
|
|
18
|
+
const start = input.cwd ?? input.workspaceRoot ?? process.cwd();
|
|
19
|
+
if (typeof start !== 'string' || !path.isAbsolute(start))
|
|
20
|
+
return undefined;
|
|
21
|
+
try {
|
|
22
|
+
const original = fs.realpathSync(start);
|
|
23
|
+
if (!fs.statSync(original).isDirectory())
|
|
24
|
+
return undefined;
|
|
25
|
+
let current = original;
|
|
26
|
+
while (true) {
|
|
27
|
+
if (fs.existsSync(path.join(current, '.git')))
|
|
28
|
+
return { sessionId, cwd: current };
|
|
29
|
+
const parent = path.dirname(current);
|
|
30
|
+
if (parent === current)
|
|
31
|
+
break;
|
|
32
|
+
current = parent;
|
|
33
|
+
}
|
|
34
|
+
// Plain folders can still preserve a summary; no recursive project scan.
|
|
35
|
+
return { sessionId, cwd: original };
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function checkpointsDirectory(home = os.homedir()) {
|
|
42
|
+
return path.join(home, '.specweave', 'checkpoints');
|
|
43
|
+
}
|
|
44
|
+
export function checkpointDirectory(input, opts = {}) {
|
|
45
|
+
const identity = checkpointIdentity(input);
|
|
46
|
+
if (!identity)
|
|
47
|
+
return undefined;
|
|
48
|
+
const key = createHash('sha256').update(identity.cwd).update('\0').update(identity.sessionId).digest('hex');
|
|
49
|
+
return path.join(checkpointsDirectory(opts.home), key);
|
|
50
|
+
}
|
|
51
|
+
function readJson(file) {
|
|
52
|
+
try {
|
|
53
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export function readSessionCheckpoint(input, opts = {}) {
|
|
60
|
+
const directory = checkpointDirectory(input, opts);
|
|
61
|
+
if (!directory)
|
|
62
|
+
return undefined;
|
|
63
|
+
const receipt = readJson(path.join(directory, 'current.json'));
|
|
64
|
+
return receipt?.version === 1 && typeof receipt.savedAt === 'string' && typeof receipt.docPath === 'string'
|
|
65
|
+
&& typeof receipt.diffPath === 'string' && fs.existsSync(receipt.docPath) && fs.existsSync(receipt.diffPath) ? receipt : undefined;
|
|
66
|
+
}
|
|
67
|
+
function leaseMatches(file, token) {
|
|
68
|
+
const lease = readJson(path.join(file, `${token}.json`));
|
|
69
|
+
return lease?.token === token && typeof lease.ticket === 'number';
|
|
70
|
+
}
|
|
71
|
+
function releaseLease(file, token) {
|
|
72
|
+
// Every contender owns a distinct filename, including after stale recovery.
|
|
73
|
+
for (const suffix of ['json', 'ready']) {
|
|
74
|
+
try {
|
|
75
|
+
fs.unlinkSync(path.join(file, `${token}.${suffix}`));
|
|
76
|
+
}
|
|
77
|
+
catch { /* already gone */ }
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
fs.rmdirSync(file);
|
|
81
|
+
}
|
|
82
|
+
catch { /* another contender still owns this directory */ }
|
|
83
|
+
}
|
|
84
|
+
function acquireLease(file, token, now) {
|
|
85
|
+
try {
|
|
86
|
+
// A short bakery election: announce choosing first, then select one greater
|
|
87
|
+
// than every existing ticket. Concurrent choosers cause a safe retry; the
|
|
88
|
+
// stable lowest (ticket, token) wins. No shared filename is ever unlinked.
|
|
89
|
+
fs.mkdirSync(file, { recursive: true, mode: 0o700 });
|
|
90
|
+
const own = path.join(file, `${token}.json`);
|
|
91
|
+
fs.writeFileSync(own, JSON.stringify({ token, at: now }), { flag: 'wx', mode: 0o600 });
|
|
92
|
+
const candidates = () => {
|
|
93
|
+
const leases = [];
|
|
94
|
+
for (const name of fs.readdirSync(file)) {
|
|
95
|
+
if (!name.endsWith('.json'))
|
|
96
|
+
continue;
|
|
97
|
+
const candidate = path.join(file, name);
|
|
98
|
+
try {
|
|
99
|
+
if (Date.now() - fs.statSync(candidate).mtimeMs > CHECKPOINT_LOCK_STALE_MS) {
|
|
100
|
+
fs.unlinkSync(candidate);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
// A half-written contender must also prevent concurrent ownership.
|
|
104
|
+
leases.push(readJson(candidate) ?? { token: name, at: now });
|
|
105
|
+
}
|
|
106
|
+
catch { /* a contender finished between readdir and read */ }
|
|
107
|
+
}
|
|
108
|
+
return leases;
|
|
109
|
+
};
|
|
110
|
+
const ticket = 1 + Math.max(0, ...candidates().map((lease) => lease.ticket ?? 0));
|
|
111
|
+
const ready = path.join(file, `${token}.ready`);
|
|
112
|
+
fs.writeFileSync(ready, JSON.stringify({ token, at: now, ticket }), { flag: 'wx', mode: 0o600 });
|
|
113
|
+
fs.renameSync(ready, own);
|
|
114
|
+
if (candidates().some((lease) => lease.token !== token &&
|
|
115
|
+
(lease.ticket === undefined || lease.ticket < ticket || (lease.ticket === ticket && lease.token < token)))) {
|
|
116
|
+
releaseLease(file, token);
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
releaseLease(file, token);
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** @internal Reserve a bounded worker slot and persist only scrubbed worker input. */
|
|
127
|
+
export function prepareSessionCheckpoint(input, opts = {}) {
|
|
128
|
+
const identity = checkpointIdentity(input);
|
|
129
|
+
const directory = checkpointDirectory(input, opts);
|
|
130
|
+
if (!identity || !directory)
|
|
131
|
+
return;
|
|
132
|
+
const now = opts.now ?? Date.now();
|
|
133
|
+
const previous = readSessionCheckpoint(input, opts);
|
|
134
|
+
const savedAt = previous ? Date.parse(previous.savedAt) : NaN;
|
|
135
|
+
if (Number.isFinite(savedAt) && now >= savedAt && now - savedAt < CHECKPOINT_INTERVAL_MS)
|
|
136
|
+
return;
|
|
137
|
+
const token = randomUUID();
|
|
138
|
+
const lock = path.join(directory, 'pending.lock');
|
|
139
|
+
if (!acquireLease(lock, token, now))
|
|
140
|
+
return;
|
|
141
|
+
let slot;
|
|
142
|
+
let requestPath;
|
|
143
|
+
try {
|
|
144
|
+
// A worker can have finished between our initial receipt read and election.
|
|
145
|
+
const latest = readSessionCheckpoint(input, opts);
|
|
146
|
+
const latestAt = latest ? Date.parse(latest.savedAt) : NaN;
|
|
147
|
+
const afterLease = opts.now ?? Date.now();
|
|
148
|
+
if (Number.isFinite(latestAt) && afterLease >= latestAt && afterLease - latestAt < CHECKPOINT_INTERVAL_MS) {
|
|
149
|
+
releaseLease(lock, token);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const slots = path.join(checkpointsDirectory(opts.home), '.workers');
|
|
153
|
+
for (let i = 0; i < CHECKPOINT_MAX_WORKERS; i++) {
|
|
154
|
+
const candidate = path.join(slots, `${i}.lock`);
|
|
155
|
+
if (acquireLease(candidate, token, now)) {
|
|
156
|
+
slot = candidate;
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (!slot) {
|
|
161
|
+
releaseLease(lock, token);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const generation = `snapshot-${now}-${token}`;
|
|
165
|
+
const summary = typeof input.last_assistant_message === 'string'
|
|
166
|
+
? scrubSecrets(input.last_assistant_message.slice(0, 32000)).scrubbed : undefined;
|
|
167
|
+
const request = { version: 1, ...identity, directory, generation, token, slot, ...(summary ? { summary } : {}) };
|
|
168
|
+
requestPath = path.join(directory, `request-${token}.json`);
|
|
169
|
+
fs.writeFileSync(requestPath, JSON.stringify(request), { flag: 'wx', mode: 0o600 });
|
|
170
|
+
return requestPath;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
if (requestPath)
|
|
174
|
+
cleanupCheckpointRequest(requestPath);
|
|
175
|
+
else {
|
|
176
|
+
releaseLease(lock, token);
|
|
177
|
+
if (slot)
|
|
178
|
+
releaseLease(slot, token);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/** Queue at most one capture per session/worktree and four per user. All errors fail open. */
|
|
183
|
+
export function queueSessionCheckpoint(input, opts = {}) {
|
|
184
|
+
const requestPath = prepareSessionCheckpoint(input, opts);
|
|
185
|
+
if (!requestPath)
|
|
186
|
+
return;
|
|
187
|
+
try {
|
|
188
|
+
const worker = fileURLToPath(new URL('./checkpoint-worker.js', import.meta.url));
|
|
189
|
+
if (!fs.existsSync(worker))
|
|
190
|
+
throw new Error('checkpoint worker is not built');
|
|
191
|
+
const child = spawn(process.execPath, [worker, requestPath], { detached: true, stdio: 'ignore', windowsHide: true });
|
|
192
|
+
child.once('error', () => cleanupCheckpointRequest(requestPath));
|
|
193
|
+
child.unref();
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
cleanupCheckpointRequest(requestPath);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/** Called by the supervisor on success, failure, or its hard deadline. */
|
|
200
|
+
export function cleanupCheckpointRequest(requestPath) {
|
|
201
|
+
const request = readJson(requestPath);
|
|
202
|
+
if (!request || request.version !== 1)
|
|
203
|
+
return;
|
|
204
|
+
releaseLease(path.join(request.directory, 'pending.lock'), request.token);
|
|
205
|
+
releaseLease(request.slot, request.token);
|
|
206
|
+
const generationDir = path.join(request.directory, request.generation);
|
|
207
|
+
const receipt = readJson(path.join(request.directory, 'current.json'));
|
|
208
|
+
if (!receipt || path.dirname(receipt.docPath) !== generationDir) {
|
|
209
|
+
try {
|
|
210
|
+
fs.rmSync(generationDir, { recursive: true, force: true });
|
|
211
|
+
}
|
|
212
|
+
catch { /* best effort */ }
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
fs.unlinkSync(requestPath);
|
|
216
|
+
}
|
|
217
|
+
catch { /* already removed */ }
|
|
218
|
+
}
|
|
219
|
+
/** Worker entry: publish the receipt last, so interruption leaves the previous snapshot usable. */
|
|
220
|
+
export async function runSessionCheckpoint(requestPath) {
|
|
221
|
+
const request = readJson(requestPath);
|
|
222
|
+
if (!request || request.version !== 1 || !leaseMatches(path.join(request.directory, 'pending.lock'), request.token))
|
|
223
|
+
return;
|
|
224
|
+
const generationDir = path.join(request.directory, request.generation);
|
|
225
|
+
const previous = readJson(path.join(request.directory, 'current.json'));
|
|
226
|
+
let published = false;
|
|
227
|
+
try {
|
|
228
|
+
fs.mkdirSync(generationDir, { mode: 0o700 });
|
|
229
|
+
const { buildWorkHandoff } = await import('./work-handoff.js');
|
|
230
|
+
const result = await buildWorkHandoff(request.cwd, {
|
|
231
|
+
checkpoint: true, checkpointRoot: request.cwd, push: false, keepClaims: true,
|
|
232
|
+
out: path.join(generationDir, 'handoff.md'), reason: 'automatic local session checkpoint', summary: request.summary,
|
|
233
|
+
});
|
|
234
|
+
if (!fs.statSync(result.docPath).size || !fs.existsSync(result.diffPath))
|
|
235
|
+
throw new Error('incomplete checkpoint');
|
|
236
|
+
if (!leaseMatches(path.join(request.directory, 'pending.lock'), request.token))
|
|
237
|
+
return;
|
|
238
|
+
const receipt = {
|
|
239
|
+
version: 1, sessionId: request.sessionId, cwd: request.cwd, savedAt: new Date().toISOString(),
|
|
240
|
+
docPath: result.docPath, diffPath: result.diffPath, ...(result.incrementId ? { incrementId: result.incrementId } : {}),
|
|
241
|
+
};
|
|
242
|
+
const temporary = path.join(request.directory, `current-${request.token}.json`);
|
|
243
|
+
fs.writeFileSync(temporary, JSON.stringify(receipt, null, 2) + '\n', { flag: 'wx', mode: 0o600 });
|
|
244
|
+
fs.renameSync(temporary, path.join(request.directory, 'current.json'));
|
|
245
|
+
published = true;
|
|
246
|
+
// Retain the preceding generation for recovery; prune only our old snapshot directories.
|
|
247
|
+
const keep = new Set([generationDir, previous && path.dirname(previous.docPath)]);
|
|
248
|
+
for (const name of fs.readdirSync(request.directory)) {
|
|
249
|
+
const file = path.join(request.directory, name);
|
|
250
|
+
if (name.startsWith('snapshot-') && !keep.has(file))
|
|
251
|
+
fs.rmSync(file, { recursive: true, force: true });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
if (!published)
|
|
256
|
+
try {
|
|
257
|
+
fs.rmSync(generationDir, { recursive: true, force: true });
|
|
258
|
+
}
|
|
259
|
+
catch { /* preserve existing receipt */ }
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=session-checkpoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-checkpoint.js","sourceRoot":"","sources":["../../../../src/core/session/session-checkpoint.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,27 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Neither Claude Code nor Codex tells a hook how much of the plan's limit is
|
|
5
|
-
* left, so the guard reads it where each tool does expose it:
|
|
6
|
-
*
|
|
7
|
-
* Claude Code the status line command receives `rate_limits.five_hour` /
|
|
8
|
-
* `seven_day` / `spend_limit` `.used_percentage` (Pro and Max,
|
|
9
|
-
* after the first reply). `specweave statusline` saves them per
|
|
10
|
-
* session under ~/.specweave/usage/.
|
|
11
|
-
* Codex every turn appends a `token_count` event with
|
|
12
|
-
* `rate_limits.primary` / `secondary` `.used_percent` to the
|
|
13
|
-
* rollout file the hook's `transcript_path` points at.
|
|
14
|
-
*
|
|
15
|
-
* The Stop hook (`specweave usage-guard`) then blocks the stop once per
|
|
16
|
-
* session and usage window when any window is at or past the threshold, and
|
|
17
|
-
* the agent runs `specweave handoff`. Under the threshold it prints `{}`: no
|
|
18
|
-
* tokens, no files.
|
|
19
|
-
*
|
|
20
|
-
* @module core/session/usage-guard
|
|
1
|
+
/** Usage readings for display only. Automatic saving is quota-independent.
|
|
2
|
+
* A plan allowance may be exhausted while paid credits or a proxy still work.
|
|
3
|
+
* Never turn these advisory readings into a stop/continue instruction.
|
|
21
4
|
*/
|
|
22
5
|
export declare const DEFAULT_THRESHOLD = 90;
|
|
23
|
-
/** The shortest usage window; a session that hit the limit may hit it again after this. */
|
|
24
|
-
export declare const LIMIT_HIT_REARM_MS: number;
|
|
25
6
|
export interface UsageWindow {
|
|
26
7
|
/** "5-hour", "weekly", "spend" or "<n>-minute". */
|
|
27
8
|
name: string;
|
|
@@ -97,32 +78,14 @@ export declare function latestClaudeReading(home?: string): UsageReading | undef
|
|
|
97
78
|
export declare function latestCodexReading(home?: string): UsageReading | undefined;
|
|
98
79
|
/** The fullest window that has not reset yet. */
|
|
99
80
|
export declare function fullest(windows: UsageWindow[], now?: number): UsageWindow | undefined;
|
|
100
|
-
|
|
81
|
+
/** Retained for integrations that imported the old guard. Never steer the model. */
|
|
101
82
|
export interface GuardInput {
|
|
102
83
|
session_id?: string;
|
|
103
84
|
transcript_path?: string;
|
|
104
85
|
stop_hook_active?: boolean;
|
|
105
86
|
}
|
|
106
|
-
export
|
|
107
|
-
|
|
108
|
-
decision?: 'block';
|
|
109
|
-
reason?: string;
|
|
110
|
-
/**
|
|
111
|
-
* Claude Code: non-error context for the model; the turn continues so it can
|
|
112
|
-
* act on it. A `decision: "block"` would show the user "Stop hook error".
|
|
113
|
-
*/
|
|
114
|
-
hookSpecificOutput?: {
|
|
115
|
-
hookEventName: 'Stop';
|
|
116
|
-
additionalContext: string;
|
|
117
|
-
};
|
|
118
|
-
/** Claude Code: a line shown to the user. */
|
|
119
|
-
systemMessage?: string;
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Stop-hook output. The first time a session is at or past the threshold in a
|
|
123
|
-
* usage window, it keeps the agent going so it hands off; otherwise `{}`.
|
|
124
|
-
*/
|
|
125
|
-
export declare function usageGuard(input: GuardInput, opts?: {
|
|
87
|
+
export type GuardOutput = Record<string, never>;
|
|
88
|
+
export declare function usageGuard(_input: GuardInput, _opts?: {
|
|
126
89
|
home?: string;
|
|
127
90
|
now?: number;
|
|
128
91
|
env?: NodeJS.ProcessEnv;
|
|
@@ -130,30 +93,4 @@ export declare function usageGuard(input: GuardInput, opts?: {
|
|
|
130
93
|
}): GuardOutput;
|
|
131
94
|
/** One short status line: the fullest windows, e.g. "5-hour 42% · weekly 12%". */
|
|
132
95
|
export declare function usageSummary(windows: UsageWindow[]): string;
|
|
133
|
-
/**
|
|
134
|
-
* When a turn fails on the rate limit, Claude Code and Grok Build fire
|
|
135
|
-
* `StopFailure` with `error: "rate_limit"`. It is the backstop for a session
|
|
136
|
-
* that ran out before the Stop hook could ask for a handoff, and the only
|
|
137
|
-
* signal Grok Build gives (it shows no usage percentage). Grok's hook input is
|
|
138
|
-
* camelCase (`sessionId`, `workspaceRoot`), Claude Code's snake_case.
|
|
139
|
-
*/
|
|
140
|
-
export interface LimitHitInput {
|
|
141
|
-
sessionId?: string;
|
|
142
|
-
session_id?: string;
|
|
143
|
-
cwd?: string;
|
|
144
|
-
workspaceRoot?: string;
|
|
145
|
-
error?: string;
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Where a rate-limited turn should hand off, or undefined when it should not:
|
|
149
|
-
* auto-handoff is off, the failure is not a rate limit, the session already
|
|
150
|
-
* already wrote one in the last five hours, or the directory is not a
|
|
151
|
-
* SpecWeave project. Writes its marker before returning, so a retry storm
|
|
152
|
-
* hands off once. The marker is separate from the Stop hook's: a session that
|
|
153
|
-
* was asked to hand off at 90% and still ran out writes a fresh handoff.
|
|
154
|
-
*/
|
|
155
|
-
export declare function limitHitTarget(input: LimitHitInput, opts?: {
|
|
156
|
-
home?: string;
|
|
157
|
-
now?: number;
|
|
158
|
-
}): string | undefined;
|
|
159
96
|
//# sourceMappingURL=usage-guard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usage-guard.d.ts","sourceRoot":"","sources":["../../../../src/core/session/usage-guard.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"usage-guard.d.ts","sourceRoot":"","sources":["../../../../src/core/session/usage-guard.ts"],"names":[],"mappings":"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"}
|