@farmslot/agent-runtime 0.1.0 → 0.2.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
CHANGED
|
@@ -4,10 +4,23 @@
|
|
|
4
4
|
|
|
5
5
|
- Active-development baseline; add user-facing changes here before release or package publication.
|
|
6
6
|
|
|
7
|
-
## 0.
|
|
7
|
+
## 0.2.0 - 2026-07-12
|
|
8
|
+
|
|
9
|
+
- fix: use workspace-linked `@farmslot/protocol` during local development so package builds cannot resolve a stale published sibling package.
|
|
10
|
+
- feat: the task-artifact contract check validates `artifacts/recipe.json` (envelope-only) and, when present, `artifacts/resolved-recipe.json` (in full, including flow-call resolution) against the shared Recipe Protocol v1 validator, with a local minimum-envelope fallback when `@farmslot/protocol` dist is not yet built.
|
|
11
|
+
|
|
12
|
+
## 0.1.1 - 2026-07-06
|
|
8
13
|
|
|
9
14
|
### Added
|
|
10
15
|
|
|
11
|
-
- Add `
|
|
12
|
-
-
|
|
13
|
-
- Add
|
|
16
|
+
- Add `checklist-target.json` resolution to `mark-checklist-step.cjs` task-dir mode so `./mark` retargets to nested-loop checklists without per-role wrapper scripts.
|
|
17
|
+
- Add `--checklist` / `--signal` overrides on task-dir invocations; signal defaults from checklist basename when omitted.
|
|
18
|
+
- Add centralized nested-loop checklist/signal registry (`DEFAULT_CHECKLIST_TARGET_REGISTRY`, `CHECKLIST_TARGET_BY_AGENT_ROLE`) and path helpers in `checklist-target.cjs`.
|
|
19
|
+
- Add sync test ensuring `checklist-target.cjs` constants stay aligned with `@farmslot/protocol/checklist-target`.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Task-dir `./mark` now requires a valid `checklist-target.json`; missing or invalid manifests fail with a teaching error instead of silently falling back to worker `TASK.md`/`SIGNAL.json`.
|
|
24
|
+
- Remove the legacy explicit-args mark surface (`mark <task.md> <signal.json> <step>`); task-dir mode is the only supported invocation.
|
|
25
|
+
|
|
26
|
+
## 0.1.0 - 2026-07-06
|
package/bin/farmslot-agent.mjs
CHANGED
|
@@ -413,7 +413,7 @@ function installMark(args) {
|
|
|
413
413
|
'#!/usr/bin/env bash',
|
|
414
414
|
'set -euo pipefail',
|
|
415
415
|
'DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"',
|
|
416
|
-
`node ${JSON.stringify(binPath)} mark "$DIR
|
|
416
|
+
`node ${JSON.stringify(binPath)} mark "$DIR" "$@"`,
|
|
417
417
|
'',
|
|
418
418
|
].join('\n'),
|
|
419
419
|
{ mode: 0o755 },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farmslot/agent-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -14,12 +14,13 @@
|
|
|
14
14
|
"default": "./dist/index.js"
|
|
15
15
|
},
|
|
16
16
|
"./scripts/mark-checklist-step.cjs": "./scripts/mark-checklist-step.cjs",
|
|
17
|
+
"./scripts/checklist-target.cjs": "./scripts/checklist-target.cjs",
|
|
17
18
|
"./scripts/worker-terminal-contract.cjs": "./scripts/worker-terminal-contract.cjs",
|
|
18
19
|
"./scripts/check-task-artifact-contract.mjs": "./scripts/check-task-artifact-contract.mjs"
|
|
19
20
|
},
|
|
20
21
|
"scripts": {
|
|
21
22
|
"typecheck": "yarn workspace @farmslot/protocol build && tsc --noEmit --project tsconfig.json",
|
|
22
|
-
"test": "node test/mark-checklist-step.test.cjs && node test/package-exports.test.cjs && node test/check-task-artifact-contract.test.cjs && node test/recipe-quality-builder.test.cjs",
|
|
23
|
+
"test": "node test/mark-checklist-step.test.cjs && node test/checklist-target-sync.test.mjs && node test/package-exports.test.cjs && node test/check-task-artifact-contract.test.cjs && node test/recipe-quality-builder.test.cjs",
|
|
23
24
|
"format": "prettier --write --ignore-path ../../.prettierignore .",
|
|
24
25
|
"format:check": "prettier --check --ignore-path ../../.prettierignore .",
|
|
25
26
|
"lint": "eslint \"src/**/*.ts\"",
|
|
@@ -30,7 +31,7 @@
|
|
|
30
31
|
"prepublishOnly": "node ../../scripts/quality/check-farmslot-package-readiness.mjs --publish --packages @farmslot/agent-runtime"
|
|
31
32
|
},
|
|
32
33
|
"dependencies": {
|
|
33
|
-
"@farmslot/protocol": "0.
|
|
34
|
+
"@farmslot/protocol": "0.8.0"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@types/node": "^22.0.0",
|
|
@@ -7,6 +7,11 @@ const require = createRequire(import.meta.url);
|
|
|
7
7
|
const { expandedArtifactsForCommand } = require('./worker-terminal-contract.cjs');
|
|
8
8
|
|
|
9
9
|
let sharedRecipeQualityValidator = null;
|
|
10
|
+
let sharedRecipeDocumentValidator = null;
|
|
11
|
+
// Mirror @farmslot/protocol recipeProtocolSchemaUrlForVersion: only v1 is a
|
|
12
|
+
// supported schema_version, so do not synthesize URLs for other versions.
|
|
13
|
+
let sharedRecipeSchemaUrlForVersion = (version) =>
|
|
14
|
+
version === 1 ? 'https://farmslot.io/schemas/recipe-v1.schema.json' : null;
|
|
10
15
|
try {
|
|
11
16
|
({ isRecipeQualityArtifact: sharedRecipeQualityValidator } =
|
|
12
17
|
await import('@farmslot/protocol/contracts/recipes'));
|
|
@@ -17,6 +22,20 @@ try {
|
|
|
17
22
|
console.warn(`[agent-runtime] using local RecipeQualityArtifact fallback: ${error.message}`);
|
|
18
23
|
}
|
|
19
24
|
}
|
|
25
|
+
try {
|
|
26
|
+
const recipeProtocol = await import('@farmslot/protocol/recipe');
|
|
27
|
+
sharedRecipeDocumentValidator = recipeProtocol.validateRecipeDocument;
|
|
28
|
+
if (typeof recipeProtocol.recipeProtocolSchemaUrlForVersion === 'function') {
|
|
29
|
+
sharedRecipeSchemaUrlForVersion = recipeProtocol.recipeProtocolSchemaUrlForVersion;
|
|
30
|
+
}
|
|
31
|
+
} catch (error) {
|
|
32
|
+
// Compatibility fail-safe: source checkouts can run before @farmslot/protocol
|
|
33
|
+
// has emitted dist/. In that degraded state, do not bypass recipe checks;
|
|
34
|
+
// enforce the minimum executable envelope locally until the shared validator loads.
|
|
35
|
+
if (process.env.FARMSLOT_DEBUG_AGENT_RUNTIME) {
|
|
36
|
+
console.warn(`[agent-runtime] using local Recipe v1 fallback: ${error.message}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
20
39
|
|
|
21
40
|
const taskDir = process.argv[2];
|
|
22
41
|
const rawArgs = process.argv.slice(3);
|
|
@@ -226,6 +245,83 @@ function validateRecipeQualityArtifact() {
|
|
|
226
245
|
}
|
|
227
246
|
}
|
|
228
247
|
|
|
248
|
+
function validateRecipeDocumentArtifact() {
|
|
249
|
+
// Mirror the artifact-package contract (protocol/recipe/artifact.ts): a failed run
|
|
250
|
+
// keeps recipe.json envelope-only and does not re-validate the composition, so a
|
|
251
|
+
// graceful failure (e.g. a flow cycle) is not rejected here.
|
|
252
|
+
const summaryText = readText('artifacts/summary.json');
|
|
253
|
+
let runFailed = false;
|
|
254
|
+
if (summaryText) {
|
|
255
|
+
try {
|
|
256
|
+
runFailed = JSON.parse(summaryText)?.status === 'fail';
|
|
257
|
+
} catch {
|
|
258
|
+
// A malformed summary is another check's concern; without a confirmed failure
|
|
259
|
+
// status, enforce the stricter (passing-run) validation as the safe default.
|
|
260
|
+
runFailed = false;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (runFailed) {
|
|
264
|
+
validateRecipeDocumentArtifactAt('artifacts/recipe.json', 'recipe.json', {
|
|
265
|
+
skipFlowCallResolution: true,
|
|
266
|
+
});
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
// Passing/unknown run: the composition must be proven — recipe.json's call.refs are
|
|
270
|
+
// proven either by resolved-recipe.json (validated in full, so recipe.json is
|
|
271
|
+
// envelope-only) or by recipe.json being self-contained (validated in full).
|
|
272
|
+
const hasResolvedRecipe = fileExists('artifacts/resolved-recipe.json');
|
|
273
|
+
validateRecipeDocumentArtifactAt('artifacts/recipe.json', 'recipe.json', {
|
|
274
|
+
skipFlowCallResolution: hasResolvedRecipe,
|
|
275
|
+
externalCatalogsResolvable: false,
|
|
276
|
+
});
|
|
277
|
+
validateRecipeDocumentArtifactAt('artifacts/resolved-recipe.json', 'resolved-recipe.json', {});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function validateRecipeDocumentArtifactAt(relPath, label, options) {
|
|
281
|
+
const text = readText(relPath);
|
|
282
|
+
if (!text) return;
|
|
283
|
+
let recipe;
|
|
284
|
+
try {
|
|
285
|
+
recipe = JSON.parse(text);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
issues.push(`${label}: invalid JSON: ${error.message}`);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (sharedRecipeDocumentValidator) {
|
|
291
|
+
const result = sharedRecipeDocumentValidator(recipe, options);
|
|
292
|
+
for (const finding of result.findings) {
|
|
293
|
+
if (finding.severity === 'error') {
|
|
294
|
+
issues.push(`${label}: ${finding.code} ${finding.path}: ${finding.message}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (!isRecord(recipe)) {
|
|
300
|
+
issues.push(`${label}: expected object`);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (recipe.schema_version !== 1) {
|
|
304
|
+
issues.push(`${label}: schema_version must equal 1`);
|
|
305
|
+
}
|
|
306
|
+
const expectedSchemaUrl = sharedRecipeSchemaUrlForVersion(recipe.schema_version);
|
|
307
|
+
if (recipe.$schema != null && recipe.$schema !== expectedSchemaUrl) {
|
|
308
|
+
issues.push(
|
|
309
|
+
`${label}: $schema must match schema_version (${expectedSchemaUrl ?? 'unknown schema_version'})`,
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
if (!isRecord(recipe.validate) || !isRecord(recipe.validate.workflow)) {
|
|
313
|
+
issues.push(`${label}: validate.workflow is required`);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const workflow = recipe.validate.workflow;
|
|
317
|
+
if (typeof workflow.entry !== 'string' || !workflow.entry.trim()) {
|
|
318
|
+
issues.push(`${label}: validate.workflow.entry must be a non-empty string`);
|
|
319
|
+
}
|
|
320
|
+
if (!isRecord(workflow.nodes) || Object.keys(workflow.nodes).length === 0) {
|
|
321
|
+
issues.push(`${label}: validate.workflow.nodes must be a non-empty object`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
229
325
|
function parseManifest() {
|
|
230
326
|
const text = readText('artifacts/evidence-manifest.json');
|
|
231
327
|
if (!text) return null;
|
|
@@ -339,6 +435,7 @@ function parseManifest() {
|
|
|
339
435
|
}
|
|
340
436
|
|
|
341
437
|
const hasRecipe = fileExists('artifacts/recipe.json');
|
|
438
|
+
validateRecipeDocumentArtifact();
|
|
342
439
|
if (
|
|
343
440
|
hasRecipe &&
|
|
344
441
|
flags.has('--require-recipe-quality-if-recipe') &&
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
|
|
4
|
+
// Canonical filename registry lives in @farmslot/protocol/checklist-target — keep in sync (see test/checklist-target-sync.test.mjs).
|
|
5
|
+
|
|
6
|
+
const CHECKLIST_TARGET_MANIFEST = 'checklist-target.json';
|
|
7
|
+
const TASK_PROGRESS_MARKDOWN = 'TASK.md';
|
|
8
|
+
const INTERACTIVE_CHECKLIST_MARKDOWN = 'CHECKLIST.md';
|
|
9
|
+
const WORKER_SIGNAL_FILE = 'SIGNAL.json';
|
|
10
|
+
const ROLE_SIGNAL_SUFFIX = '-SIGNAL.json';
|
|
11
|
+
|
|
12
|
+
const SELF_REVIEW_CHECKLIST = 'SELF-REVIEW.md';
|
|
13
|
+
const SELF_REVIEW_FIX_CHECKLIST = 'SELF-REVIEW-FIX.md';
|
|
14
|
+
const CI_FIX_CHECKLIST = 'CI-FIX.md';
|
|
15
|
+
|
|
16
|
+
function signalFileForChecklist(checklistBasename) {
|
|
17
|
+
if (
|
|
18
|
+
checklistBasename === TASK_PROGRESS_MARKDOWN ||
|
|
19
|
+
checklistBasename === INTERACTIVE_CHECKLIST_MARKDOWN
|
|
20
|
+
) {
|
|
21
|
+
return WORKER_SIGNAL_FILE;
|
|
22
|
+
}
|
|
23
|
+
const base = checklistBasename.replace(/\.md$/i, '');
|
|
24
|
+
return `${base}${ROLE_SIGNAL_SUFFIX}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function taskDirRelPath(taskDir, basename) {
|
|
28
|
+
const normalized = String(taskDir).replace(/\/+$/, '');
|
|
29
|
+
return `${normalized}/${basename}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function targetForChecklistBasename(checklistBasename) {
|
|
33
|
+
return {
|
|
34
|
+
checklist: checklistBasename,
|
|
35
|
+
signal: signalFileForChecklist(checklistBasename),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function defaultWorkerTarget(taskDir) {
|
|
40
|
+
const checklist = fs.existsSync(path.join(taskDir, INTERACTIVE_CHECKLIST_MARKDOWN))
|
|
41
|
+
? INTERACTIVE_CHECKLIST_MARKDOWN
|
|
42
|
+
: TASK_PROGRESS_MARKDOWN;
|
|
43
|
+
return targetForChecklistBasename(checklist);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const SELF_REVIEW_CHECKLIST_TARGET = targetForChecklistBasename(SELF_REVIEW_CHECKLIST);
|
|
47
|
+
const SELF_REVIEW_FIX_CHECKLIST_TARGET = targetForChecklistBasename(SELF_REVIEW_FIX_CHECKLIST);
|
|
48
|
+
const CI_FIX_CHECKLIST_TARGET = targetForChecklistBasename(CI_FIX_CHECKLIST);
|
|
49
|
+
|
|
50
|
+
const CHECKLIST_TARGET_BY_AGENT_ROLE = {
|
|
51
|
+
'self-review': SELF_REVIEW_CHECKLIST_TARGET,
|
|
52
|
+
'self-review-fix': SELF_REVIEW_FIX_CHECKLIST_TARGET,
|
|
53
|
+
'ci-fix': CI_FIX_CHECKLIST_TARGET,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const DEFAULT_CHECKLIST_TARGET_REGISTRY = {
|
|
57
|
+
manifest: CHECKLIST_TARGET_MANIFEST,
|
|
58
|
+
workerTask: TASK_PROGRESS_MARKDOWN,
|
|
59
|
+
interactiveChecklist: INTERACTIVE_CHECKLIST_MARKDOWN,
|
|
60
|
+
workerSignal: WORKER_SIGNAL_FILE,
|
|
61
|
+
roleSignalSuffix: ROLE_SIGNAL_SUFFIX,
|
|
62
|
+
roles: CHECKLIST_TARGET_BY_AGENT_ROLE,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function checklistTargetForAgentRole(role, registry = DEFAULT_CHECKLIST_TARGET_REGISTRY) {
|
|
66
|
+
return registry.roles[role] ?? null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function readManifest(taskDir) {
|
|
70
|
+
const manifestPath = path.join(taskDir, CHECKLIST_TARGET_MANIFEST);
|
|
71
|
+
if (!fs.existsSync(manifestPath)) return null;
|
|
72
|
+
let parsed;
|
|
73
|
+
try {
|
|
74
|
+
parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
if (!parsed || typeof parsed !== 'object') return null;
|
|
79
|
+
const checklist = typeof parsed.checklist === 'string' ? parsed.checklist.trim() : '';
|
|
80
|
+
if (!checklist || path.basename(checklist) !== checklist) return null;
|
|
81
|
+
const signalRaw = typeof parsed.signal === 'string' ? parsed.signal.trim() : '';
|
|
82
|
+
const signal =
|
|
83
|
+
signalRaw && path.basename(signalRaw) === signalRaw
|
|
84
|
+
? signalRaw
|
|
85
|
+
: signalFileForChecklist(checklist);
|
|
86
|
+
return { checklist, signal };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function manifestModeTeachingError(taskDir, reason) {
|
|
90
|
+
const manifestPath = path.join(path.resolve(taskDir), CHECKLIST_TARGET_MANIFEST);
|
|
91
|
+
return (
|
|
92
|
+
`Missing or invalid ${CHECKLIST_TARGET_MANIFEST} at ${manifestPath} (${reason}). ` +
|
|
93
|
+
'The gateway writes this file at task creation and on every role switch — re-run dispatch/prepare for this run, or write the manifest manually. ' +
|
|
94
|
+
'Escape hatch: farmslot-agent mark <task-dir> --checklist FILE.md <step> [--signal FILE.json].'
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function taskLocalBasenameOrThrow(value, label, expectedExt) {
|
|
99
|
+
if (!value || typeof value !== 'string') {
|
|
100
|
+
throw new Error(`${label} is required`);
|
|
101
|
+
}
|
|
102
|
+
const trimmed = value.trim();
|
|
103
|
+
if (trimmed.includes('/') || trimmed.includes('\\')) {
|
|
104
|
+
throw new Error(`${label} must be a task-local basename (got ${value})`);
|
|
105
|
+
}
|
|
106
|
+
if (!trimmed.endsWith(expectedExt)) {
|
|
107
|
+
throw new Error(`${label} must end with ${expectedExt} (got ${trimmed})`);
|
|
108
|
+
}
|
|
109
|
+
return trimmed;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function resolveChecklistTargetWithOverrides(taskDir, overrides = {}) {
|
|
113
|
+
const normalizedDir = path.resolve(taskDir);
|
|
114
|
+
if (overrides.checklist) {
|
|
115
|
+
const checklist = taskLocalBasenameOrThrow(overrides.checklist, '--checklist', '.md');
|
|
116
|
+
const signal = overrides.signal
|
|
117
|
+
? taskLocalBasenameOrThrow(overrides.signal, '--signal', '.json')
|
|
118
|
+
: signalFileForChecklist(checklist);
|
|
119
|
+
return { checklist, signal };
|
|
120
|
+
}
|
|
121
|
+
return resolveChecklistTarget(normalizedDir);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function parseTaskDirMarkArgs(taskDir, rawArgs, { isMarkStepToken, usage }) {
|
|
125
|
+
const overrides = {};
|
|
126
|
+
const positional = [];
|
|
127
|
+
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
128
|
+
const token = rawArgs[i];
|
|
129
|
+
if (token === '--checklist') {
|
|
130
|
+
overrides.checklist = rawArgs[++i];
|
|
131
|
+
if (!overrides.checklist) usage();
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (token === '--signal') {
|
|
135
|
+
overrides.signal = rawArgs[++i];
|
|
136
|
+
if (!overrides.signal) usage();
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
positional.push(token);
|
|
140
|
+
}
|
|
141
|
+
if (positional.length === 0) usage();
|
|
142
|
+
const stepRaw = positional[0];
|
|
143
|
+
if (stepRaw === '--help' || stepRaw === '-h') {
|
|
144
|
+
return { help: true };
|
|
145
|
+
}
|
|
146
|
+
if (!isMarkStepToken(stepRaw)) usage();
|
|
147
|
+
let target;
|
|
148
|
+
try {
|
|
149
|
+
target = resolveChecklistTargetWithOverrides(taskDir, overrides);
|
|
150
|
+
} catch (err) {
|
|
151
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
152
|
+
process.exit(2);
|
|
153
|
+
}
|
|
154
|
+
const normalizedDir = path.resolve(taskDir);
|
|
155
|
+
return {
|
|
156
|
+
taskPath: path.join(normalizedDir, target.checklist),
|
|
157
|
+
signalPath: path.join(normalizedDir, target.signal),
|
|
158
|
+
stepRaw,
|
|
159
|
+
rest: positional.slice(1),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function resolveChecklistTarget(taskDir) {
|
|
164
|
+
const normalizedDir = path.resolve(taskDir);
|
|
165
|
+
const manifestPath = path.join(normalizedDir, CHECKLIST_TARGET_MANIFEST);
|
|
166
|
+
if (!fs.existsSync(manifestPath)) {
|
|
167
|
+
throw new Error(manifestModeTeachingError(taskDir, 'file not found'));
|
|
168
|
+
}
|
|
169
|
+
const fromManifest = readManifest(normalizedDir);
|
|
170
|
+
if (!fromManifest) {
|
|
171
|
+
throw new Error(manifestModeTeachingError(taskDir, 'invalid JSON or checklist basename'));
|
|
172
|
+
}
|
|
173
|
+
return fromManifest;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function resolveChecklistPaths(taskDir) {
|
|
177
|
+
const target = resolveChecklistTarget(taskDir);
|
|
178
|
+
const normalizedDir = path.resolve(taskDir);
|
|
179
|
+
return {
|
|
180
|
+
taskPath: path.join(normalizedDir, target.checklist),
|
|
181
|
+
signalPath: path.join(normalizedDir, target.signal),
|
|
182
|
+
target,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
module.exports = {
|
|
187
|
+
CHECKLIST_TARGET_MANIFEST,
|
|
188
|
+
TASK_PROGRESS_MARKDOWN,
|
|
189
|
+
INTERACTIVE_CHECKLIST_MARKDOWN,
|
|
190
|
+
WORKER_SIGNAL_FILE,
|
|
191
|
+
ROLE_SIGNAL_SUFFIX,
|
|
192
|
+
SELF_REVIEW_CHECKLIST,
|
|
193
|
+
SELF_REVIEW_FIX_CHECKLIST,
|
|
194
|
+
CI_FIX_CHECKLIST,
|
|
195
|
+
SELF_REVIEW_CHECKLIST_TARGET,
|
|
196
|
+
SELF_REVIEW_FIX_CHECKLIST_TARGET,
|
|
197
|
+
CI_FIX_CHECKLIST_TARGET,
|
|
198
|
+
CHECKLIST_TARGET_BY_AGENT_ROLE,
|
|
199
|
+
DEFAULT_CHECKLIST_TARGET_REGISTRY,
|
|
200
|
+
checklistTargetForAgentRole,
|
|
201
|
+
taskDirRelPath,
|
|
202
|
+
signalFileForChecklist,
|
|
203
|
+
targetForChecklistBasename,
|
|
204
|
+
defaultWorkerTarget,
|
|
205
|
+
manifestModeTeachingError,
|
|
206
|
+
readManifest,
|
|
207
|
+
resolveChecklistTarget,
|
|
208
|
+
resolveChecklistTargetWithOverrides,
|
|
209
|
+
resolveChecklistPaths,
|
|
210
|
+
parseTaskDirMarkArgs,
|
|
211
|
+
};
|
|
@@ -6,6 +6,7 @@ const {
|
|
|
6
6
|
expandedArtifactsForCommand,
|
|
7
7
|
resolveWorkerTerminalContract,
|
|
8
8
|
} = require('./worker-terminal-contract.cjs');
|
|
9
|
+
const { parseTaskDirMarkArgs } = require('./checklist-target.cjs');
|
|
9
10
|
|
|
10
11
|
const START_COMMANDS = new Set(['start']);
|
|
11
12
|
const TERMINAL_COMMANDS = new Set(['complete', 'no-change', 'blocked']);
|
|
@@ -28,14 +29,16 @@ const FALLBACK_REPORT_ARTIFACTS = [
|
|
|
28
29
|
];
|
|
29
30
|
|
|
30
31
|
const usageLine =
|
|
31
|
-
'usage: mark <
|
|
32
|
+
'usage: mark <task-dir> [--checklist FILE.md] [--signal FILE.json] <step> [options]';
|
|
32
33
|
|
|
33
34
|
function printHelp() {
|
|
34
35
|
console.log(
|
|
35
36
|
[
|
|
36
37
|
usageLine,
|
|
37
38
|
'',
|
|
38
|
-
'
|
|
39
|
+
'Task-dir mode: farmslot-agent mark <task-dir> <step> — requires checklist-target.json written by the gateway at task creation or role switch.',
|
|
40
|
+
'Override: farmslot-agent mark <task-dir> --checklist SELF-REVIEW.md <step> — optional --signal; signal defaults from checklist name.',
|
|
41
|
+
'Bootstrap: ./mark start — role-owned signal with status running (no checklist box).',
|
|
39
42
|
'Progress: ./mark 1, ./mark 2, ... — checks the box and appends checklistTiming.',
|
|
40
43
|
'Terminal:',
|
|
41
44
|
' ./mark complete [--mark-last] [--no-self-review] [--skip-learnings] [--skip-checklist]',
|
|
@@ -55,18 +58,41 @@ function usage() {
|
|
|
55
58
|
process.exit(2);
|
|
56
59
|
}
|
|
57
60
|
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
function isMarkStepToken(token) {
|
|
62
|
+
if (token === '--help' || token === '-h') return true;
|
|
63
|
+
const lower = String(token).toLowerCase();
|
|
64
|
+
if (START_COMMANDS.has(lower) || TERMINAL_COMMANDS.has(lower)) return true;
|
|
65
|
+
const stepNumber = Number(token);
|
|
66
|
+
return Number.isInteger(stepNumber) && stepNumber >= 1;
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
function resolveMarkInvocation(rawArgs) {
|
|
70
|
+
if (rawArgs.length === 0) usage();
|
|
71
|
+
const first = rawArgs[0];
|
|
72
|
+
if (first === '--help' || first === '-h') {
|
|
73
|
+
printHelp();
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const firstPath = path.resolve(first);
|
|
78
|
+
const firstIsDir = fs.existsSync(firstPath) && fs.statSync(firstPath).isDirectory();
|
|
79
|
+
if (firstIsDir) {
|
|
80
|
+
const parsed = parseTaskDirMarkArgs(firstPath, rawArgs.slice(1), {
|
|
81
|
+
isMarkStepToken,
|
|
82
|
+
usage,
|
|
83
|
+
});
|
|
84
|
+
if (parsed.help) {
|
|
85
|
+
printHelp();
|
|
86
|
+
process.exit(0);
|
|
87
|
+
}
|
|
88
|
+
return parsed;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
usage();
|
|
68
92
|
}
|
|
69
|
-
|
|
93
|
+
|
|
94
|
+
const args = process.argv.slice(2);
|
|
95
|
+
const { taskPath, signalPath, stepRaw, rest } = resolveMarkInvocation(args);
|
|
70
96
|
|
|
71
97
|
const opts = {};
|
|
72
98
|
for (let i = 0; i < rest.length; i += 1) {
|