@deksden-com/dd-flow-cli 0.9.0-beta.11 → 0.9.0-beta.111
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 +526 -0
- package/README.md +68 -0
- package/dist/build-info.json +8 -8
- package/dist/cli/command-inputs.js +305 -0
- package/dist/cli/help.js +89 -13
- package/dist/cli/hook-ingress.js +77 -0
- package/dist/cli/input-preparation.js +116 -0
- package/dist/cli/native-hook-ingress.js +48 -0
- package/dist/cli/run-cli.js +1465 -384
- package/dist/cli.js +8 -2
- package/dist/harness-runtime/DD-ZCODE.md +99 -0
- package/dist/harness-runtime/bin/dd-agy.mjs +52 -0
- package/dist/harness-runtime/bin/dd-codex.mjs +26 -0
- package/dist/harness-runtime/bin/dd-droid.mjs +33 -0
- package/dist/harness-runtime/bin/dd-grok.mjs +30 -0
- package/dist/harness-runtime/bin/dd-opencode.mjs +21 -0
- package/dist/harness-runtime/bin/dd-zcode.mjs +83 -0
- package/dist/harness-runtime/lib/agy-hooks.d.mts +1 -0
- package/dist/harness-runtime/lib/agy-hooks.mjs +9 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +226 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +6 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +500 -0
- package/dist/harness-runtime/lib/dd-agy.mjs +59 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +149 -0
- package/dist/harness-runtime/lib/dd-codex.mjs +563 -0
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +125 -0
- package/dist/harness-runtime/lib/dd-droid.mjs +475 -0
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +394 -0
- package/dist/harness-runtime/lib/dd-grok.mjs +174 -0
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +219 -0
- package/dist/harness-runtime/lib/dd-opencode.mjs +98 -0
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +645 -0
- package/dist/harness-runtime/lib/dd-zcode.mjs +982 -0
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +13 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +144 -0
- package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
- package/dist/harness-runtime/lib/driver-recovery.mjs +129 -0
- package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +327 -0
- package/dist/harness-runtime/lib/model-observations.mjs +77 -0
- package/dist/harness-runtime/lib/native-hook-command.d.mts +3 -0
- package/dist/harness-runtime/lib/native-hook-command.mjs +108 -0
- package/dist/harness-runtime/lib/observation-clock.mjs +36 -0
- package/dist/harness-runtime/lib/operation-context.mjs +5 -0
- package/dist/harness-runtime/lib/operation-errors.mjs +19 -0
- package/dist/harness-runtime/lib/process-json.mjs +71 -0
- package/dist/harness-runtime/lib/process-snapshot.mjs +10 -0
- package/dist/harness-runtime/lib/runner-events.mjs +200 -0
- package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
- package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
- package/dist/harness-runtime/lib/tool-observations.d.mts +17 -0
- package/dist/harness-runtime/lib/tool-observations.mjs +153 -0
- package/dist/harness-runtime/lib/zcode-usage.mjs +54 -0
- package/dist/protocol/local-files.js +18 -9
- package/dist/runtime/context.js +2 -2
- package/dist/schemas/agent-profile.schema.json +1 -1
- package/dist/schemas/code-review-result.schema.json +1 -1
- package/dist/schemas/code-work-batch.schema.json +3 -3
- package/dist/schemas/code-work-result.schema.json +3 -3
- package/dist/schemas/plan-review-result.schema.json +2 -2
- package/dist/schemas/run-control-receipt.schema.json +99 -0
- package/dist/schemas/run-control-request.schema.json +36 -0
- package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
- package/dist/services/canon.js +11 -8
- package/dist/services/cleanup.js +132 -39
- package/dist/services/cli-operation-classifier.js +22 -24
- package/dist/services/code-checks.js +589 -106
- package/dist/services/codex-hook-delivery.js +28 -0
- package/dist/services/command-context.js +80 -0
- package/dist/services/config.js +11 -8
- package/dist/services/continuation-outcome.js +20 -0
- package/dist/services/controller-fanout.js +173 -0
- package/dist/services/dashboard.js +103 -31
- package/dist/services/engines.js +121 -76
- package/dist/services/eval-snapshots.js +994 -97
- package/dist/services/execution-policy.js +181 -0
- package/dist/services/external-work-launch.js +166 -0
- package/dist/services/harness-adapter.js +147 -24
- package/dist/services/harness-config.js +2 -0
- package/dist/services/hooks.js +702 -327
- package/dist/services/lanes.js +78 -65
- package/dist/services/lifecycle-command.js +113 -18
- package/dist/services/lifecycle-contract.js +30 -0
- package/dist/services/lifecycle-invocations.js +1379 -0
- package/dist/services/managed-daemon-binding.js +38 -0
- package/dist/services/managed-processes.js +302 -34
- package/dist/services/merge-queue.js +228 -149
- package/dist/services/merge-server.js +66 -28
- package/dist/services/migrations.js +37 -16
- package/dist/services/native-daemon-history.js +49 -0
- package/dist/services/native-session-control.js +39 -0
- package/dist/services/plan-runtime.js +26 -11
- package/dist/services/plans.js +28 -25
- package/dist/services/projects.js +20 -15
- package/dist/services/prompts.js +25 -16
- package/dist/services/protocols.js +160 -86
- package/dist/services/recovery-observation-budget.js +61 -0
- package/dist/services/recovery-snapshot-database.js +142 -0
- package/dist/services/repair-intents.js +40 -0
- package/dist/services/review-copy.js +136 -0
- package/dist/services/run-control-receipt.js +56 -0
- package/dist/services/run-control-worker.js +351 -0
- package/dist/services/run-control.js +888 -0
- package/dist/services/run-controller-adapter.js +231 -0
- package/dist/services/run-controller-capture.js +167 -0
- package/dist/services/run-controller-process.js +180 -0
- package/dist/services/run-controller-recovery.js +221 -0
- package/dist/services/run-controller-state.js +36 -0
- package/dist/services/run-controller.js +798 -0
- package/dist/services/run-engine-bindings.js +1 -1
- package/dist/services/run-fork.js +142 -0
- package/dist/services/run-observations.js +112 -0
- package/dist/services/run-projection.js +22 -64
- package/dist/services/run-recovery-runtime.js +80 -0
- package/dist/services/run-recovery.js +345 -0
- package/dist/services/runs.js +536 -181
- package/dist/services/runtime-budget.js +413 -0
- package/dist/services/runtime-command.js +43 -0
- package/dist/services/runtime-scope-capture.js +52 -0
- package/dist/services/runtime-scope-control.js +460 -0
- package/dist/services/runtime-scope-resume.js +577 -0
- package/dist/services/runtime-scope-stop.js +159 -0
- package/dist/services/runtime-scope-worker.js +242 -0
- package/dist/services/runtime-service.js +97 -0
- package/dist/services/schema-validation.js +10 -8
- package/dist/services/scope-writer-cleanup.js +26 -0
- package/dist/services/sessions.js +35 -69
- package/dist/services/stage-blocker.js +17 -7
- package/dist/services/stage-context.js +50 -19
- package/dist/services/stage-lifecycle.js +100 -103
- package/dist/services/stage-pause.js +88 -75
- package/dist/services/stage-report-renderer.js +116 -6
- package/dist/services/stage-work-graph.js +35 -0
- package/dist/services/usage.js +35 -98
- package/dist/services/vnext-code-review.js +197 -84
- package/dist/services/vnext-code.js +278 -251
- package/dist/services/vnext-execution-profile.js +5 -3
- package/dist/services/vnext-fanout.js +172 -17
- package/dist/services/vnext-merge.js +345 -111
- package/dist/services/vnext-plan-review.js +171 -111
- package/dist/services/vnext-plan.js +180 -87
- package/dist/services/vnext-protocolize.js +320 -74
- package/dist/services/vnext-specify.js +115 -77
- package/dist/services/work-registry.js +1001 -187
- package/dist/services/workspace-bootstrap.js +76 -0
- package/dist/services/workspace-files.js +83 -0
- package/dist/services/worktrees.js +88 -36
- package/dist/shared/entity-references.js +8 -0
- package/dist/shared/errors.js +12 -0
- package/dist/storage/database.js +692 -35
- package/dist/storage/foreign-key-transaction.js +39 -0
- package/dist/storage/immutable-input.js +45 -0
- package/dist/storage/import-guard.js +102 -0
- package/dist/storage/native-cleanup.js +52 -0
- package/dist/storage/paths.js +17 -4
- package/dist/storage/projection-lock.js +73 -0
- package/dist/storage/work-references.js +23 -0
- package/dist/storage/writer-contract.js +77 -0
- package/dist/storage/writer-migration.js +85 -0
- package/package.json +24 -13
- package/tools/repair-paused-run-status.mjs +84 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
/** Relocation changes related keys together; restore connection policy even
|
|
3
|
+
* when BEGIN or rollback fails, and never reuse a connection with unknown FK mode. */
|
|
4
|
+
export function foreignKeyTransaction(db, operation) {
|
|
5
|
+
const foreignKeys = db.get("PRAGMA foreign_keys").foreign_keys;
|
|
6
|
+
let value, primary, failed = false, committed = false;
|
|
7
|
+
try {
|
|
8
|
+
db.exec("PRAGMA foreign_keys = OFF");
|
|
9
|
+
if (db.get("PRAGMA foreign_keys")?.foreign_keys !== 0)
|
|
10
|
+
throw new AppError("foreign_key_transaction_active", "Key relocation requires its own connection transaction", 1);
|
|
11
|
+
value = db.writeTransaction(() => { db.afterCommit(() => { committed = true; }); return operation(); });
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
primary = error;
|
|
15
|
+
failed = true;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
db.exec(`PRAGMA foreign_keys = ${foreignKeys ? "ON" : "OFF"}`);
|
|
19
|
+
if (db.get("PRAGMA foreign_keys")?.foreign_keys !== foreignKeys)
|
|
20
|
+
throw new Error("Foreign-key enforcement was not restored");
|
|
21
|
+
}
|
|
22
|
+
catch (restore) {
|
|
23
|
+
const details = { foreign_keys_restore_error: String(restore), effect: committed ? "committed" : "unknown" };
|
|
24
|
+
try {
|
|
25
|
+
db.close?.();
|
|
26
|
+
}
|
|
27
|
+
catch (close) {
|
|
28
|
+
details.connection_close_error = String(close);
|
|
29
|
+
}
|
|
30
|
+
if (primary instanceof AppError)
|
|
31
|
+
Object.assign(primary.details, details);
|
|
32
|
+
else
|
|
33
|
+
primary = new AppError("foreign_key_restoration_failed", primary instanceof Error ? primary.message : "Cannot restore foreign-key enforcement", 1, { ...details, ...(failed ? { cause: String(primary) } : {}) });
|
|
34
|
+
failed = true;
|
|
35
|
+
}
|
|
36
|
+
if (failed)
|
|
37
|
+
throw primary;
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { AppError } from "../shared/errors.js";
|
|
5
|
+
/** Durable immutable candidate; only a later SQL receipt grants it authority. */
|
|
6
|
+
export function publishImmutableInput(file, bytes, conflictCode) {
|
|
7
|
+
const expected = crypto.createHash("sha256").update(bytes).digest("hex");
|
|
8
|
+
const verify = () => {
|
|
9
|
+
const stat = fs.lstatSync(file);
|
|
10
|
+
if (!stat.isFile() || stat.isSymbolicLink() || crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex") !== expected)
|
|
11
|
+
throw new AppError(conflictCode, "Immutable input differs from the accepted bytes", 1, { path: file, expected_sha256: expected });
|
|
12
|
+
};
|
|
13
|
+
const temporary = `${file}.tmp-${crypto.randomUUID()}`;
|
|
14
|
+
try {
|
|
15
|
+
fs.writeFileSync(temporary, bytes, { flag: "wx", mode: 0o600 });
|
|
16
|
+
const descriptor = fs.openSync(temporary, "r");
|
|
17
|
+
try {
|
|
18
|
+
fs.fsyncSync(descriptor);
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
fs.closeSync(descriptor);
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
fs.linkSync(temporary, file);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (error.code !== "EEXIST")
|
|
28
|
+
throw error;
|
|
29
|
+
verify();
|
|
30
|
+
}
|
|
31
|
+
const directory = fs.openSync(path.dirname(file), "r");
|
|
32
|
+
try {
|
|
33
|
+
fs.fsyncSync(directory);
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
fs.closeSync(directory);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
try {
|
|
41
|
+
fs.rmSync(temporary, { force: true });
|
|
42
|
+
}
|
|
43
|
+
catch { /* Unreferenced staging garbage must not mask the accepted input or primary error. */ }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { AppError } from "../shared/errors.js";
|
|
5
|
+
/** Outside the replaced runtime tree, so a copied source receipt cannot make
|
|
6
|
+
* an incomplete target look ready. A failed target is diagnostic evidence. */
|
|
7
|
+
export function importGuardPath(home) {
|
|
8
|
+
const absolute = canonicalHome(home);
|
|
9
|
+
return path.join(path.dirname(absolute), `.${path.basename(absolute)}.dd-flow-import.json`);
|
|
10
|
+
}
|
|
11
|
+
function canonicalHome(home) {
|
|
12
|
+
let existing = path.resolve(home);
|
|
13
|
+
const suffix = [];
|
|
14
|
+
while (!fs.existsSync(existing)) {
|
|
15
|
+
suffix.unshift(path.basename(existing));
|
|
16
|
+
existing = path.dirname(existing);
|
|
17
|
+
}
|
|
18
|
+
return path.join(fs.realpathSync(existing), ...suffix);
|
|
19
|
+
}
|
|
20
|
+
function readImportGuard(home) {
|
|
21
|
+
const file = importGuardPath(home);
|
|
22
|
+
if (!fs.existsSync(file))
|
|
23
|
+
return null;
|
|
24
|
+
let guard;
|
|
25
|
+
try {
|
|
26
|
+
guard = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
throw new AppError("snapshot_import_incomplete", "Snapshot import guard is unreadable; preserve this target for diagnosis", 1, { guard_path: file });
|
|
30
|
+
}
|
|
31
|
+
if (guard.schema_id !== "dd-flow/import-guard@1" || guard.runtime_home !== canonicalHome(home)
|
|
32
|
+
|| !/^[0-9a-f-]{36}$/.test(guard.import_id) || !/^[a-f0-9]{64}$/.test(guard.source_manifest_sha256)
|
|
33
|
+
|| !path.isAbsolute(guard.project_root) || !path.isAbsolute(guard.workspace_root)
|
|
34
|
+
|| !["in_progress", "completed"].includes(guard.status)) {
|
|
35
|
+
throw new AppError("snapshot_import_incomplete", "Snapshot import guard does not identify this target", 1, { guard_path: file });
|
|
36
|
+
}
|
|
37
|
+
return guard;
|
|
38
|
+
}
|
|
39
|
+
export function assertImportAdmission(home, permit) {
|
|
40
|
+
const guard = readImportGuard(home);
|
|
41
|
+
if (guard?.status === "in_progress" && guard.import_id !== permit)
|
|
42
|
+
throw new AppError("snapshot_import_incomplete", "Snapshot import has not completed; preserve this target for diagnosis and restore into fresh targets", 1, { guard_path: importGuardPath(home), import_id: guard.import_id });
|
|
43
|
+
}
|
|
44
|
+
function durableWrite(file, bytes, create) {
|
|
45
|
+
const target = create ? file : `${file}.tmp-${crypto.randomUUID()}`;
|
|
46
|
+
try {
|
|
47
|
+
fs.writeFileSync(target, bytes, { flag: "wx", mode: 0o600 });
|
|
48
|
+
const descriptor = fs.openSync(target, "r");
|
|
49
|
+
try {
|
|
50
|
+
fs.fsyncSync(descriptor);
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
fs.closeSync(descriptor);
|
|
54
|
+
}
|
|
55
|
+
if (!create)
|
|
56
|
+
fs.renameSync(target, file);
|
|
57
|
+
const directory = fs.openSync(path.dirname(file), "r");
|
|
58
|
+
try {
|
|
59
|
+
fs.fsyncSync(directory);
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
fs.closeSync(directory);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
if (!create)
|
|
67
|
+
fs.rmSync(target, { force: true });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export function beginSnapshotImport(input) {
|
|
71
|
+
const importId = crypto.randomUUID();
|
|
72
|
+
const guard = { schema_id: "dd-flow/import-guard@1", import_id: importId, status: "in_progress", source_manifest_sha256: input.sourceManifestSha256,
|
|
73
|
+
runtime_home: canonicalHome(input.home), project_root: path.resolve(input.projectRoot), workspace_root: path.resolve(input.workspaceRoot) };
|
|
74
|
+
durableWrite(importGuardPath(input.home), `${JSON.stringify(guard)}\n`, true);
|
|
75
|
+
return importId;
|
|
76
|
+
}
|
|
77
|
+
/** A fork with its own matching intent may rebuild a completed import when
|
|
78
|
+
* its final fork receipt was lost. Fence the existing home before removing any
|
|
79
|
+
* copied files; an interrupted rebuild remains closed to productive opens. */
|
|
80
|
+
export function rearmCompletedSnapshotImport(input) {
|
|
81
|
+
const guard = readImportGuard(input.home);
|
|
82
|
+
if (!guard)
|
|
83
|
+
return null;
|
|
84
|
+
if (guard.status !== "completed" || guard.project_root !== path.resolve(input.projectRoot)
|
|
85
|
+
|| guard.workspace_root !== path.resolve(input.workspaceRoot)
|
|
86
|
+
|| guard.source_manifest_sha256 !== input.sourceManifestSha256) {
|
|
87
|
+
throw new AppError("snapshot_import_incomplete", "Existing import does not match this completed fork; preserve it for diagnosis", 1, { guard_path: importGuardPath(input.home) });
|
|
88
|
+
}
|
|
89
|
+
const importId = crypto.randomUUID();
|
|
90
|
+
durableWrite(importGuardPath(input.home), `${JSON.stringify({ ...guard, import_id: importId, status: "in_progress" })}\n`, false);
|
|
91
|
+
return importId;
|
|
92
|
+
}
|
|
93
|
+
export function completeSnapshotImport(home, importId, completed) {
|
|
94
|
+
const guard = readImportGuard(home);
|
|
95
|
+
if (!guard || guard.import_id !== importId || guard.status !== "in_progress"
|
|
96
|
+
|| guard.project_root !== path.resolve(completed.projectRoot)
|
|
97
|
+
|| guard.workspace_root !== path.resolve(completed.workspaceRoot)
|
|
98
|
+
|| guard.source_manifest_sha256 !== completed.sourceManifestSha256) {
|
|
99
|
+
throw new AppError("snapshot_import_incomplete", "Import completion does not match its source and target paths", 1, { guard_path: importGuardPath(home) });
|
|
100
|
+
}
|
|
101
|
+
durableWrite(importGuardPath(home), `${JSON.stringify({ ...guard, status: "completed" })}\n`, false);
|
|
102
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { AppError, errorRecord } from "../shared/errors.js";
|
|
2
|
+
/** Cleanup must not replace the operation's primary failure, including thrown null/undefined. */
|
|
3
|
+
export function withStoreCleanup(operation, cleanup, details = () => ({})) {
|
|
4
|
+
let failed = false;
|
|
5
|
+
let primary;
|
|
6
|
+
let result;
|
|
7
|
+
try {
|
|
8
|
+
result = operation();
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
failed = true;
|
|
12
|
+
primary = error;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
cleanup();
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
const secondary = { ...details(), store_cleanup_errors: [errorRecord(error)] };
|
|
19
|
+
if (failed) {
|
|
20
|
+
const original = errorRecord(primary);
|
|
21
|
+
throw new AppError(original.code, original.message, original.exitCode, {
|
|
22
|
+
...original.details, ...secondary,
|
|
23
|
+
store_cleanup_errors: [...(Array.isArray(original.details.store_cleanup_errors) ? original.details.store_cleanup_errors : []), ...secondary.store_cleanup_errors]
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
throw new AppError("store_cleanup_failed", "Store operation completed but cleanup failed", 1, secondary);
|
|
27
|
+
}
|
|
28
|
+
if (failed)
|
|
29
|
+
throw primary;
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
/** A failed rollback makes even an enclosing savepoint's connection unsafe to reuse. */
|
|
33
|
+
export function rollbackStoreTransaction(db, sql, primary) {
|
|
34
|
+
try {
|
|
35
|
+
db.exec(sql);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
let closeError;
|
|
39
|
+
try {
|
|
40
|
+
db.close();
|
|
41
|
+
}
|
|
42
|
+
catch (close) {
|
|
43
|
+
closeError = errorRecord(close);
|
|
44
|
+
}
|
|
45
|
+
const original = errorRecord(primary);
|
|
46
|
+
throw new AppError(original.code, original.message, original.exitCode, {
|
|
47
|
+
...original.details, transaction_cleanup_error: errorRecord(error),
|
|
48
|
+
...(closeError ? { transaction_close_error: closeError } : {})
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
throw primary;
|
|
52
|
+
}
|
package/dist/storage/paths.js
CHANGED
|
@@ -15,6 +15,8 @@ export function resolveProjectRoot(root) {
|
|
|
15
15
|
if (!fs.existsSync(absolute)) {
|
|
16
16
|
throw new AppError("not_found", `Project root does not exist: ${absolute}`, 1);
|
|
17
17
|
}
|
|
18
|
+
if (!fs.statSync(absolute).isDirectory())
|
|
19
|
+
throw new AppError("validation", "Project root must be a directory", 2, { project_root: absolute });
|
|
18
20
|
return fs.realpathSync(absolute);
|
|
19
21
|
}
|
|
20
22
|
/** Resolve an existing path to its filesystem identity, or normalize a future path. */
|
|
@@ -24,10 +26,16 @@ export function canonicalPath(value, mustExist = true) {
|
|
|
24
26
|
return fs.realpathSync(absolute);
|
|
25
27
|
if (mustExist)
|
|
26
28
|
throw new AppError("not_found", `Path does not exist: ${absolute}`, 1, { path: absolute });
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
const missing = [];
|
|
30
|
+
let parent = absolute;
|
|
31
|
+
while (!fs.existsSync(parent)) {
|
|
32
|
+
missing.unshift(path.basename(parent));
|
|
33
|
+
const next = path.dirname(parent);
|
|
34
|
+
if (next === parent)
|
|
35
|
+
return absolute;
|
|
36
|
+
parent = next;
|
|
37
|
+
}
|
|
38
|
+
return path.join(fs.realpathSync(parent), ...missing);
|
|
31
39
|
}
|
|
32
40
|
export function assertPathWithin(root, candidate, label = "path") {
|
|
33
41
|
const canonicalRoot = canonicalPath(root);
|
|
@@ -100,6 +108,11 @@ export function stateJsonPath(projectRoot, protocolId) {
|
|
|
100
108
|
export function planJsonPath(projectRoot, protocolId) {
|
|
101
109
|
return path.join(protocolDir(projectRoot, protocolId), "plan.json");
|
|
102
110
|
}
|
|
111
|
+
/** Project canonical plan takes precedence over a retained workspace location. */
|
|
112
|
+
export function resolveProtocolPlanPath(projectRoot, protocolId, recordedPath) {
|
|
113
|
+
const canonical = planJsonPath(projectRoot, protocolId);
|
|
114
|
+
return fs.existsSync(canonical) ? canonical : recordedPath;
|
|
115
|
+
}
|
|
103
116
|
export function projectHome(ddFlowHome, projectId) {
|
|
104
117
|
return path.join(ddFlowHome, "projects", projectId);
|
|
105
118
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
import { processStartedAt } from "../services/managed-processes.js";
|
|
5
|
+
import { AppError, errorRecord } from "../shared/errors.js";
|
|
6
|
+
/** One filesystem publisher per entity; no SQLite writer is held while waiting. */
|
|
7
|
+
export function withProjectionLock(lockPath, operation) {
|
|
8
|
+
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
9
|
+
const owner = { pid: process.pid, started_at: selfStartedAt() };
|
|
10
|
+
const wait = new Int32Array(new SharedArrayBuffer(4));
|
|
11
|
+
const observed = new Map();
|
|
12
|
+
for (let tries = 0;; tries++) {
|
|
13
|
+
try {
|
|
14
|
+
fs.mkdirSync(lockPath);
|
|
15
|
+
try {
|
|
16
|
+
fs.writeFileSync(`${lockPath}/owner.json`, JSON.stringify(owner));
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
fs.rmdirSync(lockPath);
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (error.code !== "EEXIST")
|
|
26
|
+
throw error;
|
|
27
|
+
let stale = false;
|
|
28
|
+
let observedInode;
|
|
29
|
+
try {
|
|
30
|
+
observedInode = fs.statSync(lockPath).ino;
|
|
31
|
+
const current = JSON.parse(fs.readFileSync(`${lockPath}/owner.json`, "utf8"));
|
|
32
|
+
try {
|
|
33
|
+
process.kill(current.pid, 0);
|
|
34
|
+
if (!observed.has(current.pid))
|
|
35
|
+
observed.set(current.pid, processStartedAt(current.pid));
|
|
36
|
+
stale = Boolean(current.started_at && observed.get(current.pid) !== current.started_at);
|
|
37
|
+
}
|
|
38
|
+
catch (probe) {
|
|
39
|
+
stale = probe.code === "ESRCH";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
stale = false;
|
|
44
|
+
}
|
|
45
|
+
if (stale) {
|
|
46
|
+
try {
|
|
47
|
+
if (observedInode !== undefined && fs.statSync(lockPath).ino === observedInode) {
|
|
48
|
+
const moved = `${lockPath}.stale-${crypto.randomUUID()}`;
|
|
49
|
+
fs.renameSync(lockPath, moved);
|
|
50
|
+
fs.rmSync(moved, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch { /* Another publisher may own it now. */ }
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (tries >= 80)
|
|
57
|
+
throw new AppError("publication_pending", "Projection publisher is busy", 1, { effect: "committed", lock_path: lockPath });
|
|
58
|
+
Atomics.wait(wait, 0, 0, 50);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
return operation();
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
throw new AppError("publication_pending", "Committed projection needs reconciliation", 1, { effect: "committed", cause: errorRecord(error), lock_path: lockPath });
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
fs.rmSync(lockPath, { recursive: true, force: true });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
let ownStart;
|
|
72
|
+
function selfStartedAt() { if (ownStart === undefined)
|
|
73
|
+
ownStart = processStartedAt(process.pid); return ownStart; }
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
/** Read-only lookup; scope applies to exact IDs as well as short aliases. */
|
|
3
|
+
export function resolveWorkReference(context, id, scope = {}) {
|
|
4
|
+
const clauses = ["1 = 1"];
|
|
5
|
+
const params = [];
|
|
6
|
+
if (scope.projectId) {
|
|
7
|
+
clauses.push("project_id = ?");
|
|
8
|
+
params.push(scope.projectId);
|
|
9
|
+
}
|
|
10
|
+
if (scope.runId) {
|
|
11
|
+
clauses.push("run_id = ?");
|
|
12
|
+
params.push(scope.runId);
|
|
13
|
+
}
|
|
14
|
+
const where = clauses.join(" AND ");
|
|
15
|
+
const exact = context.db.get(`SELECT * FROM works WHERE ${where} AND work_id = ?`, [...params, id]);
|
|
16
|
+
if (exact)
|
|
17
|
+
return exact;
|
|
18
|
+
const matches = /^WRK-\d{3,}$/.test(id)
|
|
19
|
+
? context.db.all(`SELECT * FROM works WHERE ${where} AND work_id LIKE ? ORDER BY work_id`, [...params, `${id}-%`]) : [];
|
|
20
|
+
if (matches.length !== 1)
|
|
21
|
+
throw new AppError(matches.length ? "ambiguous_work_alias" : "not_found", matches.length ? "Short Work alias is ambiguous in the current RUN" : "Work is not registered in the current RUN", 1, { work_id: id, matches: matches.map(work => work.work_id) });
|
|
22
|
+
return matches[0];
|
|
23
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { AppError } from "../shared/errors.js";
|
|
3
|
+
import { rollbackStoreTransaction } from "./native-cleanup.js";
|
|
4
|
+
export const STORE_WRITER_VERSION = 2;
|
|
5
|
+
const version = STORE_WRITER_VERSION;
|
|
6
|
+
const table = "dd_flow_writer_contract";
|
|
7
|
+
const quote = (name) => `"${name.replaceAll('"', '""')}"`;
|
|
8
|
+
/** Publish additive schema and its writer fences in the same transaction. */
|
|
9
|
+
export function executeStoreSchema(db, sql, initialize = false) {
|
|
10
|
+
const ownsTransaction = !db.isTransaction;
|
|
11
|
+
db.exec(ownsTransaction ? "BEGIN IMMEDIATE" : "SAVEPOINT dd_flow_schema_change");
|
|
12
|
+
try {
|
|
13
|
+
const protectedStore = readStoreWriterContract(db) !== null;
|
|
14
|
+
if (initialize && !protectedStore && db.prepare("SELECT 1 FROM sqlite_schema WHERE type = 'table' AND name NOT LIKE 'sqlite_%' LIMIT 1").get()) {
|
|
15
|
+
throw new AppError("store_initialization_conflict", "Another writer initialized this store; explicit migration is required", 1);
|
|
16
|
+
}
|
|
17
|
+
const before = db.prepare("PRAGMA schema_version").get();
|
|
18
|
+
if (typeof sql === "string")
|
|
19
|
+
db.exec(sql);
|
|
20
|
+
else
|
|
21
|
+
sql();
|
|
22
|
+
const after = db.prepare("PRAGMA schema_version").get();
|
|
23
|
+
if (initialize || (protectedStore && before.schema_version !== after.schema_version))
|
|
24
|
+
installStoreWriterContract(db);
|
|
25
|
+
db.exec(ownsTransaction ? "COMMIT" : "RELEASE dd_flow_schema_change");
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
rollbackStoreTransaction(db, ownsTransaction ? "ROLLBACK" : "ROLLBACK TO dd_flow_schema_change; RELEASE dd_flow_schema_change", error);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Registration is connection-local and never changes historical input bytes. */
|
|
32
|
+
export function registerStoreWriter(db) {
|
|
33
|
+
const current = readStoreWriterContract(db);
|
|
34
|
+
if (current !== null && current !== version)
|
|
35
|
+
throw new AppError("store_writer_incompatible", "This CLI cannot write the store's required contract", 1, { required: current, supported: version });
|
|
36
|
+
db.function("dd_flow_writer_version", () => version);
|
|
37
|
+
}
|
|
38
|
+
export function readStoreWriterContract(db) {
|
|
39
|
+
if (!db.prepare("SELECT 1 FROM sqlite_schema WHERE type = 'table' AND name = ?").get(table))
|
|
40
|
+
return null;
|
|
41
|
+
const rows = db.prepare(`SELECT id, version FROM ${table}`).all();
|
|
42
|
+
if (rows.length !== 1 || rows[0]?.id !== 1 || !Number.isSafeInteger(rows[0].version) || rows[0].version < 1)
|
|
43
|
+
throw new AppError("store_writer_contract_invalid", "Store writer contract is malformed", 1);
|
|
44
|
+
return rows[0].version;
|
|
45
|
+
}
|
|
46
|
+
/** Install only at an explicit migration boundary; ordinary reads never call it. */
|
|
47
|
+
export function installStoreWriterContract(db) {
|
|
48
|
+
registerStoreWriter(db);
|
|
49
|
+
const ownsTransaction = !db.isTransaction;
|
|
50
|
+
if (ownsTransaction)
|
|
51
|
+
db.exec("BEGIN IMMEDIATE");
|
|
52
|
+
try {
|
|
53
|
+
db.exec(`CREATE TABLE IF NOT EXISTS ${table} (id INTEGER PRIMARY KEY CHECK (id = 1), version INTEGER NOT NULL);
|
|
54
|
+
INSERT OR IGNORE INTO ${table} (id, version) VALUES (1, ${version});`);
|
|
55
|
+
if (readStoreWriterContract(db) !== version)
|
|
56
|
+
throw new AppError("store_writer_incompatible", "Store writer contract changed before migration", 1);
|
|
57
|
+
const tables = db.prepare("SELECT name FROM sqlite_schema WHERE type = 'table' AND name NOT LIKE 'sqlite_%'").all();
|
|
58
|
+
for (const target of tables) {
|
|
59
|
+
const key = crypto.createHash("sha256").update(target.name).digest("hex").slice(0, 24);
|
|
60
|
+
for (const action of ["INSERT", "UPDATE", "DELETE"]) {
|
|
61
|
+
const trigger = quote(`dd_flow_writer_${key}_${action.toLowerCase()}`);
|
|
62
|
+
db.exec(`DROP TRIGGER IF EXISTS ${trigger};
|
|
63
|
+
CREATE TRIGGER ${trigger} BEFORE ${action} ON ${quote(target.name)} BEGIN
|
|
64
|
+
SELECT CASE WHEN dd_flow_writer_version() <> (SELECT version FROM ${table} WHERE id = 1)
|
|
65
|
+
OR (SELECT COUNT(*) FROM ${table}) <> 1 THEN RAISE(ABORT, 'store_writer_incompatible') END;
|
|
66
|
+
END;`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (ownsTransaction)
|
|
70
|
+
db.exec("COMMIT");
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
if (ownsTransaction)
|
|
74
|
+
rollbackStoreTransaction(db, "ROLLBACK", error);
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
import { DatabaseSync } from "node:sqlite";
|
|
5
|
+
import { AppError } from "../shared/errors.js";
|
|
6
|
+
import { installStoreWriterContract, readStoreWriterContract, registerStoreWriter, STORE_WRITER_VERSION } from "./writer-contract.js";
|
|
7
|
+
import { rollbackStoreTransaction, withStoreCleanup } from "./native-cleanup.js";
|
|
8
|
+
/** Explicit one-store cutover. The caller must separately drain external owners. */
|
|
9
|
+
export function migrateStoreWriter(file, backup) {
|
|
10
|
+
if (!path.isAbsolute(file) || !path.isAbsolute(backup) || file === backup || !fs.existsSync(file) || !fs.statSync(file).isFile())
|
|
11
|
+
throw new AppError("store_migration_path_invalid", "Migration requires an existing database and a new absolute backup path", 1);
|
|
12
|
+
const source = fs.realpathSync(file);
|
|
13
|
+
const destination = path.join(fs.realpathSync(path.dirname(backup)), path.basename(backup));
|
|
14
|
+
if (fs.lstatSync(file).isSymbolicLink() || [source, `${source}-wal`, `${source}-shm`, `${source}-journal`].includes(destination))
|
|
15
|
+
throw new AppError("store_migration_path_invalid", "Backup cannot alias the source database or its SQLite sidecars", 1);
|
|
16
|
+
let existing;
|
|
17
|
+
try {
|
|
18
|
+
existing = fs.lstatSync(backup);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
if (error.code !== "ENOENT")
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
if (existing) {
|
|
25
|
+
if (!existing.isFile() || existing.isSymbolicLink())
|
|
26
|
+
throw new AppError("store_migration_path_invalid", "Backup path already exists", 1);
|
|
27
|
+
const reader = new DatabaseSync(file, { readOnly: true });
|
|
28
|
+
return withStoreCleanup(() => {
|
|
29
|
+
const recorded = reader.prepare("SELECT 1 FROM sqlite_schema WHERE type = 'table' AND name = 'dd_flow_writer_migrations'").get()
|
|
30
|
+
? reader.prepare("SELECT receipt_json FROM dd_flow_writer_migrations WHERE backup_path = ?").get(destination) : null;
|
|
31
|
+
const receipt = recorded ? JSON.parse(String(recorded.receipt_json)) : null;
|
|
32
|
+
if (!receipt || receipt.database !== file || receipt.backup !== backup || receipt.writer_version !== readStoreWriterContract(reader) || receipt.backup_sha256 !== crypto.createHash("sha256").update(fs.readFileSync(backup)).digest("hex")) {
|
|
33
|
+
throw new AppError("store_migration_path_invalid", "Existing backup has no matching committed migration receipt", 1);
|
|
34
|
+
}
|
|
35
|
+
return receipt;
|
|
36
|
+
}, () => reader.close());
|
|
37
|
+
}
|
|
38
|
+
const db = new DatabaseSync(file);
|
|
39
|
+
let committed = false;
|
|
40
|
+
return withStoreCleanup(() => {
|
|
41
|
+
db.exec("PRAGMA busy_timeout = 1000");
|
|
42
|
+
registerStoreWriter(db);
|
|
43
|
+
db.exec("BEGIN IMMEDIATE");
|
|
44
|
+
try {
|
|
45
|
+
const previous = readStoreWriterContract(db);
|
|
46
|
+
const tables = new Set(db.prepare("SELECT name FROM sqlite_schema WHERE type = 'table'").all().map(row => row.name));
|
|
47
|
+
const gates = [
|
|
48
|
+
["managed_processes", "state IS NULL OR state NOT IN ('stopped','failed')"],
|
|
49
|
+
["run_controllers", "status IS NULL OR status NOT IN ('completed','stop_target_reached','cancelled','superseded')"],
|
|
50
|
+
["run_control_workers", "status IS NULL OR status NOT IN ('completed','failed','superseded')"],
|
|
51
|
+
["work_sessions", "status IS NULL OR status NOT IN ('completed','failed','cancelled')"]
|
|
52
|
+
];
|
|
53
|
+
for (const [table, predicate] of gates) {
|
|
54
|
+
if (tables.has(table) && db.prepare(`SELECT 1 FROM ${table} WHERE ${predicate} LIMIT 1`).get())
|
|
55
|
+
throw new AppError("store_migration_not_drained", "Store still contains unresolved writer ownership", 1, { table });
|
|
56
|
+
}
|
|
57
|
+
// Explicit drained cutover, not a live-runtime writer path: keep backup, durability
|
|
58
|
+
// and hash inside this barrier so the receipt describes exactly the fenced source.
|
|
59
|
+
// A separate reader sees committed WAL while this connection prevents new writes.
|
|
60
|
+
const reader = new DatabaseSync(file, { readOnly: true });
|
|
61
|
+
withStoreCleanup(() => reader.exec(`VACUUM INTO '${backup.replaceAll("'", "''")}'`), () => reader.close());
|
|
62
|
+
const descriptor = fs.openSync(backup, "r");
|
|
63
|
+
withStoreCleanup(() => fs.fsyncSync(descriptor), () => fs.closeSync(descriptor));
|
|
64
|
+
const directory = fs.openSync(path.dirname(backup), "r");
|
|
65
|
+
withStoreCleanup(() => fs.fsyncSync(directory), () => fs.closeSync(directory));
|
|
66
|
+
const verification = new DatabaseSync(backup, { readOnly: true });
|
|
67
|
+
withStoreCleanup(() => {
|
|
68
|
+
const check = verification.prepare("PRAGMA integrity_check").get();
|
|
69
|
+
if (Object.values(check)[0] !== "ok" || readStoreWriterContract(verification) !== previous)
|
|
70
|
+
throw new AppError("store_migration_backup_invalid", "Backup failed integrity or contract verification", 1);
|
|
71
|
+
}, () => verification.close());
|
|
72
|
+
const receipt = { database: file, backup, backup_sha256: crypto.createHash("sha256").update(fs.readFileSync(backup)).digest("hex"), previous_version: previous, writer_version: STORE_WRITER_VERSION };
|
|
73
|
+
db.exec("CREATE TABLE IF NOT EXISTS dd_flow_writer_migrations (backup_path TEXT PRIMARY KEY, receipt_json TEXT NOT NULL)");
|
|
74
|
+
db.prepare("INSERT INTO dd_flow_writer_migrations VALUES (?, ?)").run(destination, JSON.stringify(receipt));
|
|
75
|
+
installStoreWriterContract(db);
|
|
76
|
+
db.exec("COMMIT");
|
|
77
|
+
committed = true;
|
|
78
|
+
return receipt;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
rollbackStoreTransaction(db, "ROLLBACK", error);
|
|
82
|
+
}
|
|
83
|
+
}, () => { if (db.isOpen)
|
|
84
|
+
db.close(); }, () => ({ effect: committed ? "committed" : "uncommitted" }));
|
|
85
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deksden-com/dd-flow-cli",
|
|
3
|
-
"version": "0.9.0-beta.
|
|
3
|
+
"version": "0.9.0-beta.111",
|
|
4
4
|
"description": "Mechanical runtime CLI for dd-flow workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"dd-flow": "
|
|
7
|
+
"dd-flow": "dist/cli.js",
|
|
8
|
+
"dd-codex": "dist/harness-runtime/bin/dd-codex.mjs",
|
|
9
|
+
"dd-zcode": "dist/harness-runtime/bin/dd-zcode.mjs",
|
|
10
|
+
"dd-grok": "dist/harness-runtime/bin/dd-grok.mjs",
|
|
11
|
+
"dd-opencode": "dist/harness-runtime/bin/dd-opencode.mjs",
|
|
12
|
+
"dd-agy": "dist/harness-runtime/bin/dd-agy.mjs",
|
|
13
|
+
"dd-droid": "dist/harness-runtime/bin/dd-droid.mjs"
|
|
8
14
|
},
|
|
9
15
|
"files": [
|
|
10
16
|
"dist",
|
|
@@ -16,6 +22,20 @@
|
|
|
16
22
|
"node": ">=26.0.0",
|
|
17
23
|
"pnpm": ">=10.0.0"
|
|
18
24
|
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc -p tsconfig.build.json && node scripts/generate-build-info.mjs && node scripts/copy-assets.mjs",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"lint": "eslint . --max-warnings=0",
|
|
29
|
+
"test": "pnpm test:release && pnpm test:integration && pnpm test:runtime-sensitive",
|
|
30
|
+
"test:release": "node --test scripts/verify-release-build.test.mjs && vitest run --pool=forks --no-file-parallelism test/publish-release.test.ts",
|
|
31
|
+
"test:integration": "vitest run --pool=forks --no-file-parallelism --exclude test/publish-release.test.ts --exclude test/run-control-worker.test.ts --exclude test/native-adapter-contracts.test.ts",
|
|
32
|
+
"test:runtime-sensitive": "vitest run --pool=forks --no-file-parallelism test/run-control-worker.test.ts test/native-adapter-contracts.test.ts",
|
|
33
|
+
"changeset": "changeset",
|
|
34
|
+
"version-packages": "changeset version",
|
|
35
|
+
"release:candidate:pack": "node scripts/release-candidate.mjs pack",
|
|
36
|
+
"release:candidate:accept": "node scripts/release-candidate.mjs accept",
|
|
37
|
+
"release:publish": "node scripts/publish-release.mjs"
|
|
38
|
+
},
|
|
19
39
|
"devDependencies": {
|
|
20
40
|
"@changesets/cli": "^2.31.0",
|
|
21
41
|
"@eslint/js": "^9.39.1",
|
|
@@ -34,14 +54,5 @@
|
|
|
34
54
|
"publishConfig": {
|
|
35
55
|
"access": "public"
|
|
36
56
|
},
|
|
37
|
-
"license": "MIT"
|
|
38
|
-
|
|
39
|
-
"build": "tsc -p tsconfig.build.json && node scripts/generate-build-info.mjs && node scripts/copy-assets.mjs",
|
|
40
|
-
"typecheck": "tsc --noEmit",
|
|
41
|
-
"lint": "eslint . --max-warnings=0",
|
|
42
|
-
"test": "vitest run",
|
|
43
|
-
"changeset": "changeset",
|
|
44
|
-
"version-packages": "changeset version",
|
|
45
|
-
"release": "pnpm run build && changeset publish"
|
|
46
|
-
}
|
|
47
|
-
}
|
|
57
|
+
"license": "MIT"
|
|
58
|
+
}
|