@ricsam/r5d-worker 0.0.136 → 0.0.138
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -94
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/main.mjs +46 -7537
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/personal/action-scheduler.mjs +31 -0
- package/dist/mjs/personal/client.mjs +215 -0
- package/dist/mjs/personal/runtime.mjs +328 -0
- package/dist/mjs/personal/tcp.mjs +217 -0
- package/dist/mjs/runtime/adapter.mjs +42 -0
- package/dist/mjs/runtime/client.mjs +95 -0
- package/dist/mjs/runtime/daemon.mjs +131 -0
- package/dist/mjs/runtime/executor.mjs +436 -0
- package/dist/mjs/runtime/index.mjs +15 -0
- package/dist/mjs/runtime/protocol.mjs +139 -0
- package/dist/mjs/runtime/pty-host.mjs +156 -0
- package/dist/mjs/runtime/pty.mjs +127 -0
- package/dist/mjs/runtime/releases/artifacts.mjs +201 -0
- package/dist/mjs/runtime/releases/grants.mjs +93 -0
- package/dist/mjs/runtime/releases/main.mjs +101 -0
- package/dist/mjs/runtime/releases/manager.mjs +541 -0
- package/dist/mjs/runtime/releases/process.mjs +88 -0
- package/dist/mjs/runtime/releases/retirement.mjs +18 -0
- package/dist/mjs/runtime/releases/rpc-client.mjs +184 -0
- package/dist/mjs/runtime/releases/rpc-main.mjs +213 -0
- package/dist/mjs/runtime/releases/rpc-protocol.mjs +103 -0
- package/dist/mjs/runtime/storage.mjs +156 -0
- package/dist/mjs/runtime/workspace/artifacts.mjs +121 -0
- package/dist/mjs/runtime/workspace/authority.mjs +1355 -0
- package/dist/mjs/runtime/workspace/contracts.mjs +60 -0
- package/dist/mjs/runtime/workspace/file-write.mjs +23 -0
- package/dist/mjs/runtime/workspace/files.mjs +308 -0
- package/dist/mjs/runtime/workspace/http-body.mjs +45 -0
- package/dist/mjs/runtime/workspace/storage-client.mjs +59 -0
- package/dist/mjs/runtime/workspace/storage-wire.mjs +53 -0
- package/dist/types/main.d.ts +0 -1167
- package/dist/types/personal/action-scheduler.d.ts +23 -0
- package/dist/types/personal/client.d.ts +14 -0
- package/dist/types/personal/runtime.d.ts +121 -0
- package/dist/types/personal/tcp.d.ts +14 -0
- package/dist/types/runtime/client.d.ts +17 -0
- package/dist/types/runtime/daemon.d.ts +8 -0
- package/dist/types/runtime/executor.d.ts +35 -0
- package/dist/types/runtime/index.d.ts +5 -0
- package/dist/types/runtime/protocol.d.ts +544 -0
- package/dist/types/runtime/pty.d.ts +30 -0
- package/dist/types/runtime/releases/artifacts.d.ts +51 -0
- package/dist/types/runtime/releases/grants.d.ts +16 -0
- package/dist/types/runtime/releases/manager.d.ts +145 -0
- package/dist/types/runtime/releases/process.d.ts +14 -0
- package/dist/types/runtime/releases/retirement.d.ts +15 -0
- package/dist/types/runtime/releases/rpc-client.d.ts +52 -0
- package/dist/types/runtime/releases/rpc-main.d.ts +10 -0
- package/dist/types/runtime/releases/rpc-protocol.d.ts +1139 -0
- package/dist/types/runtime/storage.d.ts +21 -0
- package/dist/types/runtime/test-harness.d.ts +53 -0
- package/dist/types/runtime/workspace/artifacts.d.ts +30 -0
- package/dist/types/runtime/workspace/authority.d.ts +338 -0
- package/dist/types/runtime/workspace/contracts.d.ts +86 -0
- package/dist/types/runtime/workspace/file-write.d.ts +14 -0
- package/dist/types/runtime/workspace/files.d.ts +21 -0
- package/dist/types/runtime/workspace/http-body.d.ts +9 -0
- package/dist/types/runtime/workspace/storage-client.d.ts +23 -0
- package/dist/types/runtime/workspace/storage-wire.d.ts +138 -0
- package/package.json +8 -6
- package/dist/cjs/atomic-rename.cjs +0 -303
- package/dist/cjs/browser-upload.cjs +0 -73
- package/dist/cjs/cli-update.cjs +0 -108
- package/dist/cjs/command-launcher.cjs +0 -423
- package/dist/cjs/control-command-policy.cjs +0 -177
- package/dist/cjs/git-blob-hash.cjs +0 -41
- package/dist/cjs/git-identity.cjs +0 -39
- package/dist/cjs/git-process-environment.cjs +0 -133
- package/dist/cjs/inline-output.cjs +0 -62
- package/dist/cjs/main.cjs +0 -7511
- package/dist/cjs/managed-paths.cjs +0 -215
- package/dist/cjs/plan-lint.cjs +0 -165
- package/dist/cjs/plan-parser.cjs +0 -127
- package/dist/cjs/plan-paths.cjs +0 -53
- package/dist/cjs/port-forward-client.cjs +0 -206
- package/dist/cjs/process-tree.cjs +0 -117
- package/dist/cjs/project-checkout-garbage.cjs +0 -219
- package/dist/cjs/project-mirror-fetch-policy.cjs +0 -82
- package/dist/cjs/project-mirror-refs-token.cjs +0 -85
- package/dist/cjs/project-workspace-state.cjs +0 -972
- package/dist/cjs/project-worktrees.cjs +0 -1713
- package/dist/cjs/pty-output-coalescer.cjs +0 -87
- package/dist/cjs/recovery-journal-protocol.cjs +0 -56
- package/dist/cjs/recovery-journal-runtime.cjs +0 -133
- package/dist/cjs/recovery-journal-thread.cjs +0 -9
- package/dist/cjs/recovery-journal.cjs +0 -742
- package/dist/cjs/recovery-output-window.cjs +0 -77
- package/dist/cjs/recovery-protocol.cjs +0 -31
- package/dist/cjs/recovery-store.cjs +0 -252
- package/dist/cjs/registry-auth.cjs +0 -310
- package/dist/cjs/repository-transition-policy.cjs +0 -49
- package/dist/cjs/runtime-version.cjs +0 -88
- package/dist/cjs/session-file-mutations.cjs +0 -61
- package/dist/cjs/supervisor.cjs +0 -147
- package/dist/cjs/three-way-merge.cjs +0 -346
- package/dist/cjs/working-tree-mirror.cjs +0 -1872
- package/dist/cjs/workspace-automatic-sync-policy.cjs +0 -79
- package/dist/cjs/workspace-branch-incarnation-policy.cjs +0 -37
- package/dist/cjs/workspace-command-sync-policy.cjs +0 -112
- package/dist/cjs/workspace-command-targets.cjs +0 -63
- package/dist/cjs/workspace-filesystem-executor-thread.cjs +0 -36
- package/dist/cjs/workspace-filesystem-executor.cjs +0 -327
- package/dist/cjs/workspace-filesystem-job-types.cjs +0 -144
- package/dist/cjs/workspace-filesystem-jobs.cjs +0 -59
- package/dist/cjs/workspace-git-sync.cjs +0 -3450
- package/dist/cjs/workspace-hydration-ledger.cjs +0 -66
- package/dist/cjs/workspace-hydration-merge.cjs +0 -433
- package/dist/cjs/workspace-hydration-recovery-state.cjs +0 -53
- package/dist/cjs/workspace-incident-state.cjs +0 -37
- package/dist/cjs/workspace-merge-projection.cjs +0 -467
- package/dist/cjs/workspace-mount-boundary.cjs +0 -71
- package/dist/cjs/workspace-mount-hold-fence.cjs +0 -120
- package/dist/cjs/workspace-mutation-gate.cjs +0 -127
- package/dist/cjs/workspace-path-move.cjs +0 -195
- package/dist/cjs/workspace-preserve-only-policy.cjs +0 -36
- package/dist/cjs/workspace-project-config-policy.cjs +0 -53
- package/dist/cjs/workspace-projection-ledger.cjs +0 -81
- package/dist/cjs/workspace-publication-evidence.cjs +0 -46
- package/dist/cjs/workspace-sync-coalescer.cjs +0 -58
- package/dist/mjs/atomic-rename.mjs +0 -261
- package/dist/mjs/browser-upload.mjs +0 -42
- package/dist/mjs/command-launcher.mjs +0 -393
- package/dist/mjs/control-command-policy.mjs +0 -146
- package/dist/mjs/git-blob-hash.mjs +0 -16
- package/dist/mjs/git-identity.mjs +0 -15
- package/dist/mjs/git-process-environment.mjs +0 -105
- package/dist/mjs/inline-output.mjs +0 -37
- package/dist/mjs/managed-paths.mjs +0 -174
- package/dist/mjs/plan-lint.mjs +0 -129
- package/dist/mjs/plan-parser.mjs +0 -94
- package/dist/mjs/plan-paths.mjs +0 -25
- package/dist/mjs/port-forward-client.mjs +0 -172
- package/dist/mjs/project-checkout-garbage.mjs +0 -178
- package/dist/mjs/project-mirror-fetch-policy.mjs +0 -56
- package/dist/mjs/project-mirror-refs-token.mjs +0 -53
- package/dist/mjs/project-workspace-state.mjs +0 -937
- package/dist/mjs/project-worktrees.mjs +0 -1657
- package/dist/mjs/pty-output-coalescer.mjs +0 -61
- package/dist/mjs/recovery-journal-protocol.mjs +0 -30
- package/dist/mjs/recovery-journal-runtime.mjs +0 -112
- package/dist/mjs/recovery-journal-thread.mjs +0 -8
- package/dist/mjs/recovery-journal.mjs +0 -694
- package/dist/mjs/recovery-output-window.mjs +0 -53
- package/dist/mjs/recovery-protocol.mjs +0 -6
- package/dist/mjs/recovery-store.mjs +0 -217
- package/dist/mjs/registry-auth.mjs +0 -269
- package/dist/mjs/repository-transition-policy.mjs +0 -22
- package/dist/mjs/runtime-version.mjs +0 -54
- package/dist/mjs/session-file-mutations.mjs +0 -37
- package/dist/mjs/supervisor.mjs +0 -114
- package/dist/mjs/three-way-merge.mjs +0 -318
- package/dist/mjs/working-tree-mirror.mjs +0 -1817
- package/dist/mjs/workspace-automatic-sync-policy.mjs +0 -48
- package/dist/mjs/workspace-branch-incarnation-policy.mjs +0 -13
- package/dist/mjs/workspace-command-sync-policy.mjs +0 -84
- package/dist/mjs/workspace-command-targets.mjs +0 -37
- package/dist/mjs/workspace-filesystem-executor-thread.mjs +0 -38
- package/dist/mjs/workspace-filesystem-executor.mjs +0 -287
- package/dist/mjs/workspace-filesystem-job-types.mjs +0 -116
- package/dist/mjs/workspace-filesystem-jobs.mjs +0 -55
- package/dist/mjs/workspace-git-sync.mjs +0 -3407
- package/dist/mjs/workspace-hydration-ledger.mjs +0 -42
- package/dist/mjs/workspace-hydration-merge.mjs +0 -399
- package/dist/mjs/workspace-hydration-recovery-state.mjs +0 -29
- package/dist/mjs/workspace-incident-state.mjs +0 -12
- package/dist/mjs/workspace-merge-projection.mjs +0 -433
- package/dist/mjs/workspace-mount-boundary.mjs +0 -37
- package/dist/mjs/workspace-mount-hold-fence.mjs +0 -95
- package/dist/mjs/workspace-mutation-gate.mjs +0 -103
- package/dist/mjs/workspace-path-move.mjs +0 -160
- package/dist/mjs/workspace-preserve-only-policy.mjs +0 -11
- package/dist/mjs/workspace-project-config-policy.mjs +0 -27
- package/dist/mjs/workspace-projection-ledger.mjs +0 -57
- package/dist/mjs/workspace-publication-evidence.mjs +0 -21
- package/dist/mjs/workspace-sync-coalescer.mjs +0 -34
- package/dist/types/atomic-rename.d.ts +0 -78
- package/dist/types/browser-upload.d.ts +0 -7
- package/dist/types/command-launcher.d.ts +0 -104
- package/dist/types/control-command-policy.d.ts +0 -53
- package/dist/types/git-blob-hash.d.ts +0 -10
- package/dist/types/git-identity.d.ts +0 -9
- package/dist/types/git-process-environment.d.ts +0 -9
- package/dist/types/inline-output.d.ts +0 -3
- package/dist/types/managed-paths.d.ts +0 -19
- package/dist/types/plan-lint.d.ts +0 -16
- package/dist/types/plan-parser.d.ts +0 -38
- package/dist/types/plan-paths.d.ts +0 -36
- package/dist/types/port-forward-client.d.ts +0 -8
- package/dist/types/project-checkout-garbage.d.ts +0 -86
- package/dist/types/project-mirror-fetch-policy.d.ts +0 -84
- package/dist/types/project-mirror-refs-token.d.ts +0 -53
- package/dist/types/project-workspace-state.d.ts +0 -188
- package/dist/types/project-worktrees.d.ts +0 -397
- package/dist/types/pty-output-coalescer.d.ts +0 -20
- package/dist/types/recovery-journal-protocol.d.ts +0 -35
- package/dist/types/recovery-journal-runtime.d.ts +0 -12
- package/dist/types/recovery-journal-thread.d.ts +0 -1
- package/dist/types/recovery-journal.d.ts +0 -248
- package/dist/types/recovery-output-window.d.ts +0 -19
- package/dist/types/recovery-protocol.d.ts +0 -59
- package/dist/types/recovery-store.d.ts +0 -52
- package/dist/types/registry-auth.d.ts +0 -47
- package/dist/types/repository-transition-policy.d.ts +0 -26
- package/dist/types/runtime-version.d.ts +0 -1
- package/dist/types/session-file-mutations.d.ts +0 -22
- package/dist/types/supervisor-daemonized-fixture.d.ts +0 -1
- package/dist/types/supervisor-launcher-cleanup-fixture.d.ts +0 -1
- package/dist/types/supervisor-signal-fixture.d.ts +0 -1
- package/dist/types/supervisor.d.ts +0 -23
- package/dist/types/three-way-merge.d.ts +0 -77
- package/dist/types/working-tree-mirror.d.ts +0 -424
- package/dist/types/workspace-automatic-sync-policy.d.ts +0 -57
- package/dist/types/workspace-branch-incarnation-policy.d.ts +0 -14
- package/dist/types/workspace-command-sync-policy.d.ts +0 -57
- package/dist/types/workspace-command-targets.d.ts +0 -37
- package/dist/types/workspace-filesystem-executor-thread.d.ts +0 -1
- package/dist/types/workspace-filesystem-executor.d.ts +0 -123
- package/dist/types/workspace-filesystem-job-types.d.ts +0 -288
- package/dist/types/workspace-filesystem-jobs.d.ts +0 -7
- package/dist/types/workspace-git-sync.d.ts +0 -495
- package/dist/types/workspace-hydration-ledger.d.ts +0 -43
- package/dist/types/workspace-hydration-merge.d.ts +0 -95
- package/dist/types/workspace-hydration-recovery-state.d.ts +0 -10
- package/dist/types/workspace-incident-state.d.ts +0 -19
- package/dist/types/workspace-merge-projection.d.ts +0 -60
- package/dist/types/workspace-mount-boundary.d.ts +0 -10
- package/dist/types/workspace-mount-hold-fence.d.ts +0 -42
- package/dist/types/workspace-mutation-gate.d.ts +0 -42
- package/dist/types/workspace-path-move.d.ts +0 -21
- package/dist/types/workspace-preserve-only-policy.d.ts +0 -15
- package/dist/types/workspace-project-config-policy.d.ts +0 -48
- package/dist/types/workspace-projection-ledger.d.ts +0 -59
- package/dist/types/workspace-publication-evidence.d.ts +0 -17
- package/dist/types/workspace-sync-coalescer.d.ts +0 -28
- /package/dist/types/{credential-authority-lock-fixture.d.ts → runtime/adapter.d.ts} +0 -0
- /package/dist/types/{recovery-journal-stall-fixture.d.ts → runtime/releases/main.d.ts} +0 -0
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
const CONTROL_COMMAND_PROGRAM = "r5dctl";
|
|
2
|
-
const CONTROL_COMMAND_RUNTIME_MS = 12e4;
|
|
3
|
-
const CONTROL_COMMAND_MAX_ARGV = 64;
|
|
4
|
-
const CONTROL_COMMAND_MAX_LENGTH = 4096;
|
|
5
|
-
const CONTROL_GLOBAL_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set(["-p", "--project", "-s", "--session", "-b", "--branch"]);
|
|
6
|
-
const CONTROL_GLOBAL_FLAGS = /* @__PURE__ */ new Set(["--json", "-h", "--help", "-v", "--version"]);
|
|
7
|
-
const REFUSED_ANYWHERE = /* @__PURE__ */ new Set(["-f", "--follow", "--watch", "--base-url", "--config", "--token", "--api-key"]);
|
|
8
|
-
const CONTROL_VERBS = {
|
|
9
|
-
session: /* @__PURE__ */ new Set(["status", "stop", "prompt", "claim", "claims", "process-log"]),
|
|
10
|
-
sessions: /* @__PURE__ */ new Set(["recent"]),
|
|
11
|
-
describe: /* @__PURE__ */ new Set(["project", "branch", "session", "branch-operation", "branch-deletion"]),
|
|
12
|
-
get: /* @__PURE__ */ new Set(["projects", "branches", "sessions", "envs", "env"]),
|
|
13
|
-
conversation: /* @__PURE__ */ new Set(["overview", "inspect-node", "inspect-work"]),
|
|
14
|
-
ps: /* @__PURE__ */ new Set(["list", "history", "inspect", "stop"]),
|
|
15
|
-
workspace: /* @__PURE__ */ new Set(["status"]),
|
|
16
|
-
auth: /* @__PURE__ */ new Set(["status"]),
|
|
17
|
-
create: /* @__PURE__ */ new Set(["session"]),
|
|
18
|
-
update: /* @__PURE__ */ new Set(["session"]),
|
|
19
|
-
delete: /* @__PURE__ */ new Set(["session"]),
|
|
20
|
-
"answer-questions": null,
|
|
21
|
-
"answer-env-request": null
|
|
22
|
-
};
|
|
23
|
-
const CODE_LOADING_ENV_PATTERN = /^(NODE_OPTIONS|NODE_PATH|NODE_REPL_EXTERNAL_MODULE|BUN_OPTIONS|BUN_CONFIG_[A-Z_]+|LD_PRELOAD|LD_LIBRARY_PATH|LD_AUDIT|DYLD_[A-Z_]+|PATH|PATHEXT|PYTHONPATH|PERL5OPT|RUBYOPT|SHELL|ENV|BASH_ENV)$/i;
|
|
24
|
-
const BARE_WORD = /^[A-Za-z0-9_@%+=:,./-]+$/;
|
|
25
|
-
const DOUBLE_QUOTED_FORBIDDEN = /[$`\\!]/;
|
|
26
|
-
function tokenizePlainInvocation(command) {
|
|
27
|
-
if (command.length > CONTROL_COMMAND_MAX_LENGTH) return { reason: "command is longer than the control lane allows" };
|
|
28
|
-
if (/[\r\n]/.test(command)) return { reason: "multi-line commands are shell scripts" };
|
|
29
|
-
const argv = [];
|
|
30
|
-
let index = 0;
|
|
31
|
-
while (index < command.length) {
|
|
32
|
-
const char = command[index];
|
|
33
|
-
if (char === " " || char === " ") {
|
|
34
|
-
index += 1;
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
let word = "";
|
|
38
|
-
let quotedParts = 0;
|
|
39
|
-
let bareParts = 0;
|
|
40
|
-
while (index < command.length && command[index] !== " " && command[index] !== " ") {
|
|
41
|
-
const current = command[index];
|
|
42
|
-
if (current === "'") {
|
|
43
|
-
const end = command.indexOf("'", index + 1);
|
|
44
|
-
if (end < 0) return { reason: "unterminated single quote" };
|
|
45
|
-
word += command.slice(index + 1, end);
|
|
46
|
-
quotedParts += 1;
|
|
47
|
-
index = end + 1;
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
if (current === '"') {
|
|
51
|
-
const end = command.indexOf('"', index + 1);
|
|
52
|
-
if (end < 0) return { reason: "unterminated double quote" };
|
|
53
|
-
const inner = command.slice(index + 1, end);
|
|
54
|
-
if (DOUBLE_QUOTED_FORBIDDEN.test(inner)) return { reason: "double-quoted text would be expanded by the shell" };
|
|
55
|
-
word += inner;
|
|
56
|
-
quotedParts += 1;
|
|
57
|
-
index = end + 1;
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
let bare = "";
|
|
61
|
-
while (index < command.length && !/[\s'"]/.test(command[index])) {
|
|
62
|
-
bare += command[index];
|
|
63
|
-
index += 1;
|
|
64
|
-
}
|
|
65
|
-
if (!BARE_WORD.test(bare)) return { reason: `shell syntax in "${bare}" is not part of a plain invocation` };
|
|
66
|
-
word += bare;
|
|
67
|
-
bareParts += 1;
|
|
68
|
-
}
|
|
69
|
-
if (quotedParts + bareParts > 1) return { reason: "adjacent quoted and bare text is shell concatenation" };
|
|
70
|
-
argv.push(word);
|
|
71
|
-
if (argv.length > CONTROL_COMMAND_MAX_ARGV) return { reason: "too many arguments for a control command" };
|
|
72
|
-
}
|
|
73
|
-
if (argv.length === 0) return { reason: "empty command" };
|
|
74
|
-
return { argv };
|
|
75
|
-
}
|
|
76
|
-
function assertControlCommandArgv(argv) {
|
|
77
|
-
if (argv.length === 0 || argv.length > CONTROL_COMMAND_MAX_ARGV) throw new Error("Control commands are a single r5dctl invocation");
|
|
78
|
-
if (argv[0] !== CONTROL_COMMAND_PROGRAM) throw new Error(`Control commands must start with ${CONTROL_COMMAND_PROGRAM}`);
|
|
79
|
-
if (argv.reduce((length, argument) => length + (typeof argument === "string" ? argument.length : 0) + 1, 0) > CONTROL_COMMAND_MAX_LENGTH)
|
|
80
|
-
throw new Error("Control command arguments exceed the length limit");
|
|
81
|
-
for (const argument of argv) {
|
|
82
|
-
if (typeof argument !== "string" || argument.includes("\0") || /[\r\n]/.test(argument)) throw new Error("Control command arguments must be plain text");
|
|
83
|
-
if (REFUSED_ANYWHERE.has(argument.split("=", 1)[0])) throw new Error(`Control command option is not allowed (${argument.split("=", 1)[0]})`);
|
|
84
|
-
if (argument === "--") throw new Error("Control commands cannot use `--`");
|
|
85
|
-
}
|
|
86
|
-
let position = 1;
|
|
87
|
-
while (position < argv.length) {
|
|
88
|
-
const argument = argv[position];
|
|
89
|
-
if (CONTROL_GLOBAL_FLAGS.has(argument)) {
|
|
90
|
-
position += 1;
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
if (CONTROL_GLOBAL_FLAGS_WITH_VALUE.has(argument)) {
|
|
94
|
-
if (position + 1 >= argv.length) throw new Error(`Missing value for ${argument}`);
|
|
95
|
-
position += 2;
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
if (argument.startsWith("-")) throw new Error(`Global flag ${argument} is not allowed in a control command`);
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
const verb = argv[position];
|
|
102
|
-
if (verb === void 0) throw new Error("Control commands need a coordination verb (for example `r5dctl ps list`)");
|
|
103
|
-
const allowed = Object.hasOwn(CONTROL_VERBS, verb) ? CONTROL_VERBS[verb] : void 0;
|
|
104
|
-
if (allowed === void 0) throw new Error(`r5dctl ${verb} is not a coordination command`);
|
|
105
|
-
if (allowed === null) return;
|
|
106
|
-
const subject = argv[position + 1];
|
|
107
|
-
if (subject === void 0 || !allowed.has(subject)) {
|
|
108
|
-
throw new Error(`r5dctl ${verb}${subject ? ` ${subject}` : ""} is not a coordination command`);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
function resolveControlCommandExecutable(trustedPath, which, program = CONTROL_COMMAND_PROGRAM) {
|
|
112
|
-
const resolved = trustedPath ? which(program, { PATH: trustedPath }) : null;
|
|
113
|
-
if (!resolved) throw new Error(`${program} is not installed on the worker's trusted PATH; control commands are unavailable until it is`);
|
|
114
|
-
return resolved;
|
|
115
|
-
}
|
|
116
|
-
function controlCommandEnvironment(environment, trustedPath) {
|
|
117
|
-
const result = {};
|
|
118
|
-
for (const [key, value] of Object.entries(environment)) {
|
|
119
|
-
if (value === void 0 || CODE_LOADING_ENV_PATTERN.test(key)) continue;
|
|
120
|
-
result[key] = value;
|
|
121
|
-
}
|
|
122
|
-
if (trustedPath !== void 0) result.PATH = trustedPath;
|
|
123
|
-
return result;
|
|
124
|
-
}
|
|
125
|
-
function classifyControlCommand(command) {
|
|
126
|
-
const tokenized = tokenizePlainInvocation(command.trim());
|
|
127
|
-
if ("reason" in tokenized) return { control: false, reason: tokenized.reason };
|
|
128
|
-
const { argv } = tokenized;
|
|
129
|
-
if (argv[0] !== CONTROL_COMMAND_PROGRAM) return { control: false, reason: "not an r5dctl invocation" };
|
|
130
|
-
try {
|
|
131
|
-
assertControlCommandArgv(argv);
|
|
132
|
-
} catch (error) {
|
|
133
|
-
return { control: false, reason: error instanceof Error ? error.message : String(error) };
|
|
134
|
-
}
|
|
135
|
-
return { control: true, argv };
|
|
136
|
-
}
|
|
137
|
-
export {
|
|
138
|
-
CONTROL_COMMAND_MAX_ARGV,
|
|
139
|
-
CONTROL_COMMAND_MAX_LENGTH,
|
|
140
|
-
CONTROL_COMMAND_PROGRAM,
|
|
141
|
-
CONTROL_COMMAND_RUNTIME_MS,
|
|
142
|
-
assertControlCommandArgv,
|
|
143
|
-
classifyControlCommand,
|
|
144
|
-
controlCommandEnvironment,
|
|
145
|
-
resolveControlCommandExecutable
|
|
146
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
function gitObjectHashAlgorithmFor(objectId) {
|
|
3
|
-
if (/^[0-9a-f]{40}$/u.test(objectId)) return "sha1";
|
|
4
|
-
if (/^[0-9a-f]{64}$/u.test(objectId)) return "sha256";
|
|
5
|
-
return null;
|
|
6
|
-
}
|
|
7
|
-
function gitBlobHash(content, algorithm) {
|
|
8
|
-
const hash = createHash(algorithm);
|
|
9
|
-
hash.update(`blob ${content.length}\0`);
|
|
10
|
-
hash.update(content);
|
|
11
|
-
return hash.digest("hex");
|
|
12
|
-
}
|
|
13
|
-
export {
|
|
14
|
-
gitBlobHash,
|
|
15
|
-
gitObjectHashAlgorithmFor
|
|
16
|
-
};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
function configureVisibleGitIdentity(branchPath, identity, runGit) {
|
|
2
|
-
if (!identity) {
|
|
3
|
-
return;
|
|
4
|
-
}
|
|
5
|
-
const name = identity.name.trim();
|
|
6
|
-
const email = identity.email.trim();
|
|
7
|
-
if (!name || !email) {
|
|
8
|
-
throw new Error("Git identity must include name and email");
|
|
9
|
-
}
|
|
10
|
-
runGit(["config", "user.name", name], { cwd: branchPath });
|
|
11
|
-
runGit(["config", "user.email", email], { cwd: branchPath });
|
|
12
|
-
}
|
|
13
|
-
export {
|
|
14
|
-
configureVisibleGitIdentity
|
|
15
|
-
};
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { devNull } from "node:os";
|
|
2
|
-
const ALLOWED_GIT_ENVIRONMENT_KEYS = [
|
|
3
|
-
"PATH",
|
|
4
|
-
"USER",
|
|
5
|
-
"LOGNAME",
|
|
6
|
-
"SHELL",
|
|
7
|
-
"TMPDIR",
|
|
8
|
-
"TMP",
|
|
9
|
-
"TEMP",
|
|
10
|
-
"TZ",
|
|
11
|
-
"LANG",
|
|
12
|
-
"LANGUAGE",
|
|
13
|
-
"LC_ALL",
|
|
14
|
-
"LC_CTYPE",
|
|
15
|
-
"LC_COLLATE",
|
|
16
|
-
"LC_MESSAGES",
|
|
17
|
-
"LC_MONETARY",
|
|
18
|
-
"LC_NUMERIC",
|
|
19
|
-
"LC_TIME",
|
|
20
|
-
"LC_ADDRESS",
|
|
21
|
-
"LC_IDENTIFICATION",
|
|
22
|
-
"LC_MEASUREMENT",
|
|
23
|
-
"LC_NAME",
|
|
24
|
-
"LC_PAPER",
|
|
25
|
-
"LC_TELEPHONE",
|
|
26
|
-
"SystemRoot",
|
|
27
|
-
"SYSTEMROOT",
|
|
28
|
-
"WINDIR",
|
|
29
|
-
"COMSPEC",
|
|
30
|
-
"PATHEXT",
|
|
31
|
-
"SSL_CERT_FILE",
|
|
32
|
-
"SSL_CERT_DIR",
|
|
33
|
-
"CURL_CA_BUNDLE",
|
|
34
|
-
"GIT_SSL_CAINFO",
|
|
35
|
-
"GIT_SSL_CAPATH",
|
|
36
|
-
"GIT_EXEC_PATH",
|
|
37
|
-
"GIT_OPTIONAL_LOCKS"
|
|
38
|
-
];
|
|
39
|
-
function workerGitProcessEnvironment(source = process.env) {
|
|
40
|
-
const environment = {
|
|
41
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
42
|
-
GIT_CONFIG_GLOBAL: devNull,
|
|
43
|
-
GIT_CONFIG_NOSYSTEM: "1",
|
|
44
|
-
GIT_ATTR_NOSYSTEM: "1"
|
|
45
|
-
};
|
|
46
|
-
for (const key of ALLOWED_GIT_ENVIRONMENT_KEYS) {
|
|
47
|
-
if (source[key] !== void 0) environment[key] = source[key];
|
|
48
|
-
}
|
|
49
|
-
return environment;
|
|
50
|
-
}
|
|
51
|
-
function gitHttpAuthorizationClearArgs(remoteUrl) {
|
|
52
|
-
let remote;
|
|
53
|
-
try {
|
|
54
|
-
remote = new URL(remoteUrl);
|
|
55
|
-
} catch {
|
|
56
|
-
return ["-c", "http.extraHeader="];
|
|
57
|
-
}
|
|
58
|
-
if ((remote.protocol === "https:" || remote.protocol === "http:") && (remote.username || remote.password)) {
|
|
59
|
-
throw new Error("Git credentials must not be embedded in a remote URL");
|
|
60
|
-
}
|
|
61
|
-
if (remote.protocol !== "https:" && remote.protocol !== "http:") return ["-c", "http.extraHeader="];
|
|
62
|
-
remote.hash = "";
|
|
63
|
-
remote.search = "";
|
|
64
|
-
return ["-c", "http.extraHeader=", "-c", `http.${remote.origin}/.extraHeader=`, "-c", `http.${remote.href}.extraHeader=`];
|
|
65
|
-
}
|
|
66
|
-
function gitCredentialHelperConfigArgs(credentialHelper) {
|
|
67
|
-
return [
|
|
68
|
-
"-c",
|
|
69
|
-
"credential.helper=",
|
|
70
|
-
...credentialHelper ? ["-c", `credential.helper=${credentialHelper}`] : [],
|
|
71
|
-
"-c",
|
|
72
|
-
"credential.useHttpPath=false"
|
|
73
|
-
];
|
|
74
|
-
}
|
|
75
|
-
function gitCredentialUsernameConfigKey(remoteUrl) {
|
|
76
|
-
const remote = new URL(remoteUrl);
|
|
77
|
-
if ((remote.protocol === "https:" || remote.protocol === "http:") && (remote.username || remote.password)) {
|
|
78
|
-
throw new Error("Git credentials must not be embedded in a remote URL");
|
|
79
|
-
}
|
|
80
|
-
if (remote.protocol !== "https:" && remote.protocol !== "http:") {
|
|
81
|
-
throw new Error("Git credential usernames require an HTTP remote URL");
|
|
82
|
-
}
|
|
83
|
-
remote.hash = "";
|
|
84
|
-
remote.search = "";
|
|
85
|
-
return `credential.${remote.href}.username`;
|
|
86
|
-
}
|
|
87
|
-
function gitCredentialUsernameConfigArgs(remoteUrl, credentialUsername) {
|
|
88
|
-
if (!credentialUsername) return [];
|
|
89
|
-
if (/[\0\r\n]/.test(credentialUsername)) throw new Error("Invalid Git credential username");
|
|
90
|
-
return ["-c", `${gitCredentialUsernameConfigKey(remoteUrl)}=${credentialUsername}`];
|
|
91
|
-
}
|
|
92
|
-
function gitTransportSecurityArgs(remoteUrl, credentialHelper, credentialUsername) {
|
|
93
|
-
return [
|
|
94
|
-
...gitHttpAuthorizationClearArgs(remoteUrl),
|
|
95
|
-
...gitCredentialHelperConfigArgs(credentialHelper),
|
|
96
|
-
...gitCredentialUsernameConfigArgs(remoteUrl, credentialUsername)
|
|
97
|
-
];
|
|
98
|
-
}
|
|
99
|
-
export {
|
|
100
|
-
gitCredentialHelperConfigArgs,
|
|
101
|
-
gitCredentialUsernameConfigKey,
|
|
102
|
-
gitHttpAuthorizationClearArgs,
|
|
103
|
-
gitTransportSecurityArgs,
|
|
104
|
-
workerGitProcessEnvironment
|
|
105
|
-
};
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
const WORKER_INLINE_OUTPUT_MAX_BYTES = 8 * 1024 * 1024;
|
|
2
|
-
async function collectWorkerInlineOutput(stream, maxBytes = WORKER_INLINE_OUTPUT_MAX_BYTES, signal) {
|
|
3
|
-
if (!stream) return "";
|
|
4
|
-
signal?.throwIfAborted();
|
|
5
|
-
const reader = stream.getReader();
|
|
6
|
-
const abort = () => {
|
|
7
|
-
void reader.cancel().catch(() => {
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
|
-
signal?.addEventListener("abort", abort, { once: true });
|
|
11
|
-
const decoder = new TextDecoder();
|
|
12
|
-
const parts = [];
|
|
13
|
-
let bytes = 0;
|
|
14
|
-
try {
|
|
15
|
-
while (true) {
|
|
16
|
-
const next = await reader.read();
|
|
17
|
-
signal?.throwIfAborted();
|
|
18
|
-
if (next.done) break;
|
|
19
|
-
bytes += next.value.byteLength;
|
|
20
|
-
if (bytes > maxBytes) {
|
|
21
|
-
void reader.cancel().catch(() => {
|
|
22
|
-
});
|
|
23
|
-
throw new Error(`Inline command output exceeded ${maxBytes} bytes. Use streaming execution for large output.`);
|
|
24
|
-
}
|
|
25
|
-
parts.push(decoder.decode(next.value, { stream: true }));
|
|
26
|
-
}
|
|
27
|
-
parts.push(decoder.decode());
|
|
28
|
-
return parts.join("");
|
|
29
|
-
} finally {
|
|
30
|
-
signal?.removeEventListener("abort", abort);
|
|
31
|
-
reader.releaseLock();
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
export {
|
|
35
|
-
WORKER_INLINE_OUTPUT_MAX_BYTES,
|
|
36
|
-
collectWorkerInlineOutput
|
|
37
|
-
};
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
const BRANCH_NAME_MAX_LENGTH = 45;
|
|
4
|
-
const BRANCH_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*(?:\/[a-z0-9]+(?:-[a-z0-9]+)*)*$/;
|
|
5
|
-
const PROJECT_SEGMENT_PATTERN = /^[a-z0-9._-]+$/;
|
|
6
|
-
const WINDOWS_RESERVED_PATH_SEGMENT = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/i;
|
|
7
|
-
const INTERNAL_PROJECT_PATH_SEGMENTS = /* @__PURE__ */ new Set([".r5d-retired-checkouts"]);
|
|
8
|
-
function lstatIfExists(targetPath) {
|
|
9
|
-
try {
|
|
10
|
-
return fs.lstatSync(targetPath);
|
|
11
|
-
} catch (error) {
|
|
12
|
-
if (error.code === "ENOENT") return null;
|
|
13
|
-
throw error;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
function fsyncDirectory(directoryPath) {
|
|
17
|
-
const descriptor = fs.openSync(directoryPath, "r");
|
|
18
|
-
try {
|
|
19
|
-
fs.fsyncSync(descriptor);
|
|
20
|
-
} finally {
|
|
21
|
-
fs.closeSync(descriptor);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
function fsyncCreatedDirectoryChain(directoryPath, preexistingAncestorPath) {
|
|
25
|
-
let current = path.resolve(directoryPath);
|
|
26
|
-
const ancestor = path.resolve(preexistingAncestorPath);
|
|
27
|
-
const relative = path.relative(ancestor, current);
|
|
28
|
-
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
29
|
-
throw new Error(`Managed root escaped its durability ancestor: ${current}`);
|
|
30
|
-
}
|
|
31
|
-
while (true) {
|
|
32
|
-
fsyncDirectory(current);
|
|
33
|
-
if (current === ancestor) return;
|
|
34
|
-
current = path.dirname(current);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
function canonicalizeManagedRoot(root) {
|
|
38
|
-
const resolvedRoot = path.resolve(root.rootPath);
|
|
39
|
-
let preexistingAncestor = resolvedRoot;
|
|
40
|
-
while (!lstatIfExists(preexistingAncestor)) {
|
|
41
|
-
const parent = path.dirname(preexistingAncestor);
|
|
42
|
-
if (parent === preexistingAncestor) {
|
|
43
|
-
throw new Error(`Could not find an existing ancestor for ${root.label} (${resolvedRoot})`);
|
|
44
|
-
}
|
|
45
|
-
preexistingAncestor = parent;
|
|
46
|
-
}
|
|
47
|
-
let canonicalPreexistingAncestor;
|
|
48
|
-
try {
|
|
49
|
-
canonicalPreexistingAncestor = fs.realpathSync(preexistingAncestor);
|
|
50
|
-
if (!fs.statSync(canonicalPreexistingAncestor).isDirectory()) {
|
|
51
|
-
throw new Error("the nearest existing ancestor is not a directory");
|
|
52
|
-
}
|
|
53
|
-
} catch (error) {
|
|
54
|
-
throw new Error(`Could not validate the existing ancestor of ${root.label} (${resolvedRoot})`, { cause: error });
|
|
55
|
-
}
|
|
56
|
-
try {
|
|
57
|
-
fs.mkdirSync(resolvedRoot, { recursive: true });
|
|
58
|
-
} catch (error) {
|
|
59
|
-
throw new Error(`Could not prepare ${root.label} (${resolvedRoot}) for overlap validation`, { cause: error });
|
|
60
|
-
}
|
|
61
|
-
const rootStats = fs.statSync(resolvedRoot, { bigint: true });
|
|
62
|
-
if (!rootStats.isDirectory()) {
|
|
63
|
-
throw new Error(`Managed root is not a directory: ${root.label} (${resolvedRoot})`);
|
|
64
|
-
}
|
|
65
|
-
const canonicalRoot = fs.realpathSync(resolvedRoot);
|
|
66
|
-
fsyncCreatedDirectoryChain(canonicalRoot, canonicalPreexistingAncestor);
|
|
67
|
-
return {
|
|
68
|
-
...root,
|
|
69
|
-
rootPath: canonicalRoot,
|
|
70
|
-
identity: { device: rootStats.dev, inode: rootStats.ino }
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
function pathContains(root, candidate) {
|
|
74
|
-
const relative = path.relative(root, candidate);
|
|
75
|
-
return relative === "" || !relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative);
|
|
76
|
-
}
|
|
77
|
-
function identitiesMatch(left, right) {
|
|
78
|
-
return left.device === right.device && left.inode === right.inode;
|
|
79
|
-
}
|
|
80
|
-
function pathOrFilesystemContains(root, candidate) {
|
|
81
|
-
if (pathContains(root.rootPath, candidate.rootPath) || identitiesMatch(root.identity, candidate.identity)) {
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
let ancestor = path.dirname(candidate.rootPath);
|
|
85
|
-
while (true) {
|
|
86
|
-
const stats = fs.statSync(ancestor, { bigint: true });
|
|
87
|
-
if (identitiesMatch(root.identity, { device: stats.dev, inode: stats.ino })) return true;
|
|
88
|
-
const parent = path.dirname(ancestor);
|
|
89
|
-
if (parent === ancestor) return false;
|
|
90
|
-
ancestor = parent;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
function assertDisjointManagedRoots(roots) {
|
|
94
|
-
const canonical = roots.map(canonicalizeManagedRoot);
|
|
95
|
-
for (let leftIndex = 0; leftIndex < canonical.length; leftIndex += 1) {
|
|
96
|
-
const left = canonical[leftIndex];
|
|
97
|
-
for (let rightIndex = leftIndex + 1; rightIndex < canonical.length; rightIndex += 1) {
|
|
98
|
-
const right = canonical[rightIndex];
|
|
99
|
-
if (pathOrFilesystemContains(left, right) || pathOrFilesystemContains(right, left)) {
|
|
100
|
-
throw new Error(`Managed roots overlap: ${left.label} (${left.rootPath}) and ${right.label} (${right.rootPath}) must be disjoint`);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
function validateManagedBranchName(branchName) {
|
|
106
|
-
if (typeof branchName !== "string" || branchName.length === 0) {
|
|
107
|
-
throw new Error("Branch name is required");
|
|
108
|
-
}
|
|
109
|
-
if (branchName.length > BRANCH_NAME_MAX_LENGTH) {
|
|
110
|
-
throw new Error(`Branch name must be ${BRANCH_NAME_MAX_LENGTH} characters or fewer`);
|
|
111
|
-
}
|
|
112
|
-
if (!BRANCH_NAME_PATTERN.test(branchName)) {
|
|
113
|
-
throw new Error(`Invalid branch name from server: ${branchName}`);
|
|
114
|
-
}
|
|
115
|
-
const reserved = branchName.split("/").find((segment) => WINDOWS_RESERVED_PATH_SEGMENT.test(segment));
|
|
116
|
-
if (reserved) {
|
|
117
|
-
throw new Error(`Invalid branch name from server: reserved path segment '${reserved}'`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
function validateCheckoutPathSegments(value) {
|
|
121
|
-
if (!Array.isArray(value) || value.length !== 2) {
|
|
122
|
-
throw new Error("Invalid checkout path from server: expected namespace and project segments");
|
|
123
|
-
}
|
|
124
|
-
const segments = value;
|
|
125
|
-
for (const segment of segments) {
|
|
126
|
-
if (typeof segment !== "string" || !PROJECT_SEGMENT_PATTERN.test(segment) || segment !== segment.toLowerCase() || segment === "." || segment === ".." || segment === ".git" || INTERNAL_PROJECT_PATH_SEGMENTS.has(segment) || segment.endsWith(".") || WINDOWS_RESERVED_PATH_SEGMENT.test(segment)) {
|
|
127
|
-
throw new Error(`Invalid checkout path segment from server: ${String(segment)}`);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return [segments[0], segments[1]];
|
|
131
|
-
}
|
|
132
|
-
function assertPathInside(root, candidate, label) {
|
|
133
|
-
const resolvedRoot = path.resolve(root);
|
|
134
|
-
const resolvedCandidate = path.resolve(candidate);
|
|
135
|
-
const relative = path.relative(resolvedRoot, resolvedCandidate);
|
|
136
|
-
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
137
|
-
throw new Error(`${label} escapes its managed root: ${candidate}`);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
function assertNoSymlinkComponents(root, candidate) {
|
|
141
|
-
assertPathInside(root, candidate, "Checkout path");
|
|
142
|
-
const relative = path.relative(path.resolve(root), path.resolve(candidate));
|
|
143
|
-
let current = path.resolve(root);
|
|
144
|
-
for (const component of relative.split(path.sep).filter(Boolean)) {
|
|
145
|
-
current = path.join(current, component);
|
|
146
|
-
if (!fs.existsSync(current)) break;
|
|
147
|
-
if (fs.lstatSync(current).isSymbolicLink()) {
|
|
148
|
-
throw new Error(`Checkout path contains a symbolic link: ${current}`);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
function managedProjectRoot(projectsRoot, checkoutPathSegments) {
|
|
153
|
-
const segments = validateCheckoutPathSegments(checkoutPathSegments);
|
|
154
|
-
const result = path.join(projectsRoot, ...segments);
|
|
155
|
-
assertNoSymlinkComponents(projectsRoot, result);
|
|
156
|
-
return result;
|
|
157
|
-
}
|
|
158
|
-
function managedBranchPath(projectsRoot, checkoutPathSegments, branchName) {
|
|
159
|
-
validateManagedBranchName(branchName);
|
|
160
|
-
const projectRoot = managedProjectRoot(projectsRoot, checkoutPathSegments);
|
|
161
|
-
const result = path.join(projectRoot, ...branchName.split("/"));
|
|
162
|
-
assertNoSymlinkComponents(projectRoot, result);
|
|
163
|
-
return result;
|
|
164
|
-
}
|
|
165
|
-
export {
|
|
166
|
-
BRANCH_NAME_MAX_LENGTH,
|
|
167
|
-
BRANCH_NAME_PATTERN,
|
|
168
|
-
assertDisjointManagedRoots,
|
|
169
|
-
assertPathInside,
|
|
170
|
-
managedBranchPath,
|
|
171
|
-
managedProjectRoot,
|
|
172
|
-
validateCheckoutPathSegments,
|
|
173
|
-
validateManagedBranchName
|
|
174
|
-
};
|
package/dist/mjs/plan-lint.mjs
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import picomatch from "picomatch";
|
|
4
|
-
import { getTasksSectionRange, PLAN_TASK_LINE_REGEX } from "./plan-parser.mjs";
|
|
5
|
-
import { PLAN_EXTENSION } from "./plan-paths.mjs";
|
|
6
|
-
import { PLAN_TASK_ID_REGEX } from "./plan-parser.mjs";
|
|
7
|
-
const LEGACY_SCHEMA_FIELD_REGEX = /^\s*(type|status|parent|phase|discovered_problem)\s*:/m;
|
|
8
|
-
function lintPlanContent(planId, content) {
|
|
9
|
-
const errors = [];
|
|
10
|
-
const lines = content.split(/\r?\n/);
|
|
11
|
-
if (/^---(?:\r?\n|$)/.test(content)) {
|
|
12
|
-
errors.push({
|
|
13
|
-
planId,
|
|
14
|
-
severity: "error",
|
|
15
|
-
message: "YAML frontmatter is not supported. Use plain Markdown with a ## Tasks checklist.",
|
|
16
|
-
line: 1
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
if (LEGACY_SCHEMA_FIELD_REGEX.test(content)) {
|
|
20
|
-
errors.push({
|
|
21
|
-
planId,
|
|
22
|
-
severity: "error",
|
|
23
|
-
message: "Legacy plan schema fields are not supported. Remove type/status/parent/phase/discovered_problem metadata."
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
const titleLineIndex = lines.findIndex((line) => /^# .+\S\s*$/.test(line));
|
|
27
|
-
if (titleLineIndex === -1) {
|
|
28
|
-
errors.push({
|
|
29
|
-
planId,
|
|
30
|
-
severity: "error",
|
|
31
|
-
message: "Plan must start with or contain an H1 title, for example '# Implementation Plan'."
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
const range = getTasksSectionRange(content);
|
|
35
|
-
if (!range) {
|
|
36
|
-
errors.push({
|
|
37
|
-
planId,
|
|
38
|
-
severity: "error",
|
|
39
|
-
message: "Plan is missing a ## Tasks section."
|
|
40
|
-
});
|
|
41
|
-
return errors;
|
|
42
|
-
}
|
|
43
|
-
const taskIds = /* @__PURE__ */ new Set();
|
|
44
|
-
let taskCount = 0;
|
|
45
|
-
for (let index = range.startIndex + 1; index < range.endIndex; index += 1) {
|
|
46
|
-
const line = lines[index] ?? "";
|
|
47
|
-
if (line.trim() === "") continue;
|
|
48
|
-
const match = line.match(PLAN_TASK_LINE_REGEX);
|
|
49
|
-
if (!match || !match[3].trim()) {
|
|
50
|
-
errors.push({
|
|
51
|
-
planId,
|
|
52
|
-
severity: "error",
|
|
53
|
-
line: index + 1,
|
|
54
|
-
message: 'Malformed task line. Use "- [ ] task-id: Description" or "- [x] task-id: Description".'
|
|
55
|
-
});
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
taskCount += 1;
|
|
59
|
-
const taskId = match[2];
|
|
60
|
-
if (taskIds.has(taskId)) {
|
|
61
|
-
errors.push({
|
|
62
|
-
planId,
|
|
63
|
-
severity: "error",
|
|
64
|
-
line: index + 1,
|
|
65
|
-
message: `Duplicate task id "${taskId}".`
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
taskIds.add(taskId);
|
|
69
|
-
}
|
|
70
|
-
if (taskCount === 0) {
|
|
71
|
-
errors.push({
|
|
72
|
-
planId,
|
|
73
|
-
severity: "error",
|
|
74
|
-
message: "Plan must include at least one task in the ## Tasks section.",
|
|
75
|
-
line: range.startIndex + 1
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
return errors;
|
|
79
|
-
}
|
|
80
|
-
async function lintPlans(plansDir, glob) {
|
|
81
|
-
const errors = [];
|
|
82
|
-
let entries;
|
|
83
|
-
try {
|
|
84
|
-
entries = await fs.readdir(plansDir, { withFileTypes: true });
|
|
85
|
-
} catch (error) {
|
|
86
|
-
if (error.code !== "ENOENT") throw error;
|
|
87
|
-
entries = [];
|
|
88
|
-
}
|
|
89
|
-
const matches = glob ? picomatch(glob) : null;
|
|
90
|
-
let lintedPlanCount = 0;
|
|
91
|
-
for (const entry of entries) {
|
|
92
|
-
if (!entry.isFile() || !entry.name.endsWith(PLAN_EXTENSION)) continue;
|
|
93
|
-
const planId = entry.name.slice(0, -PLAN_EXTENSION.length);
|
|
94
|
-
if (!PLAN_TASK_ID_REGEX.test(planId) || matches && !matches(entry.name) && !matches(planId)) continue;
|
|
95
|
-
const content = await fs.readFile(path.join(plansDir, entry.name), "utf8");
|
|
96
|
-
lintedPlanCount += 1;
|
|
97
|
-
errors.push(...lintPlanContent(planId, content));
|
|
98
|
-
}
|
|
99
|
-
return {
|
|
100
|
-
type: "plan_lint",
|
|
101
|
-
errors,
|
|
102
|
-
errorCount: errors.filter((error) => error.severity === "error").length,
|
|
103
|
-
warningCount: errors.filter((error) => error.severity === "warning").length,
|
|
104
|
-
lintedPlanCount
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
function formatPlanLintErrors(errors) {
|
|
108
|
-
if (errors.length === 0) return "";
|
|
109
|
-
const lines = ["## Plan Lint Errors", ""];
|
|
110
|
-
const byPlan = /* @__PURE__ */ new Map();
|
|
111
|
-
for (const error of errors) {
|
|
112
|
-
byPlan.set(error.planId, [...byPlan.get(error.planId) ?? [], error]);
|
|
113
|
-
}
|
|
114
|
-
for (const [planId, planErrors] of byPlan) {
|
|
115
|
-
lines.push(`### ${planId}.plan.md`);
|
|
116
|
-
for (const error of planErrors) {
|
|
117
|
-
const location = error.line ? ` line ${error.line}` : "";
|
|
118
|
-
lines.push(`- ${error.severity.toUpperCase()}${location}: ${error.message}`);
|
|
119
|
-
}
|
|
120
|
-
lines.push("");
|
|
121
|
-
}
|
|
122
|
-
lines.push("Fix these issues by editing the Markdown plan file.");
|
|
123
|
-
return lines.join("\n");
|
|
124
|
-
}
|
|
125
|
-
export {
|
|
126
|
-
formatPlanLintErrors,
|
|
127
|
-
lintPlanContent,
|
|
128
|
-
lintPlans
|
|
129
|
-
};
|