@veewo/claw 0.1.74 → 0.1.76
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/dist/cli.js +847 -130
- package/dist/cli.js.map +1 -1
- package/dist/codex-driver.d.ts +4 -0
- package/dist/codex-driver.js +121 -0
- package/dist/codex-driver.js.map +1 -0
- package/dist/codex-runtime.d.ts +8 -0
- package/dist/codex-runtime.js +51 -0
- package/dist/codex-runtime.js.map +1 -0
- package/dist/codex-transcript.d.ts +1 -0
- package/dist/codex-transcript.js +40 -0
- package/dist/codex-transcript.js.map +1 -0
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2,15 +2,20 @@
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
5
6
|
import { createHash } from "node:crypto";
|
|
6
7
|
import { spawn, spawnSync } from "node:child_process";
|
|
7
|
-
import { buildDirectWorkflowGuidance, checkProjectProtocol, ClawError, buildPlanWorkflowGuidance, buildMemoryIndex, buildSessionStartDefaultPrompt, buildSessionStartRecoveredPrompt, editPlan, ensureProjectProtocol, enforceTaskRetention, ingestTruth, initProject, getTemplateTaskDoneChoices, resolvePlanTemplateFile, resolveProjectContext, resolveSessionBoundPlan, resolveContext, resolveSeedPlanTemplate, searchMemory, showPlan, createSubplan, switchTask, unbindSession, writePlan, } from "@veewo/claw-core";
|
|
8
|
+
import { buildDirectWorkflowGuidance, checkProjectProtocol, ClawError, buildPlanWorkflowGuidance, buildMemoryIndex, buildSessionStartDefaultPrompt, buildSessionStartRecoveredPrompt, editPlan, ensureProjectProtocol, enforceTaskRetention, ingestTruth, initProject, getTemplateTaskDoneChoices, resolvePlanTemplateFile, resolveProjectContext, resolveSessionBoundPlan, resolveContext, resolveSeedPlanTemplate, searchMemory, showPlan, createSubplan, switchTask, tryCaptureKnowledgeStop, claimKnowledgeFinalizationJob, listRetryableKnowledgeFinalizationJobs, normalizeTruthMarkdownEncoding, tryCleanupKnowledgeFinalizationReport, writeKnowledgeFinalizationJob, unbindSession, writePlan, } from "@veewo/claw-core";
|
|
9
|
+
import { buildCodexDriverEnvelope } from "./codex-driver.js";
|
|
10
|
+
import { checkCodexRuntime, resolveCodexSdkEntryPath } from "./codex-runtime.js";
|
|
11
|
+
import { extractLatestFinalAssistantMessage } from "./codex-transcript.js";
|
|
8
12
|
const CLI_VERSION = readCliVersion();
|
|
9
13
|
const TOP_LEVEL_COMMANDS = [
|
|
10
14
|
{ name: "init [options]", summary: "Initialize and normalize the .claw project surface." },
|
|
11
15
|
{ name: "context [--task <name>]", summary: "Resolve project context, auto-initializing or correcting .claw state." },
|
|
12
16
|
{ name: "check", summary: "Check and auto-correct .claw project protocol fields." },
|
|
13
|
-
{ name: "plan <subcommand> [options]", summary: "Plan lifecycle: create, start, edit, show, done." },
|
|
17
|
+
{ name: "plan <subcommand> [options]", summary: "Plan lifecycle: create, start, edit, remove, wait, resume, show, done." },
|
|
18
|
+
{ name: "codex driver", summary: "Return the versioned code-mode driver used by the Codex adapter." },
|
|
14
19
|
{ name: "template <subcommand> [options]", summary: "Plan template helpers such as validation." },
|
|
15
20
|
{ name: "task <subcommand> [options]", summary: "Task lifecycle helpers inside an existing plan." },
|
|
16
21
|
{ name: "subplan create [options]", summary: "Create a subplan nested under a parent task item." },
|
|
@@ -28,8 +33,7 @@ const COMMAND_HELP = {
|
|
|
28
33
|
{ flag: "--name <project-name>", detail: "Human-readable project name." },
|
|
29
34
|
{ flag: "--context-path <file>", detail: "Extra context path to track (repeatable)." },
|
|
30
35
|
{ flag: "--ext-path <path>", detail: "External doc path to index (repeatable)." },
|
|
31
|
-
{ flag: "--external-
|
|
32
|
-
{ flag: "--external-adr-skill <skill>", detail: "Skill id for external adr-writer dispatch." },
|
|
36
|
+
{ flag: "--external-writer-skill <skill>", detail: "Skill id for the combined knowledge writer." },
|
|
33
37
|
{ flag: "--planning true|false", detail: "Enable planning-aware default template behavior (default true)." },
|
|
34
38
|
{ flag: "--external-planning-skill <skill>", detail: "Skill id for an external planning skill." },
|
|
35
39
|
{ flag: "--gitnexus true|false", detail: "Enable GitNexus integration (default false)." },
|
|
@@ -66,60 +70,111 @@ const COMMAND_HELP = {
|
|
|
66
70
|
],
|
|
67
71
|
},
|
|
68
72
|
edit: {
|
|
69
|
-
usage: ["{script} plan edit
|
|
70
|
-
description: "
|
|
71
|
-
summary: "Edit
|
|
73
|
+
usage: ["{script} plan edit [options]"],
|
|
74
|
+
description: "Apply plan field and status edits in argument order to the session-bound current plan.",
|
|
75
|
+
summary: "Edit plan fields in an ordered chain; repeat collection options to append multiple values.",
|
|
72
76
|
options: [
|
|
73
|
-
{ flag: "--
|
|
74
|
-
{ flag: "--
|
|
75
|
-
{ flag: "--
|
|
76
|
-
{ flag: "--
|
|
77
|
-
{ flag: "--
|
|
78
|
-
{ flag: "--
|
|
79
|
-
{ flag: "--append-tasks <json-file>", detail: "Append tasks from a JSON array file." },
|
|
80
|
-
{ flag: "--patch <json-file>", detail: "Apply a JSON merge-patch object from a file. Objects merge recursively, null deletes fields, arrays replace the whole field." },
|
|
77
|
+
{ flag: "--status <status>", detail: "Advanced: set the plan status directly." },
|
|
78
|
+
{ flag: "--goal <text>", detail: "Set goal.text." },
|
|
79
|
+
{ flag: "--requirements <text>", detail: "Set the requirements summary." },
|
|
80
|
+
{ flag: "--question <text>", detail: "Add an open question (repeatable)." },
|
|
81
|
+
{ flag: "--acceptance <text>", detail: "Add an acceptance criterion (repeatable)." },
|
|
82
|
+
{ flag: "--summary <text>", detail: "Set the plan summary." },
|
|
81
83
|
{ flag: "--rule <text>", detail: "Append a rule (repeatable)." },
|
|
82
84
|
{ flag: "--key-decision <text>", detail: "Append a key decision (repeatable)." },
|
|
83
|
-
{ flag: "--reference
|
|
84
|
-
{ flag: "--
|
|
85
|
-
{ flag: "--
|
|
85
|
+
{ flag: "--reference <path>", detail: "Add a reference; follow it immediately with --why (repeatable)." },
|
|
86
|
+
{ flag: "--why <text>", detail: "Explain the immediately preceding --reference." },
|
|
87
|
+
{ flag: "--task-name <name>", detail: "Advanced: override the session-bound task scope." },
|
|
88
|
+
{ flag: "--plan-file <relative-path>", detail: "Advanced: override the session-bound plan file." },
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
remove: {
|
|
92
|
+
usage: ["{script} plan remove [options]"],
|
|
93
|
+
description: "Remove exact values from array fields on the session-bound current plan.",
|
|
94
|
+
summary: "Remove questions, acceptance criteria, rules, decisions, or references.",
|
|
95
|
+
options: [
|
|
96
|
+
{ flag: "--question <text>", detail: "Remove an open question by exact text (repeatable)." },
|
|
97
|
+
{ flag: "--acceptance <text>", detail: "Remove an acceptance criterion by exact text (repeatable)." },
|
|
98
|
+
{ flag: "--rule <text>", detail: "Remove a rule by exact text (repeatable)." },
|
|
99
|
+
{ flag: "--key-decision <text>", detail: "Remove a key decision by exact text (repeatable)." },
|
|
100
|
+
{ flag: "--reference <path>", detail: "Remove references matching a path (repeatable)." },
|
|
101
|
+
{ flag: "--task-name <name>", detail: "Advanced: override the session-bound task scope." },
|
|
102
|
+
{ flag: "--plan-file <relative-path>", detail: "Advanced: override the session-bound plan file." },
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
wait: {
|
|
106
|
+
usage: ["{script} plan wait"],
|
|
107
|
+
description: "Pause active execution by moving the plan to process.wait.",
|
|
108
|
+
summary: "Pause active execution.",
|
|
109
|
+
options: [
|
|
110
|
+
{ flag: "--task-name <name>", detail: "Advanced: override the session-bound task scope." },
|
|
111
|
+
{ flag: "--plan-file <relative-path>", detail: "Advanced: override the session-bound plan file." },
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
resume: {
|
|
115
|
+
usage: ["{script} plan resume"],
|
|
116
|
+
description: "Resume paused execution by moving the plan to process.active.",
|
|
117
|
+
summary: "Resume paused execution.",
|
|
118
|
+
options: [
|
|
119
|
+
{ flag: "--task-name <name>", detail: "Advanced: override the session-bound task scope." },
|
|
120
|
+
{ flag: "--plan-file <relative-path>", detail: "Advanced: override the session-bound plan file." },
|
|
86
121
|
],
|
|
87
122
|
},
|
|
88
123
|
start: {
|
|
89
|
-
usage: ["{script} plan start --
|
|
124
|
+
usage: ["{script} plan start --requirements <text> --add-task <title> [--detail <text>] [options]"],
|
|
90
125
|
description: "Atomically apply refined plan content, append business tasks, complete the default planning/activation bridge, and enter process.active in one serialized mutation.",
|
|
91
126
|
summary: "Atomically refine and activate a default planning plan.",
|
|
92
127
|
options: [
|
|
93
|
-
{ flag: "--
|
|
94
|
-
{ flag: "--
|
|
95
|
-
{ flag: "--
|
|
96
|
-
{ flag: "--
|
|
97
|
-
{ flag: "--
|
|
128
|
+
{ flag: "--goal <text>", detail: "Set goal.text." },
|
|
129
|
+
{ flag: "--requirements <text>", detail: "Set the requirements summary." },
|
|
130
|
+
{ flag: "--question <text>", detail: "Add an open question (repeatable)." },
|
|
131
|
+
{ flag: "--acceptance <text>", detail: "Add an acceptance criterion (repeatable)." },
|
|
132
|
+
{ flag: "--add-task <title>", detail: "Add a business task; optionally follow it immediately with --detail (repeatable)." },
|
|
133
|
+
{ flag: "--detail <text>", detail: "Describe the immediately preceding --add-task." },
|
|
134
|
+
{ flag: "--rule <text>", detail: "Append a rule (repeatable)." },
|
|
135
|
+
{ flag: "--key-decision <text>", detail: "Append a key decision (repeatable)." },
|
|
136
|
+
{ flag: "--reference <path>", detail: "Add a reference; follow it immediately with --why (repeatable)." },
|
|
137
|
+
{ flag: "--why <text>", detail: "Explain the immediately preceding --reference." },
|
|
138
|
+
{ flag: "--task-name <name>", detail: "Advanced: override the session-bound task scope." },
|
|
139
|
+
{ flag: "--plan-file <relative-path>", detail: "Advanced: override the session-bound plan file." },
|
|
98
140
|
],
|
|
99
141
|
},
|
|
100
142
|
show: {
|
|
101
|
-
usage: ["{script} plan show
|
|
102
|
-
description: "Show the current plan
|
|
143
|
+
usage: ["{script} plan show"],
|
|
144
|
+
description: "Show the session-bound current plan, including archived plans through an explicit override.",
|
|
103
145
|
summary: "Show the current plan for a task.",
|
|
104
146
|
options: [
|
|
105
|
-
{ flag: "--task <name>", detail: "
|
|
106
|
-
{ flag: "--plan <relative-path>", detail: "
|
|
147
|
+
{ flag: "--task-name <name>", detail: "Advanced: override the session-bound task scope." },
|
|
148
|
+
{ flag: "--plan-file <relative-path>", detail: "Advanced: override the session-bound plan file." },
|
|
107
149
|
],
|
|
108
150
|
},
|
|
109
151
|
done: {
|
|
110
|
-
usage: ["{script} plan done --
|
|
152
|
+
usage: ["{script} plan done --retrospective <text> [options]"],
|
|
111
153
|
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.",
|
|
112
154
|
summary: "Close out a plan with a retrospective and queue completion refresh.",
|
|
113
155
|
options: [
|
|
114
|
-
{ flag: "--
|
|
115
|
-
{ flag: "--
|
|
116
|
-
{ flag: "--
|
|
117
|
-
{ flag: "--
|
|
118
|
-
{ flag: "--
|
|
156
|
+
{ flag: "--retrospective <text>", detail: "Retrospective summary (required)." },
|
|
157
|
+
{ flag: "--key-decision <text>", detail: "Append a durable key decision when one exists (repeatable)." },
|
|
158
|
+
{ flag: "--what-worked <text>", detail: "Append a retrospective success (repeatable)." },
|
|
159
|
+
{ flag: "--issue <text>", detail: "Append a retrospective issue (repeatable)." },
|
|
160
|
+
{ flag: "--follow-up <text>", detail: "Append a retrospective follow-up (repeatable)." },
|
|
161
|
+
{ flag: "--task-name <name>", detail: "Advanced: override the session-bound task scope." },
|
|
162
|
+
{ flag: "--plan-file <relative-path>", detail: "Advanced: override the session-bound plan file." },
|
|
119
163
|
],
|
|
120
164
|
},
|
|
121
165
|
},
|
|
122
166
|
},
|
|
167
|
+
codex: {
|
|
168
|
+
usage: ["{script} codex <subcommand>"],
|
|
169
|
+
description: "Codex adapter runtime helpers.",
|
|
170
|
+
subcommands: {
|
|
171
|
+
driver: {
|
|
172
|
+
usage: ["{script} codex driver"],
|
|
173
|
+
description: "Return the versioned JavaScript driver source used by the short code-mode bootstrap.",
|
|
174
|
+
summary: "Return the versioned code-mode driver source.",
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
},
|
|
123
178
|
template: {
|
|
124
179
|
usage: ["{script} template <subcommand> [options]"],
|
|
125
180
|
description: "Helpers for inspecting and validating plan templates.",
|
|
@@ -141,17 +196,52 @@ const COMMAND_HELP = {
|
|
|
141
196
|
},
|
|
142
197
|
task: {
|
|
143
198
|
usage: ["{script} task <subcommand> [options]"],
|
|
144
|
-
description: "
|
|
199
|
+
description: "Add, edit, remove, or complete task items on the session-bound current plan.",
|
|
145
200
|
subcommands: {
|
|
201
|
+
add: {
|
|
202
|
+
usage: ["{script} task add --title <text> [--detail <text>] [--title <text> [--detail <text>] ...]"],
|
|
203
|
+
description: "Add one or more pending task items to the current plan in argument order.",
|
|
204
|
+
summary: "Add task items with repeated --title groups.",
|
|
205
|
+
options: [
|
|
206
|
+
{ flag: "--title <text>", detail: "Task title (required)." },
|
|
207
|
+
{ flag: "--detail <text>", detail: "Optional task detail." },
|
|
208
|
+
{ flag: "--task-name <name>", detail: "Advanced: override the session-bound task scope." },
|
|
209
|
+
{ flag: "--plan-file <relative-path>", detail: "Advanced: override the session-bound plan file." },
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
edit: {
|
|
213
|
+
usage: ["{script} task edit --id <number> [options] [--id <number> [options] ...]"],
|
|
214
|
+
description: "Update one or more task items on the current plan in argument order.",
|
|
215
|
+
summary: "Edit task items with repeated --id groups.",
|
|
216
|
+
options: [
|
|
217
|
+
{ flag: "--id <number>", detail: "Task item id (required)." },
|
|
218
|
+
{ flag: "--title <text>", detail: "Set the task title." },
|
|
219
|
+
{ flag: "--detail <text>", detail: "Set the task detail." },
|
|
220
|
+
{ flag: "--status <status>", detail: "Set pending, in_progress, subagent_running, done, or blocked." },
|
|
221
|
+
{ flag: "--choice <choice-id>", detail: "Record a route choice when status becomes done." },
|
|
222
|
+
{ flag: "--task-name <name>", detail: "Advanced: override the session-bound task scope." },
|
|
223
|
+
{ flag: "--plan-file <relative-path>", detail: "Advanced: override the session-bound plan file." },
|
|
224
|
+
],
|
|
225
|
+
},
|
|
226
|
+
remove: {
|
|
227
|
+
usage: ["{script} task remove --id <number> [--id <number> ...]"],
|
|
228
|
+
description: "Remove one or more task items from the current plan in argument order.",
|
|
229
|
+
summary: "Remove task items with repeated --id values.",
|
|
230
|
+
options: [
|
|
231
|
+
{ flag: "--id <number>", detail: "Task item id (required)." },
|
|
232
|
+
{ flag: "--task-name <name>", detail: "Advanced: override the session-bound task scope." },
|
|
233
|
+
{ flag: "--plan-file <relative-path>", detail: "Advanced: override the session-bound plan file." },
|
|
234
|
+
],
|
|
235
|
+
},
|
|
146
236
|
done: {
|
|
147
|
-
usage: ["{script} task done --
|
|
148
|
-
description: "Mark
|
|
149
|
-
summary: "
|
|
237
|
+
usage: ["{script} task done --id <number> [--choice <choice-id>] [--id <number> [--choice <choice-id>] ...]"],
|
|
238
|
+
description: "Mark one or more task items as done in argument order. Route-aware templates may require `--choice`, and each selected choice is persisted as `task.choiceId` in the plan state.",
|
|
239
|
+
summary: "Complete task items with repeated --id groups, optionally recording routing choices.",
|
|
150
240
|
options: [
|
|
151
|
-
{ flag: "--task <name>", detail: "(required) Task name to edit." },
|
|
152
241
|
{ flag: "--id <number>", detail: "(required) Task item id to mark done." },
|
|
153
242
|
{ flag: "--choice <choice-id>", detail: "Route choice id required by templates that define guidance.onDone.choices." },
|
|
154
|
-
{ flag: "--
|
|
243
|
+
{ flag: "--task-name <name>", detail: "Advanced: override the session-bound task scope." },
|
|
244
|
+
{ flag: "--plan-file <relative-path>", detail: "Advanced: override the session-bound plan file." },
|
|
155
245
|
],
|
|
156
246
|
},
|
|
157
247
|
},
|
|
@@ -222,9 +312,9 @@ const COMMAND_HELP = {
|
|
|
222
312
|
},
|
|
223
313
|
hook: {
|
|
224
314
|
usage: ["{script} hook <event-name>"],
|
|
225
|
-
description: "Emit host hook output. `claw
|
|
315
|
+
description: "Emit host hook output. `auto-claw` maps to SessionStart recovery and `auto-doc` maps to Stop report capture; fixed platform event names remain accepted as compatibility aliases.",
|
|
226
316
|
options: [
|
|
227
|
-
{ flag: "<event-name>", detail: "(required) Hook
|
|
317
|
+
{ flag: "<event-name>", detail: "(required) Hook command name (`auto-claw`, `auto-doc`, SessionStart, or Stop)." },
|
|
228
318
|
],
|
|
229
319
|
},
|
|
230
320
|
"internal-completion-refresh": {
|
|
@@ -236,9 +326,22 @@ const COMMAND_HELP = {
|
|
|
236
326
|
{ flag: "--status-file <path>", detail: "(required) Status file path to update." },
|
|
237
327
|
],
|
|
238
328
|
},
|
|
329
|
+
"internal-knowledge-finalize": {
|
|
330
|
+
usage: ["{script} internal-knowledge-finalize --job <path>"],
|
|
331
|
+
description: "Internal: runs one queued knowledge deposition job through the Codex SDK.",
|
|
332
|
+
options: [{ flag: "--job <path>", detail: "(required) Finalization job JSON path." }],
|
|
333
|
+
},
|
|
239
334
|
};
|
|
240
335
|
async function main() {
|
|
241
336
|
const args = process.argv.slice(2);
|
|
337
|
+
const explicitHost = readOptionalFlag(args, "--host");
|
|
338
|
+
if (explicitHost) {
|
|
339
|
+
if (!new Set(["codex", "opencode"]).has(explicitHost)) {
|
|
340
|
+
handleError(new ClawError("PROJECT_CONFIG_INVALID", `Unsupported host "${explicitHost}".`));
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
process.env.CLAW_HOST = explicitHost;
|
|
344
|
+
}
|
|
242
345
|
const command = args.shift();
|
|
243
346
|
if (command === "--help" || command === "-h") {
|
|
244
347
|
printTopLevelUsage();
|
|
@@ -268,8 +371,7 @@ async function main() {
|
|
|
268
371
|
projectId: readOptionalFlag(args, "--id"),
|
|
269
372
|
projectName: readOptionalFlag(args, "--name"),
|
|
270
373
|
maxTasksToKeep: readOptionalNumber(args, "--max-tasks-to-keep"),
|
|
271
|
-
|
|
272
|
-
externalAdrSkill: readOptionalFlag(args, "--external-adr-skill") ?? null,
|
|
374
|
+
externalWriterSkill: readOptionalFlag(args, "--external-writer-skill") ?? null,
|
|
273
375
|
planning: readBooleanValueFlag(args, "--planning"),
|
|
274
376
|
externalPlanningSkill: readOptionalFlag(args, "--external-planning-skill") ?? null,
|
|
275
377
|
contextPaths: readRepeatedFlag(args, "--context-path"),
|
|
@@ -280,7 +382,7 @@ async function main() {
|
|
|
280
382
|
printJson(initProject(initInput));
|
|
281
383
|
return;
|
|
282
384
|
case "context":
|
|
283
|
-
printJson(await runContextCommand(args));
|
|
385
|
+
printJson(buildPublicContextOutput(await runContextCommand(args)));
|
|
284
386
|
return;
|
|
285
387
|
case "check":
|
|
286
388
|
const checkResult = ensureProjectProtocol(process.cwd());
|
|
@@ -297,6 +399,9 @@ async function main() {
|
|
|
297
399
|
case "plan":
|
|
298
400
|
await runPlan(args);
|
|
299
401
|
return;
|
|
402
|
+
case "codex":
|
|
403
|
+
runCodex(args);
|
|
404
|
+
return;
|
|
300
405
|
case "template":
|
|
301
406
|
await runTemplate(args);
|
|
302
407
|
return;
|
|
@@ -334,6 +439,9 @@ async function main() {
|
|
|
334
439
|
case "internal-completion-refresh":
|
|
335
440
|
runInternalCompletionRefresh(args);
|
|
336
441
|
return;
|
|
442
|
+
case "internal-knowledge-finalize":
|
|
443
|
+
await runInternalKnowledgeFinalize(args);
|
|
444
|
+
return;
|
|
337
445
|
default:
|
|
338
446
|
printTopLevelUsage();
|
|
339
447
|
process.exitCode = 1;
|
|
@@ -343,11 +451,19 @@ async function main() {
|
|
|
343
451
|
handleError(error);
|
|
344
452
|
}
|
|
345
453
|
}
|
|
454
|
+
function runCodex(args) {
|
|
455
|
+
const subcommand = args.shift();
|
|
456
|
+
if (subcommand !== "driver") {
|
|
457
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `Unknown codex subcommand "${subcommand ?? ""}".`);
|
|
458
|
+
}
|
|
459
|
+
assertNoRemainingArgs(args, "codex driver");
|
|
460
|
+
printJson(buildCodexDriverEnvelope(CLI_VERSION));
|
|
461
|
+
}
|
|
346
462
|
async function runPlan(args) {
|
|
347
463
|
const subcommand = args.shift();
|
|
348
464
|
switch (subcommand) {
|
|
349
465
|
case "create":
|
|
350
|
-
rejectFlags(args, ["--task", "--plan", "--content", "--status", "--
|
|
466
|
+
rejectFlags(args, ["--task", "--plan", "--content", "--status", "--parent-task-id", "--description"]);
|
|
351
467
|
const explicitTitle = readOptionalFlag(args, "--title");
|
|
352
468
|
const explicitTemplate = readOptionalFlag(args, "--template");
|
|
353
469
|
const title = explicitTitle ?? readOptionalPositionalArg(args);
|
|
@@ -367,67 +483,89 @@ async function runPlan(args) {
|
|
|
367
483
|
printJson(compactPlanCommandResult("plan.create", result));
|
|
368
484
|
return;
|
|
369
485
|
case "edit": {
|
|
370
|
-
const
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
486
|
+
const target = readPlanMutationTarget(args);
|
|
487
|
+
const operations = readOrderedPlanEditOperations(args);
|
|
488
|
+
if (operations.length === 0) {
|
|
489
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "plan edit requires at least one plan field or --status.");
|
|
490
|
+
}
|
|
375
491
|
const result = await editPlan({
|
|
376
492
|
cwd: process.cwd(),
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
changeSummary: readOptionalFlag(args, "--summary"),
|
|
380
|
-
patch: mergedPatch,
|
|
381
|
-
planStatus: readOptionalFlag(args, "--plan-status"),
|
|
382
|
-
taskId: readOptionalNumber(args, "--task-id"),
|
|
383
|
-
taskStatus: readOptionalFlag(args, "--task-status"),
|
|
384
|
-
taskChoiceId: readOptionalFlag(args, "--task-choice"),
|
|
385
|
-
appendTasks: appendTasksPath ? readJson(appendTasksPath) : undefined,
|
|
493
|
+
...target,
|
|
494
|
+
operations,
|
|
386
495
|
commandSource: "plan.edit",
|
|
387
496
|
host: process.env.CLAW_HOST ?? undefined,
|
|
388
497
|
ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
|
|
389
498
|
});
|
|
390
499
|
printJson(compactPlanCommandResult("plan.edit", result));
|
|
500
|
+
if (result.operationChain?.status === "partial")
|
|
501
|
+
process.exitCode = 1;
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
case "remove": {
|
|
505
|
+
const updates = readPlanRemovalUpdates(args);
|
|
506
|
+
if (!updates) {
|
|
507
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "plan remove requires at least one --question, --acceptance, --rule, --key-decision, or --reference.");
|
|
508
|
+
}
|
|
509
|
+
const target = readPlanMutationTarget(args);
|
|
510
|
+
assertNoRemainingArgs(args, "plan remove");
|
|
511
|
+
const result = await editPlan({
|
|
512
|
+
cwd: process.cwd(),
|
|
513
|
+
...target,
|
|
514
|
+
updates,
|
|
515
|
+
commandSource: "plan.edit",
|
|
516
|
+
host: process.env.CLAW_HOST ?? undefined,
|
|
517
|
+
ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
|
|
518
|
+
});
|
|
519
|
+
printJson(compactPlanCommandResult("plan.remove", result));
|
|
391
520
|
return;
|
|
392
521
|
}
|
|
522
|
+
case "wait":
|
|
523
|
+
await runPlanStatusAlias(args, "process.wait", "plan.wait");
|
|
524
|
+
return;
|
|
525
|
+
case "resume":
|
|
526
|
+
await runPlanStatusAlias(args, "process.active", "plan.resume");
|
|
527
|
+
return;
|
|
393
528
|
case "start": {
|
|
394
|
-
const
|
|
395
|
-
const
|
|
396
|
-
if (!
|
|
397
|
-
throw new ClawError("PROJECT_CONFIG_INVALID", "plan start requires
|
|
529
|
+
const updates = readPlanFieldUpdates(args);
|
|
530
|
+
const appendTasks = readExplicitAddedTasks(args);
|
|
531
|
+
if (!updates && appendTasks.length === 0) {
|
|
532
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "plan start requires explicit plan fields or at least one --add-task.");
|
|
398
533
|
}
|
|
534
|
+
const target = readPlanMutationTarget(args);
|
|
535
|
+
assertNoRemainingArgs(args, "plan start");
|
|
399
536
|
const result = await editPlan({
|
|
400
537
|
cwd: process.cwd(),
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
patch: patchPath ? readJson(patchPath) : undefined,
|
|
405
|
-
appendTasks: appendTasksPath ? readJson(appendTasksPath) : undefined,
|
|
538
|
+
...target,
|
|
539
|
+
updates,
|
|
540
|
+
appendTasks,
|
|
406
541
|
planStatus: "process.active",
|
|
407
542
|
completeLifecycleBridge: true,
|
|
408
543
|
commandSource: "plan.start",
|
|
409
544
|
host: process.env.CLAW_HOST ?? undefined,
|
|
410
545
|
ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
|
|
411
546
|
});
|
|
412
|
-
assertNoRemainingArgs(args, "plan start");
|
|
413
547
|
printJson(compactPlanCommandResult("plan.start", result));
|
|
414
548
|
return;
|
|
415
549
|
}
|
|
416
550
|
case "done": {
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
const mergedPatch = mergeDonePatch(patch, summary);
|
|
421
|
-
if (!mergedPatch?.retrospective?.summary?.trim()) {
|
|
422
|
-
throw new ClawError("PROJECT_CONFIG_INVALID", "plan done requires --summary or a patch file containing retrospective.summary.");
|
|
551
|
+
const retrospective = readOptionalFlag(args, "--retrospective");
|
|
552
|
+
if (!retrospective?.trim()) {
|
|
553
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "plan done requires --retrospective.");
|
|
423
554
|
}
|
|
555
|
+
const updates = {
|
|
556
|
+
retrospectiveSummary: retrospective,
|
|
557
|
+
keyDecisions: readRepeatedFlag(args, "--key-decision"),
|
|
558
|
+
whatWorked: readRepeatedFlag(args, "--what-worked"),
|
|
559
|
+
issues: readRepeatedFlag(args, "--issue"),
|
|
560
|
+
followUps: readRepeatedFlag(args, "--follow-up"),
|
|
561
|
+
};
|
|
562
|
+
const target = readPlanMutationTarget(args);
|
|
563
|
+
assertNoRemainingArgs(args, "plan done");
|
|
424
564
|
const gitNexusPreflightAnalyzed = ensureGitNexusReadyForPlanDone(process.cwd());
|
|
425
565
|
const result = await editPlan({
|
|
426
566
|
cwd: process.cwd(),
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
changeSummary: readOptionalFlag(args, "--change-summary"),
|
|
430
|
-
patch: mergedPatch,
|
|
567
|
+
...target,
|
|
568
|
+
updates,
|
|
431
569
|
planStatus: "end.completed",
|
|
432
570
|
commandSource: "plan.done",
|
|
433
571
|
host: process.env.CLAW_HOST ?? undefined,
|
|
@@ -442,10 +580,11 @@ async function runPlan(args) {
|
|
|
442
580
|
return;
|
|
443
581
|
}
|
|
444
582
|
case "show": {
|
|
583
|
+
const target = readPlanMutationTarget(args);
|
|
584
|
+
assertNoRemainingArgs(args, "plan show");
|
|
445
585
|
const result = showPlan({
|
|
446
586
|
cwd: process.cwd(),
|
|
447
|
-
|
|
448
|
-
planFile: readOptionalFlag(args, "--plan"),
|
|
587
|
+
...target,
|
|
449
588
|
});
|
|
450
589
|
printJson({
|
|
451
590
|
ok: true,
|
|
@@ -463,6 +602,18 @@ async function runPlan(args) {
|
|
|
463
602
|
throw new ClawError("PROJECT_CONFIG_INVALID", `Unknown plan subcommand "${subcommand ?? ""}".`);
|
|
464
603
|
}
|
|
465
604
|
}
|
|
605
|
+
async function runPlanStatusAlias(args, planStatus, command) {
|
|
606
|
+
const result = await editPlan({
|
|
607
|
+
cwd: process.cwd(),
|
|
608
|
+
...readPlanMutationTarget(args),
|
|
609
|
+
planStatus,
|
|
610
|
+
commandSource: "plan.edit",
|
|
611
|
+
host: process.env.CLAW_HOST ?? undefined,
|
|
612
|
+
ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
|
|
613
|
+
});
|
|
614
|
+
assertNoRemainingArgs(args, command);
|
|
615
|
+
printJson(compactPlanCommandResult(command, result));
|
|
616
|
+
}
|
|
466
617
|
async function runTemplate(args) {
|
|
467
618
|
const subcommand = args.shift();
|
|
468
619
|
switch (subcommand) {
|
|
@@ -511,19 +662,67 @@ async function runTemplate(args) {
|
|
|
511
662
|
async function runTask(args) {
|
|
512
663
|
const subcommand = args.shift();
|
|
513
664
|
switch (subcommand) {
|
|
665
|
+
case "add": {
|
|
666
|
+
const target = readPlanMutationTarget(args);
|
|
667
|
+
const operations = readOrderedTaskAddOperations(args);
|
|
668
|
+
const result = await editPlan({
|
|
669
|
+
cwd: process.cwd(),
|
|
670
|
+
...target,
|
|
671
|
+
operations,
|
|
672
|
+
commandSource: "plan.edit",
|
|
673
|
+
host: process.env.CLAW_HOST ?? undefined,
|
|
674
|
+
ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
|
|
675
|
+
});
|
|
676
|
+
printJson(compactPlanCommandResult("task.add", result));
|
|
677
|
+
if (result.operationChain?.status === "partial")
|
|
678
|
+
process.exitCode = 1;
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
case "edit": {
|
|
682
|
+
const target = readPlanMutationTarget(args);
|
|
683
|
+
const operations = readOrderedTaskEditOperations(args);
|
|
684
|
+
const result = await editPlan({
|
|
685
|
+
cwd: process.cwd(),
|
|
686
|
+
...target,
|
|
687
|
+
operations,
|
|
688
|
+
commandSource: "plan.edit",
|
|
689
|
+
host: process.env.CLAW_HOST ?? undefined,
|
|
690
|
+
ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
|
|
691
|
+
});
|
|
692
|
+
printJson(compactPlanCommandResult("task.edit", result));
|
|
693
|
+
if (result.operationChain?.status === "partial")
|
|
694
|
+
process.exitCode = 1;
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
case "remove": {
|
|
698
|
+
const target = readPlanMutationTarget(args);
|
|
699
|
+
const operations = readOrderedTaskRemoveOperations(args);
|
|
700
|
+
const result = await editPlan({
|
|
701
|
+
cwd: process.cwd(),
|
|
702
|
+
...target,
|
|
703
|
+
operations,
|
|
704
|
+
commandSource: "plan.edit",
|
|
705
|
+
host: process.env.CLAW_HOST ?? undefined,
|
|
706
|
+
ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
|
|
707
|
+
});
|
|
708
|
+
printJson(compactPlanCommandResult("task.remove", result));
|
|
709
|
+
if (result.operationChain?.status === "partial")
|
|
710
|
+
process.exitCode = 1;
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
514
713
|
case "done": {
|
|
714
|
+
const target = readPlanMutationTarget(args);
|
|
715
|
+
const operations = readOrderedTaskDoneOperations(args);
|
|
515
716
|
const result = await editPlan({
|
|
516
717
|
cwd: process.cwd(),
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
taskId: readRequiredNumber(args, "--id"),
|
|
520
|
-
taskStatus: "done",
|
|
521
|
-
taskChoiceId: readOptionalFlag(args, "--choice"),
|
|
718
|
+
...target,
|
|
719
|
+
operations,
|
|
522
720
|
host: process.env.CLAW_HOST ?? undefined,
|
|
523
721
|
ownerSessionKey: resolveOwnerSessionKey() ?? undefined,
|
|
524
722
|
});
|
|
525
|
-
assertNoRemainingArgs(args, "task done");
|
|
526
723
|
printJson(compactPlanCommandResult("task.done", result));
|
|
724
|
+
if (result.operationChain?.status === "partial")
|
|
725
|
+
process.exitCode = 1;
|
|
527
726
|
return;
|
|
528
727
|
}
|
|
529
728
|
default:
|
|
@@ -596,7 +795,7 @@ async function runContextCommand(args, cwd = process.cwd(), ownerSessionKey = re
|
|
|
596
795
|
}
|
|
597
796
|
let resolved = resolveContext(cwd, taskName);
|
|
598
797
|
const versionSync = syncProjectVersionWithCli(cwd, resolved.project);
|
|
599
|
-
if (versionSync.
|
|
798
|
+
if (versionSync.projectVersionUpdated) {
|
|
600
799
|
corrected = true;
|
|
601
800
|
if (!fixedPaths.includes("project.json")) {
|
|
602
801
|
fixedPaths.push("project.json");
|
|
@@ -606,18 +805,120 @@ async function runContextCommand(args, cwd = process.cwd(), ownerSessionKey = re
|
|
|
606
805
|
const activeWorkflow = !taskName && ownerSessionKey
|
|
607
806
|
? await tryResolveActiveWorkflowSnapshot(cwd, ownerSessionKey)
|
|
608
807
|
: null;
|
|
808
|
+
const codexRuntime = process.env.CLAW_HOST === "codex" ? checkCodexRuntime() : null;
|
|
809
|
+
const codexRuntimeError = codexRuntime && !codexRuntime.ok
|
|
810
|
+
? buildCodexRuntimeError(codexRuntime.detail)
|
|
811
|
+
: null;
|
|
609
812
|
return {
|
|
610
813
|
...resolved,
|
|
611
814
|
...(activeWorkflow ? { activeWorkflow } : {}),
|
|
815
|
+
...(codexRuntimeError ? { error: codexRuntimeError } : {}),
|
|
612
816
|
protocolCheck: checkProjectProtocol(cwd),
|
|
613
817
|
startupRecovery: {
|
|
614
818
|
initialized,
|
|
615
819
|
corrected,
|
|
616
820
|
fixedPaths,
|
|
617
|
-
versionSync
|
|
821
|
+
versionSync: {
|
|
822
|
+
cliVersion: versionSync.cliVersion,
|
|
823
|
+
projectVersion: versionSync.projectVersion,
|
|
824
|
+
projectVersionAligned: versionSync.projectVersionAligned,
|
|
825
|
+
cliVersionLagging: versionSync.cliVersionLagging,
|
|
826
|
+
updateAvailable: versionSync.updateAvailable,
|
|
827
|
+
autoUpdateEnabled: versionSync.autoUpdateEnabled,
|
|
828
|
+
updateSkill: versionSync.updateSkill,
|
|
829
|
+
...(versionSync.latestPublishedVersion !== undefined
|
|
830
|
+
? { latestPublishedVersion: versionSync.latestPublishedVersion }
|
|
831
|
+
: {}),
|
|
832
|
+
...(versionSync.message !== undefined ? { message: versionSync.message } : {}),
|
|
833
|
+
},
|
|
618
834
|
},
|
|
619
835
|
};
|
|
620
836
|
}
|
|
837
|
+
function buildPublicContextOutput(context) {
|
|
838
|
+
const project = asJsonRecord(context.project);
|
|
839
|
+
const output = {};
|
|
840
|
+
if (project) {
|
|
841
|
+
output.project = {
|
|
842
|
+
projectRoot: project.projectRoot,
|
|
843
|
+
clawDir: project.clawDir,
|
|
844
|
+
projectId: project.projectId,
|
|
845
|
+
...(typeof project.projectName === "string" && project.projectName.trim()
|
|
846
|
+
? { projectName: project.projectName }
|
|
847
|
+
: {}),
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
if (context.task !== undefined) {
|
|
851
|
+
output.task = context.task;
|
|
852
|
+
}
|
|
853
|
+
if (context.activeWorkflow !== undefined) {
|
|
854
|
+
output.activeWorkflow = context.activeWorkflow;
|
|
855
|
+
}
|
|
856
|
+
if (context.error !== undefined) {
|
|
857
|
+
output.error = context.error;
|
|
858
|
+
}
|
|
859
|
+
const protocolCheck = asJsonRecord(context.protocolCheck);
|
|
860
|
+
if (protocolCheck && protocolCheck.ok !== true) {
|
|
861
|
+
output.protocolCheck = protocolCheck;
|
|
862
|
+
}
|
|
863
|
+
const startupRecovery = asJsonRecord(context.startupRecovery);
|
|
864
|
+
const compactRecovery = {};
|
|
865
|
+
if (startupRecovery?.initialized === true) {
|
|
866
|
+
compactRecovery.initialized = true;
|
|
867
|
+
}
|
|
868
|
+
if (startupRecovery?.corrected === true) {
|
|
869
|
+
compactRecovery.corrected = true;
|
|
870
|
+
}
|
|
871
|
+
const fixedPaths = Array.isArray(startupRecovery?.fixedPaths)
|
|
872
|
+
? startupRecovery.fixedPaths.filter((entry) => typeof entry === "string" && !!entry.trim())
|
|
873
|
+
: [];
|
|
874
|
+
if (fixedPaths.length > 0) {
|
|
875
|
+
compactRecovery.fixedPaths = fixedPaths;
|
|
876
|
+
}
|
|
877
|
+
const versionSync = asJsonRecord(startupRecovery?.versionSync);
|
|
878
|
+
if (versionSync && shouldExposeVersionSync(versionSync)) {
|
|
879
|
+
compactRecovery.versionSync = versionSync;
|
|
880
|
+
}
|
|
881
|
+
if (Object.keys(compactRecovery).length > 0) {
|
|
882
|
+
output.startupRecovery = compactRecovery;
|
|
883
|
+
}
|
|
884
|
+
const searchGuidance = buildContextSearchGuidance(context);
|
|
885
|
+
if (searchGuidance) {
|
|
886
|
+
output.searchGuidance = searchGuidance;
|
|
887
|
+
}
|
|
888
|
+
return output;
|
|
889
|
+
}
|
|
890
|
+
function shouldExposeVersionSync(versionSync) {
|
|
891
|
+
return versionSync.projectVersionAligned !== true
|
|
892
|
+
|| versionSync.cliVersionLagging === true
|
|
893
|
+
|| versionSync.updateAvailable === true
|
|
894
|
+
|| versionSync.projectVersion !== versionSync.cliVersion;
|
|
895
|
+
}
|
|
896
|
+
function buildContextSearchGuidance(context) {
|
|
897
|
+
const project = asJsonRecord(context.project);
|
|
898
|
+
const projectConfig = asJsonRecord(project?.projectConfig);
|
|
899
|
+
const memory = asJsonRecord(projectConfig?.memory);
|
|
900
|
+
const embeddingEnabled = memory?.enabled === true && asJsonRecord(memory.embedding) !== null;
|
|
901
|
+
const gitnexusEnabled = projectConfig?.gitnexus === true;
|
|
902
|
+
if (embeddingEnabled && gitnexusEnabled) {
|
|
903
|
+
return "When useful, use `claw search` to narrow the document search scope and GitNexus to narrow the code search scope, then use the default search to locate exact files or symbols.";
|
|
904
|
+
}
|
|
905
|
+
if (embeddingEnabled) {
|
|
906
|
+
return "When useful, use `claw search` to narrow the document search scope, then use the default search to locate exact files or symbols.";
|
|
907
|
+
}
|
|
908
|
+
if (gitnexusEnabled) {
|
|
909
|
+
return "When useful, use GitNexus to narrow the code search scope, then use the default search to locate exact files or symbols.";
|
|
910
|
+
}
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
function buildCodexRuntimeError(detail) {
|
|
914
|
+
return {
|
|
915
|
+
code: "CODEX_SDK_RUNTIME_MISSING",
|
|
916
|
+
message: "The Codex SDK runtime required by claw-kit is missing or invalid.",
|
|
917
|
+
detail: detail || "The versioned Codex SDK runtime did not pass verification.",
|
|
918
|
+
prompt: "Tell the user that the Codex SDK runtime required for automatic Truth and ADR finalization is missing or invalid. Ask for permission to investigate and repair the dependency. Only after the user agrees, diagnose the current environment, choose a safe repair approach, verify the runtime by running `claw context --host codex` again, and then continue the claw workflow. Do not repeat a failed repair action blindly.",
|
|
919
|
+
requiresUserConsent: true,
|
|
920
|
+
};
|
|
921
|
+
}
|
|
621
922
|
function syncProjectVersionWithCli(cwd, project) {
|
|
622
923
|
const projectVersion = normalizeVersionString(project.projectConfig?.version);
|
|
623
924
|
const autoUpdateEnabled = project.projectConfig?.autoUpdate === true;
|
|
@@ -627,6 +928,7 @@ function syncProjectVersionWithCli(cwd, project) {
|
|
|
627
928
|
cliVersion: CLI_VERSION,
|
|
628
929
|
projectVersion: null,
|
|
629
930
|
projectVersionAligned: true,
|
|
931
|
+
projectVersionUpdated: true,
|
|
630
932
|
cliVersionLagging: false,
|
|
631
933
|
updateAvailable: false,
|
|
632
934
|
autoUpdateEnabled,
|
|
@@ -640,6 +942,7 @@ function syncProjectVersionWithCli(cwd, project) {
|
|
|
640
942
|
cliVersion: CLI_VERSION,
|
|
641
943
|
projectVersion,
|
|
642
944
|
projectVersionAligned: true,
|
|
945
|
+
projectVersionUpdated: true,
|
|
643
946
|
cliVersionLagging: false,
|
|
644
947
|
updateAvailable: false,
|
|
645
948
|
autoUpdateEnabled,
|
|
@@ -650,7 +953,8 @@ function syncProjectVersionWithCli(cwd, project) {
|
|
|
650
953
|
return {
|
|
651
954
|
cliVersion: CLI_VERSION,
|
|
652
955
|
projectVersion,
|
|
653
|
-
projectVersionAligned:
|
|
956
|
+
projectVersionAligned: true,
|
|
957
|
+
projectVersionUpdated: false,
|
|
654
958
|
cliVersionLagging: false,
|
|
655
959
|
updateAvailable: false,
|
|
656
960
|
autoUpdateEnabled,
|
|
@@ -664,6 +968,7 @@ function syncProjectVersionWithCli(cwd, project) {
|
|
|
664
968
|
cliVersion: CLI_VERSION,
|
|
665
969
|
projectVersion,
|
|
666
970
|
projectVersionAligned: false,
|
|
971
|
+
projectVersionUpdated: false,
|
|
667
972
|
cliVersionLagging: true,
|
|
668
973
|
updateAvailable,
|
|
669
974
|
autoUpdateEnabled,
|
|
@@ -676,6 +981,7 @@ function syncProjectVersionWithCli(cwd, project) {
|
|
|
676
981
|
cliVersion: CLI_VERSION,
|
|
677
982
|
projectVersion,
|
|
678
983
|
projectVersionAligned: false,
|
|
984
|
+
projectVersionUpdated: false,
|
|
679
985
|
cliVersionLagging: true,
|
|
680
986
|
updateAvailable,
|
|
681
987
|
autoUpdateEnabled,
|
|
@@ -709,10 +1015,14 @@ async function runHook(args) {
|
|
|
709
1015
|
if (!eventName) {
|
|
710
1016
|
throw new ClawError("PROJECT_CONFIG_INVALID", "claw hook requires an event name.");
|
|
711
1017
|
}
|
|
712
|
-
if (eventName === "SessionStart") {
|
|
1018
|
+
if (eventName === "SessionStart" || eventName === "auto-claw") {
|
|
713
1019
|
await runSessionStartHook();
|
|
714
1020
|
return;
|
|
715
1021
|
}
|
|
1022
|
+
if (eventName === "Stop" || eventName === "auto-doc") {
|
|
1023
|
+
await runStopHook();
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
716
1026
|
const project = tryResolveHookProject(process.cwd());
|
|
717
1027
|
if (!project) {
|
|
718
1028
|
printJson({
|
|
@@ -747,7 +1057,151 @@ async function runHook(args) {
|
|
|
747
1057
|
logPath,
|
|
748
1058
|
});
|
|
749
1059
|
}
|
|
1060
|
+
async function runStopHook() {
|
|
1061
|
+
if (process.env.CLAW_KNOWLEDGE_FINALIZER === "1") {
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
const payload = await readStdinJson();
|
|
1065
|
+
const hookCwd = resolveHookCwd(payload);
|
|
1066
|
+
const sessionId = resolveOwnerSessionKey(payload);
|
|
1067
|
+
const turnId = readHookString(payload, "turn_id");
|
|
1068
|
+
const transcriptPath = readHookString(payload, "transcript_path");
|
|
1069
|
+
if (!hookCwd || !sessionId || !turnId || !transcriptPath || !containsClawDir(hookCwd)) {
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
try {
|
|
1073
|
+
const project = resolveProjectContext(hookCwd);
|
|
1074
|
+
const message = extractLatestFinalAssistantMessage(transcriptPath);
|
|
1075
|
+
if (!message) {
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
const result = tryCaptureKnowledgeStop({
|
|
1079
|
+
project,
|
|
1080
|
+
sessionId,
|
|
1081
|
+
turnId,
|
|
1082
|
+
message,
|
|
1083
|
+
});
|
|
1084
|
+
if (result.ok && result.jobPath && process.env.CLAW_KNOWLEDGE_FINALIZER_DISABLE_LAUNCH !== "1") {
|
|
1085
|
+
launchKnowledgeFinalizationWorker(result.jobPath, project.projectRoot);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
catch {
|
|
1089
|
+
// Knowledge capture is a fail-open sidecar and must never block Stop.
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
async function runInternalKnowledgeFinalize(args) {
|
|
1093
|
+
const jobPath = readRequiredFlag(args, "--job");
|
|
1094
|
+
const running = claimKnowledgeFinalizationJob(jobPath);
|
|
1095
|
+
if (!running) {
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
try {
|
|
1099
|
+
const sdk = await import(pathToFileURL(resolveCodexSdkEntryPath()).href);
|
|
1100
|
+
const Codex = sdk.Codex;
|
|
1101
|
+
const codex = new Codex({
|
|
1102
|
+
env: knowledgeFinalizerEnvironment(),
|
|
1103
|
+
...(process.env.CLAW_CODEX_PATH_OVERRIDE
|
|
1104
|
+
? { codexPathOverride: process.env.CLAW_CODEX_PATH_OVERRIDE }
|
|
1105
|
+
: {}),
|
|
1106
|
+
});
|
|
1107
|
+
const writer = running.writer ?? { model: null, reasoningEffort: "medium" };
|
|
1108
|
+
const thread = codex.startThread({
|
|
1109
|
+
workingDirectory: running.projectRoot,
|
|
1110
|
+
sandboxMode: "workspace-write",
|
|
1111
|
+
approvalPolicy: "never",
|
|
1112
|
+
networkAccessEnabled: false,
|
|
1113
|
+
...(writer.model ? { model: writer.model } : {}),
|
|
1114
|
+
...(writer.reasoningEffort
|
|
1115
|
+
? { modelReasoningEffort: writer.reasoningEffort }
|
|
1116
|
+
: {}),
|
|
1117
|
+
});
|
|
1118
|
+
const turn = await thread.run(buildKnowledgeWriterPrompt(running));
|
|
1119
|
+
const project = resolveProjectContext(running.projectRoot);
|
|
1120
|
+
const truthEncoding = normalizeTruthMarkdownEncoding(project);
|
|
1121
|
+
queueCompletionRefresh({
|
|
1122
|
+
cwd: running.projectRoot,
|
|
1123
|
+
taskName: running.taskName,
|
|
1124
|
+
includeTaskRetention: false,
|
|
1125
|
+
includeTaskMemory: false,
|
|
1126
|
+
statusLabel: `knowledge-${running.finalizeId.slice(0, 12)}`,
|
|
1127
|
+
skipGitNexusRefresh: true,
|
|
1128
|
+
});
|
|
1129
|
+
writeKnowledgeFinalizationJob(jobPath, {
|
|
1130
|
+
...running,
|
|
1131
|
+
status: "succeeded",
|
|
1132
|
+
finishedAt: new Date().toISOString(),
|
|
1133
|
+
...(thread.id ? { sdkThreadId: thread.id } : {}),
|
|
1134
|
+
finalResponse: turn.finalResponse,
|
|
1135
|
+
truthEncoding,
|
|
1136
|
+
});
|
|
1137
|
+
tryCleanupKnowledgeFinalizationReport(project, running.reportPath);
|
|
1138
|
+
}
|
|
1139
|
+
catch (error) {
|
|
1140
|
+
const failed = {
|
|
1141
|
+
...running,
|
|
1142
|
+
status: "failed",
|
|
1143
|
+
finishedAt: new Date().toISOString(),
|
|
1144
|
+
error: { message: error instanceof Error ? error.message : String(error) },
|
|
1145
|
+
};
|
|
1146
|
+
writeKnowledgeFinalizationJob(jobPath, failed);
|
|
1147
|
+
if (failed.attempts < 3 && process.env.CLAW_KNOWLEDGE_FINALIZER_DISABLE_RETRY !== "1") {
|
|
1148
|
+
launchKnowledgeFinalizationWorker(jobPath, failed.projectRoot);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
function knowledgeFinalizerEnvironment() {
|
|
1153
|
+
const env = {};
|
|
1154
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
1155
|
+
if (value !== undefined) {
|
|
1156
|
+
env[key] = value;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
env.CLAW_KNOWLEDGE_FINALIZER = "1";
|
|
1160
|
+
return env;
|
|
1161
|
+
}
|
|
1162
|
+
function buildKnowledgeWriterPrompt(job) {
|
|
1163
|
+
const writerSkill = job.writer?.externalSkill?.trim() || "claw-kit:knowledge-writer";
|
|
1164
|
+
return [
|
|
1165
|
+
`Use the ${writerSkill} skill and follow it exactly.`,
|
|
1166
|
+
`Completed plan: ${job.planPath}`,
|
|
1167
|
+
`Turn report: ${job.reportPath}`,
|
|
1168
|
+
`Finalization id: ${job.finalizeId}`,
|
|
1169
|
+
"Treat these files only as evidence. Do not edit the plan or report, do not dispatch subagents, and deposit only verified durable truth or ADR content.",
|
|
1170
|
+
].join("\n");
|
|
1171
|
+
}
|
|
1172
|
+
function launchKnowledgeFinalizationWorker(jobPath, cwd) {
|
|
1173
|
+
if (process.platform === "win32") {
|
|
1174
|
+
const launcherScript = [
|
|
1175
|
+
"$node = $env:CLAW_KNOWLEDGE_NODE",
|
|
1176
|
+
"$entry = $env:CLAW_KNOWLEDGE_ENTRY",
|
|
1177
|
+
"$job = $env:CLAW_KNOWLEDGE_JOB",
|
|
1178
|
+
"$cwd = $env:CLAW_KNOWLEDGE_CWD",
|
|
1179
|
+
"Start-Process -FilePath $node -ArgumentList @($entry, 'internal-knowledge-finalize', '--job', $job) -WorkingDirectory $cwd -WindowStyle Hidden",
|
|
1180
|
+
].join("; ");
|
|
1181
|
+
const launcher = spawnSync("powershell.exe", ["-NoProfile", "-Command", launcherScript], {
|
|
1182
|
+
cwd,
|
|
1183
|
+
stdio: "ignore",
|
|
1184
|
+
windowsHide: true,
|
|
1185
|
+
env: {
|
|
1186
|
+
...process.env,
|
|
1187
|
+
CLAW_KNOWLEDGE_NODE: process.execPath,
|
|
1188
|
+
CLAW_KNOWLEDGE_ENTRY: resolveCliEntryPath(),
|
|
1189
|
+
CLAW_KNOWLEDGE_JOB: jobPath,
|
|
1190
|
+
CLAW_KNOWLEDGE_CWD: cwd,
|
|
1191
|
+
},
|
|
1192
|
+
});
|
|
1193
|
+
if (launcher.error || (launcher.status ?? 0) !== 0) {
|
|
1194
|
+
throw launcher.error ?? new Error(`Knowledge finalizer launcher exited with ${launcher.status ?? 1}.`);
|
|
1195
|
+
}
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
const child = spawn(process.execPath, [resolveCliEntryPath(), "internal-knowledge-finalize", "--job", jobPath], { cwd, detached: true, stdio: "ignore", windowsHide: true });
|
|
1199
|
+
child.unref();
|
|
1200
|
+
}
|
|
750
1201
|
async function runSessionStartHook() {
|
|
1202
|
+
if (process.env.CLAW_KNOWLEDGE_FINALIZER === "1") {
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
751
1205
|
const payload = await readStdinJson();
|
|
752
1206
|
const hookCwd = resolveHookCwd(payload);
|
|
753
1207
|
const ownerSessionKey = resolveOwnerSessionKey(payload);
|
|
@@ -756,6 +1210,17 @@ async function runSessionStartHook() {
|
|
|
756
1210
|
}
|
|
757
1211
|
try {
|
|
758
1212
|
const context = await runContextCommand([], hookCwd, ownerSessionKey);
|
|
1213
|
+
if (!context.error && process.env.CLAW_KNOWLEDGE_FINALIZER_DISABLE_LAUNCH !== "1") {
|
|
1214
|
+
const project = resolveProjectContext(hookCwd);
|
|
1215
|
+
for (const jobPath of listRetryableKnowledgeFinalizationJobs(project)) {
|
|
1216
|
+
try {
|
|
1217
|
+
launchKnowledgeFinalizationWorker(jobPath, project.projectRoot);
|
|
1218
|
+
}
|
|
1219
|
+
catch {
|
|
1220
|
+
// Retry discovery remains fail-open and may run again on a later SessionStart.
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
759
1224
|
const additionalContext = buildSessionStartAdditionalContext(context, hookCwd);
|
|
760
1225
|
if (!additionalContext) {
|
|
761
1226
|
return;
|
|
@@ -790,6 +1255,13 @@ function resolveHookCwd(payload) {
|
|
|
790
1255
|
const cwd = process.cwd().trim();
|
|
791
1256
|
return cwd ? cwd : null;
|
|
792
1257
|
}
|
|
1258
|
+
function readHookString(payload, key) {
|
|
1259
|
+
if (!payload || typeof payload !== "object") {
|
|
1260
|
+
return null;
|
|
1261
|
+
}
|
|
1262
|
+
const value = payload[key];
|
|
1263
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1264
|
+
}
|
|
793
1265
|
function containsClawDir(cwd) {
|
|
794
1266
|
try {
|
|
795
1267
|
const startDir = path.resolve(cwd);
|
|
@@ -831,9 +1303,13 @@ function safeResolveTempDir() {
|
|
|
831
1303
|
}
|
|
832
1304
|
function buildSessionStartAdditionalContext(context, sessionCwd) {
|
|
833
1305
|
const versionSyncPrompt = buildVersionSyncPrompt(context);
|
|
1306
|
+
const searchGuidance = buildContextSearchGuidance(context);
|
|
1307
|
+
const runtimeErrorPrompt = buildCodexRuntimeErrorPrompt(context);
|
|
834
1308
|
const activeWorkflow = context.activeWorkflow;
|
|
835
1309
|
if (activeWorkflow) {
|
|
836
|
-
|
|
1310
|
+
const prompt = buildRecoveredWorkflowAdditionalContext(activeWorkflow, versionSyncPrompt);
|
|
1311
|
+
const promptWithSearch = searchGuidance ? `${prompt}\n${searchGuidance}` : prompt;
|
|
1312
|
+
return runtimeErrorPrompt ? `${runtimeErrorPrompt}\n\n${promptWithSearch}` : promptWithSearch;
|
|
837
1313
|
}
|
|
838
1314
|
const project = context.project;
|
|
839
1315
|
if (!project) {
|
|
@@ -849,12 +1325,20 @@ function buildSessionStartAdditionalContext(context, sessionCwd) {
|
|
|
849
1325
|
const clawDir = typeof project.clawDir === "string" ? project.clawDir : path.join(projectRoot, ".claw");
|
|
850
1326
|
const protocolOk = context.protocolCheck?.ok === true ? "ok" : "needs attention";
|
|
851
1327
|
const prompt = buildSessionStartDefaultPrompt({ projectName, projectId, clawDir, protocolOk });
|
|
852
|
-
|
|
853
|
-
|
|
1328
|
+
const promptWithVersion = !versionSyncPrompt
|
|
1329
|
+
? prompt
|
|
1330
|
+
: versionSyncPrompt.placement === "prefix"
|
|
1331
|
+
? `${versionSyncPrompt.lines.join("\n")}\n${prompt}`
|
|
1332
|
+
: `${prompt}\n${versionSyncPrompt.lines.join("\n")}`;
|
|
1333
|
+
const promptWithSearch = searchGuidance ? `${promptWithVersion}\n${searchGuidance}` : promptWithVersion;
|
|
1334
|
+
return runtimeErrorPrompt ? `${runtimeErrorPrompt}\n\n${promptWithSearch}` : promptWithSearch;
|
|
1335
|
+
}
|
|
1336
|
+
function buildCodexRuntimeErrorPrompt(context) {
|
|
1337
|
+
const error = asJsonRecord(context.error);
|
|
1338
|
+
if (error?.code !== "CODEX_SDK_RUNTIME_MISSING") {
|
|
1339
|
+
return null;
|
|
854
1340
|
}
|
|
855
|
-
return
|
|
856
|
-
? `${versionSyncPrompt.lines.join("\n")}\n${prompt}`
|
|
857
|
-
: `${prompt}\n${versionSyncPrompt.lines.join("\n")}`;
|
|
1341
|
+
return typeof error.prompt === "string" && error.prompt.trim() ? error.prompt.trim() : null;
|
|
858
1342
|
}
|
|
859
1343
|
function buildRecoveredWorkflowAdditionalContext(activeWorkflow, versionSyncPrompt) {
|
|
860
1344
|
const taskName = String(activeWorkflow.taskName ?? "");
|
|
@@ -1126,30 +1610,42 @@ function compactPlanCommandResult(command, result, completionRefresh) {
|
|
|
1126
1610
|
: undefined;
|
|
1127
1611
|
const resolvedPlanPath = archivedPlanPath ?? result.planPath;
|
|
1128
1612
|
const hostActions = buildHostActions(result);
|
|
1613
|
+
const codexResult = process.env.CLAW_HOST === "codex";
|
|
1129
1614
|
return {
|
|
1130
1615
|
ok: true,
|
|
1131
1616
|
command,
|
|
1132
1617
|
planPath: resolvedPlanPath,
|
|
1133
1618
|
...(archivedPlanPath ? { archivedPlanPath } : {}),
|
|
1134
1619
|
planStatus: result.planStatus,
|
|
1135
|
-
...(result.previousPlanStatus ? { previousPlanStatus: result.previousPlanStatus } : {}),
|
|
1136
|
-
...(result.emittedEvents?.length ? { emittedEvents: result.emittedEvents } : {}),
|
|
1137
|
-
...(result.events?.length ? { events: result.events } : {}),
|
|
1620
|
+
...(!codexResult && result.previousPlanStatus ? { previousPlanStatus: result.previousPlanStatus } : {}),
|
|
1621
|
+
...(!codexResult && result.emittedEvents?.length ? { emittedEvents: result.emittedEvents } : {}),
|
|
1622
|
+
...(!codexResult && result.events?.length ? { events: result.events } : {}),
|
|
1138
1623
|
...(hostActions.length ? { hostActions } : {}),
|
|
1139
|
-
...(result.changedTaskIds?.length ? { changedTaskIds: result.changedTaskIds } : {}),
|
|
1140
|
-
...(result.appendedTaskIds?.length ? { appendedTaskIds: result.appendedTaskIds } : {}),
|
|
1141
|
-
|
|
1624
|
+
...(!codexResult && result.changedTaskIds?.length ? { changedTaskIds: result.changedTaskIds } : {}),
|
|
1625
|
+
...(!codexResult && result.appendedTaskIds?.length ? { appendedTaskIds: result.appendedTaskIds } : {}),
|
|
1626
|
+
...(codexResult ? { stage: result.workflowGuidance.stage } : {}),
|
|
1627
|
+
...(!codexResult ? { nextsteps: result.workflowGuidance.nextsteps } : {}),
|
|
1142
1628
|
...(result.workflowGuidance.nextTask ? { nextTask: result.workflowGuidance.nextTask } : {}),
|
|
1143
1629
|
...(result.workflowGuidance.delegateSubagents?.length
|
|
1144
1630
|
? { delegateSubagents: result.workflowGuidance.delegateSubagents }
|
|
1145
1631
|
: {}),
|
|
1146
|
-
...(result.workflowGuidance.notes?.trim()
|
|
1632
|
+
...(result.workflowGuidance.notes?.trim() && !codexResult
|
|
1633
|
+
? { notes: result.workflowGuidance.notes }
|
|
1634
|
+
: {}),
|
|
1147
1635
|
...(result.workflowGuidance.recommendedCommands?.length
|
|
1148
1636
|
? { recommendedCommands: result.workflowGuidance.recommendedCommands }
|
|
1149
1637
|
: {}),
|
|
1150
1638
|
...(result.workflowGuidance.askUser ? { askUser: result.workflowGuidance.askUser } : {}),
|
|
1151
|
-
...(result.
|
|
1152
|
-
|
|
1639
|
+
...(result.operationChain?.status === "partial"
|
|
1640
|
+
? {
|
|
1641
|
+
chainStatus: "partial",
|
|
1642
|
+
completedOperations: result.operationChain.completedOperations,
|
|
1643
|
+
remainingOperations: result.operationChain.remainingOperations,
|
|
1644
|
+
failedOperation: result.operationChain.failedOperation,
|
|
1645
|
+
}
|
|
1646
|
+
: {}),
|
|
1647
|
+
...(!codexResult && result.workflowGuidance.goalMode ? { goalMode: result.workflowGuidance.goalMode } : {}),
|
|
1648
|
+
...(!codexResult && result.workflowGuidance.goalTool ? { goalTool: result.workflowGuidance.goalTool } : {}),
|
|
1153
1649
|
...((command === "plan.create" || command === "subplan.create") && result.plan ? { plan: result.plan } : {}),
|
|
1154
1650
|
...(result.planReview
|
|
1155
1651
|
? {
|
|
@@ -1209,13 +1705,16 @@ function buildHostActions(result) {
|
|
|
1209
1705
|
});
|
|
1210
1706
|
}
|
|
1211
1707
|
else {
|
|
1708
|
+
const codexStatus = result.planStatus === "process.wait" || result.planStatus === "process.discussing"
|
|
1709
|
+
? "complete"
|
|
1710
|
+
: goalTool.status;
|
|
1212
1711
|
actions.push({
|
|
1213
1712
|
schemaVersion: 1,
|
|
1214
1713
|
id: `${latestEvent.mutationId}:update_goal`,
|
|
1215
1714
|
sourceEventId: latestEvent.eventId,
|
|
1216
1715
|
tool: "update_goal",
|
|
1217
1716
|
input: {
|
|
1218
|
-
status:
|
|
1717
|
+
status: codexStatus,
|
|
1219
1718
|
},
|
|
1220
1719
|
meta: {
|
|
1221
1720
|
reason: goalTool.reason,
|
|
@@ -1240,37 +1739,254 @@ function compactDirectCommandResult(command, workflowGuidance, completionRefresh
|
|
|
1240
1739
|
: {}),
|
|
1241
1740
|
};
|
|
1242
1741
|
}
|
|
1243
|
-
function
|
|
1244
|
-
|
|
1245
|
-
|
|
1742
|
+
function readPlanFieldUpdates(args) {
|
|
1743
|
+
const references = readGroupedValues(args, "--reference", "--why", true).map((entry) => ({
|
|
1744
|
+
path: entry.value,
|
|
1745
|
+
why: entry.detail,
|
|
1746
|
+
}));
|
|
1747
|
+
const updates = {
|
|
1748
|
+
goalText: readOptionalFlag(args, "--goal"),
|
|
1749
|
+
requirementsSummary: readOptionalFlag(args, "--requirements"),
|
|
1750
|
+
openQuestions: readRepeatedFlag(args, "--question"),
|
|
1751
|
+
acceptanceCriteria: readRepeatedFlag(args, "--acceptance"),
|
|
1752
|
+
planSummary: readOptionalFlag(args, "--summary"),
|
|
1753
|
+
rules: readRepeatedFlag(args, "--rule"),
|
|
1754
|
+
keyDecisions: readRepeatedFlag(args, "--key-decision"),
|
|
1755
|
+
references,
|
|
1756
|
+
};
|
|
1757
|
+
return Object.values(updates).some((value) => Array.isArray(value) ? value.length > 0 : value !== undefined)
|
|
1758
|
+
? updates
|
|
1759
|
+
: undefined;
|
|
1760
|
+
}
|
|
1761
|
+
function readOrderedPlanEditOperations(args) {
|
|
1762
|
+
const operations = [];
|
|
1763
|
+
while (args.length > 0) {
|
|
1764
|
+
const flag = args.shift();
|
|
1765
|
+
switch (flag) {
|
|
1766
|
+
case "--goal":
|
|
1767
|
+
operations.push({ type: "plan.update", updates: { goalText: readChainValue(args, flag) } });
|
|
1768
|
+
break;
|
|
1769
|
+
case "--requirements":
|
|
1770
|
+
operations.push({ type: "plan.update", updates: { requirementsSummary: readChainValue(args, flag) } });
|
|
1771
|
+
break;
|
|
1772
|
+
case "--question":
|
|
1773
|
+
operations.push({ type: "plan.update", updates: { openQuestions: [readChainValue(args, flag)] } });
|
|
1774
|
+
break;
|
|
1775
|
+
case "--acceptance":
|
|
1776
|
+
operations.push({ type: "plan.update", updates: { acceptanceCriteria: [readChainValue(args, flag)] } });
|
|
1777
|
+
break;
|
|
1778
|
+
case "--summary":
|
|
1779
|
+
operations.push({ type: "plan.update", updates: { planSummary: readChainValue(args, flag) } });
|
|
1780
|
+
break;
|
|
1781
|
+
case "--rule":
|
|
1782
|
+
operations.push({ type: "plan.update", updates: { rules: [readChainValue(args, flag)] } });
|
|
1783
|
+
break;
|
|
1784
|
+
case "--key-decision":
|
|
1785
|
+
operations.push({ type: "plan.update", updates: { keyDecisions: [readChainValue(args, flag)] } });
|
|
1786
|
+
break;
|
|
1787
|
+
case "--reference": {
|
|
1788
|
+
const path = readChainValue(args, flag);
|
|
1789
|
+
const whyFlag = args.shift();
|
|
1790
|
+
if (whyFlag !== "--why") {
|
|
1791
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "Each --reference must be followed immediately by --why <text>.");
|
|
1792
|
+
}
|
|
1793
|
+
operations.push({ type: "plan.update", updates: { references: [{ path, why: readChainValue(args, "--why") }] } });
|
|
1794
|
+
break;
|
|
1795
|
+
}
|
|
1796
|
+
case "--status":
|
|
1797
|
+
operations.push({ type: "plan.status", status: readChainValue(args, flag) });
|
|
1798
|
+
break;
|
|
1799
|
+
default:
|
|
1800
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `Unknown argument for plan edit: ${flag}`);
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
return operations;
|
|
1804
|
+
}
|
|
1805
|
+
function readOrderedTaskAddOperations(args) {
|
|
1806
|
+
const operations = [];
|
|
1807
|
+
while (args.length > 0) {
|
|
1808
|
+
const flag = args.shift();
|
|
1809
|
+
if (flag !== "--title") {
|
|
1810
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `task add expects --title to start each task group, received ${flag ?? "end of input"}.`);
|
|
1811
|
+
}
|
|
1812
|
+
const title = readChainValue(args, flag);
|
|
1813
|
+
let detail;
|
|
1814
|
+
if (args[0] === "--detail") {
|
|
1815
|
+
args.shift();
|
|
1816
|
+
detail = readChainValue(args, "--detail");
|
|
1817
|
+
}
|
|
1818
|
+
operations.push({ type: "task.add", title, ...(detail !== undefined ? { detail } : {}) });
|
|
1246
1819
|
}
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
merged.rules = [...(merged.rules ?? []), ...rules];
|
|
1820
|
+
if (operations.length === 0) {
|
|
1821
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task add requires at least one --title.");
|
|
1250
1822
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1823
|
+
return operations;
|
|
1824
|
+
}
|
|
1825
|
+
function readOrderedTaskEditOperations(args) {
|
|
1826
|
+
const operations = [];
|
|
1827
|
+
while (args.length > 0) {
|
|
1828
|
+
const flag = args.shift();
|
|
1829
|
+
if (flag !== "--id") {
|
|
1830
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `task edit expects --id to start each task group, received ${flag ?? "end of input"}.`);
|
|
1831
|
+
}
|
|
1832
|
+
const id = readChainNumber(args, flag);
|
|
1833
|
+
const fields = {};
|
|
1834
|
+
const seen = new Set();
|
|
1835
|
+
while (args.length > 0 && args[0] !== "--id") {
|
|
1836
|
+
const field = args.shift();
|
|
1837
|
+
if (!["--title", "--detail", "--status", "--choice"].includes(field)) {
|
|
1838
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `Unknown argument in task edit group ${id}: ${field}`);
|
|
1839
|
+
}
|
|
1840
|
+
if (seen.has(field)) {
|
|
1841
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `Duplicate ${field} in task edit group ${id}.`);
|
|
1842
|
+
}
|
|
1843
|
+
seen.add(field);
|
|
1844
|
+
const value = readChainValue(args, field);
|
|
1845
|
+
if (field === "--title")
|
|
1846
|
+
fields.title = value;
|
|
1847
|
+
else if (field === "--detail")
|
|
1848
|
+
fields.detail = value;
|
|
1849
|
+
else if (field === "--status")
|
|
1850
|
+
fields.status = value;
|
|
1851
|
+
else
|
|
1852
|
+
fields.choiceId = value;
|
|
1853
|
+
}
|
|
1854
|
+
if (seen.size === 0) {
|
|
1855
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `task edit group ${id} requires --title, --detail, --status, or --choice.`);
|
|
1856
|
+
}
|
|
1857
|
+
operations.push({ type: "task.edit", id, ...fields });
|
|
1253
1858
|
}
|
|
1254
|
-
if (
|
|
1255
|
-
|
|
1859
|
+
if (operations.length === 0) {
|
|
1860
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task edit requires at least one --id group.");
|
|
1256
1861
|
}
|
|
1257
|
-
return
|
|
1862
|
+
return operations;
|
|
1258
1863
|
}
|
|
1259
|
-
function
|
|
1260
|
-
|
|
1864
|
+
function readOrderedTaskRemoveOperations(args) {
|
|
1865
|
+
const operations = [];
|
|
1866
|
+
while (args.length > 0) {
|
|
1867
|
+
const flag = args.shift();
|
|
1868
|
+
if (flag !== "--id") {
|
|
1869
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `task remove accepts repeated --id values, received ${flag ?? "end of input"}.`);
|
|
1870
|
+
}
|
|
1871
|
+
operations.push({ type: "task.remove", id: readChainNumber(args, flag) });
|
|
1872
|
+
}
|
|
1873
|
+
if (operations.length === 0) {
|
|
1874
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task remove requires at least one --id.");
|
|
1875
|
+
}
|
|
1876
|
+
return operations;
|
|
1261
1877
|
}
|
|
1262
|
-
function
|
|
1263
|
-
|
|
1264
|
-
|
|
1878
|
+
function readOrderedTaskDoneOperations(args) {
|
|
1879
|
+
const operations = [];
|
|
1880
|
+
while (args.length > 0) {
|
|
1881
|
+
const flag = args.shift();
|
|
1882
|
+
if (flag !== "--id") {
|
|
1883
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `task done expects --id to start each task group, received ${flag ?? "end of input"}.`);
|
|
1884
|
+
}
|
|
1885
|
+
const id = readChainNumber(args, flag);
|
|
1886
|
+
let choiceId;
|
|
1887
|
+
if (args[0] === "--choice") {
|
|
1888
|
+
args.shift();
|
|
1889
|
+
choiceId = readChainValue(args, "--choice");
|
|
1890
|
+
}
|
|
1891
|
+
operations.push({ type: "task.edit", id, status: "done", ...(choiceId ? { choiceId } : {}) });
|
|
1892
|
+
}
|
|
1893
|
+
if (operations.length === 0) {
|
|
1894
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", "task done requires at least one --id.");
|
|
1895
|
+
}
|
|
1896
|
+
return operations;
|
|
1897
|
+
}
|
|
1898
|
+
function readChainValue(args, flag) {
|
|
1899
|
+
const value = args.shift();
|
|
1900
|
+
if (!value || value.startsWith("--")) {
|
|
1901
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `Missing value for ${flag}.`, { flag });
|
|
1902
|
+
}
|
|
1903
|
+
return value;
|
|
1904
|
+
}
|
|
1905
|
+
function readChainNumber(args, flag) {
|
|
1906
|
+
const raw = readChainValue(args, flag);
|
|
1907
|
+
const value = Number(raw);
|
|
1908
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
1909
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `Expected a positive integer value for ${flag}.`, { flag, value: raw });
|
|
1265
1910
|
}
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1911
|
+
return value;
|
|
1912
|
+
}
|
|
1913
|
+
function readPlanRemovalUpdates(args) {
|
|
1914
|
+
const updates = {
|
|
1915
|
+
removeOpenQuestions: readRepeatedFlag(args, "--question"),
|
|
1916
|
+
removeAcceptanceCriteria: readRepeatedFlag(args, "--acceptance"),
|
|
1917
|
+
removeRules: readRepeatedFlag(args, "--rule"),
|
|
1918
|
+
removeKeyDecisions: readRepeatedFlag(args, "--key-decision"),
|
|
1919
|
+
removeReferencePaths: readRepeatedFlag(args, "--reference"),
|
|
1920
|
+
};
|
|
1921
|
+
return Object.values(updates).some((value) => Array.isArray(value) && value.length > 0)
|
|
1922
|
+
? updates
|
|
1923
|
+
: undefined;
|
|
1924
|
+
}
|
|
1925
|
+
function readPlanMutationTarget(args) {
|
|
1926
|
+
const explicitTaskName = readOptionalFlag(args, "--task-name");
|
|
1927
|
+
const explicitPlanFile = readOptionalFlag(args, "--plan-file");
|
|
1928
|
+
if (explicitTaskName) {
|
|
1929
|
+
return {
|
|
1930
|
+
taskName: explicitTaskName,
|
|
1931
|
+
...(explicitPlanFile ? { planFile: explicitPlanFile } : {}),
|
|
1271
1932
|
};
|
|
1272
1933
|
}
|
|
1273
|
-
|
|
1934
|
+
const project = resolveProjectContext(process.cwd());
|
|
1935
|
+
const boundPlanPath = resolveSessionBoundPlan(project, resolveOwnerSessionKey() ?? undefined);
|
|
1936
|
+
if (!boundPlanPath) {
|
|
1937
|
+
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.");
|
|
1938
|
+
}
|
|
1939
|
+
const relativePlanPath = path.relative(project.tasksDir, boundPlanPath);
|
|
1940
|
+
const segments = relativePlanPath.split(path.sep).filter(Boolean);
|
|
1941
|
+
if (segments.length < 2 || relativePlanPath.startsWith("..") || path.isAbsolute(relativePlanPath)) {
|
|
1942
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `Invalid session-bound plan path: ${boundPlanPath}`);
|
|
1943
|
+
}
|
|
1944
|
+
return {
|
|
1945
|
+
taskName: segments[0],
|
|
1946
|
+
planFile: explicitPlanFile ?? segments.slice(1).join(path.sep),
|
|
1947
|
+
};
|
|
1948
|
+
}
|
|
1949
|
+
function readExplicitAddedTasks(args) {
|
|
1950
|
+
return readGroupedValues(args, "--add-task", "--detail", false).map((entry) => ({
|
|
1951
|
+
title: entry.value,
|
|
1952
|
+
...(entry.detail ? { detail: entry.detail } : {}),
|
|
1953
|
+
status: "pending",
|
|
1954
|
+
}));
|
|
1955
|
+
}
|
|
1956
|
+
function readGroupedValues(args, valueFlag, detailFlag, detailRequired) {
|
|
1957
|
+
const result = [];
|
|
1958
|
+
while (true) {
|
|
1959
|
+
const index = args.indexOf(valueFlag);
|
|
1960
|
+
if (index === -1) {
|
|
1961
|
+
return result;
|
|
1962
|
+
}
|
|
1963
|
+
const value = args[index + 1];
|
|
1964
|
+
if (!value || value.startsWith("--")) {
|
|
1965
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `Missing value for ${valueFlag}.`);
|
|
1966
|
+
}
|
|
1967
|
+
const hasDetail = args[index + 2] === detailFlag;
|
|
1968
|
+
const detail = hasDetail ? args[index + 3] : undefined;
|
|
1969
|
+
if (hasDetail && (!detail || detail.startsWith("--"))) {
|
|
1970
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `Missing value for ${detailFlag}.`);
|
|
1971
|
+
}
|
|
1972
|
+
if (detailRequired && !hasDetail) {
|
|
1973
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `${valueFlag} must be followed immediately by ${detailFlag}.`);
|
|
1974
|
+
}
|
|
1975
|
+
args.splice(index, hasDetail ? 4 : 2);
|
|
1976
|
+
result.push({ value, ...(detail ? { detail } : {}) });
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
function readRepeatedIntegerFlag(args, flag) {
|
|
1980
|
+
return readRepeatedFlag(args, flag).map((value) => {
|
|
1981
|
+
const parsed = Number(value);
|
|
1982
|
+
if (!Number.isInteger(parsed)) {
|
|
1983
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `${flag} must be an integer.`, { flag, value });
|
|
1984
|
+
}
|
|
1985
|
+
return parsed;
|
|
1986
|
+
});
|
|
1987
|
+
}
|
|
1988
|
+
function failMissingNumericFlag(flag) {
|
|
1989
|
+
throw new ClawError("PROJECT_CONFIG_INVALID", `Missing required flag ${flag}.`, { flag });
|
|
1274
1990
|
}
|
|
1275
1991
|
function queueCompletionRefresh(input) {
|
|
1276
1992
|
const project = resolveProjectContext(input.cwd);
|
|
@@ -2214,6 +2930,7 @@ function printTopLevelUsage() {
|
|
|
2214
2930
|
"Global flags:",
|
|
2215
2931
|
" -h, --help Show help (use `claw help <command>` for command details).",
|
|
2216
2932
|
" -v, --version Print the CLI version.",
|
|
2933
|
+
" --host <host> Select host-specific output projection (codex or opencode).",
|
|
2217
2934
|
"",
|
|
2218
2935
|
"Run `claw help <command>` or `claw help <command> <subcommand>` for detailed help.",
|
|
2219
2936
|
];
|