@deksden-com/dd-flow-cli 0.7.0 → 0.8.0
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 +666 -0
- package/README.md +7 -2
- package/dist/build-info.json +5 -5
- package/dist/cli/help.js +88 -10
- package/dist/cli/run-cli.js +523 -28
- package/dist/domain/stage-catalog.js +22 -0
- package/dist/domain/validation.js +1 -1
- package/dist/schemas/code-review-decision.schema.json +26 -0
- package/dist/schemas/code-review-result.schema.json +14 -0
- package/dist/schemas/code-verification.schema.json +14 -0
- package/dist/schemas/code-work-batch.schema.json +24 -0
- package/dist/schemas/code-work-result.schema.json +16 -0
- package/dist/schemas/compatibility.schema.json +32 -0
- package/dist/schemas/flow-contract.schema.json +9 -5
- package/dist/schemas/flow-run.schema.json +16 -123
- package/dist/schemas/plan-aspect-map.schema.json +22 -0
- package/dist/schemas/plan-review-decision.schema.json +14 -0
- package/dist/schemas/plan-review-result.schema.json +42 -0
- package/dist/schemas/protocol-plan.schema.json +15 -182
- package/dist/schemas/stage-finish-input.schema.json +16 -2
- package/dist/schemas/stage-report.schema.json +8 -7
- package/dist/schemas/stage-start-response.schema.json +4 -2
- package/dist/schemas/status-report.schema.json +76 -0
- package/dist/schemas/vnext-protocol-plan.schema.json +37 -0
- package/dist/schemas/vnext-protocolize-result.schema.json +29 -0
- package/dist/schemas/vnext-specify.schema.json +45 -0
- package/dist/services/branch-context.js +1 -1
- package/dist/services/cleanup.js +8 -8
- package/dist/services/cli-operation-classifier.js +10 -2
- package/dist/services/code-checks.js +244 -0
- package/dist/services/config.js +7 -1
- package/dist/services/dashboard.js +12 -12
- package/dist/services/engines.js +1 -1
- package/dist/services/eval-snapshots.js +404 -0
- package/dist/services/hooks.js +774 -18
- package/dist/services/ids.js +16 -6
- package/dist/services/lanes.js +1 -1
- package/dist/services/merge-queue.js +5 -5
- package/dist/services/merge-worker.js +2 -2
- package/dist/services/migrations.js +2 -2
- package/dist/services/plan-runtime.js +1 -1
- package/dist/services/projects.js +4 -4
- package/dist/services/prompts.js +17 -11
- package/dist/services/protocols.js +8 -8
- package/dist/services/run-projection.js +49 -13
- package/dist/services/runs.js +504 -51
- package/dist/services/schema-validation.js +21 -3
- package/dist/services/sessions.js +51 -12
- package/dist/services/stage-blocker.js +57 -0
- package/dist/services/stage-context.js +90 -0
- package/dist/services/stage-lifecycle.js +198 -75
- package/dist/services/stage-pause.js +175 -0
- package/dist/services/stage-report-renderer.js +65 -0
- package/dist/services/usage.js +526 -18
- package/dist/services/vnext-code-review.js +305 -0
- package/dist/services/vnext-code.js +686 -0
- package/dist/services/vnext-contracts.js +1 -0
- package/dist/services/vnext-execution-profile.js +27 -0
- package/dist/services/vnext-fanout.js +79 -0
- package/dist/services/vnext-plan-review.js +499 -0
- package/dist/services/vnext-plan.js +552 -0
- package/dist/services/vnext-protocolize.js +542 -0
- package/dist/services/vnext-specify.js +595 -0
- package/dist/services/vnext-workspace-policy.js +87 -0
- package/dist/services/work-registry.js +522 -0
- package/dist/services/worktrees.js +58 -37
- package/dist/storage/database.js +263 -34
- package/dist/storage/paths.js +47 -1
- package/package.json +12 -12
|
@@ -37,53 +37,67 @@ export function createWorktreeRecord(context, input) {
|
|
|
37
37
|
worktree_path: existing.worktree_path
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
|
+
const created = createManagedWorktree(context, { projectRoot: protocol.project_root, branch: input.branch, base: input.base, path: input.path });
|
|
41
|
+
recordManagedWorktree(context, { protocolId: protocol.id, projectRoot: protocol.project_root, branch: input.branch, base: input.base, created });
|
|
42
|
+
return { ok: true, worktree: activeWorktreeRecord(context, protocol.project_id, protocol.id) };
|
|
43
|
+
}
|
|
44
|
+
/** Creates the checkout before a PRT exists. PROTOCOLIZE materializes the PRT only inside this checkout. */
|
|
45
|
+
export function createManagedWorktree(context, input) {
|
|
46
|
+
const projectRoot = resolveProjectRoot(input.projectRoot);
|
|
40
47
|
const worktrunk = ensureManagedWorktrunk(context);
|
|
41
|
-
|
|
48
|
+
// Worktrunk owns creation; the CLI supplies its project-scoped service path
|
|
49
|
+
// as a one-shot config override rather than relying on a developer's user config.
|
|
50
|
+
const command = [worktrunk.bin, "-C", projectRoot, "--config-set", `worktree-path = ${JSON.stringify(path.resolve(input.path))}`, "switch", "--create", input.branch, "--base", input.base, "--no-cd", "--format=json", "-y"];
|
|
42
51
|
const result = runExternal(command);
|
|
43
|
-
if (result.exit_code !== 0)
|
|
52
|
+
if (result.exit_code !== 0)
|
|
44
53
|
throw new AppError("worktrunk_command_unavailable", "Worktrunk switch failed", 1, { command: sanitizeCommand(command), result });
|
|
45
|
-
}
|
|
46
54
|
const response = parseWorktrunkResponse(result.stdout, command);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
if (isTrackedProjectPath(projectRoot, response.workspace_path))
|
|
56
|
+
throw new AppError("worktree_active", "Worktrunk returned a workspace inside tracked project files", 1, { path: response.workspace_path });
|
|
57
|
+
return { worktreePath: response.workspace_path, worktrunk, command, result, response };
|
|
58
|
+
}
|
|
59
|
+
/** Attaches an already-created managed checkout to a newly materialized PRT. */
|
|
60
|
+
export function recordManagedWorktree(context, input) {
|
|
61
|
+
const protocol = scopedProtocol(context, input.projectRoot, input.protocolId);
|
|
62
|
+
const existing = activeWorktreeRecord(context, protocol.project_id, protocol.id);
|
|
63
|
+
if (existing)
|
|
64
|
+
throw new AppError("worktree_already_exists", "Protocol already has an active worktree record", 1, { protocol_id: protocol.id, worktree_path: existing.worktree_path });
|
|
51
65
|
const now = context.now();
|
|
52
66
|
const state = readProtocolRuntimeState(context, protocol).state;
|
|
53
67
|
context.db.run(`INSERT INTO worktree_records
|
|
54
68
|
(protocol_id, project_id, integration_branch, feature_branch, base_ref, worktree_path,
|
|
55
69
|
worktrunk_metadata_json, bootstrap_status, status, last_command_result_json, created_at, updated_at, closed_at)
|
|
56
|
-
VALUES (?, ?, ?, ?, ?, ?, ?,
|
|
57
|
-
protocol.id,
|
|
58
|
-
protocol.project_id,
|
|
59
|
-
typeof state.workspace.integration_branch === "string" ? state.workspace.integration_branch : null,
|
|
60
|
-
input.branch,
|
|
61
|
-
input.base,
|
|
62
|
-
worktreePath,
|
|
63
|
-
JSON.stringify({ bin: worktrunk.bin, version: worktrunk.version, command, response }),
|
|
64
|
-
JSON.stringify(result),
|
|
65
|
-
now,
|
|
66
|
-
now
|
|
67
|
-
]);
|
|
70
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'active', ?, ?, ?, NULL)`, [protocol.id, protocol.project_id, typeof state.workspace.integration_branch === "string" ? state.workspace.integration_branch : null, input.branch, input.base, input.created.worktreePath, JSON.stringify({ bin: input.created.worktrunk.bin, version: input.created.worktrunk.version, command: input.created.command, response: input.created.response }), input.bootstrap?.status ?? "pending", JSON.stringify(input.bootstrap ?? input.created.result), now, now]);
|
|
68
71
|
persistProtocolState(context, protocol, {
|
|
69
72
|
...state,
|
|
70
|
-
workspace: {
|
|
71
|
-
...state.workspace,
|
|
72
|
-
feature_branch: input.branch,
|
|
73
|
-
worktree_path: worktreePath,
|
|
74
|
-
base_commit: input.base,
|
|
75
|
-
worktrunk: { bin: worktrunk.bin, version: worktrunk.version, response },
|
|
76
|
-
bootstrap: { status: typeof response.bootstrap_status === "string" ? response.bootstrap_status : "pending" }
|
|
77
|
-
},
|
|
73
|
+
workspace: { ...state.workspace, feature_branch: input.branch, worktree_path: input.created.worktreePath, base_commit: input.base, worktrunk: { bin: input.created.worktrunk.bin, version: input.created.worktrunk.version, response: input.created.response }, bootstrap: input.bootstrap ?? { status: typeof input.created.response.bootstrap_status === "string" ? input.created.response.bootstrap_status : "pending" } },
|
|
78
74
|
updated_at: now
|
|
79
75
|
});
|
|
80
|
-
appendAudit(context, {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
76
|
+
appendAudit(context, { protocolId: protocol.id, projectId: protocol.project_id, eventType: "worktree.created", payload: { protocol_id: protocol.id, feature_branch: input.branch, base: input.base, path: input.created.worktreePath } });
|
|
77
|
+
}
|
|
78
|
+
/** Bootstrap a just-created RUN workspace before a durable PRT exists. */
|
|
79
|
+
export function bootstrapManagedWorktree(context, input) {
|
|
80
|
+
const projectRoot = resolveProjectRoot(input.projectRoot);
|
|
81
|
+
const worktreePath = path.resolve(input.worktreePath);
|
|
82
|
+
const includeFile = path.join(projectRoot, ".worktreeinclude");
|
|
83
|
+
const configFile = path.join(projectRoot, ".config", "wt.toml");
|
|
84
|
+
if (!fs.existsSync(includeFile) || !fs.existsSync(configFile)) {
|
|
85
|
+
throw new AppError("bootstrap_unavailable", "Project-owned Worktrunk bootstrap files are required", 1, { include_file: includeFile, config_file: configFile });
|
|
86
|
+
}
|
|
87
|
+
const worktrunk = ensureManagedWorktrunk(context);
|
|
88
|
+
const commands = [
|
|
89
|
+
[worktrunk.bin, "-C", worktreePath, "step", "copy-ignored", "--require-include"],
|
|
90
|
+
[worktrunk.bin, "-C", worktreePath, "hook", "pre-start", "--yes"]
|
|
91
|
+
];
|
|
92
|
+
const results = [];
|
|
93
|
+
for (const command of commands) {
|
|
94
|
+
const result = runExternal(command);
|
|
95
|
+
results.push({ command: sanitizeCommand(command), ...result });
|
|
96
|
+
if (result.exit_code !== 0) {
|
|
97
|
+
throw new AppError("bootstrap_failed", "Project-owned Worktrunk bootstrap failed", 1, { command: sanitizeCommand(command), result, results });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return { status: "succeeded", commands: results };
|
|
87
101
|
}
|
|
88
102
|
export function getWorktreeStatus(context, input) {
|
|
89
103
|
const protocol = scopedProtocol(context, input.projectRoot, input.protocolId);
|
|
@@ -222,13 +236,20 @@ function parseWorktrunkResponse(stdout, command) {
|
|
|
222
236
|
throw new AppError("worktrunk_invalid_response", "Worktrunk JSON response must be an object", 1, { command: sanitizeCommand(command) });
|
|
223
237
|
}
|
|
224
238
|
const response = parsed;
|
|
225
|
-
|
|
226
|
-
|
|
239
|
+
// `wt switch --format=json` returns `path` in the pinned 0.72 release.
|
|
240
|
+
// Normalize provider output once; the rest of dd-flow uses workspace_path.
|
|
241
|
+
const workspacePath = typeof response.workspace_path === "string"
|
|
242
|
+
? response.workspace_path
|
|
243
|
+
: typeof response.path === "string"
|
|
244
|
+
? response.path
|
|
245
|
+
: null;
|
|
246
|
+
if (!workspacePath || !path.isAbsolute(workspacePath)) {
|
|
247
|
+
throw new AppError("worktrunk_invalid_response", "Worktrunk response must contain an absolute path or workspace_path", 1, {
|
|
227
248
|
command: sanitizeCommand(command),
|
|
228
249
|
response
|
|
229
250
|
});
|
|
230
251
|
}
|
|
231
|
-
return { ...response, workspace_path: path.resolve(
|
|
252
|
+
return { ...response, workspace_path: path.resolve(workspacePath) };
|
|
232
253
|
}
|
|
233
254
|
function markBootstrapFailed(context, protocol, record, failure) {
|
|
234
255
|
const now = context.now();
|
package/dist/storage/database.js
CHANGED
|
@@ -5,6 +5,74 @@ import { ensureDir } from "./paths.js";
|
|
|
5
5
|
import { AppError } from "../shared/errors.js";
|
|
6
6
|
const require = createRequire(import.meta.url);
|
|
7
7
|
const { DatabaseSync } = require("node:sqlite");
|
|
8
|
+
/** Creates the single Work/Session authority used by a fresh vNext beta runtime. */
|
|
9
|
+
export function ensureVnextWorkStorage(db) {
|
|
10
|
+
const legacy = db.get("SELECT name FROM sqlite_master WHERE type = 'table' AND name IN ('vnext_works', 'vnext_agent_turns', 'flow_agent_turns') LIMIT 1");
|
|
11
|
+
if (legacy)
|
|
12
|
+
throw new AppError("beta_storage_recreate_required", "This beta requires a fresh DD_FLOW_HOME; the previous Work runtime storage is not compatible", 1);
|
|
13
|
+
const existing = db.get("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'works'");
|
|
14
|
+
if (existing && (!/task TEXT NOT NULL/.test(existing.sql) || /launch_token|adapter_session_id/.test(existing.sql))) {
|
|
15
|
+
throw new AppError("beta_storage_recreate_required", "This beta requires a fresh DD_FLOW_HOME; the existing works schema is not compatible", 1);
|
|
16
|
+
}
|
|
17
|
+
db.exec(`
|
|
18
|
+
CREATE TABLE IF NOT EXISTS works (
|
|
19
|
+
work_id TEXT PRIMARY KEY,
|
|
20
|
+
project_id TEXT NOT NULL,
|
|
21
|
+
run_id TEXT NOT NULL,
|
|
22
|
+
parent_work_id TEXT,
|
|
23
|
+
task TEXT NOT NULL,
|
|
24
|
+
launch_policy TEXT NOT NULL DEFAULT 'reuse_allowed',
|
|
25
|
+
result_schema TEXT,
|
|
26
|
+
payload_json TEXT,
|
|
27
|
+
depends_on_json TEXT NOT NULL DEFAULT '[]',
|
|
28
|
+
status TEXT NOT NULL,
|
|
29
|
+
result TEXT,
|
|
30
|
+
created_at TEXT NOT NULL,
|
|
31
|
+
started_at TEXT,
|
|
32
|
+
updated_at TEXT NOT NULL,
|
|
33
|
+
completed_at TEXT,
|
|
34
|
+
FOREIGN KEY(project_id) REFERENCES projects(id),
|
|
35
|
+
FOREIGN KEY(project_id, run_id) REFERENCES runs(project_id, id),
|
|
36
|
+
FOREIGN KEY(parent_work_id) REFERENCES works(work_id)
|
|
37
|
+
);
|
|
38
|
+
CREATE INDEX IF NOT EXISTS idx_works_run ON works(project_id, run_id, updated_at DESC);
|
|
39
|
+
CREATE TABLE IF NOT EXISTS work_sessions (
|
|
40
|
+
id TEXT PRIMARY KEY,
|
|
41
|
+
work_id TEXT NOT NULL,
|
|
42
|
+
session_id TEXT NOT NULL,
|
|
43
|
+
hook_event_id TEXT,
|
|
44
|
+
status TEXT NOT NULL,
|
|
45
|
+
prompt_path TEXT NOT NULL,
|
|
46
|
+
result_path TEXT,
|
|
47
|
+
created_at TEXT NOT NULL,
|
|
48
|
+
updated_at TEXT NOT NULL,
|
|
49
|
+
completed_at TEXT,
|
|
50
|
+
FOREIGN KEY(work_id) REFERENCES works(work_id)
|
|
51
|
+
);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_work_sessions_work ON work_sessions(work_id, created_at DESC);
|
|
53
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_work_sessions_open ON work_sessions(work_id) WHERE status = 'running';
|
|
54
|
+
CREATE TABLE IF NOT EXISTS check_receipts (
|
|
55
|
+
id TEXT PRIMARY KEY,
|
|
56
|
+
project_id TEXT NOT NULL,
|
|
57
|
+
run_id TEXT NOT NULL,
|
|
58
|
+
work_id TEXT,
|
|
59
|
+
scope TEXT NOT NULL,
|
|
60
|
+
declaration_id TEXT NOT NULL,
|
|
61
|
+
command TEXT NOT NULL,
|
|
62
|
+
status TEXT NOT NULL,
|
|
63
|
+
exit_code INTEGER,
|
|
64
|
+
stdout_path TEXT NOT NULL,
|
|
65
|
+
stderr_path TEXT NOT NULL,
|
|
66
|
+
receipt_path TEXT NOT NULL,
|
|
67
|
+
workspace_fingerprint TEXT NOT NULL,
|
|
68
|
+
artifacts_json TEXT NOT NULL,
|
|
69
|
+
started_at TEXT NOT NULL,
|
|
70
|
+
finished_at TEXT NOT NULL,
|
|
71
|
+
FOREIGN KEY(work_id) REFERENCES works(work_id)
|
|
72
|
+
);
|
|
73
|
+
CREATE INDEX IF NOT EXISTS idx_check_receipts_run ON check_receipts(project_id, run_id, started_at);
|
|
74
|
+
`);
|
|
75
|
+
}
|
|
8
76
|
export function getDatabase(ddFlowHome, mode = "initialize") {
|
|
9
77
|
const dbPath = path.join(ddFlowHome, "db.sqlite");
|
|
10
78
|
const exists = fs.existsSync(dbPath);
|
|
@@ -15,9 +83,9 @@ export function getDatabase(ddFlowHome, mode = "initialize") {
|
|
|
15
83
|
ensureDir(ddFlowHome);
|
|
16
84
|
const db = mode === "read_existing" ? new DatabaseSync(dbPath, { readOnly: true }) : new DatabaseSync(dbPath);
|
|
17
85
|
configureDatabase(db, mode);
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
if (
|
|
86
|
+
// Internal additive schema changes must be available to every write command.
|
|
87
|
+
// Higher-level Memory Bank migrations remain explicit in services/migrations.
|
|
88
|
+
if (mode !== "read_existing")
|
|
21
89
|
migrate(db);
|
|
22
90
|
if (mode === "read_existing")
|
|
23
91
|
db.exec("PRAGMA query_only = ON");
|
|
@@ -25,6 +93,7 @@ export function getDatabase(ddFlowHome, mode = "initialize") {
|
|
|
25
93
|
return {
|
|
26
94
|
path: dbPath,
|
|
27
95
|
writable: mode !== "read_existing",
|
|
96
|
+
close: () => db.close?.(),
|
|
28
97
|
exec: (sql) => db.exec(sql),
|
|
29
98
|
run: (sql, params = []) => db.prepare(sql).run(...params),
|
|
30
99
|
get: (sql, params = []) => db.prepare(sql).get(...params),
|
|
@@ -269,9 +338,18 @@ function migrate(db) {
|
|
|
269
338
|
updated_at TEXT NOT NULL
|
|
270
339
|
);
|
|
271
340
|
|
|
272
|
-
CREATE TABLE IF NOT EXISTS
|
|
341
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
273
342
|
session_id TEXT NOT NULL,
|
|
274
343
|
project_id TEXT NOT NULL,
|
|
344
|
+
harness TEXT NOT NULL DEFAULT 'codex-desktop',
|
|
345
|
+
provider_session_id TEXT,
|
|
346
|
+
agent_id TEXT,
|
|
347
|
+
parent_id TEXT,
|
|
348
|
+
provider TEXT,
|
|
349
|
+
model TEXT,
|
|
350
|
+
reasoning TEXT,
|
|
351
|
+
mode TEXT,
|
|
352
|
+
agent_type TEXT,
|
|
275
353
|
project_root TEXT NOT NULL,
|
|
276
354
|
flow_kind TEXT NOT NULL,
|
|
277
355
|
status TEXT NOT NULL,
|
|
@@ -296,7 +374,7 @@ function migrate(db) {
|
|
|
296
374
|
FOREIGN KEY(project_id) REFERENCES projects(id)
|
|
297
375
|
);
|
|
298
376
|
|
|
299
|
-
CREATE TABLE IF NOT EXISTS
|
|
377
|
+
CREATE TABLE IF NOT EXISTS runs (
|
|
300
378
|
id TEXT NOT NULL,
|
|
301
379
|
short_id TEXT NOT NULL,
|
|
302
380
|
slug TEXT NOT NULL,
|
|
@@ -313,6 +391,7 @@ function migrate(db) {
|
|
|
313
391
|
run_dir TEXT NOT NULL,
|
|
314
392
|
run_index_path TEXT NOT NULL,
|
|
315
393
|
run_home_path TEXT,
|
|
394
|
+
run_root TEXT,
|
|
316
395
|
layout_version TEXT,
|
|
317
396
|
artifact_root_kind TEXT,
|
|
318
397
|
index_json TEXT NOT NULL,
|
|
@@ -324,10 +403,76 @@ function migrate(db) {
|
|
|
324
403
|
);
|
|
325
404
|
|
|
326
405
|
CREATE INDEX IF NOT EXISTS idx_flow_runs_project_updated
|
|
327
|
-
ON
|
|
406
|
+
ON runs(project_id, updated_at DESC);
|
|
328
407
|
|
|
329
408
|
CREATE INDEX IF NOT EXISTS idx_flow_runs_short_id
|
|
330
|
-
ON
|
|
409
|
+
ON runs(short_id);
|
|
410
|
+
|
|
411
|
+
CREATE TABLE IF NOT EXISTS works (
|
|
412
|
+
work_id TEXT PRIMARY KEY,
|
|
413
|
+
project_id TEXT NOT NULL,
|
|
414
|
+
run_id TEXT NOT NULL,
|
|
415
|
+
parent_work_id TEXT,
|
|
416
|
+
task TEXT NOT NULL,
|
|
417
|
+
launch_policy TEXT NOT NULL DEFAULT 'reuse_allowed',
|
|
418
|
+
result_schema TEXT,
|
|
419
|
+
payload_json TEXT,
|
|
420
|
+
depends_on_json TEXT NOT NULL DEFAULT '[]',
|
|
421
|
+
status TEXT NOT NULL,
|
|
422
|
+
result TEXT,
|
|
423
|
+
created_at TEXT NOT NULL,
|
|
424
|
+
started_at TEXT,
|
|
425
|
+
updated_at TEXT NOT NULL,
|
|
426
|
+
completed_at TEXT,
|
|
427
|
+
FOREIGN KEY(project_id) REFERENCES projects(id),
|
|
428
|
+
FOREIGN KEY(project_id, run_id) REFERENCES runs(project_id, id),
|
|
429
|
+
FOREIGN KEY(parent_work_id) REFERENCES works(work_id)
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
CREATE INDEX IF NOT EXISTS idx_works_run
|
|
433
|
+
ON works(project_id, run_id, updated_at DESC);
|
|
434
|
+
|
|
435
|
+
CREATE TABLE IF NOT EXISTS work_sessions (
|
|
436
|
+
id TEXT PRIMARY KEY,
|
|
437
|
+
work_id TEXT NOT NULL,
|
|
438
|
+
session_id TEXT NOT NULL,
|
|
439
|
+
hook_event_id TEXT,
|
|
440
|
+
status TEXT NOT NULL,
|
|
441
|
+
prompt_path TEXT NOT NULL,
|
|
442
|
+
result_path TEXT,
|
|
443
|
+
created_at TEXT NOT NULL,
|
|
444
|
+
updated_at TEXT NOT NULL,
|
|
445
|
+
completed_at TEXT,
|
|
446
|
+
FOREIGN KEY(work_id) REFERENCES works(work_id)
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
CREATE INDEX IF NOT EXISTS idx_work_sessions_work
|
|
450
|
+
ON work_sessions(work_id, created_at DESC);
|
|
451
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_work_sessions_open
|
|
452
|
+
ON work_sessions(work_id) WHERE status = 'running';
|
|
453
|
+
|
|
454
|
+
CREATE TABLE IF NOT EXISTS check_receipts (
|
|
455
|
+
id TEXT PRIMARY KEY,
|
|
456
|
+
project_id TEXT NOT NULL,
|
|
457
|
+
run_id TEXT NOT NULL,
|
|
458
|
+
work_id TEXT,
|
|
459
|
+
scope TEXT NOT NULL,
|
|
460
|
+
declaration_id TEXT NOT NULL,
|
|
461
|
+
command TEXT NOT NULL,
|
|
462
|
+
status TEXT NOT NULL,
|
|
463
|
+
exit_code INTEGER,
|
|
464
|
+
stdout_path TEXT NOT NULL,
|
|
465
|
+
stderr_path TEXT NOT NULL,
|
|
466
|
+
receipt_path TEXT NOT NULL,
|
|
467
|
+
workspace_fingerprint TEXT NOT NULL,
|
|
468
|
+
artifacts_json TEXT NOT NULL,
|
|
469
|
+
started_at TEXT NOT NULL,
|
|
470
|
+
finished_at TEXT NOT NULL,
|
|
471
|
+
FOREIGN KEY(work_id) REFERENCES works(work_id)
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
CREATE INDEX IF NOT EXISTS idx_check_receipts_run
|
|
475
|
+
ON check_receipts(project_id, run_id, started_at);
|
|
331
476
|
|
|
332
477
|
CREATE TABLE IF NOT EXISTS flow_run_flag_mutations (
|
|
333
478
|
project_id TEXT NOT NULL,
|
|
@@ -341,7 +486,7 @@ function migrate(db) {
|
|
|
341
486
|
FOREIGN KEY(project_id) REFERENCES projects(id)
|
|
342
487
|
);
|
|
343
488
|
|
|
344
|
-
CREATE TABLE IF NOT EXISTS
|
|
489
|
+
CREATE TABLE IF NOT EXISTS usage (
|
|
345
490
|
id TEXT PRIMARY KEY,
|
|
346
491
|
project_id TEXT NOT NULL,
|
|
347
492
|
run_id TEXT NOT NULL,
|
|
@@ -359,6 +504,10 @@ function migrate(db) {
|
|
|
359
504
|
output_tokens INTEGER,
|
|
360
505
|
reasoning_output_tokens INTEGER,
|
|
361
506
|
source_kind TEXT NOT NULL,
|
|
507
|
+
source_locator TEXT,
|
|
508
|
+
source_sha256 TEXT,
|
|
509
|
+
source_size_bytes INTEGER,
|
|
510
|
+
source_mtime_ms INTEGER,
|
|
362
511
|
token_event_at TEXT,
|
|
363
512
|
turn_id TEXT,
|
|
364
513
|
turn_attribution TEXT NOT NULL,
|
|
@@ -370,14 +519,55 @@ function migrate(db) {
|
|
|
370
519
|
);
|
|
371
520
|
|
|
372
521
|
CREATE INDEX IF NOT EXISTS idx_flow_run_usage_snapshots_run
|
|
373
|
-
ON
|
|
522
|
+
ON usage(project_id, run_id, session_id, observed_at, id);
|
|
374
523
|
|
|
375
|
-
CREATE TABLE IF NOT EXISTS
|
|
524
|
+
CREATE TABLE IF NOT EXISTS harness_usage_snapshots (
|
|
525
|
+
id TEXT PRIMARY KEY,
|
|
526
|
+
project_id TEXT NOT NULL,
|
|
527
|
+
harness TEXT NOT NULL,
|
|
528
|
+
provider_session_id TEXT NOT NULL,
|
|
529
|
+
daemon_id TEXT,
|
|
530
|
+
observed_at TEXT NOT NULL,
|
|
531
|
+
total_tokens INTEGER NOT NULL,
|
|
532
|
+
input_tokens INTEGER,
|
|
533
|
+
cache_read_input_tokens INTEGER,
|
|
534
|
+
cache_write_input_tokens INTEGER,
|
|
535
|
+
output_tokens INTEGER,
|
|
536
|
+
reasoning_output_tokens INTEGER,
|
|
537
|
+
request_count INTEGER,
|
|
538
|
+
error_count INTEGER,
|
|
539
|
+
tool_calls INTEGER,
|
|
540
|
+
tool_failures INTEGER,
|
|
541
|
+
tool_by_name_json TEXT,
|
|
542
|
+
source_kind TEXT NOT NULL DEFAULT 'zcode_session_usage_v1',
|
|
543
|
+
usage_scope TEXT NOT NULL DEFAULT 'physical_session',
|
|
544
|
+
completeness TEXT NOT NULL DEFAULT 'complete',
|
|
545
|
+
created_at TEXT NOT NULL,
|
|
546
|
+
UNIQUE(project_id, harness, provider_session_id, observed_at),
|
|
547
|
+
FOREIGN KEY(project_id) REFERENCES projects(id)
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
CREATE INDEX IF NOT EXISTS idx_harness_usage_provider
|
|
551
|
+
ON harness_usage_snapshots(project_id, harness, provider_session_id, observed_at, id);
|
|
552
|
+
|
|
553
|
+
CREATE TABLE IF NOT EXISTS hook_events (
|
|
376
554
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
377
555
|
project_id TEXT NOT NULL,
|
|
378
556
|
protocol_id TEXT,
|
|
557
|
+
harness TEXT NOT NULL DEFAULT 'codex-desktop',
|
|
558
|
+
provider_session_id TEXT,
|
|
559
|
+
parent_session_id TEXT,
|
|
560
|
+
daemon_id TEXT,
|
|
379
561
|
session_id TEXT,
|
|
562
|
+
agent_id TEXT,
|
|
380
563
|
turn_id TEXT,
|
|
564
|
+
transcript_path TEXT,
|
|
565
|
+
provider TEXT,
|
|
566
|
+
model TEXT,
|
|
567
|
+
reasoning TEXT,
|
|
568
|
+
mode TEXT,
|
|
569
|
+
agent_type TEXT,
|
|
570
|
+
cwd TEXT,
|
|
381
571
|
event_key TEXT,
|
|
382
572
|
event_name TEXT NOT NULL,
|
|
383
573
|
tool_name TEXT,
|
|
@@ -460,32 +650,71 @@ function migrate(db) {
|
|
|
460
650
|
ensureColumn(db, "merge_queue", "completed_at", "ALTER TABLE merge_queue ADD COLUMN completed_at TEXT");
|
|
461
651
|
ensureColumn(db, "codex_session_bindings", "continuation_count", "ALTER TABLE codex_session_bindings ADD COLUMN continuation_count INTEGER NOT NULL DEFAULT 0");
|
|
462
652
|
ensureColumn(db, "codex_session_bindings", "last_action_hash", "ALTER TABLE codex_session_bindings ADD COLUMN last_action_hash TEXT");
|
|
463
|
-
ensureColumn(db, "
|
|
464
|
-
ensureColumn(db, "
|
|
465
|
-
ensureColumn(db, "
|
|
466
|
-
ensureColumn(db, "
|
|
467
|
-
ensureColumn(db, "
|
|
468
|
-
ensureColumn(db, "
|
|
469
|
-
ensureColumn(db, "
|
|
470
|
-
ensureColumn(db, "
|
|
471
|
-
ensureColumn(db, "
|
|
472
|
-
ensureColumn(db, "
|
|
473
|
-
ensureColumn(db, "
|
|
474
|
-
ensureColumn(db, "
|
|
475
|
-
ensureColumn(db, "
|
|
476
|
-
db
|
|
477
|
-
ensureColumn(db, "
|
|
478
|
-
ensureColumn(db, "
|
|
479
|
-
ensureColumn(db, "
|
|
480
|
-
ensureColumn(db, "
|
|
653
|
+
ensureColumn(db, "sessions", "current_stage", "ALTER TABLE sessions ADD COLUMN current_stage TEXT");
|
|
654
|
+
ensureColumn(db, "sessions", "next_action", "ALTER TABLE sessions ADD COLUMN next_action TEXT");
|
|
655
|
+
ensureColumn(db, "sessions", "metadata_json", "ALTER TABLE sessions ADD COLUMN metadata_json TEXT NOT NULL DEFAULT '{}'");
|
|
656
|
+
ensureColumn(db, "sessions", "run_id", "ALTER TABLE sessions ADD COLUMN run_id TEXT");
|
|
657
|
+
ensureColumn(db, "sessions", "provider_session_id", "ALTER TABLE sessions ADD COLUMN provider_session_id TEXT");
|
|
658
|
+
ensureColumn(db, "sessions", "harness", "ALTER TABLE sessions ADD COLUMN harness TEXT NOT NULL DEFAULT 'codex-desktop'");
|
|
659
|
+
ensureColumn(db, "sessions", "agent_id", "ALTER TABLE sessions ADD COLUMN agent_id TEXT");
|
|
660
|
+
ensureColumn(db, "sessions", "provider", "ALTER TABLE sessions ADD COLUMN provider TEXT");
|
|
661
|
+
ensureColumn(db, "sessions", "parent_id", "ALTER TABLE sessions ADD COLUMN parent_id TEXT");
|
|
662
|
+
ensureColumn(db, "sessions", "model", "ALTER TABLE sessions ADD COLUMN model TEXT");
|
|
663
|
+
ensureColumn(db, "sessions", "reasoning", "ALTER TABLE sessions ADD COLUMN reasoning TEXT");
|
|
664
|
+
ensureColumn(db, "sessions", "mode", "ALTER TABLE sessions ADD COLUMN mode TEXT");
|
|
665
|
+
ensureColumn(db, "sessions", "agent_type", "ALTER TABLE sessions ADD COLUMN agent_type TEXT");
|
|
666
|
+
ensureColumn(db, "sessions", "parent_session_id", "ALTER TABLE sessions ADD COLUMN parent_session_id TEXT");
|
|
667
|
+
ensureColumn(db, "sessions", "role", "ALTER TABLE sessions ADD COLUMN role TEXT");
|
|
668
|
+
ensureColumn(db, "sessions", "aspect_id", "ALTER TABLE sessions ADD COLUMN aspect_id TEXT");
|
|
669
|
+
ensureColumn(db, "sessions", "plan_item_id", "ALTER TABLE sessions ADD COLUMN plan_item_id TEXT");
|
|
670
|
+
ensureColumn(db, "sessions", "session_kind", "ALTER TABLE sessions ADD COLUMN session_kind TEXT");
|
|
671
|
+
ensureColumn(db, "sessions", "coverage_units_json", "ALTER TABLE sessions ADD COLUMN coverage_units_json TEXT NOT NULL DEFAULT '[]'");
|
|
672
|
+
ensureColumn(db, "usage", "cache_read_input_tokens", "ALTER TABLE usage ADD COLUMN cache_read_input_tokens INTEGER");
|
|
673
|
+
ensureColumn(db, "usage", "cache_write_input_tokens", "ALTER TABLE usage ADD COLUMN cache_write_input_tokens INTEGER");
|
|
674
|
+
ensureColumn(db, "usage", "uncached_input_tokens", "ALTER TABLE usage ADD COLUMN uncached_input_tokens INTEGER");
|
|
675
|
+
ensureColumn(db, "usage", "source_locator", "ALTER TABLE usage ADD COLUMN source_locator TEXT");
|
|
676
|
+
ensureColumn(db, "usage", "source_sha256", "ALTER TABLE usage ADD COLUMN source_sha256 TEXT");
|
|
677
|
+
ensureColumn(db, "usage", "source_size_bytes", "ALTER TABLE usage ADD COLUMN source_size_bytes INTEGER");
|
|
678
|
+
ensureColumn(db, "usage", "source_mtime_ms", "ALTER TABLE usage ADD COLUMN source_mtime_ms INTEGER");
|
|
679
|
+
db.prepare("UPDATE usage SET cache_read_input_tokens = cached_input_tokens WHERE cache_read_input_tokens IS NULL AND cached_input_tokens IS NOT NULL").run();
|
|
680
|
+
ensureColumn(db, "runs", "run_home_path", "ALTER TABLE runs ADD COLUMN run_home_path TEXT");
|
|
681
|
+
ensureColumn(db, "runs", "run_root", "ALTER TABLE runs ADD COLUMN run_root TEXT");
|
|
682
|
+
ensureColumn(db, "runs", "layout_version", "ALTER TABLE runs ADD COLUMN layout_version TEXT");
|
|
683
|
+
ensureColumn(db, "runs", "artifact_root_kind", "ALTER TABLE runs ADD COLUMN artifact_root_kind TEXT");
|
|
684
|
+
ensureColumn(db, "works", "payload_json", "ALTER TABLE works ADD COLUMN payload_json TEXT");
|
|
685
|
+
ensureColumn(db, "check_receipts", "declaration_id", "ALTER TABLE check_receipts ADD COLUMN declaration_id TEXT NOT NULL DEFAULT 'CHK-LEGACY'");
|
|
686
|
+
ensureColumn(db, "check_receipts", "workspace_fingerprint", "ALTER TABLE check_receipts ADD COLUMN workspace_fingerprint TEXT NOT NULL DEFAULT ''");
|
|
687
|
+
ensureColumn(db, "check_receipts", "artifacts_json", "ALTER TABLE check_receipts ADD COLUMN artifacts_json TEXT NOT NULL DEFAULT '[]'");
|
|
688
|
+
ensureColumn(db, "hook_events", "event_key", "ALTER TABLE hook_events ADD COLUMN event_key TEXT");
|
|
689
|
+
ensureColumn(db, "hook_events", "match_key", "ALTER TABLE hook_events ADD COLUMN match_key TEXT");
|
|
690
|
+
ensureColumn(db, "hook_events", "claimed_at", "ALTER TABLE hook_events ADD COLUMN claimed_at TEXT");
|
|
691
|
+
ensureColumn(db, "hook_events", "agent_id", "ALTER TABLE hook_events ADD COLUMN agent_id TEXT");
|
|
692
|
+
ensureColumn(db, "hook_events", "transcript_path", "ALTER TABLE hook_events ADD COLUMN transcript_path TEXT");
|
|
693
|
+
ensureColumn(db, "hook_events", "provider", "ALTER TABLE hook_events ADD COLUMN provider TEXT");
|
|
694
|
+
ensureColumn(db, "hook_events", "model", "ALTER TABLE hook_events ADD COLUMN model TEXT");
|
|
695
|
+
ensureColumn(db, "hook_events", "reasoning", "ALTER TABLE hook_events ADD COLUMN reasoning TEXT");
|
|
696
|
+
ensureColumn(db, "hook_events", "mode", "ALTER TABLE hook_events ADD COLUMN mode TEXT");
|
|
697
|
+
ensureColumn(db, "hook_events", "agent_type", "ALTER TABLE hook_events ADD COLUMN agent_type TEXT");
|
|
698
|
+
ensureColumn(db, "hook_events", "harness", "ALTER TABLE hook_events ADD COLUMN harness TEXT NOT NULL DEFAULT 'codex-desktop'");
|
|
699
|
+
ensureColumn(db, "hook_events", "provider_session_id", "ALTER TABLE hook_events ADD COLUMN provider_session_id TEXT");
|
|
700
|
+
ensureColumn(db, "hook_events", "parent_session_id", "ALTER TABLE hook_events ADD COLUMN parent_session_id TEXT");
|
|
701
|
+
ensureColumn(db, "hook_events", "daemon_id", "ALTER TABLE hook_events ADD COLUMN daemon_id TEXT");
|
|
702
|
+
ensureColumn(db, "hook_events", "cwd", "ALTER TABLE hook_events ADD COLUMN cwd TEXT");
|
|
703
|
+
ensureColumn(db, "harness_usage_snapshots", "tool_calls", "ALTER TABLE harness_usage_snapshots ADD COLUMN tool_calls INTEGER");
|
|
704
|
+
ensureColumn(db, "harness_usage_snapshots", "tool_failures", "ALTER TABLE harness_usage_snapshots ADD COLUMN tool_failures INTEGER");
|
|
705
|
+
ensureColumn(db, "harness_usage_snapshots", "tool_by_name_json", "ALTER TABLE harness_usage_snapshots ADD COLUMN tool_by_name_json TEXT");
|
|
706
|
+
ensureColumn(db, "harness_usage_snapshots", "source_kind", "ALTER TABLE harness_usage_snapshots ADD COLUMN source_kind TEXT NOT NULL DEFAULT 'zcode_session_usage_v1'");
|
|
707
|
+
ensureColumn(db, "harness_usage_snapshots", "usage_scope", "ALTER TABLE harness_usage_snapshots ADD COLUMN usage_scope TEXT NOT NULL DEFAULT 'physical_session'");
|
|
708
|
+
ensureColumn(db, "harness_usage_snapshots", "completeness", "ALTER TABLE harness_usage_snapshots ADD COLUMN completeness TEXT NOT NULL DEFAULT 'complete'");
|
|
481
709
|
ensureColumn(db, "flow_jobs", "updated_at", "ALTER TABLE flow_jobs ADD COLUMN updated_at TEXT NOT NULL DEFAULT ''");
|
|
482
|
-
db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_codex_hook_events_event_key ON
|
|
710
|
+
db.exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_codex_hook_events_event_key ON hook_events(project_id, event_key) WHERE event_key IS NOT NULL");
|
|
711
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_codex_hook_events_bootstrap_claim ON hook_events(project_id, session_id, match_key, status, created_at DESC)");
|
|
483
712
|
ensureColumn(db, "lane_waiters", "expires_at", "ALTER TABLE lane_waiters ADD COLUMN expires_at TEXT");
|
|
484
713
|
db.exec(`
|
|
485
714
|
CREATE INDEX IF NOT EXISTS idx_flow_runs_project_updated
|
|
486
|
-
ON
|
|
715
|
+
ON runs(project_id, updated_at DESC);
|
|
487
716
|
CREATE INDEX IF NOT EXISTS idx_flow_runs_short_id
|
|
488
|
-
ON
|
|
717
|
+
ON runs(short_id);
|
|
489
718
|
`);
|
|
490
719
|
}
|
|
491
720
|
function migrateProjectScopedIdentity(db) {
|
|
@@ -500,7 +729,7 @@ function migrateProjectScopedIdentity(db) {
|
|
|
500
729
|
|
|
501
730
|
ALTER TABLE merge_queue RENAME TO merge_queue_legacy_global_identity;
|
|
502
731
|
ALTER TABLE worktree_records RENAME TO worktree_records_legacy_global_identity;
|
|
503
|
-
ALTER TABLE
|
|
732
|
+
ALTER TABLE runs RENAME TO flow_runs_legacy_global_identity;
|
|
504
733
|
ALTER TABLE protocols RENAME TO protocols_legacy_global_identity;
|
|
505
734
|
|
|
506
735
|
CREATE TABLE protocols (
|
|
@@ -581,7 +810,7 @@ function migrateProjectScopedIdentity(db) {
|
|
|
581
810
|
status, last_command_result_json, created_at, updated_at, closed_at
|
|
582
811
|
FROM worktree_records_legacy_global_identity;
|
|
583
812
|
|
|
584
|
-
CREATE TABLE
|
|
813
|
+
CREATE TABLE runs (
|
|
585
814
|
id TEXT NOT NULL,
|
|
586
815
|
short_id TEXT NOT NULL,
|
|
587
816
|
slug TEXT NOT NULL,
|
|
@@ -607,7 +836,7 @@ function migrateProjectScopedIdentity(db) {
|
|
|
607
836
|
PRIMARY KEY(project_id, id),
|
|
608
837
|
FOREIGN KEY(project_id) REFERENCES projects(id)
|
|
609
838
|
);
|
|
610
|
-
INSERT INTO
|
|
839
|
+
INSERT INTO runs
|
|
611
840
|
(id, short_id, slug, project_id, project_root, workspace_root, flow_kind,
|
|
612
841
|
subject_type, subject_id, status, verdict, next_action, runtime_path,
|
|
613
842
|
run_dir, run_index_path, run_home_path, layout_version,
|
package/dist/storage/paths.js
CHANGED
|
@@ -3,18 +3,64 @@ import os from "node:os";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { AppError } from "../shared/errors.js";
|
|
5
5
|
export function resolveDdFlowHome(env) {
|
|
6
|
+
// Keep the operator spelling stable in generated commands; containment checks
|
|
7
|
+
// canonicalize paths separately when filesystem identity matters.
|
|
6
8
|
return path.resolve(env.DD_FLOW_HOME ?? path.join(os.homedir(), ".dd-flow"));
|
|
7
9
|
}
|
|
8
10
|
export function ensureDir(dir) {
|
|
9
11
|
fs.mkdirSync(dir, { recursive: true });
|
|
10
12
|
}
|
|
11
13
|
export function resolveProjectRoot(root) {
|
|
12
|
-
const absolute =
|
|
14
|
+
const absolute = canonicalPath(root, false);
|
|
13
15
|
if (!fs.existsSync(absolute)) {
|
|
14
16
|
throw new AppError("not_found", `Project root does not exist: ${absolute}`, 1);
|
|
15
17
|
}
|
|
16
18
|
return fs.realpathSync(absolute);
|
|
17
19
|
}
|
|
20
|
+
/** Resolve an existing path to its filesystem identity, or normalize a future path. */
|
|
21
|
+
export function canonicalPath(value, mustExist = true) {
|
|
22
|
+
const absolute = path.resolve(value);
|
|
23
|
+
if (fs.existsSync(absolute))
|
|
24
|
+
return fs.realpathSync(absolute);
|
|
25
|
+
if (mustExist)
|
|
26
|
+
throw new AppError("not_found", `Path does not exist: ${absolute}`, 1, { path: absolute });
|
|
27
|
+
const parent = path.dirname(absolute);
|
|
28
|
+
if (!fs.existsSync(parent))
|
|
29
|
+
return absolute;
|
|
30
|
+
return path.join(fs.realpathSync(parent), path.basename(absolute));
|
|
31
|
+
}
|
|
32
|
+
export function assertPathWithin(root, candidate, label = "path") {
|
|
33
|
+
const canonicalRoot = canonicalPath(root);
|
|
34
|
+
const canonicalCandidate = canonicalPath(candidate, false);
|
|
35
|
+
const relative = path.relative(canonicalRoot, canonicalCandidate);
|
|
36
|
+
if (relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative)))
|
|
37
|
+
return canonicalCandidate;
|
|
38
|
+
throw new AppError("validation", `${label} must stay inside ${canonicalRoot}`, 2, { root: canonicalRoot, [label]: canonicalCandidate });
|
|
39
|
+
}
|
|
40
|
+
export function runReference(runId, relativePath) {
|
|
41
|
+
const normalized = relativePath.replaceAll(path.sep, "/").replace(/^\/+/, "");
|
|
42
|
+
if (!normalized || normalized.split("/").includes(".."))
|
|
43
|
+
throw new AppError("validation", "RUN reference must be a safe relative path", 2, { relative_path: relativePath });
|
|
44
|
+
return `run://${runId}/${normalized}`;
|
|
45
|
+
}
|
|
46
|
+
export function resolveRunReference(value, runId, runRoot) {
|
|
47
|
+
const prefix = `run://${runId}/`;
|
|
48
|
+
if (!value.startsWith(prefix))
|
|
49
|
+
return value;
|
|
50
|
+
return assertPathWithin(runRoot, path.join(runRoot, value.slice(prefix.length)), "run_reference");
|
|
51
|
+
}
|
|
52
|
+
export function resolveRunReferences(value, runId, runRoot) {
|
|
53
|
+
return value.replace(new RegExp(`run://${escapeRegExp(runId)}/([^\\s)]+)`, "g"), (reference) => resolveRunReference(reference, runId, runRoot));
|
|
54
|
+
}
|
|
55
|
+
export function writeJsonAtomic(file, value) {
|
|
56
|
+
ensureDir(path.dirname(file));
|
|
57
|
+
const temporary = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
58
|
+
fs.writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`);
|
|
59
|
+
fs.renameSync(temporary, file);
|
|
60
|
+
}
|
|
61
|
+
function escapeRegExp(value) {
|
|
62
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
63
|
+
}
|
|
18
64
|
export function protocolDir(projectRoot, protocolId) {
|
|
19
65
|
return path.join(projectRoot, ".memory-bank", "protocol", protocolId);
|
|
20
66
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deksden-com/dd-flow-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Mechanical runtime CLI for dd-flow workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,15 +16,6 @@
|
|
|
16
16
|
"node": ">=26.0.0",
|
|
17
17
|
"pnpm": ">=10.0.0"
|
|
18
18
|
},
|
|
19
|
-
"scripts": {
|
|
20
|
-
"build": "tsc -p tsconfig.build.json && node scripts/generate-build-info.mjs && node scripts/copy-assets.mjs",
|
|
21
|
-
"typecheck": "tsc --noEmit",
|
|
22
|
-
"lint": "eslint . --max-warnings=0",
|
|
23
|
-
"test": "vitest run",
|
|
24
|
-
"changeset": "changeset",
|
|
25
|
-
"version-packages": "changeset version",
|
|
26
|
-
"release": "pnpm run build && changeset publish"
|
|
27
|
-
},
|
|
28
19
|
"devDependencies": {
|
|
29
20
|
"@changesets/cli": "^2.31.0",
|
|
30
21
|
"@eslint/js": "^9.39.1",
|
|
@@ -42,5 +33,14 @@
|
|
|
42
33
|
"publishConfig": {
|
|
43
34
|
"access": "public"
|
|
44
35
|
},
|
|
45
|
-
"license": "MIT"
|
|
46
|
-
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc -p tsconfig.build.json && node scripts/generate-build-info.mjs && node scripts/copy-assets.mjs",
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"lint": "eslint . --max-warnings=0",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"changeset": "changeset",
|
|
43
|
+
"version-packages": "changeset version",
|
|
44
|
+
"release": "pnpm run build && changeset publish"
|
|
45
|
+
}
|
|
46
|
+
}
|