@farmslot/skills 0.1.1 → 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 +15 -0
- 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 +8 -2
- package/scripts/check-task-artifact-contract.mjs +24 -0
- package/scripts/mark-checklist-step.cjs +10 -269
- package/scripts/worker-terminal-contract.cjs +17 -0
- package/skills/packet/SKILL.md +136 -0
- package/skills/recipe-quality/SKILL.md +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@ All notable changes to `@farmslot/skills` are tracked here.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
- Active-development baseline; add user-facing changes here before release or package publication.
|
|
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
|
+
|
|
15
|
+
## 0.1.2 - 2026-07-03
|
|
16
|
+
|
|
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
|
|
18
|
+
- **dev / fix-bug:** terminal `./mark complete` requires `artifacts/learnings.md` + `artifacts/pr-description.md` (not `report.md`)
|
|
19
|
+
- `./mark` infers dev/fix-bug flow from embedded checklist `Skill:` header (standalone recipe skills)
|
|
20
|
+
- Export `./scripts/worker-terminal-contract.cjs` and `./scripts/check-task-artifact-contract.mjs` from the package
|
|
21
|
+
|
|
7
22
|
## 0.1.1 - 2026-06-30
|
|
8
23
|
|
|
9
24
|
- Single-path mark CLI for worker terminal signals.
|
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",
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
},
|
|
17
17
|
"./skills/*": "./skills/*/SKILL.md",
|
|
18
18
|
"./schemas/*": "./schemas/*",
|
|
19
|
-
"./scripts/mark-checklist-step.cjs": "./scripts/mark-checklist-step.cjs"
|
|
19
|
+
"./scripts/mark-checklist-step.cjs": "./scripts/mark-checklist-step.cjs",
|
|
20
|
+
"./scripts/worker-terminal-contract.cjs": "./scripts/worker-terminal-contract.cjs",
|
|
21
|
+
"./scripts/check-task-artifact-contract.mjs": "./scripts/check-task-artifact-contract.mjs"
|
|
20
22
|
},
|
|
21
23
|
"scripts": {
|
|
22
24
|
"typecheck": "tsc --noEmit --project tsconfig.json",
|
|
@@ -30,6 +32,9 @@
|
|
|
30
32
|
"prepack": "cd ../.. && node scripts/quality/check-farmslot-package-readiness.mjs --require-changelog --packages @farmslot/skills && yarn workspace @farmslot/skills build",
|
|
31
33
|
"prepublishOnly": "node ../../scripts/quality/check-farmslot-package-readiness.mjs --publish --packages @farmslot/skills"
|
|
32
34
|
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@farmslot/agent-runtime": "0.1.1"
|
|
37
|
+
},
|
|
33
38
|
"devDependencies": {
|
|
34
39
|
"@types/node": "^22.0.0",
|
|
35
40
|
"eslint": "^10.3.0",
|
|
@@ -64,6 +69,7 @@
|
|
|
64
69
|
"dist/**/*.js.map",
|
|
65
70
|
"schemas/**/*.json",
|
|
66
71
|
"scripts/**/*.cjs",
|
|
72
|
+
"scripts/**/*.mjs",
|
|
67
73
|
"skills/**/*.md",
|
|
68
74
|
"templates/**/*.json"
|
|
69
75
|
],
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
8
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
9
|
+
|
|
10
|
+
function resolveRuntimeScript() {
|
|
11
|
+
try {
|
|
12
|
+
return requireFromHere.resolve(
|
|
13
|
+
'@farmslot/agent-runtime/scripts/check-task-artifact-contract.mjs',
|
|
14
|
+
);
|
|
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');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const result = spawnSync(process.execPath, [resolveRuntimeScript(), ...process.argv.slice(2)], {
|
|
22
|
+
stdio: 'inherit',
|
|
23
|
+
});
|
|
24
|
+
process.exit(result.status ?? 1);
|
|
@@ -1,277 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const path = require('node:path');
|
|
4
|
-
|
|
5
|
-
const START_COMMANDS = new Set(['start']);
|
|
6
|
-
const TERMINAL_COMMANDS = new Set(['complete', 'no-change', 'blocked']);
|
|
7
|
-
const NO_CHANGE_REPORT = path.join('artifacts', 'no-change-report.md');
|
|
8
|
-
|
|
9
|
-
const usageLine =
|
|
10
|
-
'usage: mark <step-number|start|complete|no-change|blocked> [--reason text] [--already-fixed] [--mark-last] [--no-self-review]';
|
|
11
|
-
|
|
12
|
-
function printHelp() {
|
|
13
|
-
console.log(
|
|
14
|
-
[
|
|
15
|
-
usageLine,
|
|
16
|
-
'',
|
|
17
|
-
'Bootstrap: ./mark start — worker-owned SIGNAL.json with status running (no checklist box).',
|
|
18
|
-
'Progress: ./mark 1, ./mark 2, ... — checks the box and appends checklistTiming.',
|
|
19
|
-
'Terminal:',
|
|
20
|
-
' ./mark complete [--mark-last] [--no-self-review]',
|
|
21
|
-
' ./mark no-change --reason "..." [--already-fixed] [--mark-last]',
|
|
22
|
-
' ./mark blocked --reason "..." [--mark-last]',
|
|
23
|
-
'Do not write SIGNAL.json by hand or with echo.',
|
|
24
|
-
].join('\n'),
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function usage() {
|
|
29
|
-
console.error(usageLine);
|
|
30
|
-
console.error('run ./mark --help for details');
|
|
31
|
-
process.exit(2);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const args = process.argv.slice(2);
|
|
35
|
-
if (args.length === 1 && (args[0] === '--help' || args[0] === '-h')) {
|
|
36
|
-
printHelp();
|
|
37
|
-
process.exit(0);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const [taskPath, signalPath, stepRaw, ...rest] = args;
|
|
41
|
-
if (stepRaw === '--help' || stepRaw === '-h') {
|
|
42
|
-
printHelp();
|
|
43
|
-
process.exit(0);
|
|
44
|
-
}
|
|
45
|
-
if (!taskPath || !signalPath || !stepRaw) usage();
|
|
46
|
-
|
|
47
|
-
const opts = {};
|
|
48
|
-
for (let i = 0; i < rest.length; i += 1) {
|
|
49
|
-
const key = rest[i];
|
|
50
|
-
if (key === '--mark-last' || key === '--already-fixed' || key === '--no-self-review') {
|
|
51
|
-
opts[key.slice(2)] = true;
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
const value = rest[i + 1];
|
|
55
|
-
if (!key?.startsWith('--') || value === undefined) usage();
|
|
56
|
-
opts[key.slice(2)] = value;
|
|
57
|
-
i += 1;
|
|
58
|
-
}
|
|
2
|
+
'use strict';
|
|
59
3
|
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
const terminalCommand = TERMINAL_COMMANDS.has(stepToken) ? stepToken : null;
|
|
63
|
-
let stepNumber = null;
|
|
64
|
-
if (!isStartCommand && !terminalCommand) {
|
|
65
|
-
stepNumber = Number(stepRaw);
|
|
66
|
-
if (!Number.isInteger(stepNumber) || stepNumber < 1) usage();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (isStartCommand && Object.keys(opts).length > 0) usage();
|
|
70
|
-
if (terminalCommand === 'no-change' || terminalCommand === 'blocked') {
|
|
71
|
-
if (!opts.reason?.trim()) {
|
|
72
|
-
console.error(`${terminalCommand} requires --reason`);
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const SIGNAL_PASSTHROUGH_KEYS = ['role', 'contextId', 'prNumber'];
|
|
78
|
-
|
|
79
|
-
function pickSignalPassthrough(signal) {
|
|
80
|
-
const out = {};
|
|
81
|
-
for (const key of SIGNAL_PASSTHROUGH_KEYS) {
|
|
82
|
-
if (signal[key] !== undefined) out[key] = signal[key];
|
|
83
|
-
}
|
|
84
|
-
return out;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function resolveTerminalPreset(command) {
|
|
88
|
-
switch (command) {
|
|
89
|
-
case 'complete':
|
|
90
|
-
return { status: 'complete', outcome: 'success', disposition: 'fixed' };
|
|
91
|
-
case 'no-change':
|
|
92
|
-
return {
|
|
93
|
-
status: 'complete',
|
|
94
|
-
outcome: 'success',
|
|
95
|
-
disposition: opts['already-fixed'] ? 'already_fixed' : 'not_reproducible',
|
|
96
|
-
};
|
|
97
|
-
case 'blocked':
|
|
98
|
-
return { status: 'blocked', outcome: 'partial', disposition: 'blocked' };
|
|
99
|
-
default:
|
|
100
|
-
usage();
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function assertNoChangeReport(taskDir) {
|
|
106
|
-
const reportAbs = path.join(taskDir, NO_CHANGE_REPORT);
|
|
107
|
-
if (!fs.existsSync(reportAbs) || fs.statSync(reportAbs).size === 0) {
|
|
108
|
-
console.error(`missing required report: ${NO_CHANGE_REPORT}`);
|
|
109
|
-
process.exit(1);
|
|
110
|
-
}
|
|
111
|
-
return NO_CHANGE_REPORT;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function buildSignalUpdate(signal, terminal, target, timing, events, now, taskPath, taskDir) {
|
|
115
|
-
const base = {
|
|
116
|
-
...pickSignalPassthrough(signal),
|
|
117
|
-
step: target?.label ?? signal.step ?? 'complete',
|
|
118
|
-
checklistTiming: {
|
|
119
|
-
schemaVersion: 1,
|
|
120
|
-
source: timing.source || path.basename(taskPath),
|
|
121
|
-
events,
|
|
122
|
-
},
|
|
123
|
-
timestamp: now,
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
if (!terminal) {
|
|
127
|
-
return { ...base, status: 'running' };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const preset = resolveTerminalPreset(terminal.command);
|
|
131
|
-
const next = {
|
|
132
|
-
...base,
|
|
133
|
-
status: preset.status,
|
|
134
|
-
outcome: preset.outcome,
|
|
135
|
-
disposition: preset.disposition,
|
|
136
|
-
...(opts.reason ? { reason: opts.reason } : {}),
|
|
137
|
-
...(terminal.command === 'complete' && opts['no-self-review']
|
|
138
|
-
? { needsSelfReview: false }
|
|
139
|
-
: {}),
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
if (terminal.command === 'no-change') {
|
|
143
|
-
next.evidence = { reportPath: assertNoChangeReport(taskDir) };
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return next;
|
|
147
|
-
}
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { createRequire } = require('node:module');
|
|
148
6
|
|
|
149
|
-
|
|
150
|
-
const dir = path.dirname(file);
|
|
151
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
152
|
-
const tmp = path.join(dir, `.${path.basename(file)}.${process.pid}.tmp`);
|
|
153
|
-
fs.writeFileSync(tmp, content, mode ? { mode } : undefined);
|
|
154
|
-
fs.renameSync(tmp, file);
|
|
155
|
-
}
|
|
7
|
+
const requireFromHere = createRequire(__filename);
|
|
156
8
|
|
|
157
|
-
function
|
|
9
|
+
function resolveRuntimeScript() {
|
|
158
10
|
try {
|
|
159
|
-
return
|
|
160
|
-
} catch (
|
|
161
|
-
if (
|
|
162
|
-
|
|
11
|
+
return requireFromHere.resolve('@farmslot/agent-runtime/scripts/mark-checklist-step.cjs');
|
|
12
|
+
} catch (error) {
|
|
13
|
+
if (error.code !== 'MODULE_NOT_FOUND') throw error;
|
|
14
|
+
return path.resolve(__dirname, '../../agent-runtime/scripts/mark-checklist-step.cjs');
|
|
163
15
|
}
|
|
164
16
|
}
|
|
165
17
|
|
|
166
|
-
|
|
167
|
-
return raw
|
|
168
|
-
.replace(/^\*\*(.*?)\*\*\s*[—-]?\s*/, '$1 ')
|
|
169
|
-
.replace(/`([^`]+)`/g, '$1')
|
|
170
|
-
.replace(/\s+/g, ' ')
|
|
171
|
-
.trim();
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function parseChecklist(markdown) {
|
|
175
|
-
const lines = markdown.split(/\n/);
|
|
176
|
-
const items = [];
|
|
177
|
-
let inFence = false;
|
|
178
|
-
let seen = 0;
|
|
179
|
-
for (let i = 0; i < lines.length; i += 1) {
|
|
180
|
-
if (/^\s*```/.test(lines[i])) {
|
|
181
|
-
inFence = !inFence;
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
if (inFence) continue;
|
|
185
|
-
const match = lines[i].match(/^(\s*[-*]\s+\[)( |x|X)(\]\s+)(.*)$/);
|
|
186
|
-
if (!match) continue;
|
|
187
|
-
seen += 1;
|
|
188
|
-
items.push({
|
|
189
|
-
lineIndex: i,
|
|
190
|
-
stepNumber: seen,
|
|
191
|
-
checked: match[2].toLowerCase() === 'x',
|
|
192
|
-
prefix: match[1],
|
|
193
|
-
suffix: match[3],
|
|
194
|
-
rawLabel: match[4],
|
|
195
|
-
label: stripLabel(match[4]),
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
return { lines, items };
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function markStepInLines(lines, item) {
|
|
202
|
-
if (item.checked) return false;
|
|
203
|
-
lines[item.lineIndex] = `${item.prefix}x${item.suffix}${item.rawLabel}`;
|
|
204
|
-
return true;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function resolveTarget(taskPath, stepNumber, markLast) {
|
|
208
|
-
const original = fs.readFileSync(taskPath, 'utf8');
|
|
209
|
-
const parsed = parseChecklist(original);
|
|
210
|
-
let item = null;
|
|
211
|
-
if (stepNumber != null) {
|
|
212
|
-
item = parsed.items.find((entry) => entry.stepNumber === stepNumber) ?? null;
|
|
213
|
-
if (!item) {
|
|
214
|
-
console.error(`checklist step ${stepNumber} not found in ${taskPath}`);
|
|
215
|
-
process.exit(1);
|
|
216
|
-
}
|
|
217
|
-
} else if (markLast) {
|
|
218
|
-
const unchecked = parsed.items.filter((entry) => !entry.checked);
|
|
219
|
-
item = unchecked.length ? unchecked[unchecked.length - 1] : null;
|
|
220
|
-
}
|
|
221
|
-
let updated = original;
|
|
222
|
-
if (item) {
|
|
223
|
-
const nextLines = [...parsed.lines];
|
|
224
|
-
markStepInLines(nextLines, item);
|
|
225
|
-
updated = nextLines.join('\n');
|
|
226
|
-
if (updated !== original) atomicWrite(taskPath, updated);
|
|
227
|
-
}
|
|
228
|
-
return item
|
|
229
|
-
? { stepNumber: item.stepNumber, label: item.label }
|
|
230
|
-
: { stepNumber: null, label: 'complete' };
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const taskDir = path.dirname(signalPath);
|
|
234
|
-
const target = isStartCommand
|
|
235
|
-
? { stepNumber: null, label: 'started' }
|
|
236
|
-
: resolveTarget(taskPath, stepNumber, Boolean(terminalCommand && opts['mark-last']));
|
|
237
|
-
|
|
238
|
-
const now = new Date().toISOString();
|
|
239
|
-
const signal = readJson(signalPath);
|
|
240
|
-
const timing =
|
|
241
|
-
signal.checklistTiming && typeof signal.checklistTiming === 'object'
|
|
242
|
-
? signal.checklistTiming
|
|
243
|
-
: { schemaVersion: 1, source: path.basename(taskPath), events: [] };
|
|
244
|
-
const events = Array.isArray(timing.events) ? [...timing.events] : [];
|
|
245
|
-
|
|
246
|
-
if (
|
|
247
|
-
!isStartCommand &&
|
|
248
|
-
target.stepNumber != null &&
|
|
249
|
-
!events.some(
|
|
250
|
-
(event) =>
|
|
251
|
-
event && (event.stepNumber === target.stepNumber || event.index === target.stepNumber - 1),
|
|
252
|
-
)
|
|
253
|
-
) {
|
|
254
|
-
events.push({ stepNumber: target.stepNumber, label: target.label, checkedAt: now });
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const next = buildSignalUpdate(
|
|
258
|
-
signal,
|
|
259
|
-
terminalCommand ? { command: terminalCommand } : null,
|
|
260
|
-
target,
|
|
261
|
-
timing,
|
|
262
|
-
events,
|
|
263
|
-
now,
|
|
264
|
-
taskPath,
|
|
265
|
-
taskDir,
|
|
266
|
-
);
|
|
267
|
-
atomicWrite(signalPath, `${JSON.stringify(next, null, 2)}\n`, 0o644);
|
|
268
|
-
|
|
269
|
-
if (isStartCommand) {
|
|
270
|
-
console.log('signal started');
|
|
271
|
-
} else if (terminalCommand) {
|
|
272
|
-
console.log(
|
|
273
|
-
`signal ${terminalCommand}: status=${next.status} disposition=${next.disposition ?? 'n/a'}`,
|
|
274
|
-
);
|
|
275
|
-
} else {
|
|
276
|
-
console.log(`marked ${target.stepNumber}: ${target.label}`);
|
|
277
|
-
}
|
|
18
|
+
require(resolveRuntimeScript());
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { createRequire } = require('node:module');
|
|
5
|
+
|
|
6
|
+
const requireFromHere = createRequire(__filename);
|
|
7
|
+
|
|
8
|
+
function resolveRuntimeScript() {
|
|
9
|
+
try {
|
|
10
|
+
return requireFromHere.resolve('@farmslot/agent-runtime/scripts/worker-terminal-contract.cjs');
|
|
11
|
+
} catch (error) {
|
|
12
|
+
if (error.code !== 'MODULE_NOT_FOUND') throw error;
|
|
13
|
+
return path.resolve(__dirname, '../../agent-runtime/scripts/worker-terminal-contract.cjs');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = require(resolveRuntimeScript());
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: packet
|
|
3
|
+
description: Communicate complex agent output as compact, reviewable Farmslot operator packets with artifact anchors and safe action requests
|
|
4
|
+
compatibility: Claude, Codex, Cursor, and Markdown skill runners with access to the working directory
|
|
5
|
+
metadata:
|
|
6
|
+
package: '@farmslot/skills'
|
|
7
|
+
adr: ADR-048
|
|
8
|
+
outputs: artifacts/interactive/*.packet.json and optional sibling markdown when supported
|
|
9
|
+
allowed-tools: Read Write Bash(rg:*) Bash(node:*) Bash(git:*)
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Interactive Operator Packets
|
|
13
|
+
|
|
14
|
+
Use this skill when a plan, decision, blocker, comparison, or evidence review would be easier for the operator to inspect as a structured review packet than as terminal prose.
|
|
15
|
+
|
|
16
|
+
This skill teaches the communication pattern. It does not assume Farmslot runtime support is installed. If the current project does not support Interactive Operator Packets yet, produce a concise markdown fallback and do not claim that Command Center or Companion can render it.
|
|
17
|
+
|
|
18
|
+
## Use When
|
|
19
|
+
|
|
20
|
+
- You need the operator to review a plan before implementation.
|
|
21
|
+
- You need a decision with clear options and consequences.
|
|
22
|
+
- You found a blocker and need targeted human input.
|
|
23
|
+
- You want feedback anchored to a diff, artifact, screenshot, recipe result, or report section.
|
|
24
|
+
- You are summarizing evidence and want the operator to approve, reject, or request a revision.
|
|
25
|
+
|
|
26
|
+
## Do Not Use When
|
|
27
|
+
|
|
28
|
+
- A one-sentence answer is enough.
|
|
29
|
+
- The operator asked for raw terminal output.
|
|
30
|
+
- The action would be unsafe without an existing Farmslot confirmation surface.
|
|
31
|
+
- You would need to execute arbitrary HTML or JavaScript to make the packet useful.
|
|
32
|
+
- The packet would only restate a normal `SIGNAL.json`, recipe manifest, or PR description.
|
|
33
|
+
|
|
34
|
+
## Packet Contract
|
|
35
|
+
|
|
36
|
+
When the project supports packets, write JSON to:
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
artifacts/interactive/<short-id>.packet.json
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Use this shape:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"schema": "farmslot.interactive.operator-packet.v1",
|
|
47
|
+
"id": "review-plan-1",
|
|
48
|
+
"title": "Review implementation plan",
|
|
49
|
+
"intent": "plan",
|
|
50
|
+
"summary": "Confirm the scoped approach before changes start.",
|
|
51
|
+
"body": {
|
|
52
|
+
"format": "markdown",
|
|
53
|
+
"path": "artifacts/interactive/review-plan-1.md"
|
|
54
|
+
},
|
|
55
|
+
"anchors": [
|
|
56
|
+
{
|
|
57
|
+
"id": "diff-risk",
|
|
58
|
+
"label": "Risky diff area",
|
|
59
|
+
"artifactPath": "artifacts/diff.txt",
|
|
60
|
+
"line": 42
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
"actions": [
|
|
64
|
+
{
|
|
65
|
+
"id": "approve",
|
|
66
|
+
"label": "Approve plan",
|
|
67
|
+
"kind": "terminal.send",
|
|
68
|
+
"safety": "operator-confirmed",
|
|
69
|
+
"payload": {
|
|
70
|
+
"text": "Approved packet review-plan-1. Proceed with the scoped plan."
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
],
|
|
74
|
+
"createdAt": "2026-07-03T00:00:00.000Z"
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Keep `body.text` or the sibling markdown short. A packet is a review surface, not a report dump.
|
|
79
|
+
|
|
80
|
+
## Writing Rules
|
|
81
|
+
|
|
82
|
+
1. Start with the operator's next decision or review task.
|
|
83
|
+
2. Use a short title and one-sentence summary.
|
|
84
|
+
3. Pick one intent: `plan`, `decision`, `review`, `blocker`, `evidence`, or `comparison`.
|
|
85
|
+
4. Anchor feedback to concrete artifacts when possible.
|
|
86
|
+
5. Offer only actions the operator can understand and confirm.
|
|
87
|
+
6. Print a terminal fallback after writing the packet.
|
|
88
|
+
|
|
89
|
+
Terminal fallback format:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
Interactive packet ready: <title>
|
|
93
|
+
Packet: artifacts/interactive/<short-id>.packet.json
|
|
94
|
+
Fallback: artifacts/interactive/<short-id>.md
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
If Farmslot deep links are known to be supported, also print the route. Never encode a mutating action in the link.
|
|
98
|
+
|
|
99
|
+
## Action Safety
|
|
100
|
+
|
|
101
|
+
Allowed v1 action kinds:
|
|
102
|
+
|
|
103
|
+
- `terminal.send` — send a concise approved response back to the worker.
|
|
104
|
+
- `decision.resolve` — resolve an existing Farmslot decision.
|
|
105
|
+
- `copy` — copy text for manual use.
|
|
106
|
+
- `open-artifact` — open a referenced artifact.
|
|
107
|
+
|
|
108
|
+
Rules:
|
|
109
|
+
|
|
110
|
+
- Mark mutating actions as `operator-confirmed`.
|
|
111
|
+
- Keep generated `terminal.send` text short and specific.
|
|
112
|
+
- Do not hide shell commands inside action payloads.
|
|
113
|
+
- Do not ask the UI to execute arbitrary RPC methods.
|
|
114
|
+
- Do not treat a deep link as approval.
|
|
115
|
+
|
|
116
|
+
## Fallback Mode
|
|
117
|
+
|
|
118
|
+
If packet runtime support is absent:
|
|
119
|
+
|
|
120
|
+
1. Write the same information as markdown in the conversation or artifact directory.
|
|
121
|
+
2. Include anchors as plain file paths and line numbers.
|
|
122
|
+
3. Ask for the operator response in normal chat/terminal.
|
|
123
|
+
4. Do not say that a packet has been opened, rendered, or sent to Companion.
|
|
124
|
+
|
|
125
|
+
## Response Handling
|
|
126
|
+
|
|
127
|
+
When the operator responds to a packet, continue with the referenced packet id and anchor ids:
|
|
128
|
+
|
|
129
|
+
```text
|
|
130
|
+
Received response for packet review-plan-1:
|
|
131
|
+
- action: approve
|
|
132
|
+
- anchor: diff-risk
|
|
133
|
+
- note: Keep the protocol typed; defer HTML.
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Then act on the feedback through the normal task flow. The packet is a communication aid; the runner session remains the source of execution truth.
|
|
@@ -43,3 +43,5 @@ Return:
|
|
|
43
43
|
- weak or missing claims
|
|
44
44
|
- evidence gaps
|
|
45
45
|
- concrete recipe delta
|
|
46
|
+
|
|
47
|
+
When the task requires `artifacts/recipe-quality.json`, do not hand-author the full schema. Provide the compact fields to `farmslot-agent recipe-quality build` (or `buildRecipeQualityArtifact()` from `@farmslot/agent-runtime`) so the artifact passes the `@farmslot/protocol` validator.
|