@farmslot/skills 0.1.2 → 0.1.3
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 +7 -1
- package/README.md +3 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
- package/scripts/check-task-artifact-contract.mjs +14 -300
- package/scripts/mark-checklist-step.cjs +10 -480
- package/scripts/worker-terminal-contract.cjs +11 -376
- package/skills/packet/SKILL.md +136 -0
- package/skills/recipe-quality/SKILL.md +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,9 +6,15 @@ All notable changes to `@farmslot/skills` are tracked here.
|
|
|
6
6
|
|
|
7
7
|
- Active-development baseline; add user-facing changes here before release or package publication.
|
|
8
8
|
|
|
9
|
+
## 0.1.3 - 2026-07-08
|
|
10
|
+
|
|
11
|
+
- Update recipe-quality skill guidance to delegate runtime artifact generation to `@farmslot/agent-runtime`
|
|
12
|
+
- `packet`, a skill for packet-shaped operator communication aligned with ADR-048
|
|
13
|
+
- Convert task lifecycle scripts to compatibility shims over `@farmslot/agent-runtime`
|
|
14
|
+
|
|
9
15
|
## 0.1.2 - 2026-07-03
|
|
10
16
|
|
|
11
|
-
- Colocate finish-contract scripts in `packages/skills/scripts/` (`mark-checklist-step.cjs`, `worker-terminal-contract.cjs`, `check-task-artifact-contract.mjs`)
|
|
17
|
+
- Colocate finish-contract scripts in `packages/skills/scripts/` (`mark-checklist-step.cjs`, `worker-terminal-contract.cjs`, `check-task-artifact-contract.mjs`) for the initial `@farmslot/skills` and consensys-skills delegation path
|
|
12
18
|
- **dev / fix-bug:** terminal `./mark complete` requires `artifacts/learnings.md` + `artifacts/pr-description.md` (not `report.md`)
|
|
13
19
|
- `./mark` infers dev/fix-bug flow from embedded checklist `Skill:` header (standalone recipe skills)
|
|
14
20
|
- Export `./scripts/worker-terminal-contract.cjs` and `./scripts/check-task-artifact-contract.mjs` from the package
|
package/README.md
CHANGED
|
@@ -42,6 +42,7 @@ Supported layouts:
|
|
|
42
42
|
| `recipe-doctor` | Check local readiness for recipe authoring or execution. |
|
|
43
43
|
| `recipe-harness` | Help install or run the local recipe runtime when the project has one. |
|
|
44
44
|
| `project-adopt` | Recommend the smallest useful Farmslot integration level for the project. |
|
|
45
|
+
| `packet` | Communicate plans, blockers, decisions, and evidence reviews as compact operator packets. |
|
|
45
46
|
|
|
46
47
|
## Adoption Ladder
|
|
47
48
|
|
|
@@ -55,13 +56,13 @@ Supported layouts:
|
|
|
55
56
|
|
|
56
57
|
- `src/`: TypeScript CLI and install-library entry points.
|
|
57
58
|
- `skills/`: Packaged skill directories copied into consumer repos.
|
|
58
|
-
- `scripts/`:
|
|
59
|
+
- `scripts/`: Compatibility shims for legacy runtime paths. Runtime logic lives in `@farmslot/agent-runtime`.
|
|
59
60
|
- `templates/`: Scenario fixtures and task templates used by the recipe-cook workflow.
|
|
60
61
|
- `test/`: Node TAP-style smoke and behavior tests for installers and runner scripts.
|
|
61
62
|
|
|
62
63
|
## Maintenance rules
|
|
63
64
|
|
|
64
|
-
- `scripts/mark-checklist-step.cjs
|
|
65
|
+
- `scripts/mark-checklist-step.cjs`, `scripts/worker-terminal-contract.cjs`, and `scripts/check-task-artifact-contract.mjs` are compatibility shims that delegate to `@farmslot/agent-runtime`.
|
|
65
66
|
- Keep package payloads self-contained; consumers should not need the Farmslot monorepo.
|
|
66
67
|
- Update `CHANGELOG.md` before packaging or publishing.
|
|
67
68
|
- Run the package readiness guard when files move, bins change, or exports are added.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const FARMSLOT_SKILLS_PACKAGE = "@farmslot/skills";
|
|
2
|
-
export declare const FARMSLOT_SKILL_NAMES: readonly ["recipe-cook", "recipe-quality", "recipe-doctor", "recipe-harness", "project-adopt"];
|
|
2
|
+
export declare const FARMSLOT_SKILL_NAMES: readonly ["recipe-cook", "recipe-quality", "recipe-doctor", "recipe-harness", "project-adopt", "packet"];
|
|
3
3
|
export type FarmslotSkillName = (typeof FARMSLOT_SKILL_NAMES)[number];
|
|
4
4
|
export declare const FARMSLOT_SKILL_INSTALL_LAYOUTS: readonly ["agents", "claude", "codex", "cursor"];
|
|
5
5
|
export type FarmslotSkillInstallLayout = (typeof FARMSLOT_SKILL_INSTALL_LAYOUTS)[number];
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB,qBAAqB,CAAC;AAE1D,eAAO,MAAM,oBAAoB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB,qBAAqB,CAAC;AAE1D,eAAO,MAAM,oBAAoB,0GAOvB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE,eAAO,MAAM,8BAA8B,kDAAmD,CAAC;AAE/F,MAAM,MAAM,0BAA0B,GAAG,CAAC,OAAO,8BAA8B,CAAC,CAAC,MAAM,CAAC,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,uBAAuB,GAAG,kBAAkB,CAAC;AAE1D,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,aAAa;IACb,gBAAgB;IAChB,eAAe;IACf,gBAAgB;IAChB,eAAe;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,uBAAuB,GAAG,kBAAkB,CAAC;AAE1D,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,aAAa;IACb,gBAAgB;IAChB,eAAe;IACf,gBAAgB;IAChB,eAAe;IACf,QAAQ;CACA,CAAC;AAIX,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAU,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farmslot/skills",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -32,6 +32,9 @@
|
|
|
32
32
|
"prepack": "cd ../.. && node scripts/quality/check-farmslot-package-readiness.mjs --require-changelog --packages @farmslot/skills && yarn workspace @farmslot/skills build",
|
|
33
33
|
"prepublishOnly": "node ../../scripts/quality/check-farmslot-package-readiness.mjs --publish --packages @farmslot/skills"
|
|
34
34
|
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@farmslot/agent-runtime": "0.1.1"
|
|
37
|
+
},
|
|
35
38
|
"devDependencies": {
|
|
36
39
|
"@types/node": "^22.0.0",
|
|
37
40
|
"eslint": "^10.3.0",
|
|
@@ -1,310 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
-
const
|
|
7
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
8
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
const rawArgs = process.argv.slice(3);
|
|
11
|
-
const flags = new Set();
|
|
12
|
-
let contractPath;
|
|
13
|
-
let terminalCommand;
|
|
14
|
-
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
15
|
-
const arg = rawArgs[i];
|
|
16
|
-
if (arg === '--contract') {
|
|
17
|
-
contractPath = rawArgs[i + 1];
|
|
18
|
-
i += 1;
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
|
-
if (arg === '--terminal') {
|
|
22
|
-
terminalCommand = rawArgs[i + 1];
|
|
23
|
-
i += 1;
|
|
24
|
-
continue;
|
|
25
|
-
}
|
|
26
|
-
flags.add(arg);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (!taskDir) {
|
|
30
|
-
console.error(
|
|
31
|
-
'usage: check-task-artifact-contract.mjs <task-dir> [--contract path] [--terminal complete|no-change|blocked] [--require-recipe-quality-if-recipe] [--require-recipe-coverage-if-recipe] [--require-learnings] [--skip-learnings]',
|
|
32
|
-
);
|
|
33
|
-
process.exit(2);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const issues = [];
|
|
37
|
-
const mediaExt = /\.(png|jpe?g|gif|mp4|mov|webm)$/i;
|
|
38
|
-
const internalArtifactPath =
|
|
39
|
-
/^artifacts\/(?:harness-launch|harness-relaunch|harness-relaunch-node20|runtime-launch|runtime-relaunch|runner-blockers)\//;
|
|
40
|
-
const allowedManifestKeys = new Set([
|
|
41
|
-
'version',
|
|
42
|
-
'preferred_mode',
|
|
43
|
-
'summary',
|
|
44
|
-
'before_after_pairs',
|
|
45
|
-
'standalone',
|
|
46
|
-
'omit',
|
|
47
|
-
'videos',
|
|
48
|
-
'before_state_capture',
|
|
49
|
-
]);
|
|
50
|
-
const allowedPairKeys = new Set(['label', 'covers', 'before', 'after', 'note']);
|
|
51
|
-
const allowedStandaloneKeys = new Set(['label', 'covers', 'file', 'note']);
|
|
52
|
-
const allowedVideoKeys = new Set(['before', 'after', 'preferred', 'note']);
|
|
53
|
-
const allowedOmitKeys = new Set(['file', 'reason']);
|
|
54
|
-
|
|
55
|
-
function fileExists(rel) {
|
|
56
|
-
try {
|
|
57
|
-
return statSync(path.join(taskDir, rel)).isFile();
|
|
58
|
-
} catch {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function readText(rel) {
|
|
64
|
-
const p = path.join(taskDir, rel);
|
|
65
|
-
return existsSync(p) ? readFileSync(p, 'utf8') : null;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function isRecord(value) {
|
|
69
|
-
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function unknownKeys(record, allowed, prefix) {
|
|
73
|
-
for (const key of Object.keys(record)) {
|
|
74
|
-
if (!allowed.has(key)) issues.push(`${prefix}.${key}: unknown key`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function optionalString(record, key, prefix) {
|
|
79
|
-
if (record[key] !== undefined && typeof record[key] !== 'string') {
|
|
80
|
-
issues.push(`${prefix}.${key}: expected string`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function optionalStringArray(record, key, prefix) {
|
|
85
|
-
if (record[key] === undefined) return;
|
|
86
|
-
if (!Array.isArray(record[key]) || record[key].some((entry) => typeof entry !== 'string')) {
|
|
87
|
-
issues.push(`${prefix}.${key}: expected string[]`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function normalizeArtifactPath(value) {
|
|
92
|
-
if (typeof value !== 'string') return null;
|
|
93
|
-
const trimmed = value.trim();
|
|
94
|
-
if (!trimmed || /^(?:[a-z][a-z0-9+.-]*:|\/\/|#)/i.test(trimmed)) return null;
|
|
95
|
-
const pathPart = trimmed.split(/[?#]/, 1)[0];
|
|
96
|
-
const normalized = pathPart.replace(/\\/g, '/').replace(/^\.\/+/g, '');
|
|
97
|
-
if (!normalized || path.posix.isAbsolute(normalized) || normalized.includes('\0')) return null;
|
|
98
|
-
const segments = normalized.split('/').filter(Boolean);
|
|
99
|
-
if (segments.some((segment) => segment === '.' || segment === '..')) return null;
|
|
100
|
-
const withoutArtifacts =
|
|
101
|
-
segments[0] === 'artifacts' ? segments.slice(1).join('/') : segments.join('/');
|
|
102
|
-
if (!withoutArtifacts || !mediaExt.test(withoutArtifacts)) return null;
|
|
103
|
-
return `artifacts/${withoutArtifacts}`;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function addRef(refs, value) {
|
|
107
|
-
const normalized = normalizeArtifactPath(value);
|
|
108
|
-
if (!normalized) return;
|
|
109
|
-
if (internalArtifactPath.test(normalized)) {
|
|
110
|
-
issues.push(`evidence-manifest references internal artifact: ${normalized}`);
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
refs.add(normalized);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function validateRecipeQualityArtifact() {
|
|
117
|
-
const text = readText('artifacts/recipe-quality.json');
|
|
118
|
-
if (!text) return;
|
|
119
|
-
let artifact;
|
|
120
|
-
try {
|
|
121
|
-
artifact = JSON.parse(text);
|
|
122
|
-
} catch (error) {
|
|
123
|
-
issues.push(`recipe-quality.json: invalid JSON: ${error.message}`);
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
if (!isRecord(artifact)) {
|
|
127
|
-
issues.push('recipe-quality.json: expected object');
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
if (artifact.version !== 1) issues.push('recipe-quality.json: version must be 1');
|
|
131
|
-
if (!['pass', 'warn', 'fail'].includes(artifact.verdict)) {
|
|
132
|
-
issues.push('recipe-quality.json: verdict must be pass, warn, or fail');
|
|
133
|
-
}
|
|
134
|
-
if (!isRecord(artifact.compact) || !['PASS', 'WARN', 'FAIL'].includes(artifact.compact.verdict)) {
|
|
135
|
-
issues.push('recipe-quality.json: compact.verdict must be PASS, WARN, or FAIL');
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function parseManifest() {
|
|
140
|
-
const text = readText('artifacts/evidence-manifest.json');
|
|
141
|
-
if (!text) return null;
|
|
142
|
-
let manifest;
|
|
10
|
+
function resolveRuntimeScript() {
|
|
143
11
|
try {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
issues.push(`evidence-manifest.json: invalid JSON: ${error.message}`);
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
if (!isRecord(manifest)) {
|
|
150
|
-
issues.push('evidence-manifest.json: expected object');
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
unknownKeys(manifest, allowedManifestKeys, 'manifest');
|
|
154
|
-
if (manifest.version !== undefined && typeof manifest.version !== 'number') {
|
|
155
|
-
issues.push('manifest.version: expected number');
|
|
156
|
-
}
|
|
157
|
-
if (
|
|
158
|
-
manifest.preferred_mode !== undefined &&
|
|
159
|
-
!['screenshots', 'video'].includes(manifest.preferred_mode)
|
|
160
|
-
) {
|
|
161
|
-
issues.push('manifest.preferred_mode: expected screenshots or video');
|
|
162
|
-
}
|
|
163
|
-
optionalString(manifest, 'summary', 'manifest');
|
|
164
|
-
|
|
165
|
-
if (manifest.before_after_pairs !== undefined) {
|
|
166
|
-
if (!Array.isArray(manifest.before_after_pairs)) {
|
|
167
|
-
issues.push('manifest.before_after_pairs: expected array');
|
|
168
|
-
} else {
|
|
169
|
-
manifest.before_after_pairs.forEach((entry, index) => {
|
|
170
|
-
const prefix = `manifest.before_after_pairs[${index}]`;
|
|
171
|
-
if (!isRecord(entry)) return issues.push(`${prefix}: expected object`);
|
|
172
|
-
unknownKeys(entry, allowedPairKeys, prefix);
|
|
173
|
-
if (typeof entry.label !== 'string' || !entry.label.trim())
|
|
174
|
-
issues.push(`${prefix}.label: expected non-empty string`);
|
|
175
|
-
optionalStringArray(entry, 'covers', prefix);
|
|
176
|
-
optionalString(entry, 'before', prefix);
|
|
177
|
-
optionalString(entry, 'after', prefix);
|
|
178
|
-
optionalString(entry, 'note', prefix);
|
|
179
|
-
if (entry.before === undefined && entry.after === undefined)
|
|
180
|
-
issues.push(`${prefix}: expected before or after`);
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (manifest.standalone !== undefined) {
|
|
186
|
-
if (!Array.isArray(manifest.standalone)) {
|
|
187
|
-
issues.push('manifest.standalone: expected array');
|
|
188
|
-
} else {
|
|
189
|
-
manifest.standalone.forEach((entry, index) => {
|
|
190
|
-
const prefix = `manifest.standalone[${index}]`;
|
|
191
|
-
if (!isRecord(entry)) return issues.push(`${prefix}: expected object`);
|
|
192
|
-
unknownKeys(entry, allowedStandaloneKeys, prefix);
|
|
193
|
-
if (typeof entry.label !== 'string' || !entry.label.trim())
|
|
194
|
-
issues.push(`${prefix}.label: expected non-empty string`);
|
|
195
|
-
optionalStringArray(entry, 'covers', prefix);
|
|
196
|
-
if (typeof entry.file !== 'string' || !entry.file.trim())
|
|
197
|
-
issues.push(`${prefix}.file: expected non-empty string`);
|
|
198
|
-
optionalString(entry, 'note', prefix);
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (manifest.videos !== undefined) {
|
|
204
|
-
if (!isRecord(manifest.videos)) {
|
|
205
|
-
issues.push('manifest.videos: expected object');
|
|
206
|
-
} else {
|
|
207
|
-
unknownKeys(manifest.videos, allowedVideoKeys, 'manifest.videos');
|
|
208
|
-
optionalString(manifest.videos, 'before', 'manifest.videos');
|
|
209
|
-
optionalString(manifest.videos, 'after', 'manifest.videos');
|
|
210
|
-
optionalString(manifest.videos, 'note', 'manifest.videos');
|
|
211
|
-
if (
|
|
212
|
-
manifest.videos.preferred !== undefined &&
|
|
213
|
-
typeof manifest.videos.preferred !== 'boolean'
|
|
214
|
-
) {
|
|
215
|
-
issues.push('manifest.videos.preferred: expected boolean');
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (manifest.omit !== undefined) {
|
|
221
|
-
if (!Array.isArray(manifest.omit)) {
|
|
222
|
-
issues.push('manifest.omit: expected array');
|
|
223
|
-
} else {
|
|
224
|
-
manifest.omit.forEach((entry, index) => {
|
|
225
|
-
const prefix = `manifest.omit[${index}]`;
|
|
226
|
-
if (typeof entry === 'string') return;
|
|
227
|
-
if (!isRecord(entry)) return issues.push(`${prefix}: expected string or object`);
|
|
228
|
-
unknownKeys(entry, allowedOmitKeys, prefix);
|
|
229
|
-
if (typeof entry.file !== 'string' || !entry.file.trim())
|
|
230
|
-
issues.push(`${prefix}.file: expected non-empty string`);
|
|
231
|
-
optionalString(entry, 'reason', prefix);
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const refs = new Set();
|
|
237
|
-
for (const pair of manifest.before_after_pairs ?? []) {
|
|
238
|
-
addRef(refs, pair?.before);
|
|
239
|
-
addRef(refs, pair?.after);
|
|
240
|
-
}
|
|
241
|
-
for (const entry of manifest.standalone ?? []) addRef(refs, entry?.file);
|
|
242
|
-
addRef(refs, manifest.videos?.before);
|
|
243
|
-
addRef(refs, manifest.videos?.after);
|
|
244
|
-
|
|
245
|
-
for (const ref of [...refs].sort()) {
|
|
246
|
-
if (!fileExists(ref)) issues.push(`evidence-manifest references missing artifact: ${ref}`);
|
|
247
|
-
}
|
|
248
|
-
return { manifest, refs };
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const hasRecipe = fileExists('artifacts/recipe.json');
|
|
252
|
-
if (
|
|
253
|
-
hasRecipe &&
|
|
254
|
-
flags.has('--require-recipe-quality-if-recipe') &&
|
|
255
|
-
!fileExists('artifacts/recipe-quality.json')
|
|
256
|
-
) {
|
|
257
|
-
issues.push('recipe.json exists but artifacts/recipe-quality.json is missing');
|
|
258
|
-
}
|
|
259
|
-
if (
|
|
260
|
-
hasRecipe &&
|
|
261
|
-
flags.has('--require-recipe-coverage-if-recipe') &&
|
|
262
|
-
!fileExists('artifacts/recipe-coverage.md')
|
|
263
|
-
) {
|
|
264
|
-
issues.push('recipe.json exists but artifacts/recipe-coverage.md is missing');
|
|
265
|
-
}
|
|
266
|
-
validateRecipeQualityArtifact();
|
|
267
|
-
|
|
268
|
-
const parsed = parseManifest();
|
|
269
|
-
const coverage = readText('artifacts/recipe-coverage.md');
|
|
270
|
-
if (coverage && /\|\s*(visual|mixed)\s*\|/i.test(coverage)) {
|
|
271
|
-
const refCount = parsed?.refs?.size ?? 0;
|
|
272
|
-
if (refCount === 0)
|
|
273
|
-
issues.push(
|
|
274
|
-
'recipe-coverage.md declares visual/mixed proof but evidence-manifest has no media references',
|
|
12
|
+
return requireFromHere.resolve(
|
|
13
|
+
'@farmslot/agent-runtime/scripts/check-task-artifact-contract.mjs',
|
|
275
14
|
);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const learnings = readText('artifacts/learnings.md');
|
|
280
|
-
if (!learnings?.trim()) {
|
|
281
|
-
issues.push('artifacts/learnings.md: required non-empty learnings artifact');
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
if (contractPath && existsSync(contractPath) && terminalCommand) {
|
|
286
|
-
const contract = JSON.parse(readFileSync(contractPath, 'utf8'));
|
|
287
|
-
const expanded = expandedArtifactsForCommand(contract, terminalCommand, fileExists);
|
|
288
|
-
for (const artifactPath of expanded.artifacts) {
|
|
289
|
-
if (flags.has('--skip-learnings') && artifactPath === 'artifacts/learnings.md') continue;
|
|
290
|
-
if (artifactPath.endsWith('.md') && !readText(artifactPath)?.trim()) {
|
|
291
|
-
issues.push(`${artifactPath}: required non-empty artifact (worker terminal contract)`);
|
|
292
|
-
}
|
|
293
|
-
if (!fileExists(artifactPath)) {
|
|
294
|
-
issues.push(`${artifactPath}: required artifact missing (worker terminal contract)`);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
if (expanded.requireRecipeQuality && hasRecipe && !fileExists('artifacts/recipe-quality.json')) {
|
|
298
|
-
issues.push('recipe.json exists but artifacts/recipe-quality.json is missing');
|
|
299
|
-
}
|
|
300
|
-
if (expanded.requireRecipeCoverage && hasRecipe && !fileExists('artifacts/recipe-coverage.md')) {
|
|
301
|
-
issues.push('recipe.json exists but artifacts/recipe-coverage.md is missing');
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (error.code !== 'MODULE_NOT_FOUND') throw error;
|
|
17
|
+
return path.resolve(packageRoot, '../agent-runtime/scripts/check-task-artifact-contract.mjs');
|
|
302
18
|
}
|
|
303
19
|
}
|
|
304
20
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
console.log('TASK_ARTIFACT_CONTRACT_PASS');
|
|
21
|
+
const result = spawnSync(process.execPath, [resolveRuntimeScript(), ...process.argv.slice(2)], {
|
|
22
|
+
stdio: 'inherit',
|
|
23
|
+
});
|
|
24
|
+
process.exit(result.status ?? 1);
|