@ngockhoale/ukit 3.0.3 → 3.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/bin/ukit +12 -4
  3. package/manifests/engineConformance.yaml +29 -0
  4. package/manifests/platform.full.yaml +13 -0
  5. package/package.json +2 -1
  6. package/scripts/audit/decision-coverage.mjs +295 -0
  7. package/scripts/bench/outline-savings.mjs +19 -4
  8. package/scripts/bench/parallel-agents.mjs +15 -4
  9. package/scripts/bench/runGold.mjs +22 -4
  10. package/scripts/bench/v3-ceremony.mjs +7 -1
  11. package/scripts/bug/triage.mjs +56 -17
  12. package/scripts/index/build-index.mjs +94 -28
  13. package/scripts/index/query-index.mjs +48 -14
  14. package/scripts/index/refresh-index.mjs +142 -62
  15. package/scripts/perf/audit-perf.mjs +8 -2
  16. package/scripts/skill/audit-skill.mjs +54 -25
  17. package/src/bug/triageBug.js +9 -6
  18. package/src/cli/adapters.js +6 -0
  19. package/src/cli/commands/code.js +7 -1
  20. package/src/cli/commands/indexArgs.js +4 -2
  21. package/src/cli/commands/indexTools.js +8 -1
  22. package/src/cli/commands/install.js +13 -0
  23. package/src/cli/commands/memory.js +13 -9
  24. package/src/cli/commands/status.js +17 -1
  25. package/src/cli/commands/update.js +7 -0
  26. package/src/context/detectProjectContext.js +3 -1
  27. package/src/core/codeintel/analogy.js +1 -1
  28. package/src/core/codeintel/diagnostics.js +9 -6
  29. package/src/core/codeintel/graph.js +14 -8
  30. package/src/core/codeintel/impact.js +0 -1
  31. package/src/core/codeintel/invalidation.js +5 -3
  32. package/src/core/codeintel/packet.js +11 -0
  33. package/src/core/codeintel/router.js +17 -3
  34. package/src/core/codeintel/semanticProvider.js +1 -1
  35. package/src/core/codeintel/summaries.js +11 -7
  36. package/src/core/compact/contextBudget.js +26 -12
  37. package/src/core/compact/index.js +15 -8
  38. package/src/core/docContracts.js +10 -2
  39. package/src/core/experiments/deliberation.js +321 -0
  40. package/src/core/experiments/dynamicWorkflow.js +492 -0
  41. package/src/core/fileOps.js +8 -1
  42. package/src/core/gatewayProbe.js +29 -1
  43. package/src/core/gatewayResilienceEnv.js +44 -3
  44. package/src/core/handoffDocValidator.js +3 -1
  45. package/src/core/hookChainDoctor.js +16 -1
  46. package/src/core/memory/deltaOverlays.js +448 -0
  47. package/src/core/memory/learningCandidates.js +302 -0
  48. package/src/core/memory/migrate.js +59 -32
  49. package/src/core/memory/recordStore.js +24 -1
  50. package/src/core/memory/store.js +44 -8
  51. package/src/core/memory/storeV2.js +48 -33
  52. package/src/core/memory/userMemory.js +26 -10
  53. package/src/core/output/index.js +15 -10
  54. package/src/core/permissionPolicy.js +8 -0
  55. package/src/core/runtimeConfig.js +224 -4
  56. package/src/core/sensitiveValueScanner.js +10 -2
  57. package/src/core/taskBudgetValidator.js +12 -17
  58. package/src/core/taskProgressGuard.js +59 -9
  59. package/src/core/unattendedDoctor.js +5 -2
  60. package/src/core/uninstall.js +37 -8
  61. package/src/decision/client.js +371 -0
  62. package/src/decision/lease.js +198 -0
  63. package/src/decision/preflight.js +492 -0
  64. package/src/decision/protocol.js +308 -0
  65. package/src/decision/registry.js +384 -0
  66. package/src/decision/shadow.js +281 -0
  67. package/src/decision/statePacket.js +165 -0
  68. package/src/diagnostics/failurePatterns.js +2 -1
  69. package/src/diagnostics/ledgerFiles.js +3 -1
  70. package/src/diagnostics/routeOutcomes.js +1 -29
  71. package/src/index/buildIndex.js +23 -11
  72. package/src/index/impactContext.js +21 -2
  73. package/src/index/importResolution.js +13 -7
  74. package/src/index/queryIndex.js +11 -5
  75. package/src/index/resolveContext.js +22 -9
  76. package/src/index/taskRouting.js +63 -2
  77. package/src/index/verificationPlan.js +12 -1
  78. package/src/learning/patternProposals.js +6 -0
  79. package/src/render/renderTemplate.js +1 -1
  80. package/src/skill/auditSkill.js +3 -1
  81. package/src/stack/detectStack.js +3 -1
  82. package/template_project/.claude/agents/handoff-planner.md +2 -5
  83. package/template_project/.claude/hooks/auto-allow-bash.sh +5 -0
  84. package/template_project/.claude/hooks/block-dangerous.mjs +11 -4
  85. package/template_project/.claude/hooks/context-hardcap-gate.sh +10 -1
  86. package/template_project/.claude/hooks/handoff-model-guard.sh +14 -4
  87. package/template_project/.claude/hooks/handoff-resume.sh +10 -1
  88. package/template_project/.claude/hooks/protect-files.sh +0 -1
  89. package/template_project/.claude/hooks/record-execution.mjs +13 -1
  90. package/template_project/.claude/hooks/sensitive-data-guard.mjs +80 -5
  91. package/template_project/.claude/hooks/session-episode.sh +9 -2
  92. package/template_project/.claude/skills/pdf-processing-pro/SKILL.md +1 -1
  93. package/template_project/.claude/ukit/index/handoff-doc-validator.mjs +3 -1
  94. package/template_project/.claude/ukit/index/lib/index-core.mjs +129 -42
  95. package/template_project/.claude/ukit/index/route-task.mjs +444 -0
  96. package/template_project/.claude/ukit/index/task-budget-validator.mjs +12 -16
  97. package/template_project/.claude/ukit/index/unic-decision.mjs +786 -0
  98. package/template_project/.claude/ukit/index/verify-context.mjs +11 -0
  99. package/template_project/.claude/ukit/runtime/execution-ledger.mjs +116 -9
  100. package/template_project/.claude/ukit/runtime/project-important.mjs +9 -7
  101. package/template_project/.claude/ukit/runtime/reinject-context.mjs +48 -0
  102. package/template_project/.claude/ukit/runtime/resumable-run.mjs +596 -0
  103. package/template_project/.claude/ukit/runtime/sensitive-value-scanner.mjs +4 -7
  104. package/template_project/.claude/ukit/runtime/stop-coordinator.mjs +1 -1
  105. package/template_project/docs/AI_HANDOFF/PLAN.md +7 -7
  106. package/template_project/docs/AI_HANDOFF/RULES.md +1 -1
  107. package/template_project/ukit/storage/config.json +34 -1
  108. package/template_project/.claude/ukit/skill-router-state.json +0 -1
  109. 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): wrapper hooks pass
12
- // UKIT_HOOK_DEADLINE_MS so a wedged read can never orphan this process past the
13
- // hook budget. CLI usage never sets it and is never self-killed.
14
- const HOOK_DEADLINE_MS = Number.parseInt(process.env.UKIT_HOOK_DEADLINE_MS || '', 10);
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
- : { ...event, type: undefined },
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): a hook wrapper passes
39
- // UKIT_HOOK_DEADLINE_MS so a wedged read can never orphan this process past
40
- // the hook budget. CLI usage never sets it and is never self-killed.
41
- const HOOK_DEADLINE_MS = Number.parseInt(process.env.UKIT_HOOK_DEADLINE_MS || '', 10);
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));