@veewo/claw 0.2.21 → 0.2.23

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
@@ -1,141 +1,141 @@
1
- # @veewo/claw
2
-
3
- `@veewo/claw` is the CLI entrypoint for running the `.claw` workflow in a project.
4
-
5
- It gives agents and developers a concrete way to plan work, recall project knowledge, deposit truth and ADR notes, and close rounds out cleanly instead of leaving project state scattered across transient chats.
6
-
7
- ## What the CLI is for
8
-
9
- - initialize and normalize the `.claw` project surface
10
- - run project-scoped planning and task lifecycle commands
11
- - index and query project documentation recall
12
- - support truth ingestion and closeout flows
13
-
14
- ## Install
15
-
16
- ```bash
17
- npm install -g @veewo/claw
18
- ```
19
-
20
- After installing the CLI, project search still needs one-time setup inside each `.claw` project:
21
-
22
- 1. Run `claw context` so `.claw/project.json` is normalized and the default local embedding config is present.
23
- 2. Run `claw search index --refresh` once so the local embedding model can be downloaded or reused and the first vector index can be built.
24
-
25
- ## Host startup context
26
-
27
- `claw context --host <host>` is the single structured startup-state entrypoint
28
- for host adapters. A host Hook owns its native event payload, validates its
29
- trusted cwd and session identity, invokes `context`, and maps the JSON result
30
- to its own prompt, card, or host-action surface. The CLI does not own platform
31
- Hook event names or Hook-output envelopes.
32
-
33
- The result may contain `activeWorkflow`, `error`, `startupRecovery.versionSync`,
34
- and `searchGuidance`, in addition to project and session state. Adapters must
35
- not accept cwd or session identity from model input. A missing `activeWorkflow`
36
- means the session has no bound plan; it does not authorize plan discovery by
37
- scanning unrelated project tasks.
38
-
39
- Then run:
40
-
41
- ```bash
42
- claw init
43
- claw search index --refresh
44
- claw search "existing truth or ADR topic"
45
- claw plan create --title "My task" --goal "Define the first task"
46
- claw plan create "My templated task" --template default --goal "Route through the default template"
47
- claw plan create "Ephemeral harness" --scope session --goal "Use plan and Goal workflows without project deposition"
48
- ```
49
-
50
- `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>"`.
51
-
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
-
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.
55
-
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
-
58
- When `.claw/project.json` has `planning: true`, the default `default` template seeds one planning task in `process.discussing`. It loads the effective `externalPlanningSkill`, falling back to `claw-kit:planning`, and stays open while the requirements and proposed solution are discussed and confirmed with the user. Use `plan start` when execution tasks remain; when planning itself resolves the request, complete task 1 and close the plan.
59
-
60
- When `planning: false`, `claw plan create` seeds the smallest executable plan directly in `process.active`.
61
-
62
- ## Workflow shape
63
-
64
- In a typical round, the CLI helps land this loop in a project:
65
-
66
- `plan` -> `search and recall` -> `execute` -> `deposit truth / ADR` -> `close out`
67
-
68
- That project-level plan structure helps agents carry longer-running work more cleanly than leaving the task in loose chat state alone.
69
-
70
- ## Persistent sessions
71
-
72
- Open a persistent terminal with:
73
-
74
- ```bash
75
- claw session open <dir> <agent-session-id>
76
- ```
77
-
78
- The workdir is immutable for the lifetime of that session. Opening another
79
- directory closes the prior live connection and opens the composite identity
80
- `(canonical workdir, agent session id)`; two directories with the same agent id
81
- remain isolated.
82
-
83
- Inside the terminal, these commands implicitly target `currentPlan`:
84
-
85
- ```text
86
- plan show [--simple]
87
- plan edit ...
88
- plan wait
89
- plan done --retrospective "..."
90
- task add ...
91
- task edit --id ...
92
- task done --id ...
93
- ```
94
-
95
- Use `plan resume` to resume the retained current plan, `plan resume <planId>` to
96
- make a specified resumable plan current, and `plan leave` to enter
97
- `end.leave` and clear focus. Every `end.*` triggers end-state finalization;
98
- `end.leave` remains resumable and does not set `completedAt`.
99
-
100
- `search --dir <dir> <query>` changes only that search operation. It never
101
- changes the session workdir or current plan.
102
-
103
- On an interrupted connection, mutations are never replayed automatically.
104
- Reopen with the exact command returned by the error, then inspect
105
- `plan show --simple`. Retained v2 state expires seven days after its last
106
- update. Legacy session caches are not migrated and canonical plans are never
107
- deleted by v2 cleanup.
108
-
109
- Host adapters remain compatible with the stateless CLI. They can adopt
110
- `@veewo/claw-client` incrementally once they consume the same structured
111
- post-commit effects; opening a persistent session does not change existing
112
- adapter behavior.
113
-
114
- Codex startup workflow should rely on the session hook or startup recovery path instead of treating any extra manual recovery step as required after plan creation.
115
-
116
- ## Search and recall
117
-
118
- `claw search` is the project recall command for `.claw` memory, truth, ADR, and declared markdown docs. Use it for retained project context rather than code search.
119
-
120
- When a task needs deeper code investigation or relationship tracing, GitNexus can complement this workflow, but it is optional rather than required for using `claw` itself.
121
-
122
- Typical setup:
123
-
124
- ```bash
125
- claw context
126
- claw search index --refresh
127
- ```
128
-
129
- If you need deeper backup detail on config or recall behavior, use the adapter reference notes in [packages/codex-adapter/references/project-config-reference.md](../codex-adapter/references/project-config-reference.md) or [packages/opencode-adapter/references/project-config-reference.md](../opencode-adapter/references/project-config-reference.md).
130
-
131
- ## Configuration
132
-
133
- If you need backup `.claw/project.json` detail, start with the adapter reference notes above and use [docs/project-json-reference.md](../../docs/project-json-reference.md) only for deeper canonical detail.
134
-
135
- `claw-kit` also stays usable alongside other harnesses or external skills, so the CLI does not assume a single host or investigation surface.
136
-
137
- The config model is team-friendly as well: `.claw/project.json` carries the shared canonical workflow, while `.claw/project-override.json` leaves room for personal runtime preferences.
138
-
139
- ## Repository
140
-
141
- - [claw-kit](https://github.com/chanyuenpang/claw-kit)
1
+ # @veewo/claw
2
+
3
+ `@veewo/claw` is the CLI entrypoint for running the `.claw` workflow in a project.
4
+
5
+ It gives agents and developers a concrete way to plan work, recall project knowledge, deposit truth and ADR notes, and close rounds out cleanly instead of leaving project state scattered across transient chats.
6
+
7
+ ## What the CLI is for
8
+
9
+ - initialize and normalize the `.claw` project surface
10
+ - run project-scoped planning and task lifecycle commands
11
+ - index and query project documentation recall
12
+ - support truth ingestion and closeout flows
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npm install -g @veewo/claw
18
+ ```
19
+
20
+ After installing the CLI, project search still needs one-time setup inside each `.claw` project:
21
+
22
+ 1. Run `claw context` so `.claw/project.json` is normalized and the default local embedding config is present.
23
+ 2. Run `claw search index --refresh` once so the local embedding model can be downloaded or reused and the first vector index can be built.
24
+
25
+ ## Host startup context
26
+
27
+ `claw context --host <host>` is the single structured startup-state entrypoint
28
+ for host adapters. A host Hook owns its native event payload, validates its
29
+ trusted cwd and session identity, invokes `context`, and maps the JSON result
30
+ to its own prompt, card, or host-action surface. The CLI does not own platform
31
+ Hook event names or Hook-output envelopes.
32
+
33
+ The result may contain `activeWorkflow`, `error`, `startupRecovery.versionSync`,
34
+ and `searchGuidance`, in addition to project and session state. Adapters must
35
+ not accept cwd or session identity from model input. A missing `activeWorkflow`
36
+ means the session has no bound plan; it does not authorize plan discovery by
37
+ scanning unrelated project tasks.
38
+
39
+ Then run:
40
+
41
+ ```bash
42
+ claw init
43
+ claw search index --refresh
44
+ claw search "existing truth or ADR topic"
45
+ claw plan create --title "My task" --goal "Define the first task"
46
+ claw plan create "My templated task" --template default --goal "Route through the default template"
47
+ claw plan create "Ephemeral harness" --scope session --goal "Use plan and Goal workflows without project deposition"
48
+ ```
49
+
50
+ `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>"`.
51
+
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
+
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
+
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
+
58
+ When `.claw/project.json` has `planning: true`, the default `default` template seeds one planning task in `process.discussing`. It loads the effective `externalPlanningSkill`, falling back to `claw-kit:planning`, and stays open while the requirements and proposed solution are discussed and confirmed with the user. Use `plan start` when execution tasks remain; when planning itself resolves the request, complete task 1 and close the plan.
59
+
60
+ When `planning: false`, `claw plan create` seeds the smallest executable plan directly in `process.active`.
61
+
62
+ ## Workflow shape
63
+
64
+ In a typical round, the CLI helps land this loop in a project:
65
+
66
+ `plan` -> `search and recall` -> `execute` -> `deposit truth / ADR` -> `close out`
67
+
68
+ That project-level plan structure helps agents carry longer-running work more cleanly than leaving the task in loose chat state alone.
69
+
70
+ ## Persistent sessions
71
+
72
+ Open a persistent terminal with:
73
+
74
+ ```bash
75
+ claw session open <dir> <agent-session-id>
76
+ ```
77
+
78
+ The workdir is immutable for the lifetime of that session. Opening another
79
+ directory closes the prior live connection and opens the composite identity
80
+ `(canonical workdir, agent session id)`; two directories with the same agent id
81
+ remain isolated.
82
+
83
+ Inside the terminal, these commands implicitly target `currentPlan`:
84
+
85
+ ```text
86
+ plan show [--simple]
87
+ plan edit ...
88
+ plan wait
89
+ plan done --retrospective "..."
90
+ task add ...
91
+ task edit --id ...
92
+ task done --id ...
93
+ ```
94
+
95
+ Use `plan resume` to resume the retained current plan, `plan resume <planId>` to
96
+ make a specified resumable plan current, and `plan leave` to enter
97
+ `end.leave` and clear focus. Every `end.*` triggers end-state finalization;
98
+ `end.leave` remains resumable and does not set `completedAt`.
99
+
100
+ `search --dir <dir> <query>` changes only that search operation. It never
101
+ changes the session workdir or current plan.
102
+
103
+ On an interrupted connection, mutations are never replayed automatically.
104
+ Reopen with the exact command returned by the error, then inspect
105
+ `plan show --simple`. Retained v2 state expires seven days after its last
106
+ update. Legacy session caches are not migrated and canonical plans are never
107
+ deleted by v2 cleanup.
108
+
109
+ Host adapters remain compatible with the stateless CLI. They can adopt
110
+ `@veewo/claw-client` incrementally once they consume the same structured
111
+ post-commit effects; opening a persistent session does not change existing
112
+ adapter behavior.
113
+
114
+ Codex startup workflow should rely on the session hook or startup recovery path instead of treating any extra manual recovery step as required after plan creation.
115
+
116
+ ## Search and recall
117
+
118
+ `claw search` is the project recall command for `.claw` memory, truth, ADR, and declared markdown docs. Use it for retained project context rather than code search.
119
+
120
+ When a task needs deeper code investigation or relationship tracing, GitNexus can complement this workflow, but it is optional rather than required for using `claw` itself.
121
+
122
+ Typical setup:
123
+
124
+ ```bash
125
+ claw context
126
+ claw search index --refresh
127
+ ```
128
+
129
+ If you need deeper backup detail on config or recall behavior, use the adapter reference notes in [packages/codex-adapter/references/project-config-reference.md](../codex-adapter/references/project-config-reference.md) or [packages/opencode-adapter/references/project-config-reference.md](../opencode-adapter/references/project-config-reference.md).
130
+
131
+ ## Configuration
132
+
133
+ If you need backup `.claw/project.json` detail, start with the adapter reference notes above and use [docs/project-json-reference.md](../../docs/project-json-reference.md) only for deeper canonical detail.
134
+
135
+ `claw-kit` also stays usable alongside other harnesses or external skills, so the CLI does not assume a single host or investigation surface.
136
+
137
+ The config model is team-friendly as well: `.claw/project.json` carries the shared canonical workflow, while `.claw/project-override.json` leaves room for personal runtime preferences.
138
+
139
+ ## Repository
140
+
141
+ - [claw-kit](https://github.com/chanyuenpang/claw-kit)
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, 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, 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, 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";
@@ -21,7 +21,7 @@ const TOP_LEVEL_COMMANDS = [
21
21
  { name: "context [--task <name>]", summary: "Resolve project context, auto-initializing or correcting .claw state." },
22
22
  { name: "session clean [--expired]", summary: "Remove current or expired session workflow state." },
23
23
  { name: "check", summary: "Check and auto-correct .claw project protocol fields." },
24
- { name: "plan <subcommand> [options]", summary: "Plan lifecycle: create, start, edit, remove, wait, resume, sync, show, done." },
24
+ { name: "plan <subcommand> [options]", summary: "Plan lifecycle: create, start, edit, remove, wait, resume, leave, sync, show, done." },
25
25
  { name: "codex driver", summary: "Return the versioned code-mode driver used by the Codex adapter." },
26
26
  { name: "template <subcommand> [options]", summary: "Plan template helpers such as validation." },
27
27
  { name: "task <subcommand> [options]", summary: "Task lifecycle helpers inside an existing plan." },
@@ -142,6 +142,11 @@ const COMMAND_HELP = {
142
142
  { flag: "--plan-file <relative-path>", detail: "Advanced: override the session-bound plan file." },
143
143
  ],
144
144
  },
145
+ leave: {
146
+ usage: ["{script} plan leave"],
147
+ description: "Explicitly leave the current plan and clear its session binding so a new root plan may be created.",
148
+ summary: "Leave the current plan without completing it.",
149
+ },
145
150
  sync: {
146
151
  usage: ["{script} plan sync"],
147
152
  description: "Resynchronize a recovered active Codex plan with host progress and Goal Mode without mutating the plan.",
@@ -328,8 +333,28 @@ const COMMAND_HELP = {
328
333
  },
329
334
  knowledge: {
330
335
  usage: ["{script} knowledge <subcommand> [options]"],
331
- 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.",
332
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
+ },
333
358
  wait: {
334
359
  usage: ["{script} knowledge wait --project-root <path> --finalize-id <id> [--session-key <key>] [--timeout-ms <n>]"],
335
360
  description: "Wait for Stop capture to create a knowledge finalization job. This command does not create or inspect session bindings.",
@@ -1019,6 +1044,24 @@ function readCindyKnowledgeClaimCaptureInput() {
1019
1044
  async function runKnowledge(args) {
1020
1045
  const subcommand = args.shift();
1021
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
+ }
1022
1065
  case "list": {
1023
1066
  const projectRoot = path.resolve(readRequiredFlag(args, "--project-root"));
1024
1067
  const sessionKey = readOptionalFlag(args, "--session-key");
@@ -1200,6 +1243,98 @@ async function runKnowledge(args) {
1200
1243
  throw new ClawError("PROJECT_CONFIG_INVALID", `Unknown knowledge subcommand "${subcommand ?? ""}".`);
1201
1244
  }
1202
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
+ }
1203
1338
  async function runPlan(args, effectiveHost) {
1204
1339
  const subcommand = args.shift();
1205
1340
  switch (subcommand) {
@@ -1217,7 +1352,9 @@ async function runPlan(args, effectiveHost) {
1217
1352
  if (!title) {
1218
1353
  throw new ClawError("PROJECT_CONFIG_INVALID", "plan create requires a title. Use `claw plan create \"<title>\"` or `claw plan create --title \"<title>\"`.");
1219
1354
  }
1220
- await preparePlanCreateWorkflow(process.cwd(), resolveOwnerSessionKey(), effectiveHost, scope);
1355
+ const ownerSessionKey = resolveOwnerSessionKey();
1356
+ assertDirectRootPlanCreateAllowed(process.cwd(), ownerSessionKey, scope);
1357
+ await preparePlanCreateWorkflow(process.cwd(), ownerSessionKey, effectiveHost, scope);
1221
1358
  const result = await writePlan({
1222
1359
  cwd: process.cwd(),
1223
1360
  scope,
@@ -1225,9 +1362,18 @@ async function runPlan(args, effectiveHost) {
1225
1362
  templateFile: explicitTemplateFile ? path.resolve(process.cwd(), explicitTemplateFile) : undefined,
1226
1363
  title,
1227
1364
  goalText: readOptionalFlag(args, "--goal"),
1228
- ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
1365
+ ownerSessionKey: ownerSessionKey ?? undefined,
1229
1366
  host: effectiveHost,
1230
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
+ }
1231
1377
  assertNoRemainingArgs(args, "plan create");
1232
1378
  printJson(compactPlanCommandResult("plan.create", result, effectiveHost));
1233
1379
  return;
@@ -1253,8 +1399,8 @@ async function runPlan(args, effectiveHost) {
1253
1399
  && effectiveHost !== "cindy") {
1254
1400
  throw new ClawError("PROJECT_CONFIG_INVALID", 'knowledgeWriter.executionPolicy "subagent" is supported only by the Codex or Cindy host.', { host: effectiveHost ?? null });
1255
1401
  }
1256
- const queuePlanEndFinalization = entersEndTerminal
1257
- ? preparePlanEndFinalization(process.cwd(), ownerSessionKey)
1402
+ const terminalRefresh = entersEndTerminal
1403
+ ? preparePlanTerminalRefresh(process.cwd(), ownerSessionKey)
1258
1404
  : undefined;
1259
1405
  const result = await editPlan({
1260
1406
  cwd: process.cwd(),
@@ -1276,7 +1422,7 @@ async function runPlan(args, effectiveHost) {
1276
1422
  writer: effectiveWriter,
1277
1423
  })
1278
1424
  : undefined;
1279
- const completionRefresh = queuePlanEndFinalization?.(result.taskName);
1425
+ const completionRefresh = queueTerminalRefreshIfEntered(result, terminalRefresh);
1280
1426
  printJson(compactPlanCommandResult("plan.edit", result, effectiveHost, completionRefresh, false, knowledgeDispatch));
1281
1427
  if (result.operationChain?.status === "partial")
1282
1428
  process.exitCode = 1;
@@ -1306,6 +1452,9 @@ async function runPlan(args, effectiveHost) {
1306
1452
  case "resume":
1307
1453
  await runPlanStatusAlias(args, "process.active", "plan.resume", effectiveHost);
1308
1454
  return;
1455
+ case "leave":
1456
+ await runPlanLeave(args, effectiveHost);
1457
+ return;
1309
1458
  case "sync":
1310
1459
  await runPlanSync(args, effectiveHost);
1311
1460
  return;
@@ -1361,7 +1510,7 @@ async function runPlan(args, effectiveHost) {
1361
1510
  && effectiveHost !== "cindy") {
1362
1511
  throw new ClawError("PROJECT_CONFIG_INVALID", 'knowledgeWriter.executionPolicy "subagent" is supported only by the Codex or Cindy host.', { host: effectiveHost ?? null });
1363
1512
  }
1364
- const queuePlanEndFinalization = preparePlanEndFinalization(process.cwd(), ownerSessionKey);
1513
+ const terminalRefresh = preparePlanTerminalRefresh(process.cwd(), ownerSessionKey);
1365
1514
  const result = await editPlan({
1366
1515
  cwd: process.cwd(),
1367
1516
  ...target,
@@ -1380,7 +1529,7 @@ async function runPlan(args, effectiveHost) {
1380
1529
  writer: effectiveWriter,
1381
1530
  })
1382
1531
  : undefined;
1383
- const completionRefresh = queuePlanEndFinalization?.(result.taskName);
1532
+ const completionRefresh = queueTerminalRefreshIfEntered(result, terminalRefresh);
1384
1533
  printJson(compactPlanCommandResult("plan.done", result, effectiveHost, completionRefresh, false, knowledgeDispatch));
1385
1534
  return;
1386
1535
  }
@@ -1425,6 +1574,22 @@ async function runPlanStatusAlias(args, planStatus, command, effectiveHost) {
1425
1574
  assertNoRemainingArgs(args, command);
1426
1575
  printJson(compactPlanCommandResult(command, result, effectiveHost, undefined, true));
1427
1576
  }
1577
+ async function runPlanLeave(args, effectiveHost) {
1578
+ const ownerSessionKey = resolveOwnerSessionKey() ?? undefined;
1579
+ const target = readPlanMutationTarget(args);
1580
+ assertNoRemainingArgs(args, "plan leave");
1581
+ const terminalRefresh = preparePlanTerminalRefresh(process.cwd(), ownerSessionKey);
1582
+ const result = await editPlan({
1583
+ cwd: process.cwd(),
1584
+ ...target,
1585
+ planStatus: "end.leave",
1586
+ commandSource: "plan.edit",
1587
+ host: effectiveHost,
1588
+ ownerSessionKey,
1589
+ });
1590
+ const completionRefresh = queueTerminalRefreshIfEntered(result, terminalRefresh);
1591
+ printJson(compactPlanCommandResult("plan.leave", result, effectiveHost, completionRefresh, true));
1592
+ }
1428
1593
  async function runPlanSync(args, effectiveHost) {
1429
1594
  const ownerSessionKey = resolveOwnerSessionKey() ?? undefined;
1430
1595
  const result = showPlan({ cwd: process.cwd(), ...readPlanMutationTarget(args), ownerSessionKey });
@@ -1618,7 +1783,10 @@ async function runContextCommand(args, cwd = process.cwd(), ownerSessionKey = re
1618
1783
  let corrected = false;
1619
1784
  let fixedPaths = [];
1620
1785
  const sessionProject = resolveSessionWorkflowContext(ownerSessionKey ?? undefined);
1621
- if (sessionProject) {
1786
+ const sessionPlanPath = sessionProject && ownerSessionKey
1787
+ ? resolveSessionBoundPlan(sessionProject, ownerSessionKey)
1788
+ : null;
1789
+ if (sessionProject && sessionPlanPath) {
1622
1790
  const maintenance = effectiveHost
1623
1791
  ? null
1624
1792
  : runDailyMaintenance(sessionProject, {
@@ -2175,6 +2343,33 @@ async function preparePlanCreateWorkflow(cwd, ownerSessionKey, effectiveHost, re
2175
2343
  }
2176
2344
  await prepareProjectWorkflow(cwd, ownerSessionKey, effectiveHost, project);
2177
2345
  }
2346
+ function assertDirectRootPlanCreateAllowed(cwd, ownerSessionKey, requestedScope) {
2347
+ if (!ownerSessionKey)
2348
+ return;
2349
+ const project = requestedScope === "session"
2350
+ ? resolveSessionWorkflowContext(ownerSessionKey)
2351
+ : tryResolveHookProject(cwd);
2352
+ if (!project)
2353
+ return;
2354
+ const planPath = resolveSessionBoundPlan(project, ownerSessionKey);
2355
+ if (!planPath)
2356
+ return;
2357
+ const target = parseTaskPlanPath(project, planPath);
2358
+ if (!target) {
2359
+ throw new ClawError("PLAN_TRANSITION_CONFLICT", `Invalid session-bound plan path: ${planPath}`);
2360
+ }
2361
+ const current = showPlan({
2362
+ cwd,
2363
+ taskName: target.taskName,
2364
+ planFile: target.planFile,
2365
+ ownerSessionKey,
2366
+ });
2367
+ if (current.plan.status.startsWith("end.")) {
2368
+ unbindSession(project, ownerSessionKey);
2369
+ return;
2370
+ }
2371
+ assertRootPlanCreateAllowedForPlan(createPlanRef(project, current.taskName, current.planFile), current.plan);
2372
+ }
2178
2373
  async function prepareProjectWorkflow(cwd, ownerSessionKey, effectiveHost, project = tryResolveHookProject(cwd)) {
2179
2374
  const sessionProject = resolveSessionWorkflowContext(ownerSessionKey ?? undefined);
2180
2375
  if (sessionProject) {
@@ -3268,11 +3463,24 @@ function readRepeatedIntegerFlag(args, flag) {
3268
3463
  function failMissingNumericFlag(flag) {
3269
3464
  throw new ClawError("PROJECT_CONFIG_INVALID", `Missing required flag ${flag}.`, { flag });
3270
3465
  }
3271
- function preparePlanEndFinalization(cwd, ownerSessionKey) {
3466
+ function preparePlanTerminalRefresh(cwd, ownerSessionKey) {
3272
3467
  const workflowProject = resolveWorkflowProjectContext(cwd, ownerSessionKey);
3273
- if (workflowProject.scope !== "project")
3468
+ const refreshProject = tryResolveHookProject(workflowProject.projectRoot);
3469
+ if (!refreshProject)
3274
3470
  return undefined;
3275
- return (taskName) => queueCompletionRefresh({ cwd, taskName });
3471
+ return {
3472
+ queue: (taskName) => queueCompletionRefresh({
3473
+ cwd: refreshProject.projectRoot,
3474
+ taskName,
3475
+ includeTaskRetention: workflowProject.scope !== "session",
3476
+ }),
3477
+ };
3478
+ }
3479
+ function queueTerminalRefreshIfEntered(result, terminalRefresh) {
3480
+ if (!terminalRefresh || result.previousPlanStatus.startsWith("end.") || !result.planStatus.startsWith("end.")) {
3481
+ return undefined;
3482
+ }
3483
+ return terminalRefresh.queue(result.taskName);
3276
3484
  }
3277
3485
  function queueCompletionRefresh(input) {
3278
3486
  const project = resolveProjectContext(input.cwd);