@farmslot/agent-runtime 0.2.0 → 0.3.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 +14 -0
- package/bin/farmslot-agent.mjs +6 -0
- package/dist/execution-template/create.d.ts +7 -0
- package/dist/execution-template/create.d.ts.map +1 -0
- package/dist/execution-template/create.js +63 -0
- package/dist/execution-template/create.js.map +1 -0
- package/dist/execution-template/execution-template.test.d.ts +2 -0
- package/dist/execution-template/execution-template.test.d.ts.map +1 -0
- package/dist/execution-template/execution-template.test.js +340 -0
- package/dist/execution-template/execution-template.test.js.map +1 -0
- package/dist/execution-template/frontmatter.d.ts +16 -0
- package/dist/execution-template/frontmatter.d.ts.map +1 -0
- package/dist/execution-template/frontmatter.js +110 -0
- package/dist/execution-template/frontmatter.js.map +1 -0
- package/dist/execution-template/index.d.ts +7 -0
- package/dist/execution-template/index.d.ts.map +1 -0
- package/dist/execution-template/index.js +6 -0
- package/dist/execution-template/index.js.map +1 -0
- package/dist/execution-template/infer.d.ts +25 -0
- package/dist/execution-template/infer.d.ts.map +1 -0
- package/dist/execution-template/infer.js +122 -0
- package/dist/execution-template/infer.js.map +1 -0
- package/dist/execution-template/lint.d.ts +6 -0
- package/dist/execution-template/lint.d.ts.map +1 -0
- package/dist/execution-template/lint.js +203 -0
- package/dist/execution-template/lint.js.map +1 -0
- package/dist/execution-template/resolve.d.ts +12 -0
- package/dist/execution-template/resolve.d.ts.map +1 -0
- package/dist/execution-template/resolve.js +126 -0
- package/dist/execution-template/resolve.js.map +1 -0
- package/dist/execution-template/types.d.ts +66 -0
- package/dist/execution-template/types.d.ts.map +1 -0
- package/dist/execution-template/types.js +3 -0
- package/dist/execution-template/types.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +6 -4
- package/scripts/check-task-artifact-contract.mjs +208 -63
- package/scripts/execution-template-cli.mjs +203 -0
- package/scripts/mark-checklist-step.cjs +26 -8
- package/scripts/worker-terminal-contract.cjs +23 -21
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Standalone execution-template CLI for ADR-049.
|
|
4
|
+
* Used by `farmslot-agent execution-template` and thin Consensys wrappers.
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, statSync } from 'node:fs';
|
|
7
|
+
import { dirname, resolve } from 'node:path';
|
|
8
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
9
|
+
|
|
10
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
11
|
+
const distEntry = resolve(packageRoot, 'dist', 'index.js');
|
|
12
|
+
|
|
13
|
+
function usage(exitCode = 0) {
|
|
14
|
+
const text = [
|
|
15
|
+
'Usage: execution-template <list|lint|new> [options]',
|
|
16
|
+
'',
|
|
17
|
+
'list --dir <path> --project-worker <path> --package-templates <path>',
|
|
18
|
+
' [--project-name name] [--package-id id] [--flow f] [--run-mode m]',
|
|
19
|
+
' [--platform p] [--no-include-shadowed] [--json]',
|
|
20
|
+
'lint <file-or-dir> [--json]',
|
|
21
|
+
'new <path> [--flow f] [--run-mode m] [--platform p] [--title t] [--force] [--json]',
|
|
22
|
+
].join('\n');
|
|
23
|
+
(exitCode === 0 ? console.log : console.error)(text);
|
|
24
|
+
process.exit(exitCode);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function takeValue(args, i, flag) {
|
|
28
|
+
const value = args[i + 1];
|
|
29
|
+
if (value === undefined) throw new Error(`${flag} requires a value`);
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function loadRuntime() {
|
|
34
|
+
if (!existsSync(distEntry)) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`compiled package missing at ${distEntry}; run yarn workspace @farmslot/agent-runtime build`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return import(pathToFileURL(distEntry).href);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseRunMode(value) {
|
|
43
|
+
if (value === 'autonomous' || value === 'interactive' || value === 'validation') return value;
|
|
44
|
+
throw new Error(`--run-mode must be autonomous|interactive|validation (got ${value})`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function cmdList(args, runtime) {
|
|
48
|
+
const opts = {
|
|
49
|
+
dirs: [],
|
|
50
|
+
projectWorker: null,
|
|
51
|
+
projectName: 'project',
|
|
52
|
+
packageTemplates: null,
|
|
53
|
+
packageId: 'shared',
|
|
54
|
+
flow: undefined,
|
|
55
|
+
runMode: undefined,
|
|
56
|
+
platform: undefined,
|
|
57
|
+
includeShadowed: true,
|
|
58
|
+
json: false,
|
|
59
|
+
};
|
|
60
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
61
|
+
const arg = args[i];
|
|
62
|
+
if (arg === '--dir') opts.dirs.push(takeValue(args, i++, arg));
|
|
63
|
+
else if (arg === '--project-worker') opts.projectWorker = takeValue(args, i++, arg);
|
|
64
|
+
else if (arg === '--project-name') opts.projectName = takeValue(args, i++, arg);
|
|
65
|
+
else if (arg === '--package-templates') opts.packageTemplates = takeValue(args, i++, arg);
|
|
66
|
+
else if (arg === '--package-id') opts.packageId = takeValue(args, i++, arg);
|
|
67
|
+
else if (arg === '--flow') opts.flow = takeValue(args, i++, arg);
|
|
68
|
+
else if (arg === '--run-mode') opts.runMode = parseRunMode(takeValue(args, i++, arg));
|
|
69
|
+
else if (arg === '--platform') opts.platform = takeValue(args, i++, arg);
|
|
70
|
+
else if (arg === '--include-shadowed') opts.includeShadowed = true;
|
|
71
|
+
else if (arg === '--no-include-shadowed') opts.includeShadowed = false;
|
|
72
|
+
else if (arg === '--json') opts.json = true;
|
|
73
|
+
else if (arg === '-h' || arg === '--help') usage(0);
|
|
74
|
+
else throw new Error(`unknown option ${arg}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const sources = [];
|
|
78
|
+
for (const [index, dir] of opts.dirs.entries()) {
|
|
79
|
+
const root = resolve(dir);
|
|
80
|
+
if (!existsSync(root) || !statSync(root).isDirectory()) {
|
|
81
|
+
throw new Error(`--dir is not a directory: ${root}`);
|
|
82
|
+
}
|
|
83
|
+
sources.push(runtime.customTemplateSource(`dir-${index + 1}`, root, 'flow-tree'));
|
|
84
|
+
}
|
|
85
|
+
if (opts.projectWorker) {
|
|
86
|
+
const input = resolve(opts.projectWorker);
|
|
87
|
+
const projectTemplatesDir =
|
|
88
|
+
input.endsWith('/worker') || input.endsWith('\\worker') ? resolve(input, '..') : input;
|
|
89
|
+
// An explicitly named source that does not exist is operator error — a
|
|
90
|
+
// silent empty catalog hides typos (worker/ is appended by the source).
|
|
91
|
+
const workerRoot = resolve(projectTemplatesDir, 'worker');
|
|
92
|
+
if (!existsSync(workerRoot) || !statSync(workerRoot).isDirectory()) {
|
|
93
|
+
throw new Error(`--project-worker is not a directory: ${workerRoot}`);
|
|
94
|
+
}
|
|
95
|
+
sources.push(runtime.projectWorkerTemplateSource(opts.projectName, projectTemplatesDir));
|
|
96
|
+
}
|
|
97
|
+
if (opts.packageTemplates) {
|
|
98
|
+
const root = resolve(opts.packageTemplates);
|
|
99
|
+
if (!existsSync(root) || !statSync(root).isDirectory()) {
|
|
100
|
+
throw new Error(`--package-templates is not a directory: ${root}`);
|
|
101
|
+
}
|
|
102
|
+
sources.push(runtime.packageFlowTreeTemplateSource(opts.packageId, root));
|
|
103
|
+
}
|
|
104
|
+
if (sources.length === 0) {
|
|
105
|
+
throw new Error('provide --dir, --project-worker, and/or --package-templates');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const templates = runtime.listExecutionTemplates({
|
|
109
|
+
sources,
|
|
110
|
+
flow: opts.flow,
|
|
111
|
+
runMode: opts.runMode,
|
|
112
|
+
platform: opts.platform,
|
|
113
|
+
includeShadowed: opts.includeShadowed,
|
|
114
|
+
});
|
|
115
|
+
if (opts.json) {
|
|
116
|
+
process.stdout.write(`${JSON.stringify({ templates }, null, 2)}\n`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
for (const entry of templates) {
|
|
120
|
+
const shadow = entry.shadowedBy ? ` (shadowed by ${entry.shadowedBy})` : '';
|
|
121
|
+
process.stdout.write(
|
|
122
|
+
`${entry.id}\t${entry.flow}\t${entry.runMode ?? '-'}\t${entry.platforms.join(',')}\t${entry.sourceId}\t${entry.path}${shadow}\n`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function cmdLint(args, runtime) {
|
|
128
|
+
let target = null;
|
|
129
|
+
let json = false;
|
|
130
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
131
|
+
const arg = args[i];
|
|
132
|
+
if (arg === '--json') json = true;
|
|
133
|
+
else if (arg === '-h' || arg === '--help') usage(0);
|
|
134
|
+
else if (!arg.startsWith('-') && !target) target = arg;
|
|
135
|
+
else throw new Error(`unknown option ${arg}`);
|
|
136
|
+
}
|
|
137
|
+
if (!target) usage(2);
|
|
138
|
+
const result = runtime.lintExecutionTemplates(target);
|
|
139
|
+
if (json) {
|
|
140
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
141
|
+
} else if (result.ok) {
|
|
142
|
+
process.stdout.write(`pass ${result.filesChecked} template(s)\n`);
|
|
143
|
+
} else {
|
|
144
|
+
for (const issue of result.issues) {
|
|
145
|
+
process.stderr.write(`${issue.severity} ${issue.path}: ${issue.message}\n`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (!result.ok) process.exitCode = 1;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function cmdNew(args, runtime) {
|
|
152
|
+
let pathArg = null;
|
|
153
|
+
const opts = {
|
|
154
|
+
flow: undefined,
|
|
155
|
+
runMode: undefined,
|
|
156
|
+
platforms: undefined,
|
|
157
|
+
title: undefined,
|
|
158
|
+
force: false,
|
|
159
|
+
json: false,
|
|
160
|
+
};
|
|
161
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
162
|
+
const arg = args[i];
|
|
163
|
+
if (arg === '--flow') opts.flow = takeValue(args, i++, arg);
|
|
164
|
+
else if (arg === '--run-mode') opts.runMode = parseRunMode(takeValue(args, i++, arg));
|
|
165
|
+
else if (arg === '--platform') {
|
|
166
|
+
opts.platforms = takeValue(args, i++, arg)
|
|
167
|
+
.split(',')
|
|
168
|
+
.map((p) => p.trim())
|
|
169
|
+
.filter(Boolean);
|
|
170
|
+
} else if (arg === '--title') opts.title = takeValue(args, i++, arg);
|
|
171
|
+
else if (arg === '--force') opts.force = true;
|
|
172
|
+
else if (arg === '--json') opts.json = true;
|
|
173
|
+
else if (arg === '-h' || arg === '--help') usage(0);
|
|
174
|
+
else if (!arg.startsWith('-') && !pathArg) pathArg = arg;
|
|
175
|
+
else throw new Error(`unknown option ${arg}`);
|
|
176
|
+
}
|
|
177
|
+
if (!pathArg) usage(2);
|
|
178
|
+
const created = runtime.createExecutionTemplate({
|
|
179
|
+
path: pathArg,
|
|
180
|
+
flow: opts.flow,
|
|
181
|
+
runMode: opts.runMode,
|
|
182
|
+
platforms: opts.platforms,
|
|
183
|
+
title: opts.title,
|
|
184
|
+
force: opts.force,
|
|
185
|
+
});
|
|
186
|
+
if (opts.json) process.stdout.write(`${JSON.stringify(created, null, 2)}\n`);
|
|
187
|
+
else process.stdout.write(`created ${created.path}\n`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function main() {
|
|
191
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
192
|
+
if (!command || command === '-h' || command === '--help') usage(0);
|
|
193
|
+
const runtime = await loadRuntime();
|
|
194
|
+
if (command === 'list') await cmdList(rest, runtime);
|
|
195
|
+
else if (command === 'lint') await cmdLint(rest, runtime);
|
|
196
|
+
else if (command === 'new') await cmdNew(rest, runtime);
|
|
197
|
+
else usage(2);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
main().catch((error) => {
|
|
201
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
202
|
+
process.exit(1);
|
|
203
|
+
});
|
|
@@ -19,13 +19,16 @@ const FLOW_REPORT_ARTIFACTS = {
|
|
|
19
19
|
'review-pr': ['review.md', 'report.md'],
|
|
20
20
|
dev: ['pr-description.md', 'report.md'],
|
|
21
21
|
'pr-complete': ['comments-report.md', 'report.md'],
|
|
22
|
-
'
|
|
22
|
+
'update-branch': ['branch-update-report.md', 'report.md'],
|
|
23
|
+
'ci-fix': ['report.md'],
|
|
24
|
+
'self-review': ['review-feedback.md', 'report.md'],
|
|
25
|
+
'self-review-fix': ['report.md'],
|
|
23
26
|
};
|
|
24
27
|
const FALLBACK_REPORT_ARTIFACTS = [
|
|
25
28
|
'report.md',
|
|
26
29
|
'review.md',
|
|
27
30
|
'comments-report.md',
|
|
28
|
-
'
|
|
31
|
+
'branch-update-report.md',
|
|
29
32
|
];
|
|
30
33
|
|
|
31
34
|
const usageLine =
|
|
@@ -44,8 +47,8 @@ function printHelp() {
|
|
|
44
47
|
' ./mark complete [--mark-last] [--no-self-review] [--skip-learnings] [--skip-checklist]',
|
|
45
48
|
' ./mark no-change --reason "..." [--already-fixed] [--mark-last] [--skip-learnings] [--skip-checklist]',
|
|
46
49
|
' ./mark blocked --reason "..." [--mark-last]',
|
|
47
|
-
'Terminal success paths require non-empty artifacts/learnings.md
|
|
48
|
-
'PR flows (dev, fix-bug): write artifacts/pr-description.md. Other flows: review.md,
|
|
50
|
+
'Terminal success paths require the flow contract artifacts: contribution flows need non-empty artifacts/learnings.md plus a flow outcome artifact (complete) or no-change-report (no-change); reviewer flows (self-review, self-review-fix) need their feedback/report artifact instead of learnings.',
|
|
51
|
+
'PR flows (dev, fix-bug): write artifacts/pr-description.md. Other flows: review.md, review-feedback.md, branch-update-report.md, report.md, etc.',
|
|
49
52
|
'With --mark-last, every checklist box must be [x] unless --skip-checklist. complete also runs check-task-artifact-contract.mjs.',
|
|
50
53
|
'Do not write SIGNAL.json by hand or with echo.',
|
|
51
54
|
].join('\n'),
|
|
@@ -238,13 +241,20 @@ function inferFlowType(taskPath) {
|
|
|
238
241
|
} catch {
|
|
239
242
|
return null;
|
|
240
243
|
}
|
|
241
|
-
const workerMatch = head.match(/^#\s*Worker:\s*([^\n
|
|
244
|
+
const workerMatch = head.match(/^#\s*Worker:\s*([^\n]+)/im);
|
|
242
245
|
if (workerMatch) {
|
|
243
|
-
const label = workerMatch[1]
|
|
246
|
+
const label = workerMatch[1]
|
|
247
|
+
.split(/\s+[—-]\s+/)[0]
|
|
248
|
+
.trim()
|
|
249
|
+
.toLowerCase();
|
|
244
250
|
if (label.includes('fix-bug') || label.includes('fix bug')) return 'fix-bug';
|
|
245
251
|
if (label.includes('review-pr') || label.includes('review pr')) return 'review-pr';
|
|
246
252
|
if (label.includes('pr-complete') || label.includes('pr complete')) return 'pr-complete';
|
|
247
|
-
if (label.includes('
|
|
253
|
+
if (label.includes('update-branch') || label.includes('update branch')) return 'update-branch';
|
|
254
|
+
if (label.includes('ci-fix') || label.includes('ci fix')) return 'ci-fix';
|
|
255
|
+
if (label.includes('self-review fix') || label.includes('self-review-fix'))
|
|
256
|
+
return 'self-review-fix';
|
|
257
|
+
if (label.includes('self-review') || label.includes('self review')) return 'self-review';
|
|
248
258
|
if (label.includes('interactive dev')) return 'dev';
|
|
249
259
|
if (/\bdev\b/.test(label) && !label.includes('review')) return 'dev';
|
|
250
260
|
}
|
|
@@ -292,7 +302,15 @@ function assertArtifactContract(taskDir, contract, terminalCommand) {
|
|
|
292
302
|
if (terminalCommand) args.push('--terminal', terminalCommand);
|
|
293
303
|
if (opts['skip-learnings']) args.push('--skip-learnings');
|
|
294
304
|
} else {
|
|
295
|
-
|
|
305
|
+
// No persisted contract file — honor the resolved builtin contract instead of
|
|
306
|
+
// assuming learnings: reviewer flows (self-review, self-review-fix) require their
|
|
307
|
+
// feedback/report artifact, not artifacts/learnings.md.
|
|
308
|
+
const contractRequiresLearnings = contract
|
|
309
|
+
? (contract.commands?.[terminalCommand ?? 'complete']?.artifacts ?? []).includes(
|
|
310
|
+
LEARNINGS_ARTIFACT.split(path.sep).join('/'),
|
|
311
|
+
)
|
|
312
|
+
: true;
|
|
313
|
+
if (!opts['skip-learnings'] && contractRequiresLearnings) args.push('--require-learnings');
|
|
296
314
|
if (fs.existsSync(path.join(taskDir, 'artifacts', 'recipe.json'))) {
|
|
297
315
|
args.push('--require-recipe-coverage-if-recipe', '--require-recipe-quality-if-recipe');
|
|
298
316
|
}
|
|
@@ -36,7 +36,11 @@
|
|
|
36
36
|
* @property {'builtin' | 'project'} source
|
|
37
37
|
*/
|
|
38
38
|
|
|
39
|
-
const
|
|
39
|
+
const TERMINAL_MARK_PATTERN =
|
|
40
|
+
String.raw`(?:\.\/|\{\{TASK_DIR\}\}\/)mark\b(?:\s+--(?:checklist|signal)\s+\S+)*\s+` +
|
|
41
|
+
String.raw`(complete|no-change|blocked)\b`;
|
|
42
|
+
const TERMINAL_MARK_RE = new RegExp(TERMINAL_MARK_PATTERN);
|
|
43
|
+
const TERMINAL_MARK_GLOBAL_RE = new RegExp(TERMINAL_MARK_PATTERN, 'g');
|
|
40
44
|
const TERMINAL_COMMANDS = /** @type {const} */ (['complete', 'no-change', 'blocked']);
|
|
41
45
|
|
|
42
46
|
const LEARNINGS = 'artifacts/learnings.md';
|
|
@@ -83,7 +87,7 @@ const BUILTIN_FLOW_COMMANDS = {
|
|
|
83
87
|
},
|
|
84
88
|
blocked: { artifacts: [] },
|
|
85
89
|
},
|
|
86
|
-
'
|
|
90
|
+
'update-branch': {
|
|
87
91
|
complete: {
|
|
88
92
|
report: 'artifacts/report.md',
|
|
89
93
|
artifacts: [LEARNINGS, 'artifacts/report.md'],
|
|
@@ -102,19 +106,26 @@ const BUILTIN_FLOW_COMMANDS = {
|
|
|
102
106
|
},
|
|
103
107
|
blocked: { artifacts: [] },
|
|
104
108
|
},
|
|
109
|
+
// Reviewer flows produce feedback/report artifacts, not learnings.md — the
|
|
110
|
+
// reviewer evaluates someone else's work, so the learnings requirement that
|
|
111
|
+
// applies to contribution flows does not apply here (matches the templates
|
|
112
|
+
// and the runtime FLOW_REPORT_ARTIFACTS mapping).
|
|
105
113
|
'self-review': {
|
|
106
|
-
complete: {
|
|
114
|
+
complete: {
|
|
115
|
+
report: 'artifacts/review-feedback.md',
|
|
116
|
+
artifacts: ['artifacts/review-feedback.md'],
|
|
117
|
+
},
|
|
107
118
|
'no-change': {
|
|
108
|
-
report: 'artifacts/
|
|
109
|
-
artifacts: [
|
|
119
|
+
report: 'artifacts/review-feedback.md',
|
|
120
|
+
artifacts: ['artifacts/review-feedback.md'],
|
|
110
121
|
},
|
|
111
122
|
blocked: { artifacts: [] },
|
|
112
123
|
},
|
|
113
124
|
'self-review-fix': {
|
|
114
|
-
complete: { report: 'artifacts/report.md', artifacts: [
|
|
125
|
+
complete: { report: 'artifacts/report.md', artifacts: ['artifacts/report.md'] },
|
|
115
126
|
'no-change': {
|
|
116
127
|
report: 'artifacts/no-change-report.md',
|
|
117
|
-
artifacts: [
|
|
128
|
+
artifacts: ['artifacts/no-change-report.md'],
|
|
118
129
|
},
|
|
119
130
|
blocked: { artifacts: [] },
|
|
120
131
|
},
|
|
@@ -248,9 +259,7 @@ function templateTerminalCommands(content) {
|
|
|
248
259
|
}
|
|
249
260
|
}
|
|
250
261
|
const commands = new Set();
|
|
251
|
-
for (const match of scope.matchAll(
|
|
252
|
-
/(?:\.\/|\{\{TASK_DIR\}\}\/)mark\s+(complete|no-change|blocked)\b/g,
|
|
253
|
-
)) {
|
|
262
|
+
for (const match of scope.matchAll(TERMINAL_MARK_GLOBAL_RE)) {
|
|
254
263
|
commands.add(match[1]);
|
|
255
264
|
}
|
|
256
265
|
if (commands.size === 0 && /mark-checklist-step\.cjs[\s\S]{0,400}?\bcomplete\b/.test(scope)) {
|
|
@@ -311,21 +320,14 @@ function lintWorkerTemplateAgainstContract(templateContent, contract) {
|
|
|
311
320
|
/** @type {string[]} */
|
|
312
321
|
const issues = [];
|
|
313
322
|
if (!templateUsesTerminalMark(templateContent)) {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
)
|
|
317
|
-
return issues;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
if (contract.requireSignal) {
|
|
321
|
-
if (!templateUsesTerminalMark(templateContent)) {
|
|
323
|
+
// Omitting `./mark` entirely is only acceptable when the resolved contract
|
|
324
|
+
// does not require a terminal signal (e.g. pr-complete interactive).
|
|
325
|
+
if (contract.requireSignal) {
|
|
322
326
|
issues.push(
|
|
323
327
|
'requireSignal is true but template has no terminal `./mark` or mark-checklist-step command',
|
|
324
328
|
);
|
|
325
329
|
}
|
|
326
|
-
|
|
327
|
-
// allowed when paired with mark instructions
|
|
328
|
-
}
|
|
330
|
+
return issues;
|
|
329
331
|
}
|
|
330
332
|
|
|
331
333
|
const commands = templateTerminalCommands(templateContent);
|