@veewo/claw 0.2.22 → 0.2.24

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/README.md CHANGED
@@ -51,7 +51,7 @@ claw plan create "Ephemeral harness" --scope session --goal "Use plan and Goal w
51
51
 
52
52
  New project tasks are grouped under `.claw/tasks/YYYY-MM-DD/`. On the first `claw context` call of each local calendar day, claw performs a lock-protected maintenance pass: it removes expired entries from `.claw/runtime/tmp/`, removes workflow task directories that exceed the task TTL even when incomplete, moves eligible date-scoped task folders into the archive, applies `maxTasksToKeep` and archive TTL, removes expired or invalid bindings, and sweeps expired session workflows. This is lazy maintenance, not a background scheduler.
53
53
 
54
- `--scope session` stores the workflow in a user-level directory keyed by the platform session id, so it works without a project `.claw` directory and recovers across cwd changes. It preserves plan/task/subplan/Goal behavior while disabling project knowledge capture, memory/GitNexus refresh, and project retention. Use `claw session clean` for the current session or `claw session clean --expired` for the seven-day TTL sweep.
54
+ `--scope session` stores the workflow in a user-level directory keyed by the platform session id, so it works without a project `.claw` directory and recovers across cwd changes. It preserves plan/task/subplan/Goal behavior while disabling project knowledge capture and project retention. When the session's frozen origin belongs to a valid claw project, its terminal transition refreshes that project's memory and enabled GitNexus index; otherwise it creates no refresh state. Use `claw session clean` for the current session or `claw session clean --expired` for the seven-day TTL sweep.
55
55
 
56
56
  Projects can add reusable templates directly under `.claw/templates` with `.json`, `.js`, `.mjs`, or `.cjs` files. Put `defaultPlanTemplate` in `.claw/project.json` for a shared team default, or in `.claw/project-override.json` for a local personal override.
57
57
 
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import { createInterface } from "node:readline";
6
6
  import { pathToFileURL } from "node:url";
7
7
  import { createHash } from "node:crypto";
8
8
  import { spawn, spawnSync } from "node:child_process";
9
- import { buildDirectWorkflowGuidance, appendKnowledgeTaskConclusions, buildKnowledgeAtomicDispatch, buildKnowledgeDelegateDispatch, buildKnowledgeAssignmentTemplate, buildKnowledgeWriterAssignments, DEFAULT_MAX_TASKS_TO_KEEP, checkProjectProtocol, ClawError, assertRootPlanCreateAllowedForPlan, buildPlanWorkflowGuidance, buildMemoryIndex, buildSessionStartDefaultPrompt, buildSessionStartRecoveredPrompt, editPlan, ensureProjectProtocol, enforceTaskRetention, findKnowledgeFinalizationJobPath, findTaskDirectory, runDailyMaintenance, ingestTruth, initProject, getTemplateTaskDoneChoices, resolvePlanTemplateFile, resolvePlanEffectiveConfig, resolveKnowledgeWriterForHost, resolveProjectContext, resolveWorkflowProjectContext, resolveSessionWorkflowContext, deleteSessionWorkflow, sweepExpiredSessionWorkflows, resolveSessionBoundPlan, resolveContext, resolveSeedPlanTemplate, searchMemoryAsync, warmProjectMemoryEmbedding, showPlan, createSubplan, createPlanRef, switchTask, tryCaptureKnowledgeStop, claimKnowledgeFinalizationJob, doneKnowledgeFinalizationJob, readKnowledgeFinalizationJob, waitForKnowledgeFinalizationJobReady, listKnowledgeFinalizationJobs, listRetryableKnowledgeFinalizationJobs, normalizeTruthMarkdownEncoding, recordKnowledgeFinalizationResult, unbindSession, writePlan, } from "@veewo/claw-core";
9
+ import { buildDirectWorkflowGuidance, appendKnowledgeTaskConclusions, buildKnowledgeAtomicDispatch, buildKnowledgeDelegateDispatch, buildKnowledgeAssignmentTemplate, buildDirectKnowledgeAssignments, buildKnowledgeWriterAssignments, DEFAULT_MAX_TASKS_TO_KEEP, checkProjectProtocol, ClawError, assertRootPlanCreateAllowedForPlan, buildPlanWorkflowGuidance, buildMemoryIndex, buildSessionStartDefaultPrompt, buildSessionStartRecoveredPrompt, editPlan, ensureProjectProtocol, enforceTaskRetention, findKnowledgeFinalizationJobPath, findTaskDirectory, runDailyMaintenance, ingestTruth, initProject, getTemplateTaskDoneChoices, resolvePlanTemplateFile, resolvePlanEffectiveConfig, resolveThreadGoalPlan, resolveKnowledgeWriterForHost, resolveProjectContext, resolveWorkflowProjectContext, resolveSessionWorkflowContext, deleteSessionWorkflow, sweepExpiredSessionWorkflows, resolveSessionBoundPlan, resolveContext, resolveSeedPlanTemplate, searchMemoryAsync, warmProjectMemoryEmbedding, showPlan, createSubplan, createPlanRef, switchTask, tryCaptureKnowledgeStop, claimKnowledgeFinalizationJob, doneKnowledgeFinalizationJob, readKnowledgeFinalizationJob, waitForKnowledgeFinalizationJobReady, listKnowledgeFinalizationJobs, listRetryableKnowledgeFinalizationJobs, normalizeTruthMarkdownEncoding, governKnowledgeMarkdownPaths, resolveKnowledgeDocUpdateSnapshot, recordKnowledgeFinalizationResult, unbindSession, writePlan, } from "@veewo/claw-core";
10
10
  import { buildCodexDriverEnvelope } from "./codex-driver.js";
