@xenonbyte/da-vinci-workflow 0.1.25 → 0.2.1
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/CHANGELOG.md +37 -0
- package/README.md +48 -67
- package/README.zh-CN.md +36 -66
- package/SKILL.md +3 -0
- package/commands/claude/dv/continue.md +5 -0
- package/commands/claude/dv/design.md +1 -0
- package/commands/codex/prompts/dv-continue.md +6 -1
- package/commands/codex/prompts/dv-design.md +1 -0
- package/commands/gemini/dv/continue.toml +5 -0
- package/commands/gemini/dv/design.toml +1 -0
- package/commands/templates/dv-continue.shared.md +33 -0
- package/docs/dv-command-reference.md +45 -2
- package/docs/execution-chain-migration.md +46 -0
- package/docs/execution-chain-plan.md +125 -0
- package/docs/pencil-rendering-workflow.md +9 -7
- package/docs/prompt-entrypoints.md +6 -0
- package/docs/prompt-presets/README.md +4 -0
- package/docs/visual-assist-presets/README.md +4 -0
- package/docs/workflow-examples.md +23 -11
- package/docs/workflow-overview.md +27 -0
- package/docs/zh-CN/dv-command-reference.md +45 -2
- package/docs/zh-CN/execution-chain-migration.md +46 -0
- package/docs/zh-CN/pencil-rendering-workflow.md +9 -7
- package/docs/zh-CN/prompt-entrypoints.md +6 -0
- package/docs/zh-CN/prompt-presets/README.md +5 -1
- package/docs/zh-CN/visual-assist-presets/README.md +5 -1
- package/docs/zh-CN/workflow-examples.md +23 -11
- package/docs/zh-CN/workflow-overview.md +27 -0
- package/examples/greenfield-spec-markupflow/README.md +6 -1
- package/lib/artifact-parsers.js +120 -0
- package/lib/async-offload-worker.js +26 -0
- package/lib/async-offload.js +82 -0
- package/lib/audit-parsers.js +152 -32
- package/lib/audit.js +145 -23
- package/lib/cli.js +1068 -437
- package/lib/diff-spec.js +242 -0
- package/lib/execution-signals.js +136 -0
- package/lib/fs-safety.js +1 -4
- package/lib/icon-aliases.js +7 -7
- package/lib/icon-search.js +21 -14
- package/lib/icon-sync.js +220 -41
- package/lib/install.js +128 -60
- package/lib/lint-bindings.js +143 -0
- package/lib/lint-spec.js +408 -0
- package/lib/lint-tasks.js +176 -0
- package/lib/mcp-runtime-gate.js +4 -7
- package/lib/pen-persistence.js +318 -46
- package/lib/pencil-lock.js +237 -25
- package/lib/pencil-preflight.js +233 -12
- package/lib/pencil-session.js +216 -36
- package/lib/planning-parsers.js +567 -0
- package/lib/scaffold.js +193 -0
- package/lib/scope-check.js +603 -0
- package/lib/sidecars.js +369 -0
- package/lib/supervisor-review.js +82 -35
- package/lib/utils.js +129 -0
- package/lib/verify.js +652 -0
- package/lib/workflow-bootstrap.js +255 -0
- package/lib/workflow-contract.js +107 -0
- package/lib/workflow-persisted-state.js +297 -0
- package/lib/workflow-state.js +785 -0
- package/package.json +21 -3
- package/references/artifact-templates.md +26 -0
- package/references/checkpoints.md +16 -0
- package/references/design-inputs.md +2 -0
- package/references/modes.md +10 -0
- package/references/pencil-design-to-code.md +2 -0
- package/scripts/fixtures/complex-sample.pen +0 -295
- package/scripts/fixtures/mock-pencil.js +0 -49
- package/scripts/test-audit-context-delta.js +0 -446
- package/scripts/test-audit-design-supervisor.js +0 -691
- package/scripts/test-audit-safety.js +0 -92
- package/scripts/test-icon-aliases.js +0 -96
- package/scripts/test-icon-search.js +0 -77
- package/scripts/test-icon-sync.js +0 -178
- package/scripts/test-mcp-runtime-gate.js +0 -287
- package/scripts/test-mode-consistency.js +0 -344
- package/scripts/test-pen-persistence.js +0 -403
- package/scripts/test-pencil-lock.js +0 -130
- package/scripts/test-pencil-preflight.js +0 -169
- package/scripts/test-pencil-session.js +0 -192
- package/scripts/test-persistence-flows.js +0 -345
- package/scripts/test-supervisor-review-cli.js +0 -619
- package/scripts/test-supervisor-review-integration.js +0 -115
package/lib/cli.js
CHANGED
|
@@ -16,7 +16,10 @@ const {
|
|
|
16
16
|
writePenFromPayloadFiles,
|
|
17
17
|
snapshotPenFile,
|
|
18
18
|
ensurePenFile,
|
|
19
|
-
comparePenSync
|
|
19
|
+
comparePenSync,
|
|
20
|
+
comparePenBaselineAlignment,
|
|
21
|
+
formatPenBaselineAlignmentReport,
|
|
22
|
+
syncPenSource
|
|
20
23
|
} = require("./pen-persistence");
|
|
21
24
|
const {
|
|
22
25
|
acquirePencilLock,
|
|
@@ -42,37 +45,349 @@ const {
|
|
|
42
45
|
loadIconAliases,
|
|
43
46
|
expandQueryWithAliases
|
|
44
47
|
} = require("./icon-aliases");
|
|
48
|
+
const { sleepSync } = require("./utils");
|
|
45
49
|
const {
|
|
46
50
|
runDesignSupervisorReview,
|
|
47
51
|
formatDesignSupervisorReviewReport
|
|
48
52
|
} = require("./supervisor-review");
|
|
53
|
+
const {
|
|
54
|
+
bootstrapProjectArtifacts,
|
|
55
|
+
formatBootstrapProjectReport
|
|
56
|
+
} = require("./workflow-bootstrap");
|
|
57
|
+
const {
|
|
58
|
+
deriveWorkflowStatus,
|
|
59
|
+
formatWorkflowStatusReport,
|
|
60
|
+
formatNextStepReport
|
|
61
|
+
} = require("./workflow-state");
|
|
62
|
+
const { lintRuntimeSpecs, formatLintSpecReport } = require("./lint-spec");
|
|
63
|
+
const { runScopeCheck, formatScopeCheckReport } = require("./scope-check");
|
|
64
|
+
const { lintTasks, formatLintTasksReport } = require("./lint-tasks");
|
|
65
|
+
const { lintBindings, formatLintBindingsReport } = require("./lint-bindings");
|
|
66
|
+
const { generatePlanningSidecars, formatGenerateSidecarsReport } = require("./sidecars");
|
|
67
|
+
const {
|
|
68
|
+
verifyBindings,
|
|
69
|
+
verifyImplementation,
|
|
70
|
+
verifyStructure,
|
|
71
|
+
verifyCoverage,
|
|
72
|
+
formatVerifyReport
|
|
73
|
+
} = require("./verify");
|
|
74
|
+
const { diffSpec, formatDiffSpecReport } = require("./diff-spec");
|
|
75
|
+
const { scaffoldFromBindings, formatScaffoldReport } = require("./scaffold");
|
|
76
|
+
const { writeExecutionSignal } = require("./execution-signals");
|
|
77
|
+
|
|
78
|
+
const DEFAULT_MAX_PREFLIGHT_STDIN_BYTES = 1024 * 1024;
|
|
79
|
+
const DEFAULT_MAX_STDIN_TRANSIENT_RETRIES = 2000;
|
|
80
|
+
const DEFAULT_MAX_STDIN_TRANSIENT_BACKOFF_MS = 25;
|
|
81
|
+
const OPTION_FLAGS_WITH_VALUES = new Set([
|
|
82
|
+
"--home",
|
|
83
|
+
"--platform",
|
|
84
|
+
"--project",
|
|
85
|
+
"--mode",
|
|
86
|
+
"--change",
|
|
87
|
+
"--query",
|
|
88
|
+
"--baseline",
|
|
89
|
+
"--prefer-source",
|
|
90
|
+
"--family",
|
|
91
|
+
"--top",
|
|
92
|
+
"--catalog",
|
|
93
|
+
"--aliases",
|
|
94
|
+
"--pencil-design",
|
|
95
|
+
"--status",
|
|
96
|
+
"--source",
|
|
97
|
+
"--executed-reviewers",
|
|
98
|
+
"--codex-bin",
|
|
99
|
+
"--max-images",
|
|
100
|
+
"--review-timeout-ms",
|
|
101
|
+
"--review-concurrency",
|
|
102
|
+
"--review-retries",
|
|
103
|
+
"--review-retry-delay-ms",
|
|
104
|
+
"--review-retry-max-delay-ms",
|
|
105
|
+
"--issue-list",
|
|
106
|
+
"--revision-outcome",
|
|
107
|
+
"--timeout-ms",
|
|
108
|
+
"--ops-file",
|
|
109
|
+
"--input",
|
|
110
|
+
"--output",
|
|
111
|
+
"--from",
|
|
112
|
+
"--to",
|
|
113
|
+
"--pen",
|
|
114
|
+
"--nodes-file",
|
|
115
|
+
"--variables-file",
|
|
116
|
+
"--version",
|
|
117
|
+
"--owner",
|
|
118
|
+
"--wait-ms"
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
const HELP_OPTION_SPECS = [
|
|
122
|
+
{ flag: "--platform <value>", description: "codex, claude, gemini, or all" },
|
|
123
|
+
{ flag: "--home <path>", description: "override HOME for installation targets" },
|
|
124
|
+
{ flag: "--project <path>", description: "override project path for audit" },
|
|
125
|
+
{
|
|
126
|
+
flag: "--catalog <path>",
|
|
127
|
+
description: "icon catalog path for icon-search/icon-sync (default: ~/.da-vinci/icon-catalog.json)"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
flag: "--aliases <path>",
|
|
131
|
+
description: "icon alias mapping file for icon-search (default: ~/.da-vinci/icon-aliases.json)"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
flag: "--pencil-design <path>",
|
|
135
|
+
description: "explicit pencil-design.md path for supervisor-review"
|
|
136
|
+
},
|
|
137
|
+
{ flag: "--query <text>", description: "icon-search query text" },
|
|
138
|
+
{
|
|
139
|
+
flag: "--baseline <path>",
|
|
140
|
+
description: "comparison .pen path for multi-source baseline alignment checks (repeatable, supports comma-separated values)"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
flag: "--prefer-source <path>",
|
|
144
|
+
description: "explicit baseline source of truth when compared .pen hashes diverge"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
flag: "--sync-preferred-source",
|
|
148
|
+
description: "copy `--prefer-source` into project `--pen` when hashes diverge (begin command only)"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
flag: "--family <value>",
|
|
152
|
+
description: "icon-search family filter: all, material, rounded, outlined, sharp, lucide, feather, phosphor"
|
|
153
|
+
},
|
|
154
|
+
{ flag: "--status <value>", description: "PASS, WARN, or BLOCK for supervisor-review" },
|
|
155
|
+
{ flag: "--source <value>", description: "review source: skill, manual, inferred" },
|
|
156
|
+
{ flag: "--executed-reviewers <csv>", description: "reviewer skills that executed this review" },
|
|
157
|
+
{ flag: "--run-reviewers", description: "execute configured reviewer skills through codex exec" },
|
|
158
|
+
{
|
|
159
|
+
flag: "--review-concurrency <value>",
|
|
160
|
+
description: "max parallel reviewer executions (default 2)"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
flag: "--review-retries <value>",
|
|
164
|
+
description: "retry count per reviewer before failing (default 1)"
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
flag: "--review-retry-delay-ms <value>",
|
|
168
|
+
description: "initial retry delay in milliseconds (default 400, exponential backoff)"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
flag: "--review-retry-max-delay-ms <value>",
|
|
172
|
+
description: "cap reviewer retry backoff delay in milliseconds (default 5000)"
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
flag: "--codex-bin <path>",
|
|
176
|
+
description: "codex executable path for --run-reviewers (default: codex)"
|
|
177
|
+
},
|
|
178
|
+
{ flag: "--max-images <value>", description: "max screenshots attached to reviewer runs (default 6)" },
|
|
179
|
+
{ flag: "--review-timeout-ms <value>", description: "timeout per reviewer run in milliseconds" },
|
|
180
|
+
{ flag: "--issue-list <text>", description: "supervisor-review issue summary" },
|
|
181
|
+
{ flag: "--revision-outcome <text>", description: "supervisor-review revision result summary" },
|
|
182
|
+
{ flag: "--top <value>", description: "icon-search result count (1-50, default 8)" },
|
|
183
|
+
{ flag: "--timeout-ms <value>", description: "network timeout for icon-sync requests" },
|
|
184
|
+
{
|
|
185
|
+
flag: "--strict",
|
|
186
|
+
description: "enable strict failure mode for commands that support advisory defaults (for example icon-sync, lint-spec)"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
flag: "--continue-on-error",
|
|
190
|
+
description: "print BLOCK/FAIL command results without throwing process errors"
|
|
191
|
+
},
|
|
192
|
+
{ flag: "--json", description: "print structured JSON output when supported by the command" },
|
|
193
|
+
{ flag: "--pen <path>", description: "registered .pen path for sync checks" },
|
|
194
|
+
{
|
|
195
|
+
flag: "--from <path>",
|
|
196
|
+
description: "source path for sync-pen-source, or baseline sidecars directory for diff-spec"
|
|
197
|
+
},
|
|
198
|
+
{ flag: "--to <path>", description: "destination .pen path for sync-pen-source" },
|
|
199
|
+
{ flag: "--ops-file <path>", description: "Pencil batch operations file for preflight" },
|
|
200
|
+
{ flag: "--input <path>", description: "input .pen file for snapshot-pen" },
|
|
201
|
+
{ flag: "--output <path>", description: "output .pen file for write-pen or snapshot-pen" },
|
|
202
|
+
{ flag: "--nodes-file <path>", description: "JSON payload from batch_get for write-pen" },
|
|
203
|
+
{ flag: "--variables-file <path>", description: "JSON payload from get_variables for write-pen" },
|
|
204
|
+
{ flag: "--verify-open", description: "reopen the written .pen with Pencil after writing" },
|
|
205
|
+
{ flag: "--version <value>", description: "explicit .pen version when writing from MCP payloads" },
|
|
206
|
+
{ flag: "--mode <value>", description: "integrity or completion" },
|
|
207
|
+
{ flag: "--change <id>", description: "scope completion audit to one change id" },
|
|
208
|
+
{ flag: "--wait-ms <value>", description: "wait for the global Pencil lock before failing" },
|
|
209
|
+
{ flag: "--owner <value>", description: "human-readable lock owner label" },
|
|
210
|
+
{ flag: "--force", description: "overwrite bootstrap placeholders or force commands that explicitly support it" }
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
function readLimitedStdin(maxBytes = DEFAULT_MAX_PREFLIGHT_STDIN_BYTES, options = {}) {
|
|
214
|
+
const limit =
|
|
215
|
+
Number.isFinite(Number(maxBytes)) && Number(maxBytes) > 0
|
|
216
|
+
? Number(maxBytes)
|
|
217
|
+
: DEFAULT_MAX_PREFLIGHT_STDIN_BYTES;
|
|
218
|
+
const maxTransientRetries =
|
|
219
|
+
Number.isFinite(Number(options.maxTransientRetries)) && Number(options.maxTransientRetries) >= 0
|
|
220
|
+
? Number(options.maxTransientRetries)
|
|
221
|
+
: DEFAULT_MAX_STDIN_TRANSIENT_RETRIES;
|
|
222
|
+
const maxBackoffMs =
|
|
223
|
+
Number.isFinite(Number(options.maxBackoffMs)) && Number(options.maxBackoffMs) > 0
|
|
224
|
+
? Number(options.maxBackoffMs)
|
|
225
|
+
: DEFAULT_MAX_STDIN_TRANSIENT_BACKOFF_MS;
|
|
226
|
+
const chunks = [];
|
|
227
|
+
let totalBytes = 0;
|
|
228
|
+
let transientReadRetries = 0;
|
|
229
|
+
|
|
230
|
+
while (true) {
|
|
231
|
+
const chunk = Buffer.allocUnsafe(64 * 1024);
|
|
232
|
+
let bytesRead;
|
|
233
|
+
try {
|
|
234
|
+
bytesRead = fs.readSync(0, chunk, 0, chunk.length, null);
|
|
235
|
+
} catch (error) {
|
|
236
|
+
if (error && (error.code === "EAGAIN" || error.code === "EINTR")) {
|
|
237
|
+
transientReadRetries += 1;
|
|
238
|
+
if (transientReadRetries > maxTransientRetries) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
`Unable to read stdin after ${transientReadRetries} transient retry attempts (${error.code}).`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
sleepSyncMs(Math.min(5 * transientReadRetries, maxBackoffMs));
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
transientReadRetries = 0;
|
|
249
|
+
if (bytesRead === 0) {
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
totalBytes += bytesRead;
|
|
254
|
+
if (totalBytes > limit) {
|
|
255
|
+
throw new Error(
|
|
256
|
+
`Piped stdin payload exceeds ${limit} bytes. Use \`--ops-file <path>\` for larger preflight batches.`
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
chunks.push(Buffer.from(chunk.subarray(0, bytesRead)));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return Buffer.concat(chunks, totalBytes).toString("utf8");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function sleepSyncMs(durationMs) {
|
|
267
|
+
sleepSync(durationMs, "back off stdin retries");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function collectOptionEntries(args, name) {
|
|
271
|
+
const entries = [];
|
|
272
|
+
|
|
273
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
274
|
+
const arg = args[index];
|
|
275
|
+
if (arg === name) {
|
|
276
|
+
const next = args[index + 1];
|
|
277
|
+
if (next === undefined || String(next).startsWith("-")) {
|
|
278
|
+
entries.push({
|
|
279
|
+
form: "split",
|
|
280
|
+
hasValue: false,
|
|
281
|
+
value: ""
|
|
282
|
+
});
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
entries.push({
|
|
286
|
+
form: "split",
|
|
287
|
+
hasValue: true,
|
|
288
|
+
value: next
|
|
289
|
+
});
|
|
290
|
+
index += 1;
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (String(arg).startsWith(`${name}=`)) {
|
|
295
|
+
entries.push({
|
|
296
|
+
form: "inline",
|
|
297
|
+
hasValue: true,
|
|
298
|
+
value: String(arg).slice(name.length + 1)
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return entries;
|
|
304
|
+
}
|
|
49
305
|
|
|
50
306
|
function getOption(args, name) {
|
|
51
|
-
const
|
|
52
|
-
if (
|
|
53
|
-
return
|
|
307
|
+
const entries = collectOptionEntries(args, name);
|
|
308
|
+
if (entries.length === 0) {
|
|
309
|
+
return undefined;
|
|
54
310
|
}
|
|
55
311
|
|
|
56
|
-
const
|
|
57
|
-
if (
|
|
58
|
-
return
|
|
312
|
+
const values = entries.filter((entry) => entry.hasValue).map((entry) => String(entry.value));
|
|
313
|
+
if (values.length === 0) {
|
|
314
|
+
return undefined;
|
|
59
315
|
}
|
|
316
|
+
const uniqueValues = Array.from(new Set(values));
|
|
317
|
+
if (uniqueValues.length > 1) {
|
|
318
|
+
throw new Error(
|
|
319
|
+
`\`${name}\` was provided with conflicting values (${uniqueValues.join(", ")}). ` +
|
|
320
|
+
"Use a single value format (`--flag=value` or `--flag value`)."
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return values[values.length - 1];
|
|
325
|
+
}
|
|
60
326
|
|
|
61
|
-
|
|
327
|
+
function getOptionValues(args, name) {
|
|
328
|
+
return collectOptionEntries(args, name)
|
|
329
|
+
.filter((entry) => entry.hasValue)
|
|
330
|
+
.map((entry) => entry.value);
|
|
62
331
|
}
|
|
63
332
|
|
|
64
|
-
function
|
|
333
|
+
function shouldContinueOnError(args) {
|
|
334
|
+
return Array.isArray(args) && args.includes("--continue-on-error");
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function emitOrThrowOnStatus(status, blockedStatuses, output, continueOnError) {
|
|
338
|
+
if (!Array.isArray(blockedStatuses) || !blockedStatuses.includes(status)) {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
if (continueOnError) {
|
|
342
|
+
console.log(output);
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
throw new Error(output);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function getIntegerOption(args, name, options = {}) {
|
|
349
|
+
const raw = getOption(args, name);
|
|
350
|
+
if (raw === undefined) {
|
|
351
|
+
return undefined;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const parsed = Number.parseInt(String(raw), 10);
|
|
355
|
+
if (!Number.isFinite(parsed)) {
|
|
356
|
+
throw new Error(`\`${name}\` must be an integer.`);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (Number.isFinite(options.min) && parsed < options.min) {
|
|
360
|
+
throw new Error(`\`${name}\` must be >= ${options.min}.`);
|
|
361
|
+
}
|
|
362
|
+
if (Number.isFinite(options.max) && parsed > options.max) {
|
|
363
|
+
throw new Error(`\`${name}\` must be <= ${options.max}.`);
|
|
364
|
+
}
|
|
365
|
+
return parsed;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function getCommaSeparatedOptionValues(args, name) {
|
|
369
|
+
return getOptionValues(args, name)
|
|
370
|
+
.flatMap((value) => String(value || "").split(","))
|
|
371
|
+
.map((value) => value.trim())
|
|
372
|
+
.filter(Boolean);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function getPositionalArgs(args, optionsWithValues = OPTION_FLAGS_WITH_VALUES) {
|
|
376
|
+
const optionNames =
|
|
377
|
+
optionsWithValues instanceof Set ? Array.from(optionsWithValues) : [...optionsWithValues];
|
|
378
|
+
const optionsWithValuesSet =
|
|
379
|
+
optionsWithValues instanceof Set ? optionsWithValues : new Set(optionsWithValues);
|
|
65
380
|
const positional = [];
|
|
66
381
|
|
|
67
382
|
for (let index = 0; index < args.length; index += 1) {
|
|
68
383
|
const arg = args[index];
|
|
69
384
|
|
|
70
|
-
if (
|
|
385
|
+
if (optionsWithValuesSet.has(arg)) {
|
|
71
386
|
index += 1;
|
|
72
387
|
continue;
|
|
73
388
|
}
|
|
74
389
|
|
|
75
|
-
if (
|
|
390
|
+
if (optionNames.some((name) => String(arg).startsWith(`${name}=`))) {
|
|
76
391
|
continue;
|
|
77
392
|
}
|
|
78
393
|
|
|
@@ -87,16 +402,68 @@ function getPositionalArgs(args, optionsWithValues = []) {
|
|
|
87
402
|
}
|
|
88
403
|
|
|
89
404
|
function formatStatus(status) {
|
|
90
|
-
|
|
405
|
+
const lines = [
|
|
91
406
|
`Da Vinci v${status.version}`,
|
|
92
407
|
`Home: ${status.homeDir}`,
|
|
93
408
|
`Codex: prompt=${status.codex.prompt ? "yes" : "no"} skill=${status.codex.skill ? "yes" : "no"}`,
|
|
94
409
|
`Claude: command=${status.claude.command ? "yes" : "no"} actions=${status.claude.actionSet ? "yes" : "no"}`,
|
|
95
410
|
`Gemini: command=${status.gemini.command ? "yes" : "no"} actions=${status.gemini.actionSet ? "yes" : "no"}`
|
|
96
|
-
]
|
|
411
|
+
];
|
|
412
|
+
|
|
413
|
+
appendStatusIssues(lines, "codex prompt", status.codex.promptMissing, status.codex.promptMismatched, status.codex.promptUnreadable);
|
|
414
|
+
appendStatusIssues(lines, "codex skill", status.codex.skillMissing, status.codex.skillMismatched, status.codex.skillUnreadable);
|
|
415
|
+
appendStatusIssues(lines, "claude command", status.claude.commandMissing, status.claude.commandMismatched, status.claude.commandUnreadable);
|
|
416
|
+
appendStatusIssues(lines, "claude actions", status.claude.actionSetMissing, status.claude.actionSetMismatched, status.claude.actionSetUnreadable);
|
|
417
|
+
appendStatusIssues(lines, "gemini command", status.gemini.commandMissing, status.gemini.commandMismatched, status.gemini.commandUnreadable);
|
|
418
|
+
appendStatusIssues(lines, "gemini actions", status.gemini.actionSetMissing, status.gemini.actionSetMismatched, status.gemini.actionSetUnreadable);
|
|
419
|
+
|
|
420
|
+
return lines.join("\n");
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function appendStatusIssues(lines, label, missing = [], mismatched = [], unreadable = []) {
|
|
424
|
+
if (missing.length > 0) {
|
|
425
|
+
lines.push(` ${label} missing: ${missing.join(", ")}`);
|
|
426
|
+
}
|
|
427
|
+
if (mismatched.length > 0) {
|
|
428
|
+
lines.push(` ${label} stale/mismatched: ${mismatched.join(", ")}`);
|
|
429
|
+
}
|
|
430
|
+
if (unreadable.length > 0) {
|
|
431
|
+
lines.push(` ${label} unreadable: ${unreadable.join(", ")}`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function persistExecutionSignal(projectPath, changeId, surface, result, strict = false) {
|
|
436
|
+
try {
|
|
437
|
+
writeExecutionSignal(projectPath, {
|
|
438
|
+
changeId: changeId || "global",
|
|
439
|
+
surface,
|
|
440
|
+
status: result.status,
|
|
441
|
+
advisory: strict ? false : true,
|
|
442
|
+
strict,
|
|
443
|
+
failures: result.failures || [],
|
|
444
|
+
warnings: result.warnings || [],
|
|
445
|
+
notes: result.notes || []
|
|
446
|
+
});
|
|
447
|
+
} catch (error) {
|
|
448
|
+
// Signals are advisory metadata and should not break command execution.
|
|
449
|
+
const code = error && error.code ? String(error.code).toUpperCase() : "";
|
|
450
|
+
if (code === "EACCES" || code === "ENOSPC") {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const message = error && error.message ? error.message : String(error);
|
|
455
|
+
console.error(
|
|
456
|
+
`Warning: failed to persist execution signal (${surface}) for change ${changeId || "global"}: ${message}`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
97
459
|
}
|
|
98
460
|
|
|
99
461
|
function printHelp() {
|
|
462
|
+
const optionLines = HELP_OPTION_SPECS.map((optionSpec) => {
|
|
463
|
+
const paddedFlag = optionSpec.flag.padEnd(30, " ");
|
|
464
|
+
return ` ${paddedFlag}${optionSpec.description}`;
|
|
465
|
+
});
|
|
466
|
+
|
|
100
467
|
console.log(
|
|
101
468
|
[
|
|
102
469
|
"Da Vinci CLI",
|
|
@@ -105,104 +472,437 @@ function printHelp() {
|
|
|
105
472
|
" da-vinci install --platform codex,claude,gemini",
|
|
106
473
|
" da-vinci uninstall --platform codex,claude,gemini",
|
|
107
474
|
" da-vinci status",
|
|
475
|
+
" da-vinci workflow-status [--project <path>] [--change <id>] [--json]",
|
|
476
|
+
" da-vinci next-step [--project <path>] [--change <id>] [--json]",
|
|
477
|
+
" da-vinci lint-spec [--project <path>] [--change <id>] [--strict] [--json]",
|
|
478
|
+
" da-vinci scope-check [--project <path>] [--change <id>] [--strict] [--json]",
|
|
479
|
+
" da-vinci lint-tasks [--project <path>] [--change <id>] [--strict] [--json]",
|
|
480
|
+
" da-vinci lint-bindings [--project <path>] [--change <id>] [--strict] [--json]",
|
|
481
|
+
" da-vinci generate-sidecars [--project <path>] [--change <id>] [--json]",
|
|
482
|
+
" da-vinci verify-bindings [--project <path>] [--change <id>] [--strict] [--json]",
|
|
483
|
+
" da-vinci verify-implementation [--project <path>] [--change <id>] [--strict] [--json]",
|
|
484
|
+
" da-vinci verify-structure [--project <path>] [--change <id>] [--strict] [--json]",
|
|
485
|
+
" da-vinci verify-coverage [--project <path>] [--change <id>] [--strict] [--json]",
|
|
486
|
+
" da-vinci diff-spec [--project <path>] [--change <id>] [--from <sidecars-dir>] [--json]",
|
|
487
|
+
" da-vinci scaffold [--project <path>] [--change <id>] [--output <path>] [--json]",
|
|
108
488
|
" da-vinci validate-assets",
|
|
489
|
+
" da-vinci bootstrap-project --project <path> [--change <id>] [--force]",
|
|
109
490
|
" da-vinci audit [project-path]",
|
|
110
491
|
" da-vinci icon-sync [--output <path>] [--timeout-ms <value>] [--strict]",
|
|
111
492
|
" da-vinci icon-search --query <text> [--family <value>] [--top <value>] [--catalog <path>] [--aliases <path>] [--json]",
|
|
112
|
-
" da-vinci supervisor-review --project <path> --change <id> [--run-reviewers] [--review-concurrency <value>] [--review-retries <value>] [--review-retry-delay-ms <value>] [--source <skill|manual|inferred>] [--executed-reviewers <csv>] [--status <PASS|WARN|BLOCK>] [--issue-list <text>] [--revision-outcome <text>] [--write] [--json]",
|
|
493
|
+
" da-vinci supervisor-review --project <path> --change <id> [--run-reviewers] [--review-concurrency <value>] [--review-retries <value>] [--review-retry-delay-ms <value>] [--review-retry-max-delay-ms <value>] [--source <skill|manual|inferred>] [--executed-reviewers <csv>] [--status <PASS|WARN|BLOCK>] [--issue-list <text>] [--revision-outcome <text>] [--write] [--json]",
|
|
113
494
|
" da-vinci preflight-pencil --ops-file <path>",
|
|
114
495
|
" da-vinci ensure-pen --output <path>",
|
|
115
496
|
" da-vinci write-pen --output <path> --nodes-file <path> [--variables-file <path>]",
|
|
116
497
|
" da-vinci check-pen-sync --pen <path> --nodes-file <path> [--variables-file <path>]",
|
|
498
|
+
" da-vinci check-pen-baseline --pen <path> --baseline <path>[,<path>...] [--baseline <path>] [--prefer-source <path>]",
|
|
499
|
+
" da-vinci sync-pen-source --from <path> --to <path>",
|
|
117
500
|
" da-vinci snapshot-pen --input <path> --output <path>",
|
|
118
501
|
" da-vinci pencil-lock acquire --project <path>",
|
|
119
502
|
" da-vinci pencil-lock release --project <path>",
|
|
120
503
|
" da-vinci pencil-lock status",
|
|
121
|
-
" da-vinci pencil-session begin --project <path> --pen <path>",
|
|
504
|
+
" da-vinci pencil-session begin --project <path> --pen <path> [--baseline <path>[,<path>...]] [--prefer-source <path>] [--sync-preferred-source]",
|
|
122
505
|
" da-vinci pencil-session persist --project <path> --pen <path> --nodes-file <path> [--variables-file <path>]",
|
|
123
506
|
" da-vinci pencil-session end --project <path> --pen <path> --nodes-file <path> [--variables-file <path>]",
|
|
124
507
|
" da-vinci pencil-session status --project <path>",
|
|
125
508
|
" da-vinci --version",
|
|
126
509
|
"",
|
|
127
510
|
"Options:",
|
|
128
|
-
|
|
129
|
-
" --home <path> override HOME for installation targets",
|
|
130
|
-
" --project <path> override project path for audit",
|
|
131
|
-
" --catalog <path> icon catalog path for icon-search/icon-sync (default: ~/.da-vinci/icon-catalog.json)",
|
|
132
|
-
" --aliases <path> icon alias mapping file for icon-search (default: ~/.da-vinci/icon-aliases.json)",
|
|
133
|
-
" --pencil-design <path> explicit pencil-design.md path for supervisor-review",
|
|
134
|
-
" --query <text> icon-search query text",
|
|
135
|
-
" --family <value> icon-search family filter: all, material, rounded, outlined, sharp, lucide, feather, phosphor",
|
|
136
|
-
" --status <value> PASS, WARN, or BLOCK for supervisor-review",
|
|
137
|
-
" --source <value> review source: skill, manual, inferred",
|
|
138
|
-
" --executed-reviewers <csv> reviewer skills that executed this review",
|
|
139
|
-
" --run-reviewers execute configured reviewer skills through codex exec",
|
|
140
|
-
" --review-concurrency <value> max parallel reviewer executions (default 2)",
|
|
141
|
-
" --review-retries <value> retry count per reviewer before failing (default 1)",
|
|
142
|
-
" --review-retry-delay-ms <value> initial retry delay in milliseconds (default 400, exponential backoff)",
|
|
143
|
-
" --codex-bin <path> codex executable path for --run-reviewers (default: codex)",
|
|
144
|
-
" --max-images <value> max screenshots attached to reviewer runs (default 6)",
|
|
145
|
-
" --review-timeout-ms <value> timeout per reviewer run in milliseconds",
|
|
146
|
-
" --issue-list <text> supervisor-review issue summary",
|
|
147
|
-
" --revision-outcome <text> supervisor-review revision result summary",
|
|
148
|
-
" --top <value> icon-search result count (1-50, default 8)",
|
|
149
|
-
" --timeout-ms <value> network timeout for icon-sync requests",
|
|
150
|
-
" --strict fail icon-sync when any upstream source request fails",
|
|
151
|
-
" --json print structured JSON for icon-search",
|
|
152
|
-
" --pen <path> registered .pen path for sync checks",
|
|
153
|
-
" --ops-file <path> Pencil batch operations file for preflight",
|
|
154
|
-
" --input <path> input .pen file for snapshot-pen",
|
|
155
|
-
" --output <path> output .pen file for write-pen or snapshot-pen",
|
|
156
|
-
" --nodes-file <path> JSON payload from batch_get for write-pen",
|
|
157
|
-
" --variables-file <path> JSON payload from get_variables for write-pen",
|
|
158
|
-
" --verify-open reopen the written .pen with Pencil after writing",
|
|
159
|
-
" --version <value> explicit .pen version when writing from MCP payloads",
|
|
160
|
-
" --mode <value> integrity or completion",
|
|
161
|
-
" --change <id> scope completion audit to one change id",
|
|
162
|
-
" --wait-ms <value> wait for the global Pencil lock before failing",
|
|
163
|
-
" --owner <value> human-readable lock owner label",
|
|
164
|
-
" --force force a lock release held by another project"
|
|
511
|
+
...optionLines
|
|
165
512
|
].join("\n")
|
|
166
513
|
);
|
|
167
514
|
}
|
|
168
515
|
|
|
169
|
-
async function
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
const
|
|
516
|
+
async function handleIconSearchCommand(argv, homeDir) {
|
|
517
|
+
const family = getOption(argv, "--family") || "all";
|
|
518
|
+
const top = getIntegerOption(argv, "--top", { min: 1, max: 50 });
|
|
519
|
+
const queryOption = getOption(argv, "--query");
|
|
520
|
+
const catalogPath = getOption(argv, "--catalog");
|
|
521
|
+
const aliasesPath = getOption(argv, "--aliases");
|
|
522
|
+
const iconPositional = getPositionalArgs(argv.slice(1), [
|
|
173
523
|
"--home",
|
|
174
|
-
"--platform",
|
|
175
|
-
"--project",
|
|
176
|
-
"--mode",
|
|
177
|
-
"--change",
|
|
178
524
|
"--query",
|
|
179
525
|
"--family",
|
|
180
526
|
"--top",
|
|
181
527
|
"--catalog",
|
|
182
|
-
"--aliases"
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
"
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
528
|
+
"--aliases"
|
|
529
|
+
]);
|
|
530
|
+
const query = queryOption || iconPositional.join(" ").trim();
|
|
531
|
+
|
|
532
|
+
if (!query) {
|
|
533
|
+
throw new Error("`icon-search` requires `--query <text>` or positional query text.");
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
let loadedCatalog = null;
|
|
537
|
+
let loadedCatalogPath = null;
|
|
538
|
+
let catalogLoadError = null;
|
|
539
|
+
let loadedAliases = null;
|
|
540
|
+
let loadedAliasesPath = null;
|
|
541
|
+
let aliasesLoadError = null;
|
|
542
|
+
let aliasExpansion = {
|
|
543
|
+
extraTokens: [],
|
|
544
|
+
matchedAliases: []
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
try {
|
|
548
|
+
const loaded = loadIconCatalog({
|
|
549
|
+
catalogPath,
|
|
550
|
+
homeDir
|
|
551
|
+
});
|
|
552
|
+
loadedCatalog = loaded.catalog;
|
|
553
|
+
loadedCatalogPath = loaded.catalogPath;
|
|
554
|
+
} catch (error) {
|
|
555
|
+
catalogLoadError = error.message || String(error);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
try {
|
|
559
|
+
const loaded = loadIconAliases({
|
|
560
|
+
aliasPath: aliasesPath,
|
|
561
|
+
homeDir
|
|
562
|
+
});
|
|
563
|
+
loadedAliases = loaded;
|
|
564
|
+
loadedAliasesPath = loaded.aliasPath;
|
|
565
|
+
aliasExpansion = expandQueryWithAliases(query, loaded.aliases);
|
|
566
|
+
} catch (error) {
|
|
567
|
+
aliasesLoadError = error.message || String(error);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const result = searchIconLibrary(query, {
|
|
571
|
+
family,
|
|
572
|
+
top,
|
|
573
|
+
catalog: loadedCatalog ? loadedCatalog.icons : [],
|
|
574
|
+
extraQueryTokens: aliasExpansion.extraTokens
|
|
575
|
+
});
|
|
576
|
+
const jsonOutput = argv.includes("--json");
|
|
577
|
+
|
|
578
|
+
const resultWithMeta = {
|
|
579
|
+
...result,
|
|
580
|
+
catalog: {
|
|
581
|
+
path: loadedCatalogPath || "(unresolved)",
|
|
582
|
+
loaded: Boolean(loadedCatalog),
|
|
583
|
+
iconCount: loadedCatalog ? loadedCatalog.iconCount : 0,
|
|
584
|
+
generatedAt: loadedCatalog ? loadedCatalog.generatedAt : null,
|
|
585
|
+
error: catalogLoadError
|
|
586
|
+
},
|
|
587
|
+
aliases: {
|
|
588
|
+
path: loadedAliasesPath || "(unresolved)",
|
|
589
|
+
loaded: loadedAliases ? Boolean(loadedAliases.loaded) : false,
|
|
590
|
+
available: Boolean(loadedAliases),
|
|
591
|
+
source: loadedAliases ? loadedAliases.source : null,
|
|
592
|
+
matched: aliasExpansion.matchedAliases.length,
|
|
593
|
+
extraTokens: aliasExpansion.extraTokens,
|
|
594
|
+
error: aliasesLoadError
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
if (jsonOutput) {
|
|
599
|
+
console.log(JSON.stringify(resultWithMeta, null, 2));
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (resultWithMeta.catalog.loaded) {
|
|
604
|
+
console.log(
|
|
605
|
+
`Icon catalog: ${resultWithMeta.catalog.path} (${resultWithMeta.catalog.iconCount} icons, ${resultWithMeta.catalog.generatedAt})`
|
|
606
|
+
);
|
|
607
|
+
} else if (resultWithMeta.catalog.error) {
|
|
608
|
+
console.log(
|
|
609
|
+
`Icon catalog: ${resultWithMeta.catalog.path} (load failed: ${resultWithMeta.catalog.error}; using built-in fallback index)`
|
|
610
|
+
);
|
|
611
|
+
} else {
|
|
612
|
+
console.log(
|
|
613
|
+
`Icon catalog: ${resultWithMeta.catalog.path} (not found; using built-in fallback index; run \`da-vinci icon-sync\`)`
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (resultWithMeta.aliases.available) {
|
|
618
|
+
console.log(
|
|
619
|
+
`Icon aliases: ${resultWithMeta.aliases.path} (${resultWithMeta.aliases.source}, matched ${resultWithMeta.aliases.matched})`
|
|
620
|
+
);
|
|
621
|
+
} else if (resultWithMeta.aliases.error) {
|
|
622
|
+
console.log(
|
|
623
|
+
`Icon aliases: ${resultWithMeta.aliases.path} (load failed: ${resultWithMeta.aliases.error})`
|
|
624
|
+
);
|
|
625
|
+
} else {
|
|
626
|
+
console.log(
|
|
627
|
+
`Icon aliases: ${resultWithMeta.aliases.path} (not found; using built-in defaults only)`
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
console.log(formatIconSearchReport(result));
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
async function handleSupervisorReviewCommand(argv) {
|
|
635
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
636
|
+
console.log(
|
|
637
|
+
[
|
|
638
|
+
"da-vinci supervisor-review",
|
|
639
|
+
"",
|
|
640
|
+
"Usage:",
|
|
641
|
+
" da-vinci supervisor-review --project <path> --change <id> [--run-reviewers] [--review-concurrency <value>] [--review-retries <value>] [--review-retry-delay-ms <value>] [--review-retry-max-delay-ms <value>] [--source <skill|manual|inferred>] [--executed-reviewers <csv>] [--status <PASS|WARN|BLOCK>] [--issue-list <text>] [--revision-outcome <text>] [--write] [--json]",
|
|
642
|
+
" da-vinci supervisor-review --project <path> --pencil-design <path> [--run-reviewers] [--review-concurrency <value>] [--review-retries <value>] [--review-retry-delay-ms <value>] [--review-retry-max-delay-ms <value>] [--source <skill|manual|inferred>] [--executed-reviewers <csv>] [--status <PASS|WARN|BLOCK>] [--issue-list <text>] [--revision-outcome <text>] [--write] [--json]",
|
|
643
|
+
"",
|
|
644
|
+
"Notes:",
|
|
645
|
+
" - omit --status to infer a conservative review status from current design artifacts",
|
|
646
|
+
" - use --run-reviewers to execute configured reviewer skills automatically via codex exec",
|
|
647
|
+
" - `design-supervisor review` is a compatibility alias for this command"
|
|
648
|
+
].join("\n")
|
|
649
|
+
);
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
const projectPath = getOption(argv, "--project") || process.cwd();
|
|
654
|
+
const changeId = getOption(argv, "--change");
|
|
655
|
+
const pencilDesignPath = getOption(argv, "--pencil-design");
|
|
656
|
+
const status = getOption(argv, "--status");
|
|
657
|
+
const source = getOption(argv, "--source");
|
|
658
|
+
const executedReviewers = getOption(argv, "--executed-reviewers");
|
|
659
|
+
const codexBin = getOption(argv, "--codex-bin");
|
|
660
|
+
const maxImages = getIntegerOption(argv, "--max-images", { min: 0 });
|
|
661
|
+
const reviewerTimeoutMs = getIntegerOption(argv, "--review-timeout-ms", { min: 1 });
|
|
662
|
+
const reviewConcurrency = getIntegerOption(argv, "--review-concurrency", { min: 1 });
|
|
663
|
+
const reviewerRetries = getIntegerOption(argv, "--review-retries", { min: 0 });
|
|
664
|
+
const reviewerRetryDelayMs = getIntegerOption(argv, "--review-retry-delay-ms", { min: 0 });
|
|
665
|
+
const reviewerRetryMaxDelayMs = getIntegerOption(argv, "--review-retry-max-delay-ms", {
|
|
666
|
+
min: 0
|
|
667
|
+
});
|
|
668
|
+
const issueList = getOption(argv, "--issue-list");
|
|
669
|
+
const revisionOutcome = getOption(argv, "--revision-outcome");
|
|
670
|
+
const write = argv.includes("--write");
|
|
671
|
+
const acceptWarn = argv.includes("--accept-warn");
|
|
672
|
+
const runReviewers = argv.includes("--run-reviewers");
|
|
673
|
+
const jsonOutput = argv.includes("--json");
|
|
674
|
+
|
|
675
|
+
const result = await runDesignSupervisorReview({
|
|
676
|
+
projectPath,
|
|
677
|
+
changeId,
|
|
678
|
+
pencilDesignPath,
|
|
679
|
+
status,
|
|
680
|
+
source,
|
|
681
|
+
executedReviewers,
|
|
682
|
+
codexBin,
|
|
683
|
+
maxImages,
|
|
684
|
+
reviewerTimeoutMs,
|
|
685
|
+
reviewConcurrency,
|
|
686
|
+
reviewerRetries,
|
|
687
|
+
reviewerRetryDelayMs,
|
|
688
|
+
reviewerRetryMaxDelayMs,
|
|
689
|
+
issueList,
|
|
690
|
+
revisionOutcome,
|
|
691
|
+
write,
|
|
692
|
+
acceptWarn,
|
|
693
|
+
runReviewers
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
if (jsonOutput) {
|
|
697
|
+
console.log(JSON.stringify(result, null, 2));
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
console.log(formatDesignSupervisorReviewReport(result));
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function handlePencilLockCommand(argv, homeDir) {
|
|
705
|
+
const subcommand = getPositionalArgs(argv.slice(1), [
|
|
706
|
+
"--home",
|
|
707
|
+
"--project",
|
|
708
|
+
"--owner",
|
|
709
|
+
"--wait-ms"
|
|
710
|
+
])[0];
|
|
711
|
+
const projectPath = getOption(argv, "--project") || process.cwd();
|
|
712
|
+
const owner = getOption(argv, "--owner");
|
|
713
|
+
const waitMs = getIntegerOption(argv, "--wait-ms", { min: 0 });
|
|
714
|
+
const force = argv.includes("--force");
|
|
715
|
+
|
|
716
|
+
if (!subcommand || ["acquire", "release", "status"].includes(subcommand) === false) {
|
|
717
|
+
throw new Error("`pencil-lock` requires one of: acquire, release, status.");
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
if (subcommand === "acquire") {
|
|
721
|
+
const result = acquirePencilLock({
|
|
722
|
+
projectPath,
|
|
723
|
+
owner,
|
|
724
|
+
waitMs,
|
|
725
|
+
homeDir
|
|
726
|
+
});
|
|
727
|
+
console.log(`${result.alreadyHeld ? "Reused" : "Acquired"} Pencil lock at ${result.lockPath}`);
|
|
728
|
+
console.log(`Project: ${result.lock.projectPath}`);
|
|
729
|
+
console.log(`Owner: ${result.lock.owner}`);
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
if (subcommand === "release") {
|
|
734
|
+
const result = releasePencilLock({
|
|
735
|
+
projectPath,
|
|
736
|
+
force,
|
|
737
|
+
homeDir
|
|
738
|
+
});
|
|
739
|
+
if (!result.hadLock) {
|
|
740
|
+
console.log(`No Pencil lock was present at ${result.lockPath}`);
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
console.log(`Released Pencil lock at ${result.lockPath}`);
|
|
744
|
+
console.log(`Previous project: ${result.lock.projectPath}`);
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const result = getPencilLockStatus({ homeDir });
|
|
749
|
+
console.log(`Lock path: ${result.lockPath}`);
|
|
750
|
+
if (!result.lock) {
|
|
751
|
+
console.log("Status: unlocked");
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
console.log("Status: locked");
|
|
755
|
+
console.log(`Project: ${result.lock.projectPath}`);
|
|
756
|
+
console.log(`Owner: ${result.lock.owner}`);
|
|
757
|
+
console.log(`PID: ${result.lock.pid}`);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
function handlePencilSessionCommand(argv, homeDir) {
|
|
761
|
+
const subcommand = getPositionalArgs(argv.slice(1), [
|
|
762
|
+
"--home",
|
|
763
|
+
"--project",
|
|
199
764
|
"--pen",
|
|
765
|
+
"--baseline",
|
|
766
|
+
"--prefer-source",
|
|
200
767
|
"--nodes-file",
|
|
201
768
|
"--variables-file",
|
|
202
769
|
"--version",
|
|
203
770
|
"--owner",
|
|
204
771
|
"--wait-ms"
|
|
205
|
-
]);
|
|
772
|
+
])[0];
|
|
773
|
+
const projectPath = getOption(argv, "--project") || process.cwd();
|
|
774
|
+
const penPath = getOption(argv, "--pen");
|
|
775
|
+
const baselinePaths = getCommaSeparatedOptionValues(argv, "--baseline");
|
|
776
|
+
const preferredSource = getOption(argv, "--prefer-source");
|
|
777
|
+
const nodesFile = getOption(argv, "--nodes-file");
|
|
778
|
+
const variablesFile = getOption(argv, "--variables-file");
|
|
779
|
+
const version = getOption(argv, "--version");
|
|
780
|
+
const verifyWithPencil = argv.includes("--verify-open");
|
|
781
|
+
const owner = getOption(argv, "--owner");
|
|
782
|
+
const waitMs = getIntegerOption(argv, "--wait-ms", { min: 0 });
|
|
783
|
+
const force = argv.includes("--force");
|
|
784
|
+
const syncPreferredSource = argv.includes("--sync-preferred-source");
|
|
785
|
+
|
|
786
|
+
if (!subcommand || ["begin", "persist", "end", "status"].includes(subcommand) === false) {
|
|
787
|
+
throw new Error("`pencil-session` requires one of: begin, persist, end, status.");
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (subcommand === "begin") {
|
|
791
|
+
if (!penPath) {
|
|
792
|
+
throw new Error("`pencil-session begin` requires `--pen <path>`.");
|
|
793
|
+
}
|
|
794
|
+
if (preferredSource && baselinePaths.length === 0) {
|
|
795
|
+
throw new Error("`pencil-session begin --prefer-source` requires at least one `--baseline <path>`.");
|
|
796
|
+
}
|
|
797
|
+
if (syncPreferredSource && !preferredSource) {
|
|
798
|
+
throw new Error("`pencil-session begin --sync-preferred-source` requires `--prefer-source <path>`.");
|
|
799
|
+
}
|
|
800
|
+
if (syncPreferredSource && baselinePaths.length === 0) {
|
|
801
|
+
throw new Error("`pencil-session begin --sync-preferred-source` requires at least one `--baseline <path>`.");
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const result = beginPencilSession({
|
|
805
|
+
projectPath,
|
|
806
|
+
penPath,
|
|
807
|
+
baselinePaths,
|
|
808
|
+
preferredSource,
|
|
809
|
+
syncPreferredSource,
|
|
810
|
+
version,
|
|
811
|
+
verifyWithPencil,
|
|
812
|
+
owner,
|
|
813
|
+
waitMs,
|
|
814
|
+
homeDir
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
console.log(`Began Pencil session for ${result.projectRoot}`);
|
|
818
|
+
console.log(`Pen path: ${result.penPath}`);
|
|
819
|
+
console.log(`Session state: ${result.sessionStatePath}`);
|
|
820
|
+
console.log(`Snapshot hash: ${result.session.lastPersistedHash}`);
|
|
821
|
+
if (result.session.baselineCheck) {
|
|
822
|
+
console.log(`Baseline status: ${result.session.baselineCheck.status}`);
|
|
823
|
+
console.log(`Baseline decision: ${result.session.baselineCheck.decision}`);
|
|
824
|
+
}
|
|
825
|
+
if (result.session.baselineSync) {
|
|
826
|
+
console.log(`Baseline synced from: ${result.session.baselineSync.sourcePath}`);
|
|
827
|
+
}
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
if (subcommand === "persist") {
|
|
832
|
+
if (!penPath || !nodesFile) {
|
|
833
|
+
throw new Error("`pencil-session persist` requires `--pen <path>` and `--nodes-file <path>`.");
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
const result = persistPencilSession({
|
|
837
|
+
projectPath,
|
|
838
|
+
penPath,
|
|
839
|
+
nodesFile,
|
|
840
|
+
variablesFile,
|
|
841
|
+
version,
|
|
842
|
+
verifyWithPencil,
|
|
843
|
+
homeDir
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
console.log(`Persisted Pencil session for ${result.projectRoot}`);
|
|
847
|
+
console.log(`Pen path: ${result.penPath}`);
|
|
848
|
+
console.log(`Session state: ${result.sessionStatePath}`);
|
|
849
|
+
console.log(`Snapshot hash: ${result.session.lastPersistedHash}`);
|
|
850
|
+
console.log(`In sync: ${result.syncResult.inSync ? "yes" : "no"}`);
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
if (subcommand === "end") {
|
|
855
|
+
if (!penPath) {
|
|
856
|
+
throw new Error("`pencil-session end` requires `--pen <path>`.");
|
|
857
|
+
}
|
|
858
|
+
if (!nodesFile && !force) {
|
|
859
|
+
throw new Error(
|
|
860
|
+
"`pencil-session end` requires `--nodes-file <path>` (and `--variables-file <path>` when available). Use `--force` only for emergency lock release."
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
const result = endPencilSession({
|
|
865
|
+
projectPath,
|
|
866
|
+
penPath,
|
|
867
|
+
nodesFile,
|
|
868
|
+
variablesFile,
|
|
869
|
+
version,
|
|
870
|
+
homeDir,
|
|
871
|
+
force
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
console.log(`Ended Pencil session for ${result.projectRoot}`);
|
|
875
|
+
console.log(`Pen path: ${result.penPath}`);
|
|
876
|
+
console.log(`Session state: ${result.sessionStatePath}`);
|
|
877
|
+
console.log(`Final status: ${result.session.status}`);
|
|
878
|
+
if (result.syncResult) {
|
|
879
|
+
console.log(`Final sync verified: ${result.syncResult.inSync ? "yes" : "no"}`);
|
|
880
|
+
} else if (force) {
|
|
881
|
+
console.log("Final sync verified: skipped (force shutdown)");
|
|
882
|
+
}
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
const result = getPencilSessionStatus({
|
|
887
|
+
projectPath,
|
|
888
|
+
homeDir
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
console.log(`Project: ${result.projectRoot}`);
|
|
892
|
+
console.log(`Session state: ${result.sessionStatePath}`);
|
|
893
|
+
console.log(`Session status: ${result.session ? result.session.status : "missing"}`);
|
|
894
|
+
console.log(`Lock status: ${result.lockStatus.lock ? "locked" : "unlocked"}`);
|
|
895
|
+
if (result.session) {
|
|
896
|
+
console.log(`Pen path: ${result.session.penPath}`);
|
|
897
|
+
console.log(`Last persisted hash: ${result.session.lastPersistedHash || "(missing)"}`);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
async function runCli(argv) {
|
|
902
|
+
const [command] = argv;
|
|
903
|
+
const homeDir = getOption(argv, "--home");
|
|
904
|
+
const positionalArgs = getPositionalArgs(argv.slice(1), OPTION_FLAGS_WITH_VALUES);
|
|
905
|
+
const continueOnError = shouldContinueOnError(argv);
|
|
206
906
|
|
|
207
907
|
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
208
908
|
printHelp();
|
|
@@ -237,6 +937,216 @@ async function runCli(argv) {
|
|
|
237
937
|
return;
|
|
238
938
|
}
|
|
239
939
|
|
|
940
|
+
if (command === "workflow-status") {
|
|
941
|
+
const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
|
|
942
|
+
const changeId = getOption(argv, "--change");
|
|
943
|
+
const result = deriveWorkflowStatus(projectPath, { changeId });
|
|
944
|
+
|
|
945
|
+
if (argv.includes("--json")) {
|
|
946
|
+
console.log(JSON.stringify(result, null, 2));
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
console.log(formatWorkflowStatusReport(result));
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
if (command === "next-step") {
|
|
955
|
+
const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
|
|
956
|
+
const changeId = getOption(argv, "--change");
|
|
957
|
+
const result = deriveWorkflowStatus(projectPath, { changeId });
|
|
958
|
+
|
|
959
|
+
if (argv.includes("--json")) {
|
|
960
|
+
console.log(JSON.stringify(result.nextStep || null, null, 2));
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
console.log(formatNextStepReport(result));
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
if (command === "lint-spec") {
|
|
969
|
+
const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
|
|
970
|
+
const changeId = getOption(argv, "--change");
|
|
971
|
+
const strict = argv.includes("--strict");
|
|
972
|
+
const result = lintRuntimeSpecs(projectPath, { changeId, strict });
|
|
973
|
+
persistExecutionSignal(projectPath, result.changeId || changeId, "lint-spec", result, strict);
|
|
974
|
+
const useJson = argv.includes("--json");
|
|
975
|
+
const output = useJson ? JSON.stringify(result, null, 2) : formatLintSpecReport(result);
|
|
976
|
+
|
|
977
|
+
if (emitOrThrowOnStatus(result.status, ["BLOCK"], output, continueOnError)) {
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
console.log(output);
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
if (command === "scope-check") {
|
|
986
|
+
const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
|
|
987
|
+
const changeId = getOption(argv, "--change");
|
|
988
|
+
const strict = argv.includes("--strict");
|
|
989
|
+
const result = runScopeCheck(projectPath, { changeId, strict });
|
|
990
|
+
persistExecutionSignal(projectPath, result.changeId || changeId, "scope-check", result, strict);
|
|
991
|
+
const useJson = argv.includes("--json");
|
|
992
|
+
const output = useJson ? JSON.stringify(result, null, 2) : formatScopeCheckReport(result);
|
|
993
|
+
|
|
994
|
+
if (emitOrThrowOnStatus(result.status, ["BLOCK"], output, continueOnError)) {
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
console.log(output);
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
if (command === "lint-tasks") {
|
|
1003
|
+
const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
|
|
1004
|
+
const changeId = getOption(argv, "--change");
|
|
1005
|
+
const strict = argv.includes("--strict");
|
|
1006
|
+
const result = lintTasks(projectPath, { changeId, strict });
|
|
1007
|
+
persistExecutionSignal(projectPath, result.changeId || changeId, "lint-tasks", result, strict);
|
|
1008
|
+
const useJson = argv.includes("--json");
|
|
1009
|
+
const output = useJson ? JSON.stringify(result, null, 2) : formatLintTasksReport(result);
|
|
1010
|
+
|
|
1011
|
+
if (emitOrThrowOnStatus(result.status, ["BLOCK"], output, continueOnError)) {
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
console.log(output);
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
if (command === "lint-bindings") {
|
|
1020
|
+
const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
|
|
1021
|
+
const changeId = getOption(argv, "--change");
|
|
1022
|
+
const strict = argv.includes("--strict");
|
|
1023
|
+
const result = lintBindings(projectPath, { changeId, strict });
|
|
1024
|
+
persistExecutionSignal(projectPath, result.changeId || changeId, "lint-bindings", result, strict);
|
|
1025
|
+
const useJson = argv.includes("--json");
|
|
1026
|
+
const output = useJson ? JSON.stringify(result, null, 2) : formatLintBindingsReport(result);
|
|
1027
|
+
|
|
1028
|
+
if (emitOrThrowOnStatus(result.status, ["BLOCK"], output, continueOnError)) {
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
console.log(output);
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
if (command === "generate-sidecars") {
|
|
1037
|
+
const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
|
|
1038
|
+
const changeId = getOption(argv, "--change");
|
|
1039
|
+
const result = generatePlanningSidecars(projectPath, { changeId, write: true });
|
|
1040
|
+
persistExecutionSignal(projectPath, result.changeId || changeId, "generate-sidecars", result, false);
|
|
1041
|
+
const useJson = argv.includes("--json");
|
|
1042
|
+
const output = useJson ? JSON.stringify(result, null, 2) : formatGenerateSidecarsReport(result);
|
|
1043
|
+
|
|
1044
|
+
if (emitOrThrowOnStatus(result.status, ["BLOCK"], output, continueOnError)) {
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
console.log(output);
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
if (command === "verify-bindings") {
|
|
1053
|
+
const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
|
|
1054
|
+
const changeId = getOption(argv, "--change");
|
|
1055
|
+
const strict = argv.includes("--strict");
|
|
1056
|
+
const result = verifyBindings(projectPath, { changeId, strict });
|
|
1057
|
+
persistExecutionSignal(projectPath, result.changeId || changeId, "verify-bindings", result, strict);
|
|
1058
|
+
const useJson = argv.includes("--json");
|
|
1059
|
+
const output = useJson
|
|
1060
|
+
? JSON.stringify(result, null, 2)
|
|
1061
|
+
: formatVerifyReport(result, "Da Vinci verify-bindings");
|
|
1062
|
+
if (emitOrThrowOnStatus(result.status, ["BLOCK"], output, continueOnError)) {
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
console.log(output);
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
if (command === "verify-implementation") {
|
|
1070
|
+
const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
|
|
1071
|
+
const changeId = getOption(argv, "--change");
|
|
1072
|
+
const strict = argv.includes("--strict");
|
|
1073
|
+
const result = verifyImplementation(projectPath, { changeId, strict });
|
|
1074
|
+
persistExecutionSignal(projectPath, result.changeId || changeId, "verify-implementation", result, strict);
|
|
1075
|
+
const useJson = argv.includes("--json");
|
|
1076
|
+
const output = useJson
|
|
1077
|
+
? JSON.stringify(result, null, 2)
|
|
1078
|
+
: formatVerifyReport(result, "Da Vinci verify-implementation");
|
|
1079
|
+
if (emitOrThrowOnStatus(result.status, ["BLOCK"], output, continueOnError)) {
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
console.log(output);
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
if (command === "verify-structure") {
|
|
1087
|
+
const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
|
|
1088
|
+
const changeId = getOption(argv, "--change");
|
|
1089
|
+
const strict = argv.includes("--strict");
|
|
1090
|
+
const result = verifyStructure(projectPath, { changeId, strict });
|
|
1091
|
+
persistExecutionSignal(projectPath, result.changeId || changeId, "verify-structure", result, strict);
|
|
1092
|
+
const useJson = argv.includes("--json");
|
|
1093
|
+
const output = useJson
|
|
1094
|
+
? JSON.stringify(result, null, 2)
|
|
1095
|
+
: formatVerifyReport(result, "Da Vinci verify-structure");
|
|
1096
|
+
if (emitOrThrowOnStatus(result.status, ["BLOCK"], output, continueOnError)) {
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
console.log(output);
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
if (command === "verify-coverage") {
|
|
1104
|
+
const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
|
|
1105
|
+
const changeId = getOption(argv, "--change");
|
|
1106
|
+
const strict = argv.includes("--strict");
|
|
1107
|
+
const result = verifyCoverage(projectPath, { changeId, strict });
|
|
1108
|
+
persistExecutionSignal(projectPath, result.changeId || changeId, "verify-coverage", result, strict);
|
|
1109
|
+
const useJson = argv.includes("--json");
|
|
1110
|
+
const output = useJson
|
|
1111
|
+
? JSON.stringify(result, null, 2)
|
|
1112
|
+
: formatVerifyReport(result, "Da Vinci verify-coverage");
|
|
1113
|
+
if (emitOrThrowOnStatus(result.status, ["BLOCK"], output, continueOnError)) {
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
console.log(output);
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
if (command === "diff-spec") {
|
|
1121
|
+
const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
|
|
1122
|
+
const changeId = getOption(argv, "--change");
|
|
1123
|
+
const fromDir = getOption(argv, "--from");
|
|
1124
|
+
const result = diffSpec(projectPath, { changeId, fromDir });
|
|
1125
|
+
persistExecutionSignal(projectPath, result.changeId || changeId, "diff-spec", result, false);
|
|
1126
|
+
const useJson = argv.includes("--json");
|
|
1127
|
+
const output = useJson ? JSON.stringify(result, null, 2) : formatDiffSpecReport(result);
|
|
1128
|
+
if (emitOrThrowOnStatus(result.status, ["BLOCK"], output, continueOnError)) {
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
console.log(output);
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
if (command === "scaffold") {
|
|
1136
|
+
const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
|
|
1137
|
+
const changeId = getOption(argv, "--change");
|
|
1138
|
+
const outputDir = getOption(argv, "--output");
|
|
1139
|
+
const result = scaffoldFromBindings(projectPath, { changeId, outputDir });
|
|
1140
|
+
persistExecutionSignal(projectPath, result.changeId || changeId, "scaffold", result, false);
|
|
1141
|
+
const useJson = argv.includes("--json");
|
|
1142
|
+
const output = useJson ? JSON.stringify(result, null, 2) : formatScaffoldReport(result);
|
|
1143
|
+
if (emitOrThrowOnStatus(result.status, ["BLOCK"], output, continueOnError)) {
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
console.log(output);
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
240
1150
|
if (command === "validate-assets") {
|
|
241
1151
|
const result = validateAssets();
|
|
242
1152
|
console.log(`Da Vinci v${result.version} assets are complete (${result.requiredAssets} required files).`);
|
|
@@ -250,17 +1160,26 @@ async function runCli(argv) {
|
|
|
250
1160
|
const result = auditProject(projectPath, { mode, changeId });
|
|
251
1161
|
const report = formatAuditReport(result);
|
|
252
1162
|
|
|
253
|
-
if (result.status
|
|
254
|
-
|
|
1163
|
+
if (emitOrThrowOnStatus(result.status, ["FAIL"], report, continueOnError)) {
|
|
1164
|
+
return;
|
|
255
1165
|
}
|
|
256
1166
|
|
|
257
1167
|
console.log(report);
|
|
258
1168
|
return;
|
|
259
1169
|
}
|
|
260
1170
|
|
|
1171
|
+
if (command === "bootstrap-project") {
|
|
1172
|
+
const projectPath = getOption(argv, "--project") || positionalArgs[0] || process.cwd();
|
|
1173
|
+
const changeId = getOption(argv, "--change");
|
|
1174
|
+
const force = argv.includes("--force");
|
|
1175
|
+
const result = bootstrapProjectArtifacts(projectPath, { changeId, force });
|
|
1176
|
+
console.log(formatBootstrapProjectReport(result));
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
261
1180
|
if (command === "icon-sync") {
|
|
262
1181
|
const outputPath = getOption(argv, "--output") || getOption(argv, "--catalog");
|
|
263
|
-
const timeoutMs =
|
|
1182
|
+
const timeoutMs = getIntegerOption(argv, "--timeout-ms", { min: 1 });
|
|
264
1183
|
const strict = argv.includes("--strict");
|
|
265
1184
|
|
|
266
1185
|
const result = await syncIconCatalog({
|
|
@@ -275,129 +1194,7 @@ async function runCli(argv) {
|
|
|
275
1194
|
}
|
|
276
1195
|
|
|
277
1196
|
if (command === "icon-search") {
|
|
278
|
-
|
|
279
|
-
const topRaw = getOption(argv, "--top");
|
|
280
|
-
const queryOption = getOption(argv, "--query");
|
|
281
|
-
const catalogPath = getOption(argv, "--catalog");
|
|
282
|
-
const aliasesPath = getOption(argv, "--aliases");
|
|
283
|
-
const iconPositional = getPositionalArgs(argv.slice(1), [
|
|
284
|
-
"--home",
|
|
285
|
-
"--query",
|
|
286
|
-
"--family",
|
|
287
|
-
"--top",
|
|
288
|
-
"--catalog",
|
|
289
|
-
"--aliases"
|
|
290
|
-
]);
|
|
291
|
-
const query = queryOption || iconPositional.join(" ").trim();
|
|
292
|
-
|
|
293
|
-
if (!query) {
|
|
294
|
-
throw new Error("`icon-search` requires `--query <text>` or positional query text.");
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
let top;
|
|
298
|
-
if (topRaw !== undefined) {
|
|
299
|
-
top = Number.parseInt(topRaw, 10);
|
|
300
|
-
if (!Number.isFinite(top) || top < 1 || top > 50) {
|
|
301
|
-
throw new Error("`icon-search --top` must be an integer between 1 and 50.");
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
let loadedCatalog = null;
|
|
306
|
-
let loadedCatalogPath = null;
|
|
307
|
-
let catalogLoadError = null;
|
|
308
|
-
let loadedAliases = null;
|
|
309
|
-
let loadedAliasesPath = null;
|
|
310
|
-
let aliasesLoadError = null;
|
|
311
|
-
let aliasExpansion = {
|
|
312
|
-
extraTokens: [],
|
|
313
|
-
matchedAliases: []
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
try {
|
|
317
|
-
const loaded = loadIconCatalog({
|
|
318
|
-
catalogPath,
|
|
319
|
-
homeDir
|
|
320
|
-
});
|
|
321
|
-
loadedCatalog = loaded.catalog;
|
|
322
|
-
loadedCatalogPath = loaded.catalogPath;
|
|
323
|
-
} catch (error) {
|
|
324
|
-
catalogLoadError = error.message || String(error);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
try {
|
|
328
|
-
const loaded = loadIconAliases({
|
|
329
|
-
aliasPath: aliasesPath,
|
|
330
|
-
homeDir
|
|
331
|
-
});
|
|
332
|
-
loadedAliases = loaded;
|
|
333
|
-
loadedAliasesPath = loaded.aliasPath;
|
|
334
|
-
aliasExpansion = expandQueryWithAliases(query, loaded.aliases);
|
|
335
|
-
} catch (error) {
|
|
336
|
-
aliasesLoadError = error.message || String(error);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
const result = searchIconLibrary(query, {
|
|
340
|
-
family,
|
|
341
|
-
top,
|
|
342
|
-
catalog: loadedCatalog ? loadedCatalog.icons : [],
|
|
343
|
-
extraQueryTokens: aliasExpansion.extraTokens
|
|
344
|
-
});
|
|
345
|
-
const jsonOutput = argv.includes("--json");
|
|
346
|
-
|
|
347
|
-
const resultWithMeta = {
|
|
348
|
-
...result,
|
|
349
|
-
catalog: {
|
|
350
|
-
path: loadedCatalogPath || "(unresolved)",
|
|
351
|
-
loaded: Boolean(loadedCatalog),
|
|
352
|
-
iconCount: loadedCatalog ? loadedCatalog.iconCount : 0,
|
|
353
|
-
generatedAt: loadedCatalog ? loadedCatalog.generatedAt : null,
|
|
354
|
-
error: catalogLoadError
|
|
355
|
-
},
|
|
356
|
-
aliases: {
|
|
357
|
-
path: loadedAliasesPath || "(unresolved)",
|
|
358
|
-
loaded: loadedAliases ? Boolean(loadedAliases.loaded) : false,
|
|
359
|
-
available: Boolean(loadedAliases),
|
|
360
|
-
source: loadedAliases ? loadedAliases.source : null,
|
|
361
|
-
matched: aliasExpansion.matchedAliases.length,
|
|
362
|
-
extraTokens: aliasExpansion.extraTokens,
|
|
363
|
-
error: aliasesLoadError
|
|
364
|
-
}
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
if (jsonOutput) {
|
|
368
|
-
console.log(JSON.stringify(resultWithMeta, null, 2));
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
if (resultWithMeta.catalog.loaded) {
|
|
373
|
-
console.log(
|
|
374
|
-
`Icon catalog: ${resultWithMeta.catalog.path} (${resultWithMeta.catalog.iconCount} icons, ${resultWithMeta.catalog.generatedAt})`
|
|
375
|
-
);
|
|
376
|
-
} else if (resultWithMeta.catalog.error) {
|
|
377
|
-
console.log(
|
|
378
|
-
`Icon catalog: ${resultWithMeta.catalog.path} (load failed: ${resultWithMeta.catalog.error}; using built-in fallback index)`
|
|
379
|
-
);
|
|
380
|
-
} else {
|
|
381
|
-
console.log(
|
|
382
|
-
`Icon catalog: ${resultWithMeta.catalog.path} (not found; using built-in fallback index; run \`da-vinci icon-sync\`)`
|
|
383
|
-
);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
if (resultWithMeta.aliases.available) {
|
|
387
|
-
console.log(
|
|
388
|
-
`Icon aliases: ${resultWithMeta.aliases.path} (${resultWithMeta.aliases.source}, matched ${resultWithMeta.aliases.matched})`
|
|
389
|
-
);
|
|
390
|
-
} else if (resultWithMeta.aliases.error) {
|
|
391
|
-
console.log(
|
|
392
|
-
`Icon aliases: ${resultWithMeta.aliases.path} (load failed: ${resultWithMeta.aliases.error})`
|
|
393
|
-
);
|
|
394
|
-
} else {
|
|
395
|
-
console.log(
|
|
396
|
-
`Icon aliases: ${resultWithMeta.aliases.path} (not found; using built-in defaults only)`
|
|
397
|
-
);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
console.log(formatIconSearchReport(result));
|
|
1197
|
+
await handleIconSearchCommand(argv, homeDir);
|
|
401
1198
|
return;
|
|
402
1199
|
}
|
|
403
1200
|
|
|
@@ -408,7 +1205,7 @@ async function runCli(argv) {
|
|
|
408
1205
|
if (opsFile) {
|
|
409
1206
|
operations = readOperations(opsFile);
|
|
410
1207
|
} else if (!process.stdin.isTTY) {
|
|
411
|
-
operations =
|
|
1208
|
+
operations = readLimitedStdin();
|
|
412
1209
|
} else {
|
|
413
1210
|
throw new Error("`preflight-pencil` requires `--ops-file <path>` or piped stdin input.");
|
|
414
1211
|
}
|
|
@@ -416,8 +1213,8 @@ async function runCli(argv) {
|
|
|
416
1213
|
const result = preflightPencilBatch(operations);
|
|
417
1214
|
const report = formatPencilPreflightReport(result);
|
|
418
1215
|
|
|
419
|
-
if (result.status
|
|
420
|
-
|
|
1216
|
+
if (emitOrThrowOnStatus(result.status, ["FAIL"], report, continueOnError)) {
|
|
1217
|
+
return;
|
|
421
1218
|
}
|
|
422
1219
|
|
|
423
1220
|
console.log(report);
|
|
@@ -425,69 +1222,7 @@ async function runCli(argv) {
|
|
|
425
1222
|
}
|
|
426
1223
|
|
|
427
1224
|
if (command === "supervisor-review") {
|
|
428
|
-
|
|
429
|
-
console.log(
|
|
430
|
-
[
|
|
431
|
-
"da-vinci supervisor-review",
|
|
432
|
-
"",
|
|
433
|
-
"Usage:",
|
|
434
|
-
" da-vinci supervisor-review --project <path> --change <id> [--run-reviewers] [--review-concurrency <value>] [--review-retries <value>] [--review-retry-delay-ms <value>] [--source <skill|manual|inferred>] [--executed-reviewers <csv>] [--status <PASS|WARN|BLOCK>] [--issue-list <text>] [--revision-outcome <text>] [--write] [--json]",
|
|
435
|
-
" da-vinci supervisor-review --project <path> --pencil-design <path> [--run-reviewers] [--review-concurrency <value>] [--review-retries <value>] [--review-retry-delay-ms <value>] [--source <skill|manual|inferred>] [--executed-reviewers <csv>] [--status <PASS|WARN|BLOCK>] [--issue-list <text>] [--revision-outcome <text>] [--write] [--json]",
|
|
436
|
-
"",
|
|
437
|
-
"Notes:",
|
|
438
|
-
" - omit --status to infer a conservative review status from current design artifacts",
|
|
439
|
-
" - use --run-reviewers to execute configured reviewer skills automatically via codex exec",
|
|
440
|
-
" - `design-supervisor review` is a compatibility alias for this command"
|
|
441
|
-
].join("\n")
|
|
442
|
-
);
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
const projectPath = getOption(argv, "--project") || process.cwd();
|
|
447
|
-
const changeId = getOption(argv, "--change");
|
|
448
|
-
const pencilDesignPath = getOption(argv, "--pencil-design");
|
|
449
|
-
const status = getOption(argv, "--status");
|
|
450
|
-
const source = getOption(argv, "--source");
|
|
451
|
-
const executedReviewers = getOption(argv, "--executed-reviewers");
|
|
452
|
-
const codexBin = getOption(argv, "--codex-bin");
|
|
453
|
-
const maxImages = getOption(argv, "--max-images");
|
|
454
|
-
const reviewerTimeoutMs = getOption(argv, "--review-timeout-ms");
|
|
455
|
-
const reviewConcurrency = getOption(argv, "--review-concurrency");
|
|
456
|
-
const reviewerRetries = getOption(argv, "--review-retries");
|
|
457
|
-
const reviewerRetryDelayMs = getOption(argv, "--review-retry-delay-ms");
|
|
458
|
-
const issueList = getOption(argv, "--issue-list");
|
|
459
|
-
const revisionOutcome = getOption(argv, "--revision-outcome");
|
|
460
|
-
const write = argv.includes("--write");
|
|
461
|
-
const acceptWarn = argv.includes("--accept-warn");
|
|
462
|
-
const runReviewers = argv.includes("--run-reviewers");
|
|
463
|
-
const jsonOutput = argv.includes("--json");
|
|
464
|
-
|
|
465
|
-
const result = await runDesignSupervisorReview({
|
|
466
|
-
projectPath,
|
|
467
|
-
changeId,
|
|
468
|
-
pencilDesignPath,
|
|
469
|
-
status,
|
|
470
|
-
source,
|
|
471
|
-
executedReviewers,
|
|
472
|
-
codexBin,
|
|
473
|
-
maxImages,
|
|
474
|
-
reviewerTimeoutMs,
|
|
475
|
-
reviewConcurrency,
|
|
476
|
-
reviewerRetries,
|
|
477
|
-
reviewerRetryDelayMs,
|
|
478
|
-
issueList,
|
|
479
|
-
revisionOutcome,
|
|
480
|
-
write,
|
|
481
|
-
acceptWarn,
|
|
482
|
-
runReviewers
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
if (jsonOutput) {
|
|
486
|
-
console.log(JSON.stringify(result, null, 2));
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
console.log(formatDesignSupervisorReviewReport(result));
|
|
1225
|
+
await handleSupervisorReviewCommand(argv);
|
|
491
1226
|
return;
|
|
492
1227
|
}
|
|
493
1228
|
|
|
@@ -577,11 +1312,69 @@ async function runCli(argv) {
|
|
|
577
1312
|
console.log(`State file: ${result.statePath}`);
|
|
578
1313
|
console.log(`Snapshot hash: ${result.liveHash}`);
|
|
579
1314
|
if (!result.usedStateFile) {
|
|
580
|
-
console.log(
|
|
1315
|
+
console.log(
|
|
1316
|
+
result.stateHash
|
|
1317
|
+
? "State file hash was stale; sync comparison fell back to hashing the disk .pen file directly."
|
|
1318
|
+
: "State file was missing; sync comparison fell back to hashing the disk .pen file directly."
|
|
1319
|
+
);
|
|
581
1320
|
}
|
|
582
1321
|
return;
|
|
583
1322
|
}
|
|
584
1323
|
|
|
1324
|
+
if (command === "check-pen-baseline") {
|
|
1325
|
+
const penPath = getOption(argv, "--pen");
|
|
1326
|
+
const baselinePaths = getCommaSeparatedOptionValues(argv, "--baseline");
|
|
1327
|
+
const preferredSource = getOption(argv, "--prefer-source");
|
|
1328
|
+
|
|
1329
|
+
if (!penPath || baselinePaths.length === 0) {
|
|
1330
|
+
throw new Error(
|
|
1331
|
+
"`check-pen-baseline` requires `--pen <path>` and at least one `--baseline <path>`."
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
const result = comparePenBaselineAlignment({
|
|
1336
|
+
penPath,
|
|
1337
|
+
baselinePaths,
|
|
1338
|
+
preferredSource
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
if (
|
|
1342
|
+
emitOrThrowOnStatus(
|
|
1343
|
+
result.status,
|
|
1344
|
+
["BLOCK"],
|
|
1345
|
+
[
|
|
1346
|
+
"Baseline alignment check failed.",
|
|
1347
|
+
formatPenBaselineAlignmentReport(result)
|
|
1348
|
+
].join("\n"),
|
|
1349
|
+
continueOnError
|
|
1350
|
+
)
|
|
1351
|
+
) {
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
console.log(formatPenBaselineAlignmentReport(result));
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
if (command === "sync-pen-source") {
|
|
1360
|
+
const sourcePath = getOption(argv, "--from");
|
|
1361
|
+
const targetPath = getOption(argv, "--to");
|
|
1362
|
+
|
|
1363
|
+
if (!sourcePath || !targetPath) {
|
|
1364
|
+
throw new Error("`sync-pen-source` requires `--from <path>` and `--to <path>`.");
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
const result = syncPenSource({
|
|
1368
|
+
sourcePath,
|
|
1369
|
+
targetPath
|
|
1370
|
+
});
|
|
1371
|
+
|
|
1372
|
+
console.log(`Synced .pen source from ${result.sourcePath} to ${result.targetPath}`);
|
|
1373
|
+
console.log(`State file: ${result.statePath}`);
|
|
1374
|
+
console.log(`Snapshot hash: ${result.state.snapshotHash}`);
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
585
1378
|
if (command === "snapshot-pen") {
|
|
586
1379
|
const inputPath = getOption(argv, "--input");
|
|
587
1380
|
const outputPath = getOption(argv, "--output");
|
|
@@ -610,175 +1403,12 @@ async function runCli(argv) {
|
|
|
610
1403
|
}
|
|
611
1404
|
|
|
612
1405
|
if (command === "pencil-lock") {
|
|
613
|
-
|
|
614
|
-
"--home",
|
|
615
|
-
"--project",
|
|
616
|
-
"--owner",
|
|
617
|
-
"--wait-ms"
|
|
618
|
-
])[0];
|
|
619
|
-
const projectPath = getOption(argv, "--project") || process.cwd();
|
|
620
|
-
const owner = getOption(argv, "--owner");
|
|
621
|
-
const waitMs = getOption(argv, "--wait-ms");
|
|
622
|
-
const force = argv.includes("--force");
|
|
623
|
-
|
|
624
|
-
if (!subcommand || ["acquire", "release", "status"].includes(subcommand) === false) {
|
|
625
|
-
throw new Error("`pencil-lock` requires one of: acquire, release, status.");
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
if (subcommand === "acquire") {
|
|
629
|
-
const result = acquirePencilLock({
|
|
630
|
-
projectPath,
|
|
631
|
-
owner,
|
|
632
|
-
waitMs,
|
|
633
|
-
homeDir
|
|
634
|
-
});
|
|
635
|
-
console.log(`${result.alreadyHeld ? "Reused" : "Acquired"} Pencil lock at ${result.lockPath}`);
|
|
636
|
-
console.log(`Project: ${result.lock.projectPath}`);
|
|
637
|
-
console.log(`Owner: ${result.lock.owner}`);
|
|
638
|
-
return;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
if (subcommand === "release") {
|
|
642
|
-
const result = releasePencilLock({
|
|
643
|
-
projectPath,
|
|
644
|
-
force,
|
|
645
|
-
homeDir
|
|
646
|
-
});
|
|
647
|
-
if (!result.hadLock) {
|
|
648
|
-
console.log(`No Pencil lock was present at ${result.lockPath}`);
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
console.log(`Released Pencil lock at ${result.lockPath}`);
|
|
652
|
-
console.log(`Previous project: ${result.lock.projectPath}`);
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
const result = getPencilLockStatus({ homeDir });
|
|
657
|
-
console.log(`Lock path: ${result.lockPath}`);
|
|
658
|
-
if (!result.lock) {
|
|
659
|
-
console.log("Status: unlocked");
|
|
660
|
-
return;
|
|
661
|
-
}
|
|
662
|
-
console.log("Status: locked");
|
|
663
|
-
console.log(`Project: ${result.lock.projectPath}`);
|
|
664
|
-
console.log(`Owner: ${result.lock.owner}`);
|
|
665
|
-
console.log(`PID: ${result.lock.pid}`);
|
|
1406
|
+
handlePencilLockCommand(argv, homeDir);
|
|
666
1407
|
return;
|
|
667
1408
|
}
|
|
668
1409
|
|
|
669
1410
|
if (command === "pencil-session") {
|
|
670
|
-
|
|
671
|
-
"--home",
|
|
672
|
-
"--project",
|
|
673
|
-
"--pen",
|
|
674
|
-
"--nodes-file",
|
|
675
|
-
"--variables-file",
|
|
676
|
-
"--version",
|
|
677
|
-
"--owner",
|
|
678
|
-
"--wait-ms"
|
|
679
|
-
])[0];
|
|
680
|
-
const projectPath = getOption(argv, "--project") || process.cwd();
|
|
681
|
-
const penPath = getOption(argv, "--pen");
|
|
682
|
-
const nodesFile = getOption(argv, "--nodes-file");
|
|
683
|
-
const variablesFile = getOption(argv, "--variables-file");
|
|
684
|
-
const version = getOption(argv, "--version");
|
|
685
|
-
const verifyWithPencil = argv.includes("--verify-open");
|
|
686
|
-
const owner = getOption(argv, "--owner");
|
|
687
|
-
const waitMs = getOption(argv, "--wait-ms");
|
|
688
|
-
const force = argv.includes("--force");
|
|
689
|
-
|
|
690
|
-
if (!subcommand || ["begin", "persist", "end", "status"].includes(subcommand) === false) {
|
|
691
|
-
throw new Error("`pencil-session` requires one of: begin, persist, end, status.");
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
if (subcommand === "begin") {
|
|
695
|
-
if (!penPath) {
|
|
696
|
-
throw new Error("`pencil-session begin` requires `--pen <path>`.");
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
const result = beginPencilSession({
|
|
700
|
-
projectPath,
|
|
701
|
-
penPath,
|
|
702
|
-
version,
|
|
703
|
-
verifyWithPencil,
|
|
704
|
-
owner,
|
|
705
|
-
waitMs,
|
|
706
|
-
homeDir
|
|
707
|
-
});
|
|
708
|
-
|
|
709
|
-
console.log(`Began Pencil session for ${result.projectRoot}`);
|
|
710
|
-
console.log(`Pen path: ${result.penPath}`);
|
|
711
|
-
console.log(`Session state: ${result.sessionStatePath}`);
|
|
712
|
-
console.log(`Snapshot hash: ${result.session.lastPersistedHash}`);
|
|
713
|
-
return;
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
if (subcommand === "persist") {
|
|
717
|
-
if (!penPath || !nodesFile) {
|
|
718
|
-
throw new Error("`pencil-session persist` requires `--pen <path>` and `--nodes-file <path>`.");
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
const result = persistPencilSession({
|
|
722
|
-
projectPath,
|
|
723
|
-
penPath,
|
|
724
|
-
nodesFile,
|
|
725
|
-
variablesFile,
|
|
726
|
-
version,
|
|
727
|
-
verifyWithPencil,
|
|
728
|
-
homeDir
|
|
729
|
-
});
|
|
730
|
-
|
|
731
|
-
console.log(`Persisted Pencil session for ${result.projectRoot}`);
|
|
732
|
-
console.log(`Pen path: ${result.penPath}`);
|
|
733
|
-
console.log(`Session state: ${result.sessionStatePath}`);
|
|
734
|
-
console.log(`Snapshot hash: ${result.session.lastPersistedHash}`);
|
|
735
|
-
console.log(`In sync: ${result.syncResult.inSync ? "yes" : "no"}`);
|
|
736
|
-
return;
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
if (subcommand === "end") {
|
|
740
|
-
if (!penPath) {
|
|
741
|
-
throw new Error("`pencil-session end` requires `--pen <path>`.");
|
|
742
|
-
}
|
|
743
|
-
if (!nodesFile && !force) {
|
|
744
|
-
throw new Error(
|
|
745
|
-
"`pencil-session end` requires `--nodes-file <path>` (and `--variables-file <path>` when available). Use `--force` only for emergency lock release."
|
|
746
|
-
);
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
const result = endPencilSession({
|
|
750
|
-
projectPath,
|
|
751
|
-
penPath,
|
|
752
|
-
nodesFile,
|
|
753
|
-
variablesFile,
|
|
754
|
-
version,
|
|
755
|
-
homeDir,
|
|
756
|
-
force
|
|
757
|
-
});
|
|
758
|
-
|
|
759
|
-
console.log(`Ended Pencil session for ${result.projectRoot}`);
|
|
760
|
-
console.log(`Pen path: ${result.penPath}`);
|
|
761
|
-
console.log(`Session state: ${result.sessionStatePath}`);
|
|
762
|
-
console.log(`Final status: ${result.session.status}`);
|
|
763
|
-
if (result.syncResult) {
|
|
764
|
-
console.log(`Final sync verified: ${result.syncResult.inSync ? "yes" : "no"}`);
|
|
765
|
-
}
|
|
766
|
-
return;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
const result = getPencilSessionStatus({
|
|
770
|
-
projectPath,
|
|
771
|
-
homeDir
|
|
772
|
-
});
|
|
773
|
-
|
|
774
|
-
console.log(`Project: ${result.projectRoot}`);
|
|
775
|
-
console.log(`Session state: ${result.sessionStatePath}`);
|
|
776
|
-
console.log(`Session status: ${result.session ? result.session.status : "missing"}`);
|
|
777
|
-
console.log(`Lock status: ${result.lockStatus.lock ? "locked" : "unlocked"}`);
|
|
778
|
-
if (result.session) {
|
|
779
|
-
console.log(`Pen path: ${result.session.penPath}`);
|
|
780
|
-
console.log(`Last persisted hash: ${result.session.lastPersistedHash || "(missing)"}`);
|
|
781
|
-
}
|
|
1411
|
+
handlePencilSessionCommand(argv, homeDir);
|
|
782
1412
|
return;
|
|
783
1413
|
}
|
|
784
1414
|
|
|
@@ -786,5 +1416,6 @@ async function runCli(argv) {
|
|
|
786
1416
|
}
|
|
787
1417
|
|
|
788
1418
|
module.exports = {
|
|
789
|
-
runCli
|
|
1419
|
+
runCli,
|
|
1420
|
+
readLimitedStdin
|
|
790
1421
|
};
|