@ngockhoale/ukit 3.0.2 → 3.0.4
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/CHANGELOG.md +21 -0
- package/bin/ukit +12 -4
- package/manifests/engineConformance.yaml +29 -0
- package/manifests/platform.full.yaml +13 -0
- package/package.json +2 -1
- package/scripts/audit/decision-coverage.mjs +295 -0
- package/scripts/bench/outline-savings.mjs +19 -4
- package/scripts/bench/parallel-agents.mjs +15 -4
- package/scripts/bench/runGold.mjs +22 -4
- package/scripts/bench/v3-ceremony.mjs +7 -1
- package/scripts/bug/triage.mjs +56 -17
- package/scripts/index/build-index.mjs +94 -28
- package/scripts/index/query-index.mjs +48 -14
- package/scripts/index/refresh-index.mjs +142 -62
- package/scripts/perf/audit-perf.mjs +8 -2
- package/scripts/skill/audit-skill.mjs +54 -25
- package/src/bug/triageBug.js +9 -6
- package/src/cli/adapters.js +6 -0
- package/src/cli/commands/code.js +7 -1
- package/src/cli/commands/indexArgs.js +4 -2
- package/src/cli/commands/indexTools.js +8 -1
- package/src/cli/commands/install.js +13 -0
- package/src/cli/commands/memory.js +10 -3
- package/src/cli/commands/status.js +17 -1
- package/src/cli/commands/update.js +7 -0
- package/src/context/detectProjectContext.js +3 -1
- package/src/core/codeintel/analogy.js +1 -1
- package/src/core/codeintel/diagnostics.js +9 -6
- package/src/core/codeintel/graph.js +14 -8
- package/src/core/codeintel/impact.js +0 -1
- package/src/core/codeintel/invalidation.js +5 -3
- package/src/core/codeintel/packet.js +11 -0
- package/src/core/codeintel/router.js +17 -3
- package/src/core/codeintel/semanticProvider.js +1 -1
- package/src/core/codeintel/summaries.js +11 -7
- package/src/core/compact/contextBudget.js +26 -12
- package/src/core/compact/index.js +15 -8
- package/src/core/docContracts.js +10 -2
- package/src/core/experiments/deliberation.js +321 -0
- package/src/core/experiments/dynamicWorkflow.js +492 -0
- package/src/core/fileOps.js +8 -1
- package/src/core/gatewayProbe.js +29 -1
- package/src/core/gatewayResilienceEnv.js +44 -3
- package/src/core/handoffDocValidator.js +3 -1
- package/src/core/hookChainDoctor.js +16 -1
- package/src/core/memory/deltaOverlays.js +448 -0
- package/src/core/memory/learningCandidates.js +302 -0
- package/src/core/memory/migrate.js +59 -32
- package/src/core/memory/recordStore.js +24 -1
- package/src/core/memory/store.js +44 -8
- package/src/core/memory/storeV2.js +48 -33
- package/src/core/memory/userMemory.js +11 -10
- package/src/core/output/index.js +15 -10
- package/src/core/permissionPolicy.js +8 -0
- package/src/core/runtimeConfig.js +224 -4
- package/src/core/sensitiveValueScanner.js +10 -2
- package/src/core/taskBudgetValidator.js +12 -17
- package/src/core/taskProgressGuard.js +59 -9
- package/src/core/unattendedDoctor.js +5 -2
- package/src/core/uninstall.js +37 -8
- package/src/decision/client.js +371 -0
- package/src/decision/lease.js +198 -0
- package/src/decision/preflight.js +492 -0
- package/src/decision/protocol.js +308 -0
- package/src/decision/registry.js +384 -0
- package/src/decision/shadow.js +281 -0
- package/src/decision/statePacket.js +165 -0
- package/src/diagnostics/failurePatterns.js +2 -1
- package/src/diagnostics/ledgerFiles.js +3 -1
- package/src/diagnostics/routeOutcomes.js +1 -29
- package/src/index/buildIndex.js +23 -11
- package/src/index/impactContext.js +21 -2
- package/src/index/importResolution.js +13 -7
- package/src/index/queryIndex.js +11 -5
- package/src/index/resolveContext.js +16 -6
- package/src/index/taskRouting.js +63 -2
- package/src/index/verificationPlan.js +12 -1
- package/src/learning/patternProposals.js +6 -0
- package/src/render/renderTemplate.js +1 -1
- package/src/skill/auditSkill.js +3 -1
- package/src/stack/detectStack.js +3 -1
- package/template_project/.claude/agents/handoff-planner.md +2 -5
- package/template_project/.claude/hooks/auto-allow-bash.sh +5 -0
- package/template_project/.claude/hooks/block-dangerous.mjs +11 -4
- package/template_project/.claude/hooks/context-hardcap-gate.sh +10 -1
- package/template_project/.claude/hooks/handoff-model-guard.sh +14 -4
- package/template_project/.claude/hooks/handoff-resume.sh +10 -1
- package/template_project/.claude/hooks/protect-files.sh +0 -1
- package/template_project/.claude/hooks/record-execution.mjs +13 -1
- package/template_project/.claude/hooks/sensitive-data-guard.mjs +71 -5
- package/template_project/.claude/hooks/session-episode.sh +9 -2
- package/template_project/.claude/skills/pdf-processing-pro/SKILL.md +1 -1
- package/template_project/.claude/ukit/index/handoff-doc-validator.mjs +3 -1
- package/template_project/.claude/ukit/index/lib/index-core.mjs +123 -39
- package/template_project/.claude/ukit/index/route-task.mjs +444 -0
- package/template_project/.claude/ukit/index/task-budget-validator.mjs +12 -16
- package/template_project/.claude/ukit/index/unic-decision.mjs +786 -0
- package/template_project/.claude/ukit/index/verify-context.mjs +11 -0
- package/template_project/.claude/ukit/runtime/execution-ledger.mjs +116 -9
- package/template_project/.claude/ukit/runtime/project-important.mjs +9 -7
- package/template_project/.claude/ukit/runtime/reinject-context.mjs +48 -0
- package/template_project/.claude/ukit/runtime/resumable-run.mjs +596 -0
- package/template_project/.claude/ukit/runtime/sensitive-value-scanner.mjs +4 -7
- package/template_project/.claude/ukit/runtime/stop-coordinator.mjs +1 -1
- package/template_project/docs/AI_HANDOFF/PLAN.md +7 -7
- package/template_project/docs/AI_HANDOFF/RULES.md +1 -1
- package/template_project/ukit/storage/config.json +34 -1
- package/template_project/.claude/ukit/skill-router-state.json +0 -1
- package/template_project/.ukit/storage/cache/hook-latency/unknown.jsonl +0 -4
|
@@ -195,6 +195,17 @@ async function deriveVerificationPlan({
|
|
|
195
195
|
reasons.push('highRiskFallback');
|
|
196
196
|
commands.push(buildScriptCommand(packageManager, 'test'));
|
|
197
197
|
}
|
|
198
|
+
|
|
199
|
+
// Non-empty floor (RULES.md test-selection §3): when nothing targeted and
|
|
200
|
+
// no fallback resolved, the plan must still route the broadest test
|
|
201
|
+
// script the project declares — an empty selection is never permitted.
|
|
202
|
+
if (commands.length === 0 && fallbackCommands.length === 0) {
|
|
203
|
+
const floorScript = scripts['test:release-core'] ? 'test:release-core' : (scripts.test ? 'test' : null);
|
|
204
|
+
if (floorScript) {
|
|
205
|
+
reasons.push('nonEmptyFloor');
|
|
206
|
+
fallbackCommands.push(buildScriptCommand(packageManager, floorScript));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
198
209
|
}
|
|
199
210
|
|
|
200
211
|
if (commands.length === 0 && fallbackCommands.length === 0) {
|
|
@@ -8,13 +8,10 @@ import path from 'node:path';
|
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import { withAsyncLock, LOCK_MAX_SLICE_MS } from './async-lock.mjs';
|
|
10
10
|
|
|
11
|
-
// Hook-context self-deadline (2.4.1 orphan-leak class):
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
if (Number.isFinite(HOOK_DEADLINE_MS) && HOOK_DEADLINE_MS > 0) {
|
|
16
|
-
setTimeout(() => process.exit(0), HOOK_DEADLINE_MS).unref();
|
|
17
|
-
}
|
|
11
|
+
// Hook-context self-deadline (2.4.1 orphan-leak class): armed ONLY on direct
|
|
12
|
+
// CLI invocation (see isDirectRun below) — never at import time. An import-time
|
|
13
|
+
// process.exit timer races the importer's own §8-announcing deadline and exits
|
|
14
|
+
// the host hook silently (BUG-C23-04 class regression).
|
|
18
15
|
|
|
19
16
|
const LEDGER_VERSION = 1;
|
|
20
17
|
const RESUME_INTENT_VERSION = 1;
|
|
@@ -973,7 +970,7 @@ const JOURNAL_VERSION = 1;
|
|
|
973
970
|
const MAX_JOURNAL_SEEN = 64;
|
|
974
971
|
const MAX_JOURNAL_RECORDS = 128;
|
|
975
972
|
const MAX_JOURNAL_QUARANTINE_LINES = 32;
|
|
976
|
-
const LEDGER_EVENT_TYPES = new Set(['receipt', 'continuation', 'notified', 'stop-progress']);
|
|
973
|
+
const LEDGER_EVENT_TYPES = new Set(['receipt', 'continuation', 'notified', 'stop-progress', 'resumable-run']);
|
|
977
974
|
const RECEIPT_KINDS = new Set(['source', 'write', 'verification']);
|
|
978
975
|
|
|
979
976
|
function journalPathFor(target) {
|
|
@@ -1090,6 +1087,28 @@ function sanitizeReceiptForJournal(receipt) {
|
|
|
1090
1087
|
return clean;
|
|
1091
1088
|
}
|
|
1092
1089
|
|
|
1090
|
+
// TASK-007 (M04.1, FR-015): the resumable-run event carries a bounded pointer to
|
|
1091
|
+
// the C10 record — never the record itself (it lives under .ukit/storage/runs/).
|
|
1092
|
+
// Whitelisted fields only, each coerced to a bounded string/number so a journal
|
|
1093
|
+
// record can never carry prompt text or unbounded payloads.
|
|
1094
|
+
const JOURNAL_RUN_FIELDS = ['taskId', 'taskBoundary', 'stage', 'status', 'updatedAt'];
|
|
1095
|
+
const JOURNAL_RUN_FIELD_MAX = 160;
|
|
1096
|
+
|
|
1097
|
+
function sanitizeRunForJournal(run) {
|
|
1098
|
+
if (!run || typeof run !== 'object' || Array.isArray(run)) return null;
|
|
1099
|
+
const clean = {};
|
|
1100
|
+
for (const key of JOURNAL_RUN_FIELDS) {
|
|
1101
|
+
const value = run[key];
|
|
1102
|
+
if (value === undefined || value === null || value === '') continue;
|
|
1103
|
+
if (typeof value === 'number') {
|
|
1104
|
+
if (Number.isFinite(value)) clean[key] = value;
|
|
1105
|
+
continue;
|
|
1106
|
+
}
|
|
1107
|
+
clean[key] = String(value).slice(0, JOURNAL_RUN_FIELD_MAX);
|
|
1108
|
+
}
|
|
1109
|
+
return typeof clean.taskId === 'string' && clean.taskId ? clean : null;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1093
1112
|
function buildJournalRecord({ event, eventId, payload, routeState }) {
|
|
1094
1113
|
return {
|
|
1095
1114
|
v: JOURNAL_VERSION,
|
|
@@ -1102,7 +1121,9 @@ function buildJournalRecord({ event, eventId, payload, routeState }) {
|
|
|
1102
1121
|
type: event.type,
|
|
1103
1122
|
event: event.type === 'receipt'
|
|
1104
1123
|
? { receipt: sanitizeReceiptForJournal(event.receipt), vibecode: event.vibecode === true }
|
|
1105
|
-
:
|
|
1124
|
+
: event.type === 'resumable-run'
|
|
1125
|
+
? { run: sanitizeRunForJournal(event.run) }
|
|
1126
|
+
: { ...event, type: undefined },
|
|
1106
1127
|
};
|
|
1107
1128
|
}
|
|
1108
1129
|
|
|
@@ -1160,6 +1181,12 @@ function parseJournalRecord(line, expectedSessionKey) {
|
|
|
1160
1181
|
const receipt = record.event.receipt;
|
|
1161
1182
|
if (!receipt || typeof receipt !== 'object' || !RECEIPT_KINDS.has(receipt.kind)) return null;
|
|
1162
1183
|
}
|
|
1184
|
+
if (record.type === 'resumable-run') {
|
|
1185
|
+
const run = record.event.run;
|
|
1186
|
+
if (!run || typeof run !== 'object' || typeof run.taskId !== 'string' || !run.taskId) {
|
|
1187
|
+
return null;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1163
1190
|
return record;
|
|
1164
1191
|
}
|
|
1165
1192
|
|
|
@@ -1195,6 +1222,9 @@ function applyJournalRecord(ledger, record) {
|
|
|
1195
1222
|
if (record.type === 'notified') {
|
|
1196
1223
|
return { ledger: { ...ledger, notified: true, updatedAt: Date.now() }, value: null };
|
|
1197
1224
|
}
|
|
1225
|
+
if (record.type === 'resumable-run') {
|
|
1226
|
+
return { ledger: applyResumableRunToLedger(ledger, record.event?.run), value: null };
|
|
1227
|
+
}
|
|
1198
1228
|
if (record.type === 'stop-progress') {
|
|
1199
1229
|
return applyStopProgressToLedger(ledger);
|
|
1200
1230
|
}
|
|
@@ -1424,6 +1454,25 @@ function applyStopProgressToLedger(ledger) {
|
|
|
1424
1454
|
};
|
|
1425
1455
|
}
|
|
1426
1456
|
|
|
1457
|
+
// TASK-007 (FR-015): the resumable-run event lands as a bounded pointer on the
|
|
1458
|
+
// ledger — newest updatedAt wins so a journaled replay can never resurrect a
|
|
1459
|
+
// superseded record pointer.
|
|
1460
|
+
function applyResumableRunToLedger(ledger, run) {
|
|
1461
|
+
if (!run || typeof run !== 'object' || typeof run.taskId !== 'string' || !run.taskId) {
|
|
1462
|
+
return ledger;
|
|
1463
|
+
}
|
|
1464
|
+
const incomingAt = Number.isFinite(run.updatedAt) ? run.updatedAt : Date.now();
|
|
1465
|
+
const currentAt = Number.isFinite(ledger?.resumableRun?.updatedAt)
|
|
1466
|
+
? ledger.resumableRun.updatedAt
|
|
1467
|
+
: -Infinity;
|
|
1468
|
+
if (incomingAt < currentAt) return ledger;
|
|
1469
|
+
return {
|
|
1470
|
+
...ledger,
|
|
1471
|
+
resumableRun: { ...run, updatedAt: incomingAt },
|
|
1472
|
+
updatedAt: Date.now(),
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1427
1476
|
function liveBaseLedger(event, current, payload, routeState, harness, fallbackLedger) {
|
|
1428
1477
|
if (event.type === 'receipt') {
|
|
1429
1478
|
// A missing/foreign route state must never blank the evidence this session already
|
|
@@ -1481,6 +1530,8 @@ export async function recordLedgerEvent(event, {
|
|
|
1481
1530
|
ledger = applyContinuationToLedger(ledger, event);
|
|
1482
1531
|
} else if (event.type === 'notified') {
|
|
1483
1532
|
ledger = { ...ledger, notified: true, updatedAt: Date.now() };
|
|
1533
|
+
} else if (event.type === 'resumable-run') {
|
|
1534
|
+
ledger = applyResumableRunToLedger(ledger, event.run);
|
|
1484
1535
|
} else if (event.type === 'stop-progress') {
|
|
1485
1536
|
if (!current && drained.applied === 0) {
|
|
1486
1537
|
// A stop with no ledger at all creates nothing (historic noteStopProgress shape).
|
|
@@ -2175,6 +2226,57 @@ export async function appendDecision(projectRoot, {
|
|
|
2175
2226
|
}
|
|
2176
2227
|
}
|
|
2177
2228
|
|
|
2229
|
+
// --- M07 decision-plane receipts (TASK-005, SPEC §5 FR-008/FR-011) -----------
|
|
2230
|
+
// Typed wrapper over appendDecision for shadow/applied/fallback decision
|
|
2231
|
+
// receipts. The receipt contract is codes and bands only — never prompts,
|
|
2232
|
+
// state text, raw probabilities, or secrets — so every cell is coerced to a
|
|
2233
|
+
// bounded code string before it reaches the TSV. `kind` is the receipt class:
|
|
2234
|
+
// 'shadow' (advisory run, deterministic policy stayed authoritative),
|
|
2235
|
+
// 'applied' (canary/default accepted answer), 'fallback' (adapter failed and
|
|
2236
|
+
// the deterministic baseline stayed in force).
|
|
2237
|
+
const DECISION_RECEIPT_KINDS = new Set(['shadow', 'applied', 'fallback']);
|
|
2238
|
+
const DECISION_RECEIPT_CELL_MAX = 160;
|
|
2239
|
+
|
|
2240
|
+
function receiptCell(value) {
|
|
2241
|
+
return String(value ?? 'none')
|
|
2242
|
+
.replace(/[\t\n\r]+/g, ' ')
|
|
2243
|
+
.trim()
|
|
2244
|
+
.slice(0, DECISION_RECEIPT_CELL_MAX) || 'none';
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
export async function appendDecisionReceipt(projectRoot, {
|
|
2248
|
+
kind = 'shadow',
|
|
2249
|
+
boundary = '',
|
|
2250
|
+
stage = '',
|
|
2251
|
+
outcomeClass = '',
|
|
2252
|
+
checkpoint = null,
|
|
2253
|
+
latencyClass = 'unknown',
|
|
2254
|
+
decisionKeys = [],
|
|
2255
|
+
agreement = 'unknown',
|
|
2256
|
+
fallbackCode = null,
|
|
2257
|
+
now = Date.now(),
|
|
2258
|
+
} = {}) {
|
|
2259
|
+
if (!DECISION_RECEIPT_KINDS.has(kind)) {
|
|
2260
|
+
return { appended: false, reason: 'invalid-receipt-kind' };
|
|
2261
|
+
}
|
|
2262
|
+
const keys = (Array.isArray(decisionKeys) ? decisionKeys : [])
|
|
2263
|
+
.map((k) => receiptCell(k))
|
|
2264
|
+
.join(',');
|
|
2265
|
+
return appendDecision(projectRoot, {
|
|
2266
|
+
phase: 'decision-plane',
|
|
2267
|
+
decision: `${receiptCell(boundary || 'unknown')}-${kind}`,
|
|
2268
|
+
why: `stage=${receiptCell(stage)}`,
|
|
2269
|
+
evidence: [
|
|
2270
|
+
receiptCell(checkpoint ?? 'no-checkpoint'),
|
|
2271
|
+
receiptCell(latencyClass),
|
|
2272
|
+
`keys=${keys || 'none'}`,
|
|
2273
|
+
].join(' '),
|
|
2274
|
+
result: `${receiptCell(outcomeClass)} agree=${receiptCell(agreement)}`
|
|
2275
|
+
+ (fallbackCode ? ` fallback=${receiptCell(fallbackCode)}` : ''),
|
|
2276
|
+
now,
|
|
2277
|
+
});
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2178
2280
|
function isDirectRun() {
|
|
2179
2281
|
const argvPath = process.argv[1];
|
|
2180
2282
|
if (!argvPath) return false;
|
|
@@ -2194,6 +2296,11 @@ function isDirectRun() {
|
|
|
2194
2296
|
}
|
|
2195
2297
|
|
|
2196
2298
|
if (isDirectRun()) {
|
|
2299
|
+
// Self-deadline for direct hook/CLI invocation only (see note at top).
|
|
2300
|
+
const HOOK_DEADLINE_MS = Number.parseInt(process.env.UKIT_HOOK_DEADLINE_MS || '', 10);
|
|
2301
|
+
if (Number.isFinite(HOOK_DEADLINE_MS) && HOOK_DEADLINE_MS > 0) {
|
|
2302
|
+
setTimeout(() => process.exit(0), HOOK_DEADLINE_MS).unref();
|
|
2303
|
+
}
|
|
2197
2304
|
main().catch((error) => {
|
|
2198
2305
|
process.stderr.write(`[ukit-execution-ledger] ${error?.message || error}\n`);
|
|
2199
2306
|
process.exitCode = 1;
|
|
@@ -35,13 +35,10 @@ import {
|
|
|
35
35
|
loadSensitiveAllowlist,
|
|
36
36
|
} from './sensitive-value-scanner.mjs';
|
|
37
37
|
|
|
38
|
-
// Hook-context self-deadline (2.4.1 orphan-leak class):
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
if (Number.isFinite(HOOK_DEADLINE_MS) && HOOK_DEADLINE_MS > 0) {
|
|
43
|
-
setTimeout(() => process.exit(0), HOOK_DEADLINE_MS).unref();
|
|
44
|
-
}
|
|
38
|
+
// Hook-context self-deadline (2.4.1 orphan-leak class): armed ONLY on direct
|
|
39
|
+
// CLI invocation (see the argv[1] guard at the bottom) — never at import time.
|
|
40
|
+
// An import-time process.exit timer races the importer's own §8-announcing
|
|
41
|
+
// deadline and exits the host hook silently (BUG-C23-04 class).
|
|
45
42
|
|
|
46
43
|
export const PROJECT_IMPORTANT_FILENAME = 'PROJECT_IMPORTANT.md';
|
|
47
44
|
export const PROJECT_IMPORTANT_CODEPOINT_LIMIT = 6000;
|
|
@@ -372,6 +369,11 @@ if (
|
|
|
372
369
|
process.argv[1]
|
|
373
370
|
&& fs.realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)
|
|
374
371
|
) {
|
|
372
|
+
// Self-deadline for direct hook invocation only (see note at top).
|
|
373
|
+
const HOOK_DEADLINE_MS = Number.parseInt(process.env.UKIT_HOOK_DEADLINE_MS || '', 10);
|
|
374
|
+
if (Number.isFinite(HOOK_DEADLINE_MS) && HOOK_DEADLINE_MS > 0) {
|
|
375
|
+
setTimeout(() => process.exit(0), HOOK_DEADLINE_MS).unref();
|
|
376
|
+
}
|
|
375
377
|
try {
|
|
376
378
|
const projectRoot = process.env.UKIT_PROJECT_ROOT
|
|
377
379
|
|| process.env.CLAUDE_PROJECT_DIR
|
|
@@ -20,6 +20,10 @@ import {
|
|
|
20
20
|
writeCompactPressureState,
|
|
21
21
|
writeThresholdCompactPlan,
|
|
22
22
|
} from './compact-threshold.mjs';
|
|
23
|
+
import {
|
|
24
|
+
readResumableRun,
|
|
25
|
+
resumableRunSourceFingerprint,
|
|
26
|
+
} from './resumable-run.mjs';
|
|
23
27
|
|
|
24
28
|
// Hook-context self-deadline (2.4.1 orphan-leak class): the reinject-context hook passes
|
|
25
29
|
// UKIT_HOOK_DEADLINE_MS so a wedged read can never orphan this process past the hook
|
|
@@ -66,6 +70,46 @@ const INLINE_SUMMARY_TAIL_PATTERNS = [
|
|
|
66
70
|
/\b(Duration|Start at)\b/i,
|
|
67
71
|
];
|
|
68
72
|
|
|
73
|
+
|
|
74
|
+
// TASK-007 (M04.1, FR-015): on compaction resume, consume the SAME C10 record
|
|
75
|
+
// route-task persisted (no second format, no master-doc reread). The shared
|
|
76
|
+
// route state carries the bounded pointer (routeSummary.resumableRun); the
|
|
77
|
+
// record under .ukit/storage/runs/ supplies phase/nextAction/hypotheses.
|
|
78
|
+
// Stage 'off' (or absent/malformed) → no read at all. A stale record still
|
|
79
|
+
// reinjects its surviving sections — the invalidation already dropped the
|
|
80
|
+
// dependent plans. Never throws: resume is advisory.
|
|
81
|
+
async function buildResumableRunLines(projectRoot, state, config) {
|
|
82
|
+
try {
|
|
83
|
+
const stage = config?.continuity?.resumableRun?.stage;
|
|
84
|
+
if (typeof stage !== 'string' || stage === 'off') return [];
|
|
85
|
+
const pointer = state?.routeSummary?.resumableRun;
|
|
86
|
+
if (!pointer || typeof pointer.taskId !== 'string' || !pointer.taskId) return [];
|
|
87
|
+
const sourceFingerprint = await resumableRunSourceFingerprint(projectRoot);
|
|
88
|
+
const resumed = await readResumableRun(projectRoot, {
|
|
89
|
+
taskId: pointer.taskId,
|
|
90
|
+
taskBoundary: typeof pointer.taskBoundary === 'string' ? pointer.taskBoundary : undefined,
|
|
91
|
+
sourceFingerprint,
|
|
92
|
+
});
|
|
93
|
+
if (resumed.status === 'absent' || resumed.status === 'invalid') return [];
|
|
94
|
+
const record = resumed.record;
|
|
95
|
+
if (!record) return [];
|
|
96
|
+
const lines = [];
|
|
97
|
+
if (resumed.status === 'stale') {
|
|
98
|
+
lines.push('- Resumed run: stale — source/index changed since persist; dependent plans were invalidated, completed receipts kept.');
|
|
99
|
+
}
|
|
100
|
+
const phase = String(record.workflow?.phase ?? '').trim();
|
|
101
|
+
if (phase) lines.push(`- Resumed run phase: ${phase}`);
|
|
102
|
+
const nextAction = typeof record.nextAction === 'string' ? record.nextAction.trim() : '';
|
|
103
|
+
if (nextAction) lines.push(`- Resumed next action: ${nextAction}`);
|
|
104
|
+
for (const hypothesis of (Array.isArray(record.hypotheses) ? record.hypotheses : []).slice(0, 3)) {
|
|
105
|
+
const text = String(hypothesis ?? '').trim();
|
|
106
|
+
if (text) lines.push(`- Resumed hypothesis: ${text}`);
|
|
107
|
+
}
|
|
108
|
+
return lines;
|
|
109
|
+
} catch {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
69
113
|
async function main() {
|
|
70
114
|
const projectRoot = process.env.CLAUDE_PROJECT_DIR && path.isAbsolute(process.env.CLAUDE_PROJECT_DIR)
|
|
71
115
|
? process.env.CLAUDE_PROJECT_DIR
|
|
@@ -109,6 +153,7 @@ async function main() {
|
|
|
109
153
|
...await buildPreviousContextLines(projectRoot, state, config),
|
|
110
154
|
...await buildRecentOutputLines(projectRoot, config),
|
|
111
155
|
...await buildResolverHintLines(projectRoot, state),
|
|
156
|
+
...await buildResumableRunLines(projectRoot, state, config),
|
|
112
157
|
...await buildDuraOneHintLines(projectRoot),
|
|
113
158
|
// Post-compact discipline: the host summary keeps whatever it kept — the fastest way to
|
|
114
159
|
// re-inflate a just-compacted session is re-reading old files/logs to "recover" detail
|
|
@@ -141,6 +186,9 @@ async function main() {
|
|
|
141
186
|
|| line.startsWith('- Decision rule:')
|
|
142
187
|
|| line.startsWith('- Previous context:')
|
|
143
188
|
|| line.startsWith('- Recent command output:')
|
|
189
|
+
|| line.startsWith('- Resumed run')
|
|
190
|
+
|| line.startsWith('- Resumed next action:')
|
|
191
|
+
|| line.startsWith('- Resumed hypothesis:')
|
|
144
192
|
|| line.startsWith('- Recent delegation hint:')
|
|
145
193
|
));
|
|
146
194
|
const maxTokens = Math.max(160, Math.min(640, Number(config.memory?.maxInjectionTokens) || 320));
|