11
11
  import { buildCodexHostActions } from "./codex-host-actions.js";
12
12
  import { checkCodexRuntime, resolveCodexSdkEntryPath } from "./codex-runtime.js";
@@ -333,8 +333,28 @@ const COMMAND_HELP = {
333
333
  },
334
334
  knowledge: {
335
335
  usage: ["{script} knowledge <subcommand> [options]"],
336
- description: "Session-bound lifecycle commands for a queued knowledge finalization job.",
336
+ description: "Commands for queued knowledge finalization jobs and explicit same-agent knowledge capture.",
337
337
  subcommands: {
338
+ prepare: {
339
+ usage: ["{script} knowledge prepare --source agent-memory --project-root <path>"],
340
+ description: "Read the current project configuration and return the immutable assignment projection for one explicit manual knowledge capture. It never initializes, repairs, or mutates the project.",
341
+ summary: "Prepare explicit same-agent knowledge capture.",
342
+ options: [
343
+ { flag: "--source agent-memory", detail: "(required) Only source supported by the manual same-agent route." },
344
+ { flag: "--project-root <path>", detail: "(required) Project root used to resolve current team and personal configuration." },
345
+ ],
346
+ },
347
+ complete: {
348
+ usage: ["{script} knowledge complete --source agent-memory --project-root <path> --config-fingerprint <hash> [--changed-truth <absolute-path> ...]"],
349
+ description: "Validate the prepared configuration, govern declared canonical paths, normalize knowledge encoding, and queue the existing completion refresh without creating a report or job.",
350
+ summary: "Complete explicit same-agent knowledge capture.",
351
+ options: [
352
+ { flag: "--source agent-memory", detail: "(required) Only source supported by the manual same-agent route." },
353
+ { flag: "--project-root <path>", detail: "(required) Project root used to resolve current configuration." },
354
+ { flag: "--config-fingerprint <hash>", detail: "(required) Fingerprint returned by knowledge prepare." },
355
+ { flag: "--changed-truth <absolute-path>", detail: "Canonical Truth or ADR Markdown path changed by this capture (repeatable)." },
356
+ ],
357
+ },
338
358
  wait: {
339
359
  usage: ["{script} knowledge wait --project-root <path> --finalize-id <id> [--session-key <key>] [--timeout-ms <n>]"],
340
360
  description: "Wait for Stop capture to create a knowledge finalization job. This command does not create or inspect session bindings.",
@@ -1024,6 +1044,24 @@ function readCindyKnowledgeClaimCaptureInput() {
1024
1044
  async function runKnowledge(args) {
1025
1045
  const subcommand = args.shift();
1026
1046
  switch (subcommand) {
1047
+ case "prepare": {
1048
+ const source = readRequiredFlag(args, "--source");
1049
+ const projectRoot = path.resolve(readRequiredFlag(args, "--project-root"));
1050
+ assertNoRemainingArgs(args, "knowledge prepare");
1051
+ assertDirectKnowledgeSource(source);
1052
+ printJson(prepareDirectKnowledgeCapture(projectRoot));
1053
+ return;
1054
+ }
1055
+ case "complete": {
1056
+ const source = readRequiredFlag(args, "--source");
1057
+ const projectRoot = path.resolve(readRequiredFlag(args, "--project-root"));
1058
+ const configFingerprint = readRequiredFlag(args, "--config-fingerprint");
1059
+ const changedTruth = readRepeatedFlag(args, "--changed-truth");
1060
+ assertNoRemainingArgs(args, "knowledge complete");
1061
+ assertDirectKnowledgeSource(source);
1062
+ printJson(completeDirectKnowledgeCapture({ projectRoot, configFingerprint, changedTruth }));
1063
+ return;
1064
+ }
1027
1065
  case "list": {
1028
1066
  const projectRoot = path.resolve(readRequiredFlag(args, "--project-root"));
1029
1067
  const sessionKey = readOptionalFlag(args, "--session-key");
@@ -1205,6 +1243,98 @@ async function runKnowledge(args) {
1205
1243
  throw new ClawError("PROJECT_CONFIG_INVALID", `Unknown knowledge subcommand "${subcommand ?? ""}".`);
1206
1244
  }
1207
1245
  }
1246
+ function prepareDirectKnowledgeCapture(projectRoot) {
1247
+ const project = resolveProjectContext(projectRoot);
1248
+ const ownerSessionKey = resolveOwnerSessionKey() ?? undefined;
1249
+ if (ownerSessionKey && resolveSessionBoundPlan(project, ownerSessionKey)) {
1250
+ throw new ClawError("KNOWLEDGE_DIRECT_WORKFLOW_ACTIVE", "Manual knowledge capture cannot run while this session has an active claw workflow.");
1251
+ }
1252
+ const writer = project.projectConfig?.knowledgeWriter;
1253
+ const docUpdate = resolveKnowledgeDocUpdateSnapshot(project);
1254
+ const assignments = buildDirectKnowledgeAssignments({ writer, ...(docUpdate ? { docUpdate } : {}) });
1255
+ for (const assignment of assignments) {
1256
+ for (const resourcePath of [assignment.contractPath, assignment.formatPath]) {
1257
+ if (resourcePath && (!fs.existsSync(resourcePath) || !fs.statSync(resourcePath).isFile())) {
1258
+ throw new ClawError("KNOWLEDGE_DIRECT_RESOURCE_MISSING", "A required knowledge-capture contract resource is unavailable.", { resourcePath });
1259
+ }
1260
+ }
1261
+ }
1262
+ const configFingerprint = directKnowledgeConfigFingerprint({ project, assignments, docUpdate });
1263
+ return {
1264
+ ok: true,
1265
+ command: "knowledge.prepare",
1266
+ schemaVersion: 1,
1267
+ source: "agent-memory",
1268
+ project: { projectRoot: project.projectRoot, truthDir: project.truthDir },
1269
+ configFingerprint,
1270
+ assignments,
1271
+ refresh: {
1272
+ operations: ["memory.reindex.project", ...(project.projectConfig?.gitnexus === true ? ["gitnexus.refresh"] : [])],
1273
+ },
1274
+ };
1275
+ }
1276
+ function completeDirectKnowledgeCapture(input) {
1277
+ const prepared = prepareDirectKnowledgeCapture(input.projectRoot);
1278
+ if (prepared.configFingerprint !== input.configFingerprint) {
1279
+ throw new ClawError("KNOWLEDGE_DIRECT_CONFIG_CHANGED", "Knowledge-capture configuration changed after prepare; run knowledge prepare again before completion.", { expected: input.configFingerprint, actual: prepared.configFingerprint });
1280
+ }
1281
+ const project = resolveProjectContext(input.projectRoot);
1282
+ const relativePaths = input.changedTruth.map((candidate) => relativeTruthPath(project.truthDir, candidate));
1283
+ const builtin = prepared.assignments.find((assignment) => assignment.kind === "builtin");
1284
+ const governance = builtin
1285
+ ? governKnowledgeMarkdownPaths({
1286
+ truthDir: project.truthDir,
1287
+ relativePaths,
1288
+ datedSectionsToKeep: builtin.datedSectionsToKeep ?? 6,
1289
+ })
1290
+ : { changedFiles: 0, compactedFiles: 0, removedSections: 0, files: [] };
1291
+ const truthEncoding = normalizeTruthMarkdownEncoding(project);
1292
+ const refresh = queueCompletionRefresh({
1293
+ cwd: project.projectRoot,
1294
+ taskName: "knowledge-capture",
1295
+ includeTaskRetention: false,
1296
+ statusLabel: "knowledge-capture",
1297
+ });
1298
+ return {
1299
+ ok: true,
1300
+ command: "knowledge.complete",
1301
+ source: "agent-memory",
1302
+ configFingerprint: prepared.configFingerprint,
1303
+ changedTruth: relativePaths,
1304
+ governance,
1305
+ truthEncoding,
1306
+ asyncRefresh: refresh.asyncRefresh,
1307
+ };
1308
+ }
1309
+ function assertDirectKnowledgeSource(source) {
1310
+ if (source !== "agent-memory") {
1311
+ throw new ClawError("PROJECT_CONFIG_INVALID", 'Manual knowledge capture supports only --source agent-memory.');
1312
+ }
1313
+ }
1314
+ function relativeTruthPath(truthDir, candidate) {
1315
+ const root = path.resolve(truthDir);
1316
+ const target = path.resolve(candidate);
1317
+ const relative = path.relative(root, target);
1318
+ if (!relative || relative.startsWith("..") || path.isAbsolute(relative) || !/\.md$/iu.test(relative)) {
1319
+ throw new ClawError("KNOWLEDGE_DIRECT_PATH_INVALID", "Changed knowledge paths must be Markdown files inside the resolved truth directory.", { candidate, truthDir: root });
1320
+ }
1321
+ return relative.replaceAll("\\", "/");
1322
+ }
1323
+ function directKnowledgeConfigFingerprint(input) {
1324
+ const payload = {
1325
+ schemaVersion: 1,
1326
+ truthDir: path.resolve(input.project.truthDir),
1327
+ assignments: input.assignments.map((assignment) => ({
1328
+ kind: assignment.kind,
1329
+ skill: assignment.skill ?? null,
1330
+ datedSectionsToKeep: assignment.datedSectionsToKeep ?? null,
1331
+ })),
1332
+ externalDocPaths: input.docUpdate?.externalDocPaths ?? [],
1333
+ gitnexus: input.project.projectConfig?.gitnexus === true,
1334
+ memoryRefresh: input.project.projectConfig?.memory?.enabled !== false,
1335
+ };
1336
+ return `sha256:${createHash("sha256").update(JSON.stringify(payload)).digest("hex")}`;
1337
+ }
1208
1338
  async function runPlan(args, effectiveHost) {
1209
1339
  const subcommand = args.shift();
1210
1340
  switch (subcommand) {
@@ -1223,7 +1353,7 @@ async function runPlan(args, effectiveHost) {
1223
1353
  throw new ClawError("PROJECT_CONFIG_INVALID", "plan create requires a title. Use `claw plan create \"<title>\"` or `claw plan create --title \"<title>\"`.");
1224
1354
  }
1225
1355
  const ownerSessionKey = resolveOwnerSessionKey();
1226
- assertDirectRootPlanCreateAllowed(process.cwd(), ownerSessionKey);
1356
+ assertDirectRootPlanCreateAllowed(process.cwd(), ownerSessionKey, scope);
1227
1357
  await preparePlanCreateWorkflow(process.cwd(), ownerSessionKey, effectiveHost, scope);
1228
1358
  const result = await writePlan({
1229
1359
  cwd: process.cwd(),
@@ -1235,6 +1365,15 @@ async function runPlan(args, effectiveHost) {
1235
1365
  ownerSessionKey: ownerSessionKey ?? undefined,
1236
1366
  host: effectiveHost,
1237
1367
  });
1368
+ if (ownerSessionKey && scope !== "session") {
1369
+ const sessionProject = resolveSessionWorkflowContext(ownerSessionKey);
1370
+ if (sessionProject) {
1371
+ const relativePlanPath = path.relative(sessionProject.clawDir, result.planPath);
1372
+ if (relativePlanPath.startsWith("..") || path.isAbsolute(relativePlanPath)) {
1373
+ unbindSession(sessionProject, ownerSessionKey);
1374
+ }
1375
+ }
1376
+ }
1238
1377
  assertNoRemainingArgs(args, "plan create");
1239
1378
  printJson(compactPlanCommandResult("plan.create", result, effectiveHost));
1240
1379
  return;
@@ -1260,8 +1399,8 @@ async function runPlan(args, effectiveHost) {
1260
1399
  && effectiveHost !== "cindy") {
1261
1400
  throw new ClawError("PROJECT_CONFIG_INVALID", 'knowledgeWriter.executionPolicy "subagent" is supported only by the Codex or Cindy host.', { host: effectiveHost ?? null });
1262
1401
  }
1263
- const queuePlanEndFinalization = entersEndTerminal
1264
- ? preparePlanEndFinalization(process.cwd(), ownerSessionKey)
1402
+ const terminalRefresh = entersEndTerminal
1403
+ ? preparePlanTerminalRefresh(process.cwd(), ownerSessionKey)
1265
1404
  : undefined;
1266
1405
  const result = await editPlan({
1267
1406
  cwd: process.cwd(),
@@ -1283,7 +1422,7 @@ async function runPlan(args, effectiveHost) {
1283
1422
  writer: effectiveWriter,
1284
1423
  })
1285
1424
  : undefined;
1286
- const completionRefresh = queuePlanEndFinalization?.(result.taskName);
1425
+ const completionRefresh = queueTerminalRefreshIfEntered(result, terminalRefresh);
1287
1426
  printJson(compactPlanCommandResult("plan.edit", result, effectiveHost, completionRefresh, false, knowledgeDispatch));
1288
1427
  if (result.operationChain?.status === "partial")
1289
1428
  process.exitCode = 1;
@@ -1371,7 +1510,7 @@ async function runPlan(args, effectiveHost) {
1371
1510
  && effectiveHost !== "cindy") {
1372
1511
  throw new ClawError("PROJECT_CONFIG_INVALID", 'knowledgeWriter.executionPolicy "subagent" is supported only by the Codex or Cindy host.', { host: effectiveHost ?? null });
1373
1512
  }
1374
- const queuePlanEndFinalization = preparePlanEndFinalization(process.cwd(), ownerSessionKey);
1513
+ const terminalRefresh = preparePlanTerminalRefresh(process.cwd(), ownerSessionKey);
1375
1514
  const result = await editPlan({
1376
1515
  cwd: process.cwd(),
1377
1516
  ...target,
@@ -1390,7 +1529,7 @@ async function runPlan(args, effectiveHost) {
1390
1529
  writer: effectiveWriter,
1391
1530
  })
1392
1531
  : undefined;
1393
- const completionRefresh = queuePlanEndFinalization?.(result.taskName);
1532
+ const completionRefresh = queueTerminalRefreshIfEntered(result, terminalRefresh);
1394
1533
  printJson(compactPlanCommandResult("plan.done", result, effectiveHost, completionRefresh, false, knowledgeDispatch));
1395
1534
  return;
1396
1535
  }
@@ -1439,6 +1578,7 @@ async function runPlanLeave(args, effectiveHost) {
1439
1578
  const ownerSessionKey = resolveOwnerSessionKey() ?? undefined;
1440
1579
  const target = readPlanMutationTarget(args);
1441
1580
  assertNoRemainingArgs(args, "plan leave");
1581
+ const terminalRefresh = preparePlanTerminalRefresh(process.cwd(), ownerSessionKey);
1442
1582
  const result = await editPlan({
1443
1583
  cwd: process.cwd(),
1444
1584
  ...target,
@@ -1447,7 +1587,8 @@ async function runPlanLeave(args, effectiveHost) {
1447
1587
  host: effectiveHost,
1448
1588
  ownerSessionKey,
1449
1589
  });
1450
- printJson(compactPlanCommandResult("plan.leave", result, effectiveHost, undefined, true));
1590
+ const completionRefresh = queueTerminalRefreshIfEntered(result, terminalRefresh);
1591
+ printJson(compactPlanCommandResult("plan.leave", result, effectiveHost, completionRefresh, true));
1451
1592
  }
1452
1593
  async function runPlanSync(args, effectiveHost) {
1453
1594
  const ownerSessionKey = resolveOwnerSessionKey() ?? undefined;
@@ -1458,12 +1599,20 @@ async function runPlanSync(args, effectiveHost) {
1458
1599
  return;
1459
1600
  }
1460
1601
  const project = resolveWorkflowProjectContext(process.cwd(), ownerSessionKey);
1602
+ const goalPlan = resolveThreadGoalPlan({
1603
+ cwd: process.cwd(),
1604
+ taskName: result.taskName,
1605
+ focusedPlan: result.plan,
1606
+ ownerSessionKey,
1607
+ });
1461
1608
  const workflowGuidance = await buildPlanWorkflowGuidance({
1462
1609
  taskName: result.taskName,
1463
1610
  planFile: result.planFile,
1464
1611
  plan: result.plan,
1465
1612
  projectRoot: project.projectRoot,
1466
1613
  projectConfig: project.projectConfig,
1614
+ goalPlan,
1615
+ goalProjectConfig: resolvePlanEffectiveConfig(project.projectConfig, goalPlan),
1467
1616
  scope: project.scope,
1468
1617
  previousStatus: "process.wait",
1469
1618
  host: effectiveHost,
@@ -1642,7 +1791,10 @@ async function runContextCommand(args, cwd = process.cwd(), ownerSessionKey = re
1642
1791
  let corrected = false;
1643
1792
  let fixedPaths = [];
1644
1793
  const sessionProject = resolveSessionWorkflowContext(ownerSessionKey ?? undefined);
1645
- if (sessionProject) {
1794
+ const sessionPlanPath = sessionProject && ownerSessionKey
1795
+ ? resolveSessionBoundPlan(sessionProject, ownerSessionKey)
1796
+ : null;
1797
+ if (sessionProject && sessionPlanPath) {
1646
1798
  const maintenance = effectiveHost
1647
1799
  ? null
1648
1800
  : runDailyMaintenance(sessionProject, {
@@ -2199,10 +2351,14 @@ async function preparePlanCreateWorkflow(cwd, ownerSessionKey, effectiveHost, re
2199
2351
  }
2200
2352
  await prepareProjectWorkflow(cwd, ownerSessionKey, effectiveHost, project);
2201
2353
  }
2202
- function assertDirectRootPlanCreateAllowed(cwd, ownerSessionKey) {
2354
+ function assertDirectRootPlanCreateAllowed(cwd, ownerSessionKey, requestedScope) {
2203
2355
  if (!ownerSessionKey)
2204
2356
  return;
2205
- const project = resolveWorkflowProjectContext(cwd, ownerSessionKey);
2357
+ const project = requestedScope === "session"
2358
+ ? resolveSessionWorkflowContext(ownerSessionKey)
2359
+ : tryResolveHookProject(cwd);
2360
+ if (!project)
2361
+ return;
2206
2362
  const planPath = resolveSessionBoundPlan(project, ownerSessionKey);
2207
2363
  if (!planPath)
2208
2364
  return;
@@ -2604,7 +2760,7 @@ function buildSessionStartAdditionalContext(context, sessionCwd, effectiveHost)
2604
2760
  if (activeWorkflow) {
2605
2761
  const prompt = buildRecoveredWorkflowAdditionalContext(activeWorkflow, versionSyncPrompt);
2606
2762
  const recoverySyncPrompt = effectiveHost === "codex" && activeWorkflow.planStatus === "process.active"
2607
- ? "Before continuing, run `claw plan sync` once through the fixed Codex driver to restore host progress and Goal Mode."
2763
+ ? "Before continuing, run `claw plan sync` once through the fixed Codex driver to restore focused-plan progress and reconcile the root-plan Goal."
2608
2764
  : "";
2609
2765
  const promptWithSync = recoverySyncPrompt ? `${prompt}\n${recoverySyncPrompt}` : prompt;
2610
2766
  const promptWithSearch = searchGuidance ? `${promptWithSync}\n${searchGuidance}` : promptWithSync;
@@ -3315,11 +3471,24 @@ function readRepeatedIntegerFlag(args, flag) {
3315
3471
  function failMissingNumericFlag(flag) {
3316
3472
  throw new ClawError("PROJECT_CONFIG_INVALID", `Missing required flag ${flag}.`, { flag });
3317
3473
  }
3318
- function preparePlanEndFinalization(cwd, ownerSessionKey) {
3474
+ function preparePlanTerminalRefresh(cwd, ownerSessionKey) {
3319
3475
  const workflowProject = resolveWorkflowProjectContext(cwd, ownerSessionKey);
3320
- if (workflowProject.scope !== "project")
3476
+ const refreshProject = tryResolveHookProject(workflowProject.projectRoot);
3477
+ if (!refreshProject)
3321
3478
  return undefined;
3322
- return (taskName) => queueCompletionRefresh({ cwd, taskName });
3479
+ return {
3480
+ queue: (taskName) => queueCompletionRefresh({
3481
+ cwd: refreshProject.projectRoot,
3482
+ taskName,
3483
+ includeTaskRetention: workflowProject.scope !== "session",
3484
+ }),
3485
+ };
3486
+ }
3487
+ function queueTerminalRefreshIfEntered(result, terminalRefresh) {
3488
+ if (!terminalRefresh || result.previousPlanStatus.startsWith("end.") || !result.planStatus.startsWith("end.")) {
3489
+ return undefined;
3490
+ }
3491
+ return terminalRefresh.queue(result.taskName);
3323
3492
  }
3324
3493
  function queueCompletionRefresh(input) {
3325
3494
  const project = resolveProjectContext(input.cwd);