@veewo/claw 0.1.93 → 0.1.95

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
@@ -35,6 +35,8 @@ claw plan create "Ephemeral harness" --scope session --goal "Use plan and Goal w
35
35
 
36
36
  `claw plan create` uses explicit `--template` first, otherwise the project's configured `defaultPlanTemplate`, and finally falls back to the built-in `default` template. You can select a template explicitly with `claw plan create "<title>" --template <name>` or `claw plan create --template <name> --title "<title>"`.
37
37
 
38
+ 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 clears `.claw/runtime/tmp/` (and removes legacy `.claw/tmp/`), moves date-scoped task folders from before yesterday into the archive even when old plans have no `completedAt`, archives legacy flat tasks by `plan.updatedAt` (falling back to the plan file timestamp), applies `maxTasksToKeep` to archived tasks, removes bindings to plans no longer under active tasks, and sweeps expired session workflows. This is lazy maintenance, not a background scheduler.
39
+
38
40
  `--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.
39
41
 
40
42
  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.
package/dist/bin.js CHANGED
@@ -9,11 +9,23 @@ process.emitWarning = ((warning, ...args) => {
9
9
  });
10
10
  void main();
11
11
  async function main() {
12
- if (!await shouldLoadCliForInvocation(process.argv.slice(2), process.cwd(), process.env)) {
12
+ const args = process.argv.slice(2);
13
+ if (!await shouldLoadCliForInvocation(args, process.cwd(), process.env)) {
14
+ return;
15
+ }
16
+ if (isSearchInvocation(args)) {
17
+ const { runSearchEntry } = await import("./search-entry.js");
18
+ await runSearchEntry(args.slice(1));
13
19
  return;
14
20
  }
15
21
  await import("./cli.js");
16
22
  }
23
+ function isSearchInvocation(args) {
24
+ return args[0] === "search"
25
+ && args[1] !== "help"
26
+ && !args.includes("--help")
27
+ && !args.includes("-h");
28
+ }
17
29
  function shouldSuppressSqliteExperimentalWarning(warning, args) {
18
30
  const message = typeof warning === "string"
19
31
  ? warning
package/dist/bin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAE3E,MAAM,mBAAmB,GAAG,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAE9D,OAAO,CAAC,WAAW,GAAG,CAAC,CAAC,OAAgB,EAAE,GAAG,IAAe,EAAE,EAAE;IAC9D,IAAI,uCAAuC,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;QAC3D,OAAO;IACT,CAAC;IACD,OAAO,mBAAmB,CAAC,OAAgB,EAAE,GAAI,IAAW,CAAC,CAAC;AAChE,CAAC,CAA+B,CAAC;AAEjC,KAAK,IAAI,EAAE,CAAC;AAEZ,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC,MAAM,0BAA0B,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACzF,OAAO;IACT,CAAC;IACD,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,uCAAuC,CAAC,OAAgB,EAAE,IAAe;IAChF,MAAM,OAAO,GACX,OAAO,OAAO,KAAK,QAAQ;QACzB,CAAC,CAAC,OAAO;QACT,CAAC,CAAC,OAAO,YAAY,KAAK;YACxB,CAAC,CAAC,OAAO,CAAC,OAAO;YACjB,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACxB,MAAM,IAAI,GACR,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ;QACzB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACT,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,IAAI,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC;YAC3D,CAAC,CAAC,MAAM,CAAE,IAAI,CAAC,CAAC,CAAwB,CAAC,IAAI,IAAI,EAAE,CAAC;YACpD,CAAC,CAAC,OAAO,YAAY,KAAK;gBACxB,CAAC,CAAC,OAAO,CAAC,IAAI;gBACd,CAAC,CAAC,EAAE,CAAC;IAEb,OAAO,IAAI,KAAK,qBAAqB,IAAI,OAAO,CAAC,QAAQ,CAAC,mCAAmC,CAAC,CAAC;AACjG,CAAC"}
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAE3E,MAAM,mBAAmB,GAAG,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAE9D,OAAO,CAAC,WAAW,GAAG,CAAC,CAAC,OAAgB,EAAE,GAAG,IAAe,EAAE,EAAE;IAC9D,IAAI,uCAAuC,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;QAC3D,OAAO;IACT,CAAC;IACD,OAAO,mBAAmB,CAAC,OAAgB,EAAE,GAAI,IAAW,CAAC,CAAC;AAChE,CAAC,CAA+B,CAAC;AAEjC,KAAK,IAAI,EAAE,CAAC;AAEZ,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,IAAI,CAAC,MAAM,0BAA0B,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACxE,OAAO;IACT,CAAC;IACD,IAAI,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7B,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;QAC7D,MAAM,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACpC,OAAO;IACT,CAAC;IACD,MAAM,MAAM,CAAC,UAAU,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAc;IACxC,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ;WACtB,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM;WAClB,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;WACxB,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC;AAED,SAAS,uCAAuC,CAAC,OAAgB,EAAE,IAAe;IAChF,MAAM,OAAO,GACX,OAAO,OAAO,KAAK,QAAQ;QACzB,CAAC,CAAC,OAAO;QACT,CAAC,CAAC,OAAO,YAAY,KAAK;YACxB,CAAC,CAAC,OAAO,CAAC,OAAO;YACjB,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACxB,MAAM,IAAI,GACR,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ;QACzB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACT,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,IAAI,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC;YAC3D,CAAC,CAAC,MAAM,CAAE,IAAI,CAAC,CAAC,CAAwB,CAAC,IAAI,IAAI,EAAE,CAAC;YACpD,CAAC,CAAC,OAAO,YAAY,KAAK;gBACxB,CAAC,CAAC,OAAO,CAAC,IAAI;gBACd,CAAC,CAAC,EAAE,CAAC;IAEb,OAAO,IAAI,KAAK,qBAAqB,IAAI,OAAO,CAAC,QAAQ,CAAC,mCAAmC,CAAC,CAAC;AACjG,CAAC"}
package/dist/cli.js CHANGED
@@ -5,21 +5,20 @@ import path from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
6
  import { createHash } from "node:crypto";
7
7
  import { spawn, spawnSync } from "node:child_process";
8
- import { buildDirectWorkflowGuidance, DEFAULT_MAX_TASKS_TO_KEEP, checkProjectProtocol, ClawError, buildPlanWorkflowGuidance, buildMemoryIndex, buildSessionStartDefaultPrompt, buildSessionStartRecoveredPrompt, editPlan, ensureProjectProtocol, enforceTaskRetention, ingestTruth, initProject, getTemplateTaskDoneChoices, resolvePlanTemplateFile, resolveProjectContext, resolveWorkflowProjectContext, resolveSessionWorkflowContext, deleteSessionWorkflow, sweepExpiredSessionWorkflows, resolveSessionBoundPlan, resolveContext, resolveSeedPlanTemplate, searchMemory, warmProjectMemoryEmbedding, showPlan, createSubplan, switchTask, tryCaptureKnowledgeStop, claimKnowledgeFinalizationJob, changedKnowledgeMarkdownPaths, listRetryableKnowledgeFinalizationJobs, governChangedKnowledgeMarkdown, normalizeTruthMarkdownEncoding, recordKnowledgeFinalizationResult, snapshotKnowledgeMarkdown, writeKnowledgeFinalizationJob, unbindSession, writePlan, } from "@veewo/claw-core";
8
+ import { buildDirectWorkflowGuidance, DEFAULT_MAX_TASKS_TO_KEEP, checkProjectProtocol, ClawError, buildPlanWorkflowGuidance, buildMemoryIndex, buildSessionStartDefaultPrompt, buildSessionStartRecoveredPrompt, editPlan, ensureProjectProtocol, enforceTaskRetention, findTaskDirectory, runDailyMaintenance, ingestTruth, initProject, getTemplateTaskDoneChoices, resolvePlanTemplateFile, resolveProjectContext, resolveWorkflowProjectContext, resolveSessionWorkflowContext, deleteSessionWorkflow, sweepExpiredSessionWorkflows, resolveSessionBoundPlan, resolveContext, resolveSeedPlanTemplate, searchMemoryAsync, warmProjectMemoryEmbedding, showPlan, createSubplan, switchTask, tryCaptureKnowledgeStop, claimKnowledgeFinalizationJob, changedKnowledgeMarkdownPaths, listRetryableKnowledgeFinalizationJobs, governChangedKnowledgeMarkdown, normalizeTruthMarkdownEncoding, recordKnowledgeFinalizationResult, snapshotKnowledgeMarkdown, writeKnowledgeFinalizationJob, unbindSession, writePlan, } from "@veewo/claw-core";
9
9
  import { buildCodexDriverEnvelope } from "./codex-driver.js";
10
10
  import { checkCodexRuntime, resolveCodexSdkEntryPath } from "./codex-runtime.js";
11
11
  import { extractLatestFinalAssistantMessage, extractTaskDoneConclusions, } from "./codex-transcript.js";
12
12
  import { consumeBufferedHookInput } from "./knowledge-hook-preflight.js";
13
13
  import { resolveInvocationHost, withoutInvocationHost } from "./invocation-host.js";
14
14
  import { runOpencodeKnowledgeWriter } from "./opencode-runner.js";
15
- import { captureKnowledgeGitState, commitKnowledgeDocumentation } from "./knowledge-git-commit.js";
16
15
  const CLI_VERSION = readCliVersion();
17
16
  const TOP_LEVEL_COMMANDS = [
18
17
  { name: "init [options]", summary: "Initialize and normalize the .claw project surface." },
19
18
  { name: "context [--task <name>]", summary: "Resolve project context, auto-initializing or correcting .claw state." },
20
19
  { name: "session clean [--expired]", summary: "Remove current or expired session workflow state." },
21
20
  { name: "check", summary: "Check and auto-correct .claw project protocol fields." },
22
- { name: "plan <subcommand> [options]", summary: "Plan lifecycle: create, start, edit, remove, wait, resume, show, done." },
21
+ { name: "plan <subcommand> [options]", summary: "Plan lifecycle: create, start, edit, remove, wait, resume, sync, show, done." },
23
22
  { name: "codex driver", summary: "Return the versioned code-mode driver used by the Codex adapter." },
24
23
  { name: "template <subcommand> [options]", summary: "Plan template helpers such as validation." },
25
24
  { name: "task <subcommand> [options]", summary: "Task lifecycle helpers inside an existing plan." },
@@ -93,6 +92,10 @@ const COMMAND_HELP = {
93
92
  { flag: "--summary <text>", detail: "Set the plan summary." },
94
93
  { flag: "--rule <text>", detail: "Append a rule (repeatable)." },
95
94
  { flag: "--key-decision <text>", detail: "Append a key decision (repeatable)." },
95
+ { flag: "--retrospective <text>", detail: "Set the retrospective summary; required before --status end.completed." },
96
+ { flag: "--what-worked <text>", detail: "Append a retrospective success (repeatable)." },
97
+ { flag: "--issue <text>", detail: "Append a retrospective issue (repeatable)." },
98
+ { flag: "--follow-up <text>", detail: "Append a retrospective follow-up (repeatable)." },
96
99
  { flag: "--reference <path>", detail: "Add a reference; follow it immediately with --why (repeatable)." },
97
100
  { flag: "--why <text>", detail: "Explain the immediately preceding --reference." },
98
101
  { flag: "--task-name <name>", detail: "Advanced: override the session-bound task scope." },
@@ -131,6 +134,11 @@ const COMMAND_HELP = {
131
134
  { flag: "--plan-file <relative-path>", detail: "Advanced: override the session-bound plan file." },
132
135
  ],
133
136
  },
137
+ sync: {
138
+ usage: ["{script} plan sync"],
139
+ description: "Resynchronize a recovered active Codex plan with host progress and Goal Mode without mutating the plan.",
140
+ summary: "Resync a recovered active Codex plan.",
141
+ },
134
142
  start: {
135
143
  usage: ["{script} plan start --requirements <text> --add-task <title> [--detail <text>] [options]"],
136
144
  description: "Atomically apply refined plan content, append outcome tasks, and execute the current template task's plan-start guidance in one serialized mutation.",
@@ -161,8 +169,8 @@ const COMMAND_HELP = {
161
169
  },
162
170
  done: {
163
171
  usage: ["{script} plan done --retrospective <text> [options]"],
164
- description: "Close out a plan: write a retrospective, mark status end.completed with completedAt, retain it for at least one hour, sweep older completed tasks into the archive, and queue the async completion refresh.",
165
- summary: "Close out a plan with a retrospective and queue completion refresh.",
172
+ description: "Shortcut for applying closeout fields and --status end.completed in one ordered plan edit; it retains the task for at least one hour, sweeps older completed tasks into the archive, and queues the async completion refresh.",
173
+ summary: "Shortcut for completing a plan with a retrospective and queueing completion refresh.",
166
174
  options: [
167
175
  { flag: "--retrospective <text>", detail: "Retrospective summary (required)." },
168
176
  { flag: "--key-decision <text>", detail: "Append a durable key decision when one exists (repeatable)." },
@@ -443,7 +451,7 @@ async function main() {
443
451
  }));
444
452
  return;
445
453
  case "search":
446
- runSearch(args);
454
+ await runSearch(args);
447
455
  return;
448
456
  case "direct":
449
457
  runDirect(args, effectiveHost);
@@ -541,15 +549,21 @@ async function runPlan(args, effectiveHost) {
541
549
  if (operations.length === 0) {
542
550
  throw new ClawError("PROJECT_CONFIG_INVALID", "plan edit requires at least one plan field or --status.");
543
551
  }
552
+ const ownerSessionKey = resolveOwnerSessionKey() ?? undefined;
553
+ const entersEndState = requestsPlanEndState(operations);
554
+ const queuePlanEndFinalization = entersEndState
555
+ ? preparePlanEndFinalization(process.cwd(), ownerSessionKey)
556
+ : undefined;
544
557
  const result = await editPlan({
545
558
  cwd: process.cwd(),
546
559
  ...target,
547
560
  operations,
548
561
  commandSource: "plan.edit",
549
562
  host: effectiveHost,
550
- ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
563
+ ownerSessionKey,
551
564
  });
552
- printJson(compactPlanCommandResult("plan.edit", result, effectiveHost));
565
+ const completionRefresh = queuePlanEndFinalization?.(result.taskName);
566
+ printJson(compactPlanCommandResult("plan.edit", result, effectiveHost, completionRefresh));
553
567
  if (result.operationChain?.status === "partial")
554
568
  process.exitCode = 1;
555
569
  return;
@@ -578,6 +592,9 @@ async function runPlan(args, effectiveHost) {
578
592
  case "resume":
579
593
  await runPlanStatusAlias(args, "process.active", "plan.resume", effectiveHost);
580
594
  return;
595
+ case "sync":
596
+ await runPlanSync(args, effectiveHost);
597
+ return;
581
598
  case "start": {
582
599
  const updates = readPlanFieldUpdates(args);
583
600
  const appendTasks = readExplicitAddedTasks(args);
@@ -614,26 +631,16 @@ async function runPlan(args, effectiveHost) {
614
631
  const target = readPlanMutationTarget(args);
615
632
  assertNoRemainingArgs(args, "plan done");
616
633
  const ownerSessionKey = resolveOwnerSessionKey() ?? undefined;
617
- const workflowProject = resolveWorkflowProjectContext(process.cwd(), ownerSessionKey);
618
- const gitNexusPreflightAnalyzed = workflowProject.scope === "project"
619
- ? ensureGitNexusReadyForPlanDone(process.cwd())
620
- : false;
634
+ const queuePlanEndFinalization = preparePlanEndFinalization(process.cwd(), ownerSessionKey);
621
635
  const result = await editPlan({
622
636
  cwd: process.cwd(),
623
637
  ...target,
624
- updates,
625
- planStatus: "end.completed",
638
+ operations: planCompletionOperations(updates),
626
639
  commandSource: "plan.done",
627
640
  host: effectiveHost,
628
641
  ownerSessionKey,
629
642
  });
630
- const completionRefresh = workflowProject.scope === "project"
631
- ? queueCompletionRefresh({
632
- cwd: process.cwd(),
633
- taskName: result.taskName,
634
- skipGitNexusRefresh: gitNexusPreflightAnalyzed,
635
- })
636
- : undefined;
643
+ const completionRefresh = queuePlanEndFinalization?.(result.taskName);
637
644
  printJson(compactPlanCommandResult("plan.done", result, effectiveHost, completionRefresh));
638
645
  return;
639
646
  }
@@ -671,7 +678,28 @@ async function runPlanStatusAlias(args, planStatus, command, effectiveHost) {
671
678
  ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
672
679
  });
673
680
  assertNoRemainingArgs(args, command);
674
- printJson(compactPlanCommandResult(command, result, effectiveHost));
681
+ printJson(compactPlanCommandResult(command, result, effectiveHost, undefined, true));
682
+ }
683
+ async function runPlanSync(args, effectiveHost) {
684
+ const ownerSessionKey = resolveOwnerSessionKey() ?? undefined;
685
+ const result = showPlan({ cwd: process.cwd(), ...readPlanMutationTarget(args), ownerSessionKey });
686
+ assertNoRemainingArgs(args, "plan sync");
687
+ if (result.plan.status !== "process.active") {
688
+ printJson({ ok: true, command: "plan.sync", planPath: result.planPath, planStatus: result.plan.status });
689
+ return;
690
+ }
691
+ const project = resolveWorkflowProjectContext(process.cwd(), ownerSessionKey);
692
+ const workflowGuidance = await buildPlanWorkflowGuidance({
693
+ taskName: result.taskName,
694
+ planFile: result.planFile,
695
+ plan: result.plan,
696
+ projectRoot: project.projectRoot,
697
+ projectConfig: project.projectConfig,
698
+ previousStatus: "process.wait",
699
+ host: effectiveHost,
700
+ recoveryResync: true,
701
+ });
702
+ printJson(compactPlanCommandResult("plan.sync", { ...result, planStatus: result.plan.status, workflowGuidance }, effectiveHost, undefined, true));
675
703
  }
676
704
  async function runTemplate(args) {
677
705
  const subcommand = args.shift();
@@ -790,7 +818,7 @@ async function runTask(args, effectiveHost) {
790
818
  throw new ClawError("PROJECT_CONFIG_INVALID", `Unknown task subcommand "${subcommand ?? ""}".`);
791
819
  }
792
820
  }
793
- function runSearch(args) {
821
+ async function runSearch(args) {
794
822
  const subcommand = args[0];
795
823
  if (subcommand === "index") {
796
824
  args.shift();
@@ -815,7 +843,7 @@ function runSearch(args) {
815
843
  printJson({
816
844
  ok: true,
817
845
  command: "search",
818
- ...searchMemory({
846
+ ...await searchMemoryAsync({
819
847
  cwd: process.cwd(),
820
848
  limit: readOptionalNumber(args, "--limit"),
821
849
  query: readRequiredSearchQuery(args),
@@ -844,6 +872,7 @@ async function runContextCommand(args, cwd = process.cwd(), ownerSessionKey = re
844
872
  let fixedPaths = [];
845
873
  const sessionProject = resolveSessionWorkflowContext(ownerSessionKey ?? undefined);
846
874
  if (sessionProject) {
875
+ const maintenance = runDailyMaintenance(sessionProject, { excludeSessionKey: ownerSessionKey ?? undefined, includeProject: false });
847
876
  const activeWorkflow = !taskName && ownerSessionKey
848
877
  ? await tryResolveActiveWorkflowSnapshot(cwd, ownerSessionKey, effectiveHost)
849
878
  : null;
@@ -854,6 +883,7 @@ async function runContextCommand(args, cwd = process.cwd(), ownerSessionKey = re
854
883
  return {
855
884
  project: sessionProject,
856
885
  ...(activeWorkflow ? { activeWorkflow } : {}),
886
+ ...(maintenance.ran ? { maintenance } : {}),
857
887
  ...(codexRuntimeError ? { error: codexRuntimeError } : {}),
858
888
  };
859
889
  }
@@ -869,6 +899,7 @@ async function runContextCommand(args, cwd = process.cwd(), ownerSessionKey = re
869
899
  initProject({ cwd, version: CLI_VERSION });
870
900
  initialized = true;
871
901
  }
902
+ const maintenance = runDailyMaintenance(resolveProjectContext(cwd), { excludeSessionKey: ownerSessionKey ?? undefined });
872
903
  let resolved = resolveContext(cwd, taskName);
873
904
  const versionSync = syncProjectVersionWithCli(cwd, resolved.project);
874
905
  if (versionSync.projectVersionUpdated) {
@@ -888,6 +919,7 @@ async function runContextCommand(args, cwd = process.cwd(), ownerSessionKey = re
888
919
  launchProjectEmbeddingWarmup(resolved.project);
889
920
  return {
890
921
  ...resolved,
922
+ ...(maintenance.ran ? { maintenance } : {}),
891
923
  ...(activeWorkflow ? { activeWorkflow } : {}),
892
924
  ...(codexRuntimeError ? { error: codexRuntimeError } : {}),
893
925
  protocolCheck: checkProjectProtocol(cwd),
@@ -1221,10 +1253,6 @@ async function runInternalKnowledgeFinalize(args) {
1221
1253
  const project = resolveProjectContext(running.projectRoot);
1222
1254
  const useBuiltInAutomation = usesBuiltInKnowledgeWriter(running);
1223
1255
  const knowledgeBefore = snapshotKnowledgeMarkdown(project.truthDir);
1224
- const autoCommitKnowledge = project.projectConfig?.autoCommitKnowledge !== false;
1225
- const knowledgeGitState = autoCommitKnowledge
1226
- ? captureKnowledgeGitState(running.projectRoot, project.truthDir)
1227
- : null;
1228
1256
  const writerRun = await runKnowledgeWriterForJob(running);
1229
1257
  const knowledgeGovernance = useBuiltInAutomation
1230
1258
  ? governChangedKnowledgeMarkdown({
@@ -1268,14 +1296,6 @@ async function runInternalKnowledgeFinalize(args) {
1268
1296
  ...(knowledgeGovernance ? { knowledgeGovernance } : {}),
1269
1297
  truthEncoding,
1270
1298
  });
1271
- if (autoCommitKnowledge) {
1272
- commitKnowledgeDocumentation({
1273
- state: knowledgeGitState,
1274
- truthDir: project.truthDir,
1275
- changedPaths: changedKnowledgePaths,
1276
- message: running.taskName,
1277
- });
1278
- }
1279
1299
  }
1280
1300
  catch (error) {
1281
1301
  const failed = {
@@ -1477,7 +1497,7 @@ async function runSessionStartHook(effectiveHost) {
1477
1497
  }
1478
1498
  }
1479
1499
  }
1480
- const additionalContext = buildSessionStartAdditionalContext(context, hookCwd);
1500
+ const additionalContext = buildSessionStartAdditionalContext(context, hookCwd, effectiveHost);
1481
1501
  if (!additionalContext) {
1482
1502
  return;
1483
1503
  }
@@ -1557,14 +1577,18 @@ function safeResolveTempDir() {
1557
1577
  return null;
1558
1578
  }
1559
1579
  }
1560
- function buildSessionStartAdditionalContext(context, sessionCwd) {
1580
+ function buildSessionStartAdditionalContext(context, sessionCwd, effectiveHost) {
1561
1581
  const versionSyncPrompt = buildVersionSyncPrompt(context);
1562
1582
  const searchGuidance = buildContextSearchGuidance(context);
1563
1583
  const runtimeErrorPrompt = buildCodexRuntimeErrorPrompt(context);
1564
1584
  const activeWorkflow = context.activeWorkflow;
1565
1585
  if (activeWorkflow) {
1566
1586
  const prompt = buildRecoveredWorkflowAdditionalContext(activeWorkflow, versionSyncPrompt);
1567
- const promptWithSearch = searchGuidance ? `${prompt}\n${searchGuidance}` : prompt;
1587
+ const recoverySyncPrompt = effectiveHost === "codex" && activeWorkflow.planStatus === "process.active"
1588
+ ? "Before continuing, run `claw plan sync` once through the fixed Codex driver to restore host progress and Goal Mode."
1589
+ : "";
1590
+ const promptWithSync = recoverySyncPrompt ? `${prompt}\n${recoverySyncPrompt}` : prompt;
1591
+ const promptWithSearch = searchGuidance ? `${promptWithSync}\n${searchGuidance}` : promptWithSync;
1568
1592
  return runtimeErrorPrompt ? `${runtimeErrorPrompt}\n\n${promptWithSearch}` : promptWithSearch;
1569
1593
  }
1570
1594
  const project = context.project;
@@ -1731,18 +1755,15 @@ async function tryResolveActiveWorkflowSnapshot(cwd, ownerSessionKey, effectiveH
1731
1755
  return null;
1732
1756
  }
1733
1757
  try {
1734
- const relativePlanPath = path.relative(project.tasksDir, planPath);
1735
- const segments = relativePlanPath.split(path.sep);
1736
- const taskName = segments.shift();
1737
- const planFile = segments.join("/");
1738
- if (!taskName || !planFile) {
1758
+ const target = parseTaskPlanPath(project, planPath);
1759
+ if (!target) {
1739
1760
  unbindSession(project, ownerSessionKey);
1740
1761
  return null;
1741
1762
  }
1742
1763
  const result = showPlan({
1743
1764
  cwd,
1744
- taskName,
1745
- planFile,
1765
+ taskName: target.taskName,
1766
+ planFile: target.planFile,
1746
1767
  ownerSessionKey,
1747
1768
  });
1748
1769
  if (result.plan.status.startsWith("end.")) {
@@ -1859,16 +1880,15 @@ function readJson(filePath) {
1859
1880
  function stripBom(content) {
1860
1881
  return content.charCodeAt(0) === 0xfeff ? content.slice(1) : content;
1861
1882
  }
1862
- function compactPlanCommandResult(command, result, effectiveHost, completionRefresh) {
1883
+ function compactPlanCommandResult(command, result, effectiveHost, completionRefresh, forceProjectionSync = false) {
1863
1884
  const archivedPlanPath = completionRefresh?.taskRetention.archivedCurrentTask?.taskName === result.taskName &&
1864
1885
  completionRefresh.taskRetention.archivedCurrentTask.archivedPlanPath
1865
1886
  ? completionRefresh.taskRetention.archivedCurrentTask.archivedPlanPath
1866
1887
  : undefined;
1867
1888
  const resolvedPlanPath = archivedPlanPath ?? result.planPath;
1868
1889
  const codexResult = effectiveHost === "codex";
1869
- const hostActions = codexResult ? buildHostActions(result) : [];
1890
+ const hostActions = codexResult ? buildHostActions(result, { forceProjectionSync, actionIdPrefix: command === "plan.sync" ? `plan.sync:${createHash("sha256").update(result.planPath).digest("hex").slice(0, 16)}` : undefined }) : [];
1870
1891
  const nextsteps = codexResult
1871
- && command === "plan.done"
1872
1892
  && result.planStatus === "end.completed"
1873
1893
  && result.workflowGuidance.goalTool?.tool === "update_goal"
1874
1894
  && result.workflowGuidance.goalTool.status === "complete"
@@ -1878,7 +1898,7 @@ function compactPlanCommandResult(command, result, effectiveHost, completionRefr
1878
1898
  const includePlan = Boolean((command === "plan.create" || command === "subplan.create")
1879
1899
  && result.plan
1880
1900
  && (!codexResult || result.workflowGuidance.stage === "discussion"));
1881
- const achievement = command === "plan.done" && result.planStatus === "end.completed" && result.plan
1901
+ const achievement = result.planStatus === "end.completed" && result.plan
1882
1902
  ? {
1883
1903
  status: result.planStatus,
1884
1904
  title: result.plan.title,
@@ -1902,7 +1922,7 @@ function compactPlanCommandResult(command, result, effectiveHost, completionRefr
1902
1922
  ...(!codexResult && result.changedTaskIds?.length ? { changedTaskIds: result.changedTaskIds } : {}),
1903
1923
  ...(!codexResult && result.appendedTaskIds?.length ? { appendedTaskIds: result.appendedTaskIds } : {}),
1904
1924
  ...(codexResult ? { stage: result.workflowGuidance.stage } : {}),
1905
- ...(!codexResult || command === "plan.done"
1925
+ ...(!codexResult || result.planStatus === "end.completed"
1906
1926
  ? { nextsteps }
1907
1927
  : {}),
1908
1928
  ...(result.workflowGuidance.nextTask ? { nextTask: result.workflowGuidance.nextTask } : {}),
@@ -1937,9 +1957,10 @@ function compactPlanCommandResult(command, result, effectiveHost, completionRefr
1937
1957
  ...(!codexResult || !includePlan ? { planSummary } : {}),
1938
1958
  };
1939
1959
  }
1940
- function buildHostActions(result) {
1960
+ function buildHostActions(result, options = {}) {
1941
1961
  const latestEvent = result.events?.at(-1);
1942
- if (!latestEvent) {
1962
+ const actionIdPrefix = options.actionIdPrefix ?? latestEvent?.mutationId;
1963
+ if (!actionIdPrefix) {
1943
1964
  return [];
1944
1965
  }
1945
1966
  const actions = [];
@@ -1950,18 +1971,20 @@ function buildHostActions(result) {
1950
1971
  if (isSubplanGoalHandoff && goalTool?.tool === "update_goal") {
1951
1972
  actions.push({
1952
1973
  schemaVersion: 1,
1953
- id: `${latestEvent.mutationId}:update_goal`,
1974
+ id: `${actionIdPrefix}:update_goal`,
1954
1975
  tool: "update_goal",
1955
1976
  input: {
1956
1977
  status: "complete",
1957
1978
  },
1958
1979
  });
1959
1980
  }
1960
- if (result.plan && codexPlanProjectionChanged(result.previousPlan, result.plan, result.planStatus)) {
1981
+ if (result.plan
1982
+ && result.plan.tasks.length > 0
1983
+ && (options.forceProjectionSync || codexPlanProjectionChanged(result.previousPlan, result.plan, result.planStatus))) {
1961
1984
  const plan = buildCodexPlanProjection(result.plan, result.planStatus);
1962
1985
  actions.push({
1963
1986
  schemaVersion: 1,
1964
- id: `${latestEvent.mutationId}:update_plan`,
1987
+ id: `${actionIdPrefix}:update_plan`,
1965
1988
  tool: "update_plan",
1966
1989
  input: {
1967
1990
  explanation: result.workflowGuidance.summary,
@@ -1973,7 +1996,7 @@ function buildHostActions(result) {
1973
1996
  if (goalTool.tool === "create_goal") {
1974
1997
  actions.push({
1975
1998
  schemaVersion: 1,
1976
- id: `${latestEvent.mutationId}:create_goal`,
1999
+ id: `${actionIdPrefix}:create_goal`,
1977
2000
  tool: "create_goal",
1978
2001
  input: {
1979
2002
  objective: goalTool.objective,
@@ -1986,7 +2009,7 @@ function buildHostActions(result) {
1986
2009
  : goalTool.status;
1987
2010
  actions.push({
1988
2011
  schemaVersion: 1,
1989
- id: `${latestEvent.mutationId}:update_goal`,
2012
+ id: `${actionIdPrefix}:update_goal`,
1990
2013
  tool: "update_goal",
1991
2014
  input: {
1992
2015
  status: codexStatus,
@@ -2003,12 +2026,13 @@ function codexPlanProjectionChanged(previousPlan, plan, planStatus) {
2003
2026
  !== JSON.stringify(buildCodexPlanProjection(plan, planStatus));
2004
2027
  }
2005
2028
  function buildCodexPlanProjection(plan, planStatus) {
2006
- let assignedInProgress = false;
2029
+ const activeTask = plan.tasks.find((task) => task.status === "in_progress" || task.status === "subagent_running") ?? (planStatus === "process.active"
2030
+ ? plan.tasks.find((task) => task.status !== "done")
2031
+ : undefined);
2007
2032
  return plan.tasks.map((task) => {
2008
2033
  let status = task.status === "done" ? "completed" : "pending";
2009
- if (!assignedInProgress && planStatus === "process.active" && task.status !== "done") {
2034
+ if (task.id === activeTask?.id) {
2010
2035
  status = "in_progress";
2011
- assignedInProgress = true;
2012
2036
  }
2013
2037
  return { step: task.title, status };
2014
2038
  });
@@ -2070,6 +2094,18 @@ function readOrderedPlanEditOperations(args) {
2070
2094
  case "--key-decision":
2071
2095
  operations.push({ type: "plan.update", updates: { keyDecisions: [readChainValue(args, flag)] } });
2072
2096
  break;
2097
+ case "--retrospective":
2098
+ operations.push({ type: "plan.update", updates: { retrospectiveSummary: readChainValue(args, flag) } });
2099
+ break;
2100
+ case "--what-worked":
2101
+ operations.push({ type: "plan.update", updates: { whatWorked: [readChainValue(args, flag)] } });
2102
+ break;
2103
+ case "--issue":
2104
+ operations.push({ type: "plan.update", updates: { issues: [readChainValue(args, flag)] } });
2105
+ break;
2106
+ case "--follow-up":
2107
+ operations.push({ type: "plan.update", updates: { followUps: [readChainValue(args, flag)] } });
2108
+ break;
2073
2109
  case "--reference": {
2074
2110
  const path = readChainValue(args, flag);
2075
2111
  const whyFlag = args.shift();
@@ -2088,6 +2124,15 @@ function readOrderedPlanEditOperations(args) {
2088
2124
  }
2089
2125
  return operations;
2090
2126
  }
2127
+ function requestsPlanEndState(operations) {
2128
+ return operations.some((operation) => operation.type === "plan.status" && operation.status.startsWith("end."));
2129
+ }
2130
+ function planCompletionOperations(updates) {
2131
+ return [
2132
+ { type: "plan.update", updates },
2133
+ { type: "plan.status", status: "end.completed" },
2134
+ ];
2135
+ }
2091
2136
  function readOrderedTaskAddOperations(args) {
2092
2137
  const operations = [];
2093
2138
  while (args.length > 0) {
@@ -2222,16 +2267,25 @@ function readPlanMutationTarget(args) {
2222
2267
  if (!boundPlanPath) {
2223
2268
  throw new ClawError("PROJECT_CONFIG_INVALID", "No plan is bound to the current session. Create or recover a plan first, or use --task-name and optional --plan-file as an advanced override.");
2224
2269
  }
2225
- const relativePlanPath = path.relative(project.tasksDir, boundPlanPath);
2226
- const segments = relativePlanPath.split(path.sep).filter(Boolean);
2227
- if (segments.length < 2 || relativePlanPath.startsWith("..") || path.isAbsolute(relativePlanPath)) {
2270
+ const target = parseTaskPlanPath(project, boundPlanPath);
2271
+ if (!target) {
2228
2272
  throw new ClawError("PROJECT_CONFIG_INVALID", `Invalid session-bound plan path: ${boundPlanPath}`);
2229
2273
  }
2230
2274
  return {
2231
- taskName: segments[0],
2232
- planFile: explicitPlanFile ?? segments.slice(1).join(path.sep),
2275
+ taskName: target.taskName,
2276
+ planFile: explicitPlanFile ?? target.planFile,
2233
2277
  };
2234
2278
  }
2279
+ function parseTaskPlanPath(project, planPath) {
2280
+ const relativePlanPath = path.relative(project.tasksDir, planPath);
2281
+ if (relativePlanPath.startsWith("..") || path.isAbsolute(relativePlanPath))
2282
+ return null;
2283
+ const segments = relativePlanPath.split(path.sep).filter(Boolean);
2284
+ const dated = /^\d{4}-\d{2}-\d{2}$/.test(segments[0] ?? "");
2285
+ const taskName = dated ? segments[1] : segments[0];
2286
+ const planFile = (dated ? segments.slice(2) : segments.slice(1)).join(path.sep);
2287
+ return taskName && planFile ? { taskName, planFile } : null;
2288
+ }
2235
2289
  function readExplicitAddedTasks(args) {
2236
2290
  return readGroupedValues(args, "--add-task", "--detail", false).map((entry) => ({
2237
2291
  title: entry.value,
@@ -2274,6 +2328,13 @@ function readRepeatedIntegerFlag(args, flag) {
2274
2328
  function failMissingNumericFlag(flag) {
2275
2329
  throw new ClawError("PROJECT_CONFIG_INVALID", `Missing required flag ${flag}.`, { flag });
2276
2330
  }
2331
+ function preparePlanEndFinalization(cwd, ownerSessionKey) {
2332
+ const workflowProject = resolveWorkflowProjectContext(cwd, ownerSessionKey);
2333
+ if (workflowProject.scope !== "project")
2334
+ return undefined;
2335
+ const skipGitNexusRefresh = ensureGitNexusReadyForPlanFinalization(cwd);
2336
+ return (taskName) => queueCompletionRefresh({ cwd, taskName, skipGitNexusRefresh });
2337
+ }
2277
2338
  function queueCompletionRefresh(input) {
2278
2339
  const project = resolveProjectContext(input.cwd);
2279
2340
  const includeTaskRetention = input.includeTaskRetention ?? true;
@@ -2283,6 +2344,7 @@ function queueCompletionRefresh(input) {
2283
2344
  : {
2284
2345
  enabled: false,
2285
2346
  maxTasksToKeep: project.projectConfig?.maxTasksToKeep ?? DEFAULT_MAX_TASKS_TO_KEEP,
2347
+ archivedTasks: [],
2286
2348
  prunedArchivedTasks: [],
2287
2349
  };
2288
2350
  const startedAt = new Date().toISOString();
@@ -2639,7 +2701,7 @@ function computeCompletionDirtyHash(cwd, taskName, operations) {
2639
2701
  const roots = [
2640
2702
  path.join(project.clawDir, "memory.md"),
2641
2703
  path.join(project.clawDir, "truth"),
2642
- path.join(project.tasksDir, taskName),
2704
+ findTaskDirectory(project, taskName) ?? path.join(project.tasksDir, taskName),
2643
2705
  ];
2644
2706
  const files = roots.flatMap((root) => listCompletionFingerprintFiles(root)).sort();
2645
2707
  for (const filePath of files) {
@@ -2753,7 +2815,7 @@ function isWindowsAccessViolation(result) {
2753
2815
  && result.status !== null
2754
2816
  && (result.status >>> 0) === 0xc0000005;
2755
2817
  }
2756
- function ensureGitNexusReadyForPlanDone(cwd) {
2818
+ function ensureGitNexusReadyForPlanFinalization(cwd) {
2757
2819
  const project = resolveProjectContext(cwd);
2758
2820
  if (project.projectConfig?.gitnexus !== true) {
2759
2821
  return false;