@farmslot/agent-runtime 0.8.1 → 0.10.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 +26 -1
- package/README.md +2 -2
- package/bin/farmslot-agent.mjs +3 -43
- package/dist/execution-template/create.d.ts.map +1 -1
- package/dist/execution-template/create.js +4 -9
- package/dist/execution-template/create.js.map +1 -1
- package/dist/execution-template/execution-template.test.js +85 -34
- package/dist/execution-template/execution-template.test.js.map +1 -1
- package/dist/execution-template/frontmatter.d.ts +6 -3
- package/dist/execution-template/frontmatter.d.ts.map +1 -1
- package/dist/execution-template/frontmatter.js +8 -10
- package/dist/execution-template/frontmatter.js.map +1 -1
- package/dist/execution-template/index.d.ts +2 -2
- package/dist/execution-template/index.d.ts.map +1 -1
- package/dist/execution-template/index.js +2 -2
- package/dist/execution-template/index.js.map +1 -1
- package/dist/execution-template/infer.d.ts +2 -3
- package/dist/execution-template/infer.d.ts.map +1 -1
- package/dist/execution-template/infer.js +2 -19
- package/dist/execution-template/infer.js.map +1 -1
- package/dist/execution-template/lint.d.ts.map +1 -1
- package/dist/execution-template/lint.js +12 -14
- package/dist/execution-template/lint.js.map +1 -1
- package/dist/execution-template/resolve.d.ts.map +1 -1
- package/dist/execution-template/resolve.js +0 -2
- package/dist/execution-template/resolve.js.map +1 -1
- package/dist/execution-template/select.d.ts.map +1 -1
- package/dist/execution-template/select.js +4 -8
- package/dist/execution-template/select.js.map +1 -1
- package/dist/execution-template/snapshot.d.ts.map +1 -1
- package/dist/execution-template/snapshot.js +0 -1
- package/dist/execution-template/snapshot.js.map +1 -1
- package/dist/execution-template/types.d.ts +10 -5
- package/dist/execution-template/types.d.ts.map +1 -1
- package/dist/execution-template/types.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/native/account-claude.d.ts +5 -0
- package/dist/native/account-claude.d.ts.map +1 -0
- package/dist/native/account-claude.js +42 -0
- package/dist/native/account-claude.js.map +1 -0
- package/dist/native/account-codex.d.ts +4 -0
- package/dist/native/account-codex.d.ts.map +1 -0
- package/dist/native/account-codex.js +51 -0
- package/dist/native/account-codex.js.map +1 -0
- package/dist/native/account-common.d.ts +30 -0
- package/dist/native/account-common.d.ts.map +1 -0
- package/dist/native/account-common.js +155 -0
- package/dist/native/account-common.js.map +1 -0
- package/dist/native/account-cursor.d.ts +5 -0
- package/dist/native/account-cursor.d.ts.map +1 -0
- package/dist/native/account-cursor.js +48 -0
- package/dist/native/account-cursor.js.map +1 -0
- package/dist/native/account-grok.d.ts +4 -0
- package/dist/native/account-grok.d.ts.map +1 -0
- package/dist/native/account-grok.js +55 -0
- package/dist/native/account-grok.js.map +1 -0
- package/dist/native/account-observation.test.d.ts +2 -0
- package/dist/native/account-observation.test.d.ts.map +1 -0
- package/dist/native/account-observation.test.js +256 -0
- package/dist/native/account-observation.test.js.map +1 -0
- package/dist/native/account-profiles.d.ts +11 -0
- package/dist/native/account-profiles.d.ts.map +1 -0
- package/dist/native/account-profiles.js +172 -0
- package/dist/native/account-profiles.js.map +1 -0
- package/dist/native/account-profiles.test.d.ts +2 -0
- package/dist/native/account-profiles.test.d.ts.map +1 -0
- package/dist/native/account-profiles.test.js +172 -0
- package/dist/native/account-profiles.test.js.map +1 -0
- package/dist/native/account-types.d.ts +11 -0
- package/dist/native/account-types.d.ts.map +1 -0
- package/dist/native/account-types.js +2 -0
- package/dist/native/account-types.js.map +1 -0
- package/dist/native/acp-requests.d.ts +11 -0
- package/dist/native/acp-requests.d.ts.map +1 -0
- package/dist/native/acp-requests.js +109 -0
- package/dist/native/acp-requests.js.map +1 -0
- package/dist/native/acp-rpc.d.ts +15 -0
- package/dist/native/acp-rpc.d.ts.map +1 -0
- package/dist/native/acp-rpc.js +73 -0
- package/dist/native/acp-rpc.js.map +1 -0
- package/dist/native/acp.d.ts +15 -0
- package/dist/native/acp.d.ts.map +1 -0
- package/dist/native/acp.js +285 -0
- package/dist/native/acp.js.map +1 -0
- package/dist/native/acp.test.d.ts +2 -0
- package/dist/native/acp.test.d.ts.map +1 -0
- package/dist/native/acp.test.js +257 -0
- package/dist/native/acp.test.js.map +1 -0
- package/dist/native/affinity.test.d.ts +2 -0
- package/dist/native/affinity.test.d.ts.map +1 -0
- package/dist/native/affinity.test.js +30 -0
- package/dist/native/affinity.test.js.map +1 -0
- package/dist/native/claude.d.ts +11 -0
- package/dist/native/claude.d.ts.map +1 -0
- package/dist/native/claude.js +504 -0
- package/dist/native/claude.js.map +1 -0
- package/dist/native/client.d.ts +34 -0
- package/dist/native/client.d.ts.map +1 -0
- package/dist/native/client.js +217 -0
- package/dist/native/client.js.map +1 -0
- package/dist/native/codex.d.ts +3 -0
- package/dist/native/codex.d.ts.map +1 -0
- package/dist/native/codex.js +324 -0
- package/dist/native/codex.js.map +1 -0
- package/dist/native/cursor-model.d.ts +6 -0
- package/dist/native/cursor-model.d.ts.map +1 -0
- package/dist/native/cursor-model.js +74 -0
- package/dist/native/cursor-model.js.map +1 -0
- package/dist/native/cursor-model.test.d.ts +2 -0
- package/dist/native/cursor-model.test.d.ts.map +1 -0
- package/dist/native/cursor-model.test.js +40 -0
- package/dist/native/cursor-model.test.js.map +1 -0
- package/dist/native/cursor.d.ts +2 -0
- package/dist/native/cursor.d.ts.map +1 -0
- package/dist/native/cursor.js +30 -0
- package/dist/native/cursor.js.map +1 -0
- package/dist/native/durability.test.d.ts +2 -0
- package/dist/native/durability.test.d.ts.map +1 -0
- package/dist/native/durability.test.js +976 -0
- package/dist/native/durability.test.js.map +1 -0
- package/dist/native/filesystem-policy.test.d.ts +2 -0
- package/dist/native/filesystem-policy.test.d.ts.map +1 -0
- package/dist/native/filesystem-policy.test.js +136 -0
- package/dist/native/filesystem-policy.test.js.map +1 -0
- package/dist/native/grok.d.ts +2 -0
- package/dist/native/grok.d.ts.map +1 -0
- package/dist/native/grok.js +17 -0
- package/dist/native/grok.js.map +1 -0
- package/dist/native/host.d.ts +2 -0
- package/dist/native/host.d.ts.map +1 -0
- package/dist/native/host.js +129 -0
- package/dist/native/host.js.map +1 -0
- package/dist/native/index.d.ts +7 -0
- package/dist/native/index.d.ts.map +1 -0
- package/dist/native/index.js +6 -0
- package/dist/native/index.js.map +1 -0
- package/dist/native/ipc.d.ts +108 -0
- package/dist/native/ipc.d.ts.map +1 -0
- package/dist/native/ipc.js +181 -0
- package/dist/native/ipc.js.map +1 -0
- package/dist/native/manager.d.ts +61 -0
- package/dist/native/manager.d.ts.map +1 -0
- package/dist/native/manager.js +850 -0
- package/dist/native/manager.js.map +1 -0
- package/dist/native/process-tree.d.ts +26 -0
- package/dist/native/process-tree.d.ts.map +1 -0
- package/dist/native/process-tree.js +231 -0
- package/dist/native/process-tree.js.map +1 -0
- package/dist/native/process-tree.test.d.ts +2 -0
- package/dist/native/process-tree.test.d.ts.map +1 -0
- package/dist/native/process-tree.test.js +174 -0
- package/dist/native/process-tree.test.js.map +1 -0
- package/dist/native/process.d.ts +38 -0
- package/dist/native/process.d.ts.map +1 -0
- package/dist/native/process.js +246 -0
- package/dist/native/process.js.map +1 -0
- package/dist/native/process.test.d.ts +2 -0
- package/dist/native/process.test.d.ts.map +1 -0
- package/dist/native/process.test.js +56 -0
- package/dist/native/process.test.js.map +1 -0
- package/dist/native/profile-service.d.ts +5 -0
- package/dist/native/profile-service.d.ts.map +1 -0
- package/dist/native/profile-service.js +76 -0
- package/dist/native/profile-service.js.map +1 -0
- package/dist/native/registry.d.ts +20 -0
- package/dist/native/registry.d.ts.map +1 -0
- package/dist/native/registry.js +89 -0
- package/dist/native/registry.js.map +1 -0
- package/dist/native/registry.test.d.ts +2 -0
- package/dist/native/registry.test.d.ts.map +1 -0
- package/dist/native/registry.test.js +13 -0
- package/dist/native/registry.test.js.map +1 -0
- package/dist/native/review-sandbox.d.ts +12 -0
- package/dist/native/review-sandbox.d.ts.map +1 -0
- package/dist/native/review-sandbox.js +6 -0
- package/dist/native/review-sandbox.js.map +1 -0
- package/dist/native/review-sandbox.test.d.ts +2 -0
- package/dist/native/review-sandbox.test.d.ts.map +1 -0
- package/dist/native/review-sandbox.test.js +40 -0
- package/dist/native/review-sandbox.test.js.map +1 -0
- package/dist/native/service.d.ts +7 -0
- package/dist/native/service.d.ts.map +1 -0
- package/dist/native/service.js +151 -0
- package/dist/native/service.js.map +1 -0
- package/dist/native/storage.d.ts +11 -0
- package/dist/native/storage.d.ts.map +1 -0
- package/dist/native/storage.js +102 -0
- package/dist/native/storage.js.map +1 -0
- package/dist/native/supervisor.d.ts +2 -0
- package/dist/native/supervisor.d.ts.map +1 -0
- package/dist/native/supervisor.js +215 -0
- package/dist/native/supervisor.js.map +1 -0
- package/dist/native/types.d.ts +36 -0
- package/dist/native/types.d.ts.map +1 -0
- package/dist/native/types.js +2 -0
- package/dist/native/types.js.map +1 -0
- package/dist/native/worker-history.d.ts +16 -0
- package/dist/native/worker-history.d.ts.map +1 -0
- package/dist/native/worker-history.js +77 -0
- package/dist/native/worker-history.js.map +1 -0
- package/dist/native/worker-history.test.d.ts +2 -0
- package/dist/native/worker-history.test.d.ts.map +1 -0
- package/dist/native/worker-history.test.js +195 -0
- package/dist/native/worker-history.test.js.map +1 -0
- package/dist/native/worker-launch.d.ts +72 -0
- package/dist/native/worker-launch.d.ts.map +1 -0
- package/dist/native/worker-launch.js +134 -0
- package/dist/native/worker-launch.js.map +1 -0
- package/dist/native/worker-launch.test.d.ts +2 -0
- package/dist/native/worker-launch.test.d.ts.map +1 -0
- package/dist/native/worker-launch.test.js +61 -0
- package/dist/native/worker-launch.test.js.map +1 -0
- package/dist/native/worker-relocation.test.d.ts +2 -0
- package/dist/native/worker-relocation.test.d.ts.map +1 -0
- package/dist/native/worker-relocation.test.js +110 -0
- package/dist/native/worker-relocation.test.js.map +1 -0
- package/dist/native/worker-service.d.ts +4 -0
- package/dist/native/worker-service.d.ts.map +1 -0
- package/dist/native/worker-service.js +85 -0
- package/dist/native/worker-service.js.map +1 -0
- package/dist/native/workspace-files.d.ts +6 -0
- package/dist/native/workspace-files.d.ts.map +1 -0
- package/dist/native/workspace-files.js +62 -0
- package/dist/native/workspace-files.js.map +1 -0
- package/dist/native/workspace.d.ts +5 -0
- package/dist/native/workspace.d.ts.map +1 -0
- package/dist/native/workspace.js +120 -0
- package/dist/native/workspace.js.map +1 -0
- package/dist/task-init/index.d.ts +70 -0
- package/dist/task-init/index.d.ts.map +1 -0
- package/dist/task-init/index.js +124 -0
- package/dist/task-init/index.js.map +1 -0
- package/dist/task-init/task-document.d.ts +98 -0
- package/dist/task-init/task-document.d.ts.map +1 -0
- package/dist/task-init/task-document.js +184 -0
- package/dist/task-init/task-document.js.map +1 -0
- package/dist/task-init/write.d.ts +42 -0
- package/dist/task-init/write.d.ts.map +1 -0
- package/dist/task-init/write.js +63 -0
- package/dist/task-init/write.js.map +1 -0
- package/package.json +13 -4
- package/scripts/checklist-target.cjs +5 -3
- package/scripts/cursor-session-discovery.cjs +48 -0
- package/scripts/execution-template-cli.mjs +17 -18
- package/scripts/mark-checklist-step.cjs +1 -1
- package/scripts/review-filesystem.cjs +39 -0
- package/scripts/review-session.cjs +45 -0
- package/scripts/review-terminal.cjs +134 -0
- package/scripts/task-init-cli.mjs +166 -0
- package/scripts/worker-terminal-contract.cjs +15 -3
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Read-only recovery for older terminal launches that did not persist their chat ID.
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const input = JSON.parse(process.argv.at(-1));
|
|
5
|
+
const root = input.root.replace(/^~(?=\/|$)/, process.env.HOME);
|
|
6
|
+
const matches = [];
|
|
7
|
+
if (
|
|
8
|
+
!path.isAbsolute(input.cwd) ||
|
|
9
|
+
!Number.isFinite(input.startedAt) ||
|
|
10
|
+
!Number.isFinite(input.completedAt) ||
|
|
11
|
+
input.completedAt < input.startedAt
|
|
12
|
+
)
|
|
13
|
+
throw Error('Invalid session discovery bounds');
|
|
14
|
+
function directories(parent) {
|
|
15
|
+
try {
|
|
16
|
+
return fs
|
|
17
|
+
.readdirSync(parent, { withFileTypes: true })
|
|
18
|
+
.filter((entry) => entry.isDirectory())
|
|
19
|
+
.map((entry) => path.join(parent, entry.name));
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (error.code === 'ENOENT') return [];
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
for (const workspace of directories(root))
|
|
26
|
+
for (const chat of directories(workspace)) {
|
|
27
|
+
if (!/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(path.basename(chat))) continue;
|
|
28
|
+
let meta;
|
|
29
|
+
try {
|
|
30
|
+
meta = JSON.parse(fs.readFileSync(path.join(chat, 'meta.json'), 'utf8'));
|
|
31
|
+
} catch (error) {
|
|
32
|
+
// Concurrently created or truncated metadata is not evidence of a recoverable chat.
|
|
33
|
+
if (error.code === 'ENOENT' || error instanceof SyntaxError) continue;
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
if (
|
|
37
|
+
meta.schemaVersion === 1 &&
|
|
38
|
+
meta.hasConversation === true &&
|
|
39
|
+
meta.cwd === input.cwd &&
|
|
40
|
+
Number.isFinite(meta.createdAtMs) &&
|
|
41
|
+
meta.createdAtMs >= input.startedAt &&
|
|
42
|
+
meta.createdAtMs <= input.completedAt
|
|
43
|
+
)
|
|
44
|
+
matches.push({ sessionId: path.basename(chat) });
|
|
45
|
+
}
|
|
46
|
+
if (matches.length > 1)
|
|
47
|
+
throw Error('Several chats match the review workspace; session recovery is ambiguous');
|
|
48
|
+
process.stdout.write(JSON.stringify(matches[0] ?? null));
|
|
@@ -17,10 +17,12 @@ function usage(exitCode = 0) {
|
|
|
17
17
|
'list --dir <path> --domain-dir <domain=path> --project-worker <path> --package-templates <path>',
|
|
18
18
|
' [--project-name name] [--package-id id] [--flow f] [--run-mode m]',
|
|
19
19
|
' [--platform p] [--domain d] [--no-include-shadowed] [--json]',
|
|
20
|
-
'materialize <output> --flow f --run-mode m
|
|
20
|
+
'materialize <output> --flow f --platform p [--run-mode m] [--domain d] [--id id]',
|
|
21
21
|
' [the same source options as list] [--provenance path] [--json]',
|
|
22
22
|
'lint <file-or-dir> [--json]',
|
|
23
|
-
'new <path> [--flow f] [--
|
|
23
|
+
'new <path> [--flow f] [--platform p] [--title t] [--force] [--json]',
|
|
24
|
+
'',
|
|
25
|
+
'--run-mode is the run mode used to match project default rules; templates have none.',
|
|
24
26
|
].join('\n');
|
|
25
27
|
(exitCode === 0 ? console.log : console.error)(text);
|
|
26
28
|
process.exit(exitCode);
|
|
@@ -32,7 +34,7 @@ function takeValue(args, i, flag) {
|
|
|
32
34
|
return value;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
async function loadRuntime() {
|
|
37
|
+
export async function loadRuntime() {
|
|
36
38
|
if (!existsSync(distEntry)) {
|
|
37
39
|
throw new Error(
|
|
38
40
|
`compiled package missing at ${distEntry}; run yarn workspace @farmslot/agent-runtime build`,
|
|
@@ -58,7 +60,7 @@ function parseDomainDir(value) {
|
|
|
58
60
|
return { domain, root: value.slice(separator + 1) };
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
function parseCatalogArgs(args, { materialize = false } = {}) {
|
|
63
|
+
export function parseCatalogArgs(args, { materialize = false } = {}) {
|
|
62
64
|
const opts = {
|
|
63
65
|
dirs: [],
|
|
64
66
|
domainDirs: [],
|
|
@@ -101,7 +103,7 @@ function parseCatalogArgs(args, { materialize = false } = {}) {
|
|
|
101
103
|
return opts;
|
|
102
104
|
}
|
|
103
105
|
|
|
104
|
-
function buildSources(opts, runtime) {
|
|
106
|
+
export function buildSources(opts, runtime) {
|
|
105
107
|
const sources = [];
|
|
106
108
|
const domainSourceIds = new Set();
|
|
107
109
|
for (const [index, dir] of opts.dirs.entries()) {
|
|
@@ -174,18 +176,17 @@ async function cmdList(args, runtime) {
|
|
|
174
176
|
if (opts.id) throw new Error('--id is only valid with materialize');
|
|
175
177
|
const sources = buildSources(opts, runtime);
|
|
176
178
|
const templates =
|
|
177
|
-
opts.flow && opts.platform
|
|
179
|
+
opts.flow && opts.platform
|
|
178
180
|
? runtime.listCompatibleExecutionTemplates({
|
|
179
181
|
sources,
|
|
180
182
|
flow: opts.flow,
|
|
181
183
|
platform: opts.platform,
|
|
182
|
-
runMode: opts.runMode,
|
|
184
|
+
...(opts.runMode ? { runMode: opts.runMode } : {}),
|
|
183
185
|
...(opts.domain ? { domain: opts.domain } : {}),
|
|
184
186
|
})
|
|
185
187
|
: runtime.listExecutionTemplates({
|
|
186
188
|
sources,
|
|
187
189
|
flow: opts.flow,
|
|
188
|
-
runMode: opts.runMode,
|
|
189
190
|
platform: opts.platform,
|
|
190
191
|
domain: opts.domain,
|
|
191
192
|
includeShadowed: opts.includeShadowed,
|
|
@@ -203,7 +204,7 @@ async function cmdList(args, runtime) {
|
|
|
203
204
|
for (const entry of catalog) {
|
|
204
205
|
const shadow = entry.shadowedBy ? ` (shadowed by ${entry.shadowedBy})` : '';
|
|
205
206
|
process.stdout.write(
|
|
206
|
-
`${entry.id}\t${entry.flow}\t${entry.
|
|
207
|
+
`${entry.id}\t${entry.flow}\t${entry.platforms.join(',')}\t${entry.sourceId}${shadow}\n`,
|
|
207
208
|
);
|
|
208
209
|
}
|
|
209
210
|
}
|
|
@@ -212,7 +213,6 @@ async function cmdMaterialize(args, runtime) {
|
|
|
212
213
|
const opts = parseCatalogArgs(args, { materialize: true });
|
|
213
214
|
if (!opts.output) throw new Error('materialize requires <output>');
|
|
214
215
|
if (!opts.flow) throw new Error('materialize requires --flow');
|
|
215
|
-
if (!opts.runMode) throw new Error('materialize requires --run-mode');
|
|
216
216
|
if (!opts.platform) throw new Error('materialize requires --platform');
|
|
217
217
|
const output = resolve(opts.output);
|
|
218
218
|
if (existsSync(output)) {
|
|
@@ -227,7 +227,7 @@ async function cmdMaterialize(args, runtime) {
|
|
|
227
227
|
sources: buildSources(opts, runtime),
|
|
228
228
|
flow: opts.flow,
|
|
229
229
|
platform: opts.platform,
|
|
230
|
-
runMode: opts.runMode,
|
|
230
|
+
...(opts.runMode ? { runMode: opts.runMode } : {}),
|
|
231
231
|
...(opts.domain ? { domain: opts.domain } : {}),
|
|
232
232
|
...(opts.id ? { explicitId: opts.id } : {}),
|
|
233
233
|
});
|
|
@@ -282,7 +282,6 @@ async function cmdNew(args, runtime) {
|
|
|
282
282
|
let pathArg = null;
|
|
283
283
|
const opts = {
|
|
284
284
|
flow: undefined,
|
|
285
|
-
runMode: undefined,
|
|
286
285
|
platforms: undefined,
|
|
287
286
|
title: undefined,
|
|
288
287
|
force: false,
|
|
@@ -291,7 +290,6 @@ async function cmdNew(args, runtime) {
|
|
|
291
290
|
for (let i = 0; i < args.length; i += 1) {
|
|
292
291
|
const arg = args[i];
|
|
293
292
|
if (arg === '--flow') opts.flow = takeValue(args, i++, arg);
|
|
294
|
-
else if (arg === '--run-mode') opts.runMode = parseRunMode(takeValue(args, i++, arg));
|
|
295
293
|
else if (arg === '--platform') {
|
|
296
294
|
opts.platforms = takeValue(args, i++, arg)
|
|
297
295
|
.split(',')
|
|
@@ -308,7 +306,6 @@ async function cmdNew(args, runtime) {
|
|
|
308
306
|
const created = runtime.createExecutionTemplate({
|
|
309
307
|
path: pathArg,
|
|
310
308
|
flow: opts.flow,
|
|
311
|
-
runMode: opts.runMode,
|
|
312
309
|
platforms: opts.platforms,
|
|
313
310
|
title: opts.title,
|
|
314
311
|
force: opts.force,
|
|
@@ -328,7 +325,9 @@ async function main() {
|
|
|
328
325
|
else usage(2);
|
|
329
326
|
}
|
|
330
327
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
328
|
+
if (process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url) {
|
|
329
|
+
main().catch((error) => {
|
|
330
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
331
|
+
process.exit(1);
|
|
332
|
+
});
|
|
333
|
+
}
|
|
@@ -44,7 +44,7 @@ function printHelp() {
|
|
|
44
44
|
[
|
|
45
45
|
usageLine,
|
|
46
46
|
'',
|
|
47
|
-
'Task-dir mode: farmslot-agent mark <task-dir> <step> —
|
|
47
|
+
'Task-dir mode: farmslot-agent mark <task-dir> <step> — marks CHECKLIST.md (or TASK.md) unless checklist-target.json, written by a role switch, points elsewhere.',
|
|
48
48
|
'Override: farmslot-agent mark <task-dir> --checklist SELF-REVIEW.md <step> — optional --signal; signal defaults from checklist name.',
|
|
49
49
|
'Bootstrap: ./mark start — role-owned signal with status running (no checklist box).',
|
|
50
50
|
'Progress: ./mark 1, ./mark 2, ... — checks the box and appends checklistTiming.',
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const { accessSync, constants } = require('node:fs');
|
|
2
|
+
const { mkdir, realpath } = require('node:fs/promises');
|
|
3
|
+
const { join } = require('node:path');
|
|
4
|
+
|
|
5
|
+
function available() {
|
|
6
|
+
if (process.platform !== 'darwin') return false;
|
|
7
|
+
try {
|
|
8
|
+
accessSync('/usr/bin/sandbox-exec', constants.X_OK);
|
|
9
|
+
return true;
|
|
10
|
+
} catch (error) {
|
|
11
|
+
if (['ENOENT', 'EACCES'].includes(error.code)) return false;
|
|
12
|
+
throw error;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function sandbox(policy, runtimeRoots) {
|
|
16
|
+
if (!available())
|
|
17
|
+
throw new Error('This runner needs the macOS review sandbox on its execution node');
|
|
18
|
+
const temporaryDirectory = join(policy.writableRoots[0], '.review-runtime', 'tmp');
|
|
19
|
+
await mkdir(temporaryDirectory, { recursive: true, mode: 0o700 });
|
|
20
|
+
const writable = await Promise.all(policy.writableRoots.map((root) => realpath(root)));
|
|
21
|
+
const readOnly = await Promise.all(policy.readOnlyRoots.map((root) => realpath(root)));
|
|
22
|
+
for (const root of runtimeRoots) {
|
|
23
|
+
await mkdir(root, { recursive: true, mode: 0o700 });
|
|
24
|
+
writable.push(await realpath(root));
|
|
25
|
+
}
|
|
26
|
+
const profile = [
|
|
27
|
+
'(version 1)',
|
|
28
|
+
'(allow default)',
|
|
29
|
+
'(deny file-write*)',
|
|
30
|
+
...writable.map((root) => `(allow file-write* (subpath ${JSON.stringify(root)}))`),
|
|
31
|
+
'(allow file-write* (literal "/dev/null") (literal "/dev/tty"))',
|
|
32
|
+
...readOnly.map((root) => `(deny file-write* (subpath ${JSON.stringify(root)}))`),
|
|
33
|
+
].join('\n');
|
|
34
|
+
return {
|
|
35
|
+
sandbox: { executable: '/usr/bin/sandbox-exec', args: ['-p', profile] },
|
|
36
|
+
temporaryDirectory,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
module.exports = { available, sandbox };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Persist a runner-owned chat identity before launching a managed review terminal.
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const cp = require('node:child_process');
|
|
5
|
+
const input = JSON.parse(process.argv[2]);
|
|
6
|
+
const file = path.join(input.task, '.runner-session.json');
|
|
7
|
+
const identity = { runId: input.runId, workspaceId: input.workspaceId, runner: input.runner };
|
|
8
|
+
if (!path.isAbsolute(input.task) || !path.isAbsolute(input.cwd))
|
|
9
|
+
throw Error('Invalid session workspace');
|
|
10
|
+
if (fs.existsSync(file)) {
|
|
11
|
+
const saved = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
12
|
+
for (const key of Object.keys(identity))
|
|
13
|
+
if (saved[key] !== identity[key]) throw Error('Runner session identity changed');
|
|
14
|
+
if (input.resumeSessionId && saved.sessionId !== input.resumeSessionId)
|
|
15
|
+
throw Error('Retained session changed');
|
|
16
|
+
process.stdout.write(JSON.stringify(saved));
|
|
17
|
+
} else {
|
|
18
|
+
let sessionId = input.resumeSessionId;
|
|
19
|
+
if (!sessionId) {
|
|
20
|
+
const env = { ...process.env, ...input.environment.set };
|
|
21
|
+
for (const key of [
|
|
22
|
+
...input.environment.unset,
|
|
23
|
+
'FARMSLOT_NODE_TOKEN',
|
|
24
|
+
'FARMSLOT_GATEWAY_TOKEN',
|
|
25
|
+
'FARMSLOT_GATEWAY_PASSWORD',
|
|
26
|
+
'CLAUDECODE',
|
|
27
|
+
])
|
|
28
|
+
delete env[key];
|
|
29
|
+
const result = cp.spawnSync(input.argv[0], input.argv.slice(1), {
|
|
30
|
+
cwd: input.cwd,
|
|
31
|
+
env,
|
|
32
|
+
encoding: 'utf8',
|
|
33
|
+
timeout: 20000,
|
|
34
|
+
});
|
|
35
|
+
if (result.error) throw result.error;
|
|
36
|
+
if (result.status !== 0) throw Error(result.stderr || 'Could not reserve runner session');
|
|
37
|
+
sessionId = result.stdout.trim();
|
|
38
|
+
}
|
|
39
|
+
// create-chat documents a UUID result. Never infer identity from terminal text.
|
|
40
|
+
if (!/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(sessionId))
|
|
41
|
+
throw Error('Runner returned an invalid chat ID');
|
|
42
|
+
const saved = { ...identity, sessionId };
|
|
43
|
+
fs.writeFileSync(file, JSON.stringify(saved), { flag: 'wx', mode: 0o600 });
|
|
44
|
+
process.stdout.write(JSON.stringify(saved));
|
|
45
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Runner-neutral terminal lifecycle. Inputs come from the owning gateway, never PR content.
|
|
2
|
+
const fs = require('node:fs'),
|
|
3
|
+
path = require('node:path'),
|
|
4
|
+
cp = require('node:child_process');
|
|
5
|
+
const { sandbox } = require('./review-filesystem.cjs');
|
|
6
|
+
const input = JSON.parse(process.argv[2]);
|
|
7
|
+
const quote = (s) => "'" + s.replaceAll("'", "'\\''") + "'";
|
|
8
|
+
const tmux = (args) => cp.spawnSync('tmux', args, { encoding: 'utf8' });
|
|
9
|
+
const check = (result) => {
|
|
10
|
+
if (result.status !== 0)
|
|
11
|
+
throw Error(result.stderr || result.error?.message || 'Terminal operation failed');
|
|
12
|
+
return result.stdout.trim();
|
|
13
|
+
};
|
|
14
|
+
const target = input.session;
|
|
15
|
+
async function main() {
|
|
16
|
+
if (
|
|
17
|
+
!/^[a-zA-Z0-9_-]+$/.test(input.session) ||
|
|
18
|
+
!path.isAbsolute(input.task) ||
|
|
19
|
+
!path.isAbsolute(input.cwd)
|
|
20
|
+
)
|
|
21
|
+
throw Error('Invalid review terminal identity');
|
|
22
|
+
const marker = path.join(input.task, '.terminal-launch.json');
|
|
23
|
+
const cancelled = path.join(input.task, '.terminal-cancelled');
|
|
24
|
+
const existing = fs.existsSync(marker) ? JSON.parse(fs.readFileSync(marker, 'utf8')) : undefined;
|
|
25
|
+
if (
|
|
26
|
+
existing &&
|
|
27
|
+
(existing.runId !== input.runId ||
|
|
28
|
+
existing.workspaceId !== input.workspaceId ||
|
|
29
|
+
existing.session !== input.session)
|
|
30
|
+
)
|
|
31
|
+
throw Error('Review terminal identity changed');
|
|
32
|
+
const has = tmux(['has-session', '-t', '=' + target]);
|
|
33
|
+
if (![0, 1].includes(has.status)) check(has);
|
|
34
|
+
if (
|
|
35
|
+
has.status === 0 &&
|
|
36
|
+
check(tmux(['show-option', '-v', '-t', target, '@farmslot-review-workspace'])) !==
|
|
37
|
+
input.workspaceId
|
|
38
|
+
)
|
|
39
|
+
throw Error('Review terminal belongs to another workspace');
|
|
40
|
+
if (input.action === 'stop') {
|
|
41
|
+
fs.writeFileSync(cancelled, input.runId, { mode: 0o600 });
|
|
42
|
+
if (has.status === 0) check(tmux(['kill-session', '-t', target]));
|
|
43
|
+
process.stdout.write(JSON.stringify({ stopped: true }));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (input.action === 'inspect') {
|
|
47
|
+
process.stdout.write(JSON.stringify({ exists: has.status === 0, launch: existing }));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (fs.existsSync(cancelled)) throw new Error('Review terminal launch was cancelled');
|
|
51
|
+
if (existing) {
|
|
52
|
+
if (has.status !== 0) throw Error('Review terminal exited; explicit retry required');
|
|
53
|
+
process.stdout.write(JSON.stringify(existing));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (has.status === 0) throw Error('Review terminal exists without a launch record');
|
|
57
|
+
const cwd = fs.realpathSync(input.cwd),
|
|
58
|
+
task = fs.realpathSync(input.task);
|
|
59
|
+
const gitRoot = check(
|
|
60
|
+
cp.spawnSync('git', ['-C', cwd, 'rev-parse', '--path-format=absolute', '--git-common-dir'], {
|
|
61
|
+
encoding: 'utf8',
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
const policy = {
|
|
65
|
+
readOnlyRoots: [
|
|
66
|
+
cwd,
|
|
67
|
+
fs.realpathSync(gitRoot),
|
|
68
|
+
...(input.support ? [fs.realpathSync(input.support)] : []),
|
|
69
|
+
],
|
|
70
|
+
writableRoots: [task],
|
|
71
|
+
};
|
|
72
|
+
const runtimeRoots = input.runtimeRoots.map((root) =>
|
|
73
|
+
root.replace(/^~(?=\/|$)/, process.env.HOME),
|
|
74
|
+
);
|
|
75
|
+
const guard = await sandbox(policy, runtimeRoots);
|
|
76
|
+
const promptFile = path.join(task, '.terminal-prompt.txt');
|
|
77
|
+
fs.writeFileSync(promptFile, input.prompt, { mode: 0o600 });
|
|
78
|
+
if (input.setup) check(cp.spawnSync('/bin/sh', ['-c', input.setup], { cwd, encoding: 'utf8' }));
|
|
79
|
+
const environment = {
|
|
80
|
+
...process.env,
|
|
81
|
+
...input.environment.set,
|
|
82
|
+
TMPDIR: guard.temporaryDirectory,
|
|
83
|
+
DISABLE_OMX: '1',
|
|
84
|
+
DISABLE_OMC: '1',
|
|
85
|
+
};
|
|
86
|
+
for (const key of [
|
|
87
|
+
...input.environment.unset,
|
|
88
|
+
'CLAUDECODE',
|
|
89
|
+
'FARMSLOT_NODE_TOKEN',
|
|
90
|
+
'FARMSLOT_GATEWAY_TOKEN',
|
|
91
|
+
'FARMSLOT_GATEWAY_PASSWORD',
|
|
92
|
+
])
|
|
93
|
+
delete environment[key];
|
|
94
|
+
const commandFile = path.join(task, '.terminal-start.cjs');
|
|
95
|
+
const record = {
|
|
96
|
+
runId: input.runId,
|
|
97
|
+
workspaceId: input.workspaceId,
|
|
98
|
+
session: input.session,
|
|
99
|
+
startedAt: new Date().toISOString(),
|
|
100
|
+
};
|
|
101
|
+
const launch = `const cp=require('node:child_process');const env={...process.env};for(const key of ['FARMSLOT_NODE_TOKEN','FARMSLOT_GATEWAY_TOKEN','FARMSLOT_GATEWAY_PASSWORD','CLAUDECODE'])delete env[key];const r=cp.spawnSync(${JSON.stringify(guard.sandbox.executable)},${JSON.stringify([...guard.sandbox.args, '/bin/sh', '-c', input.command])},{cwd:${JSON.stringify(cwd)},env,stdio:'inherit'});process.exit(r.status??1);`;
|
|
102
|
+
fs.writeFileSync(commandFile, launch, { mode: 0o600 });
|
|
103
|
+
const envArgs = Object.entries(environment)
|
|
104
|
+
.filter(([, value]) => typeof value === 'string')
|
|
105
|
+
.flatMap(([name, value]) => ['-e', name + '=' + value]);
|
|
106
|
+
fs.writeFileSync(marker, JSON.stringify(record), { mode: 0o600, flag: 'wx' });
|
|
107
|
+
check(
|
|
108
|
+
tmux([
|
|
109
|
+
'new-session',
|
|
110
|
+
'-d',
|
|
111
|
+
...envArgs,
|
|
112
|
+
'-s',
|
|
113
|
+
input.session,
|
|
114
|
+
'-c',
|
|
115
|
+
cwd,
|
|
116
|
+
`${quote(process.execPath)} ${quote(commandFile)}`,
|
|
117
|
+
';',
|
|
118
|
+
'set-option',
|
|
119
|
+
'-t',
|
|
120
|
+
target,
|
|
121
|
+
'@farmslot-review-workspace',
|
|
122
|
+
input.workspaceId,
|
|
123
|
+
]),
|
|
124
|
+
);
|
|
125
|
+
if (fs.existsSync(cancelled)) {
|
|
126
|
+
check(tmux(['kill-session', '-t', target]));
|
|
127
|
+
throw new Error('Review terminal launch was cancelled');
|
|
128
|
+
}
|
|
129
|
+
process.stdout.write(JSON.stringify(record));
|
|
130
|
+
}
|
|
131
|
+
main().catch((error) => {
|
|
132
|
+
process.stderr.write(String(error));
|
|
133
|
+
process.exitCode = 1;
|
|
134
|
+
});
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `farmslot-agent task init`: write a task directory from a selected execution
|
|
4
|
+
* template. Thin wrappers (mm-harness) add their own defaults and forward here.
|
|
5
|
+
*/
|
|
6
|
+
import { execFileSync } from 'node:child_process';
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { pathToFileURL } from 'node:url';
|
|
10
|
+
|
|
11
|
+
import { buildSources, loadRuntime, parseCatalogArgs } from './execution-template-cli.mjs';
|
|
12
|
+
|
|
13
|
+
function usage(exitCode = 0) {
|
|
14
|
+
const text = [
|
|
15
|
+
'Usage: task init <task-dir> --flow f --platform p --template id --title t [options]',
|
|
16
|
+
'',
|
|
17
|
+
' Template sources: --dir <path> --domain-dir <domain=path> --project-worker <path>',
|
|
18
|
+
' --package-templates <path> [--package-id id] [--project-name name] [--domain d]',
|
|
19
|
+
' Run: [--run-mode m] — run mode used to match project default rules',
|
|
20
|
+
' Task: --title t [--task-text s | --task-file path] [--ticket key] [--source-ref url]',
|
|
21
|
+
' Identity: [--surface s] [--project name] [--repo owner/name] [--attempt-id id]',
|
|
22
|
+
' Rendering: [--var KEY=VALUE]... [--task-dir-label label] [--mode-preamble text]',
|
|
23
|
+
' [--addendum-file path] [--mark-command "cmd"] [--json]',
|
|
24
|
+
'',
|
|
25
|
+
'Writes TASK.md, CHECKLIST.md, mark, inputs/handoff.json, inputs/worker-terminal-contract.json.',
|
|
26
|
+
].join('\n');
|
|
27
|
+
(exitCode === 0 ? console.log : console.error)(text);
|
|
28
|
+
process.exit(exitCode);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function takeValue(args, i, flag) {
|
|
32
|
+
const value = args[i + 1];
|
|
33
|
+
if (value === undefined) throw new Error(`${flag} requires a value`);
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseArgs(args) {
|
|
38
|
+
const opts = {
|
|
39
|
+
taskDir: null,
|
|
40
|
+
template: null,
|
|
41
|
+
title: null,
|
|
42
|
+
taskText: null,
|
|
43
|
+
taskFile: null,
|
|
44
|
+
ticket: null,
|
|
45
|
+
sourceRef: null,
|
|
46
|
+
surface: 'cli',
|
|
47
|
+
project: null,
|
|
48
|
+
repo: null,
|
|
49
|
+
attemptId: null,
|
|
50
|
+
vars: {},
|
|
51
|
+
taskDirLabel: null,
|
|
52
|
+
modePreamble: null,
|
|
53
|
+
addendumFile: null,
|
|
54
|
+
markCommand: null,
|
|
55
|
+
json: false,
|
|
56
|
+
};
|
|
57
|
+
const catalogArgs = [];
|
|
58
|
+
// The task dir is the first positional, before any flag, as the usage line says.
|
|
59
|
+
if (args[0] && !args[0].startsWith('-')) opts.taskDir = args.shift();
|
|
60
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
61
|
+
const arg = args[i];
|
|
62
|
+
if (arg === '--template') opts.template = takeValue(args, i++, arg);
|
|
63
|
+
else if (arg === '--title') opts.title = takeValue(args, i++, arg);
|
|
64
|
+
else if (arg === '--task-text') opts.taskText = takeValue(args, i++, arg);
|
|
65
|
+
else if (arg === '--task-file') opts.taskFile = takeValue(args, i++, arg);
|
|
66
|
+
else if (arg === '--ticket') opts.ticket = takeValue(args, i++, arg);
|
|
67
|
+
else if (arg === '--source-ref') opts.sourceRef = takeValue(args, i++, arg);
|
|
68
|
+
else if (arg === '--surface') opts.surface = takeValue(args, i++, arg);
|
|
69
|
+
else if (arg === '--project') opts.project = takeValue(args, i++, arg);
|
|
70
|
+
else if (arg === '--repo') opts.repo = takeValue(args, i++, arg);
|
|
71
|
+
else if (arg === '--attempt-id') opts.attemptId = takeValue(args, i++, arg);
|
|
72
|
+
else if (arg === '--task-dir-label') opts.taskDirLabel = takeValue(args, i++, arg);
|
|
73
|
+
else if (arg === '--mode-preamble') opts.modePreamble = takeValue(args, i++, arg);
|
|
74
|
+
else if (arg === '--addendum-file') opts.addendumFile = takeValue(args, i++, arg);
|
|
75
|
+
else if (arg === '--mark-command') opts.markCommand = takeValue(args, i++, arg);
|
|
76
|
+
else if (arg === '--var') {
|
|
77
|
+
const pair = takeValue(args, i++, arg);
|
|
78
|
+
const eq = pair.indexOf('=');
|
|
79
|
+
if (eq <= 0) throw new Error('--var must use KEY=VALUE');
|
|
80
|
+
opts.vars[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
81
|
+
} else if (arg === '--json') {
|
|
82
|
+
opts.json = true;
|
|
83
|
+
catalogArgs.push(arg);
|
|
84
|
+
} else if (arg === '-h' || arg === '--help') usage(0);
|
|
85
|
+
else catalogArgs.push(arg);
|
|
86
|
+
}
|
|
87
|
+
if (!opts.taskDir) throw new Error('task init requires <task-dir>');
|
|
88
|
+
if (!opts.template) throw new Error('task init requires --template <id>');
|
|
89
|
+
if (!opts.title) throw new Error('task init requires --title');
|
|
90
|
+
const catalog = parseCatalogArgs([...catalogArgs, '--id', opts.template]);
|
|
91
|
+
if (!catalog.flow) throw new Error('task init requires --flow');
|
|
92
|
+
if (!catalog.platform) throw new Error('task init requires --platform');
|
|
93
|
+
return { opts, catalog };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function gitOutput(args) {
|
|
97
|
+
try {
|
|
98
|
+
return execFileSync('git', args, {
|
|
99
|
+
encoding: 'utf8',
|
|
100
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
101
|
+
}).trim();
|
|
102
|
+
} catch {
|
|
103
|
+
// Not a git checkout, or no remote: identity falls back to the directory name.
|
|
104
|
+
return '';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function main() {
|
|
109
|
+
const args = process.argv.slice(2);
|
|
110
|
+
if (args.length === 0 || args[0] === '-h' || args[0] === '--help') usage(0);
|
|
111
|
+
const { opts, catalog } = parseArgs(args);
|
|
112
|
+
const runtime = await loadRuntime();
|
|
113
|
+
|
|
114
|
+
const description = opts.taskFile
|
|
115
|
+
? readFileSync(path.resolve(opts.taskFile), 'utf8').trim()
|
|
116
|
+
: (opts.taskText ?? '').trim();
|
|
117
|
+
const toplevel = gitOutput(['rev-parse', '--show-toplevel']);
|
|
118
|
+
const originUrl = gitOutput(['remote', 'get-url', 'origin']);
|
|
119
|
+
const repo = opts.repo ?? (originUrl ? runtime.portableRepoIdentity(originUrl) : undefined);
|
|
120
|
+
const project =
|
|
121
|
+
opts.project ??
|
|
122
|
+
(repo ? repo.split('/').pop() : path.basename(toplevel || process.cwd())).toLowerCase();
|
|
123
|
+
// Rendered inside taskInit with the same vars as the checklist, so an addendum
|
|
124
|
+
// that works on the farm works here.
|
|
125
|
+
const addendum = opts.addendumFile ? readFileSync(path.resolve(opts.addendumFile), 'utf8') : null;
|
|
126
|
+
|
|
127
|
+
const result = await runtime.taskInit({
|
|
128
|
+
taskDir: path.resolve(opts.taskDir),
|
|
129
|
+
taskDirLabel: opts.taskDirLabel ?? opts.taskDir,
|
|
130
|
+
flow: catalog.flow,
|
|
131
|
+
...(catalog.runMode ? { runMode: catalog.runMode } : {}),
|
|
132
|
+
platform: catalog.platform,
|
|
133
|
+
...(catalog.domain ? { domain: catalog.domain } : {}),
|
|
134
|
+
template: { sources: buildSources(catalog, runtime), explicitId: opts.template },
|
|
135
|
+
task: {
|
|
136
|
+
title: opts.title,
|
|
137
|
+
description,
|
|
138
|
+
sourceKind: opts.taskFile ? 'file' : 'text',
|
|
139
|
+
...(opts.ticket ? { ticket: opts.ticket } : {}),
|
|
140
|
+
...(opts.sourceRef ? { sourceRef: opts.sourceRef } : {}),
|
|
141
|
+
},
|
|
142
|
+
handoff: {
|
|
143
|
+
surface: opts.surface,
|
|
144
|
+
project,
|
|
145
|
+
...(repo ? { repo } : {}),
|
|
146
|
+
...(opts.attemptId ? { attemptId: opts.attemptId } : {}),
|
|
147
|
+
},
|
|
148
|
+
vars: opts.vars,
|
|
149
|
+
...(opts.modePreamble ? { modePreamble: opts.modePreamble } : {}),
|
|
150
|
+
addendum,
|
|
151
|
+
...(opts.markCommand ? { markCommand: opts.markCommand } : {}),
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
if (opts.json) {
|
|
155
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
156
|
+
} else {
|
|
157
|
+
process.stdout.write(`Task document: ${result.taskDocument}\nChecklist: ${result.checklist}\n`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url) {
|
|
162
|
+
main().catch((error) => {
|
|
163
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
164
|
+
process.exit(1);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
@@ -286,8 +286,11 @@ function templateBodyScope(content) {
|
|
|
286
286
|
/**
|
|
287
287
|
* Deterministic structure checks beyond terminal artifact contract.
|
|
288
288
|
* @param {string} templateContent
|
|
289
|
+
* @param {{ roleChecklist?: boolean }} [options] roleChecklist: a nested-loop role
|
|
290
|
+
* checklist (self-review, self-review-fix, ci-fix) rendered beside an existing
|
|
291
|
+
* task dir; it may keep its own `## Task` block.
|
|
289
292
|
*/
|
|
290
|
-
function lintWorkerTemplateStructure(templateContent) {
|
|
293
|
+
function lintWorkerTemplateStructure(templateContent, options = {}) {
|
|
291
294
|
/** @type {string[]} */
|
|
292
295
|
const issues = [];
|
|
293
296
|
if (/(?<!\{)\{TASK_DIR\}(?!\})/.test(templateContent)) {
|
|
@@ -302,8 +305,17 @@ function lintWorkerTemplateStructure(templateContent) {
|
|
|
302
305
|
issues.push('terminal template missing `## Checklist` or `## Completion signal` section');
|
|
303
306
|
}
|
|
304
307
|
|
|
305
|
-
|
|
306
|
-
|
|
308
|
+
// The task writer generates TASK.md (ticket block, description, acceptance
|
|
309
|
+
// criteria) and renders the flow template into CHECKLIST.md. A flow template
|
|
310
|
+
// that still carries the old `## Task` header duplicates that block inside
|
|
311
|
+
// the checklist.
|
|
312
|
+
if (
|
|
313
|
+
!options.roleChecklist &&
|
|
314
|
+
(/^## Task\s*$/m.test(templateContent) || /^TASK_DIR:\s/m.test(templateContent))
|
|
315
|
+
) {
|
|
316
|
+
issues.push(
|
|
317
|
+
'carries a `## Task` block or `TASK_DIR:` line — the task writer generates TASK.md; keep only the checklist',
|
|
318
|
+
);
|
|
307
319
|
}
|
|
308
320
|
|
|
309
321
|
const scope = templateBodyScope(templateContent);
|