@farmslot/skills 0.3.0 → 0.5.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,6 +4,16 @@ 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.5.0 - 2026-09-20
10
+
11
+ - README documents the checklist-shaped skill rule: a skill invoked as a child checklist unit (ADR-060) needs `##` sections and `- [ ]` rows the shared parser can enumerate, informational sections named so the skip list drops them, and numbered labels matching their positions. A prose-only skill cannot be a child unit.
12
+
13
+ ## 0.4.0 - 2026-09-17
14
+
15
+ - Recipe Cook lane: the runner envelope reports `terminal_status` (`done|blocked|failed`) and `terminal_reason`; the lane no longer reads or rewrites a `STATUS:` line in the run-local TASK.md, and `meta.json` records `terminal_reason`. Templates and skill text drop the field. An envelope without a valid `terminal_status`, or a blocked/failed one without `terminal_reason`, fails the run; the model output stays in `runner-output.txt`. The `tmux-model-driver` launcher and watcher stop mentioning or parsing the field.
16
+
7
17
  ## 0.3.0 - 2026-08-03
8
18
 
9
19
  - Teach recipe authors to start from existing parameterized recipes and compose them node by node before adding new capabilities.
package/README.md CHANGED
@@ -44,6 +44,35 @@ Supported layouts:
44
44
  | `project-adopt` | Recommend the smallest useful Farmslot integration level for the project. |
45
45
  | `packet` | Communicate plans, blockers, decisions, and evidence reviews as compact operator packets. |
46
46
 
47
+ ## Checklist-shaped skills
48
+
49
+ A checklist step can hand its work to a skill as a **child unit**: the step registers the
50
+ skill, `mark` materializes its body as `subtasks/<id>.md` beside the task checklist, and the
51
+ worker marks the skill's own rows so progress is visible per row instead of one box open for
52
+ the whole review. Farmslot only materializes and observes files; nothing is spawned.
53
+
54
+ A skill can be a child unit when its body is enumerable by the same parser the task checklist
55
+ uses:
56
+
57
+ - `##` (or deeper) headings and `- [ ]` rows, with at least one row. A prose-only skill cannot be
58
+ a child unit.
59
+ - Name informational sections so the shared skip list drops them: `Rules`, `Description`,
60
+ `Task`, `Acceptance Criteria`, `Affected Area`, `Screenshots`, `Comments`, `Root Cause`,
61
+ `Recipe ACs`, `Pre-merge`. Boxes there are never steps.
62
+ - If a row label carries its own number (`**3. …**`), it must match the row's position. The
63
+ numbering guard refuses a body where they diverge, because `mark` targets positions.
64
+
65
+ A template step registers the installed skill and finishes it:
66
+
67
+ ```bash
68
+ {{TASK_DIR}}/mark sub start perps-review --step 21 --from .agents/skills/mms-perps-review-pr/skill.md
69
+ {{TASK_DIR}}/mark sub perps-review 1 # …one per row
70
+ {{TASK_DIR}}/mark sub perps-review complete --report artifacts/review.md
71
+ ```
72
+
73
+ The step's own box is ticked by that `complete`, so the template says what to register, and the
74
+ skill owns what to check.
75
+
47
76
  ## Adoption Ladder
48
77
 
49
78
  1. Skills only: author and review recipes without a runner.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farmslot/skills",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -33,7 +33,7 @@
33
33
  "prepublishOnly": "node ../../scripts/quality/check-farmslot-package-readiness.mjs --publish --packages @farmslot/skills"
34
34
  },
35
35
  "dependencies": {
36
- "@farmslot/agent-runtime": "0.6.0"
36
+ "@farmslot/agent-runtime": "0.13.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/node": "^22.0.0",
@@ -429,7 +429,9 @@ function buildPrompt({
429
429
  '',
430
430
  '## Required output JSON schema',
431
431
  '{',
432
- ' "task_markdown": "full TASK.md content based on the provided template. Set STATUS to the correct terminal value: done, blocked, or failed. Inside ## Validation Evidence include the exact standalone token RECIPE_COOK_VALIDATION_PENDING.",',
432
+ ' "task_markdown": "full TASK.md content based on the provided template. Inside ## Validation Evidence include the exact standalone token RECIPE_COOK_VALIDATION_PENDING.",',
433
+ ' "terminal_status": "done|blocked|failed",',
434
+ ' "terminal_reason": "one sentence naming the concrete blocker or failure; null when done",',
433
435
  ' "recipe_json": { "valid": "json object" },',
434
436
  ' "recipe_cook_json": { "valid": "json object" } or null,',
435
437
  ' "evidence_verdict": "good|ok|bad",',
@@ -451,7 +453,8 @@ function buildPrompt({
451
453
  '',
452
454
  'The JSON object you return is the final serialized payload for those files.',
453
455
  'Emit the JSON last, after you have fully reasoned through:',
454
- '- the rewritten TASK.md contents, including the correct terminal STATUS',
456
+ '- the rewritten TASK.md contents',
457
+ '- the terminal_status (done, blocked, or failed) and, for blocked or failed, its concrete terminal_reason',
455
458
  '- the recipe artifact contents',
456
459
  '- the learning artifact contents',
457
460
  '- what validation commands should run',
@@ -769,26 +772,47 @@ function renderValidationEvidence(validationResults, cdpPort, repoRoot) {
769
772
  return lines.join('\n');
770
773
  }
771
774
 
772
- function deriveTerminalState(taskMarkdown, validationResults) {
775
+ // Terminal state comes from the runner's explicit envelope field, never from
776
+ // status text in the task markdown. Two evidence-based overrides remain: an
777
+ // UNRESOLVED proof target blocks, and a failed validation step fails.
778
+ function deriveTerminalState(envelope, taskMarkdown, validationResults) {
773
779
  const text = String(taskMarkdown || '');
774
- if (/\bSTATUS:\s*blocked\b/i.test(text)) {
775
- return { status: 'blocked', outcome: 'partial' };
780
+ // A reason only travels with a non-done envelope; a note attached to a done
781
+ // envelope must not be presented as the blocker or failure cause. The two
782
+ // evidence overrides below are reachable only for done envelopes, so they
783
+ // synthesize their own reason.
784
+ const reason =
785
+ envelope.terminal_status !== 'done' &&
786
+ typeof envelope.terminal_reason === 'string' &&
787
+ envelope.terminal_reason.trim()
788
+ ? envelope.terminal_reason.trim()
789
+ : null;
790
+ if (envelope.terminal_status === 'blocked') {
791
+ return { status: 'blocked', outcome: 'partial', reason };
776
792
  }
777
- if (/\bSTATUS:\s*failed\b/i.test(text)) {
778
- return { status: 'failed', outcome: 'failure' };
793
+ if (envelope.terminal_status === 'failed') {
794
+ return { status: 'failed', outcome: 'failure', reason };
779
795
  }
780
796
  if (/^\s*[-*]\s+.*:\s*UNRESOLVED\b/m.test(text)) {
781
- return { status: 'blocked', outcome: 'partial' };
797
+ return {
798
+ status: 'blocked',
799
+ outcome: 'partial',
800
+ reason: 'a required proof target remains UNRESOLVED',
801
+ };
782
802
  }
783
803
 
784
- const failingStep = Object.values(validationResults || {}).find(
785
- (result) => result.exit_code !== 0 && result.exit_code !== -1,
804
+ const failingStep = Object.entries(validationResults || {}).find(
805
+ ([, result]) => result.exit_code !== 0 && result.exit_code !== -1,
786
806
  );
787
807
  if (failingStep) {
788
- return { status: 'failed', outcome: 'failure' };
808
+ return {
809
+ status: 'failed',
810
+ outcome: 'failure',
811
+ reason: `validation step ${failingStep[0]} exited ${failingStep[1].exit_code}`,
812
+ };
789
813
  }
790
814
 
791
- return { status: 'done', outcome: 'success' };
815
+ return { status: 'done', outcome: 'success', reason: null };
792
816
  }
793
817
 
794
818
  function injectValidationEvidence(taskMarkdown, evidenceBlock) {
@@ -987,6 +1011,15 @@ async function main() {
987
1011
  typeof envelope.next_delta === 'string' && envelope.next_delta.trim(),
988
1012
  'Runner output missing next_delta',
989
1013
  );
1014
+ assert(
1015
+ ['done', 'blocked', 'failed'].includes(envelope.terminal_status),
1016
+ `Runner output missing terminal_status (done|blocked|failed). See ${path.join(runDir, 'runner-output.txt')}`,
1017
+ );
1018
+ assert(
1019
+ envelope.terminal_status === 'done' ||
1020
+ (typeof envelope.terminal_reason === 'string' && envelope.terminal_reason.trim()),
1021
+ `Runner output missing terminal_reason for terminal_status ${envelope.terminal_status}. See ${path.join(runDir, 'runner-output.txt')}`,
1022
+ );
990
1023
  writeJson(path.join(runDir, 'runner-response.json'), envelope);
991
1024
 
992
1025
  const recipePath = path.join(artifactsDir, 'recipe.json');
@@ -1009,11 +1042,7 @@ async function main() {
1009
1042
  const evidenceBlock = renderValidationEvidence(validationResults, effectiveCdpPort, projectRoot);
1010
1043
  const normalizedTask = normalizeTaskArtifactDir(envelope.task_markdown, artifactsDir);
1011
1044
  let finalTask = injectValidationEvidence(normalizedTask, evidenceBlock);
1012
- const terminalState = deriveTerminalState(finalTask, validationResults);
1013
- finalTask = finalTask.replace(
1014
- /STATUS:\s*(pending|working|done|blocked|failed)/i,
1015
- `STATUS: ${terminalState.status}`,
1016
- );
1045
+ const terminalState = deriveTerminalState(envelope, finalTask, validationResults);
1017
1046
  writeFile(path.join(runDir, 'TASK.md'), finalTask);
1018
1047
 
1019
1048
  const verdict = mapVerdict(envelope.evidence_verdict, validationResults);
@@ -1036,6 +1065,7 @@ async function main() {
1036
1065
  : terminalState.outcome === 'partial'
1037
1066
  ? 'blocked'
1038
1067
  : 'completed',
1068
+ terminal_reason: terminalState.reason,
1039
1069
  });
1040
1070
  writeJson(path.join(artifactsDir, 'grade.json'), {
1041
1071
  recipe_semantic: verdict,
@@ -93,7 +93,7 @@ Completion requires:
93
93
  - unresolved-target audit written
94
94
  - validation evidence recorded
95
95
  - all required proof targets either `PROVEN` or explicitly non-applicable by contract
96
- - `STATUS: done` in the run-local copied `TASK.md`
96
+ - `terminal_status: done` in the run envelope; `blocked` and `failed` carry a concrete `terminal_reason`
97
97
 
98
98
  ## Hard Rules
99
99
 
@@ -119,7 +119,7 @@ Completion requires:
119
119
  | Rewriting long repeated setup chains | reuse parameterized recipes through `call` |
120
120
  | Forcing a canonical recipe when the claim is unresolved | stop or emit only resolved targets with explicit gaps |
121
121
  | Treating interactive farmslot execution like chat-only drafting | keep driving the workspace until artifacts + validation evidence exist |
122
- | Writing recipe artifacts but forgetting the copied `TASK.md` | rewrite the task file and update `STATUS` / evidence sections before stopping |
122
+ | Writing recipe artifacts but forgetting the copied `TASK.md` | rewrite the task file and update the evidence sections before stopping |
123
123
  | Inventing non-canonical runner fields like `timeout` on `wait_for` | use documented runner field names such as `timeout_ms` so live validation honors the intended timing |
124
124
 
125
125
  ## Stop Conditions
@@ -12,7 +12,6 @@ SOURCE_KIND:
12
12
  SOURCE_REF:
13
13
  ARTIFACT_DIR:
14
14
  VALIDATION_MODE:
15
- STATUS: pending
16
15
  ```
17
16
 
18
17
  ## Source Material
@@ -87,7 +86,7 @@ It must include:
87
86
 
88
87
  ## Checklist
89
88
 
90
- - [ ] **1. Fill the task block** — set `TARGET_REPO`, `SOURCE_KIND`, `SOURCE_REF`, `ARTIFACT_DIR`, `VALIDATION_MODE`, `STATUS: working`.
89
+ - [ ] **1. Fill the task block** — set `TARGET_REPO`, `SOURCE_KIND`, `SOURCE_REF`, `ARTIFACT_DIR`, `VALIDATION_MODE`.
91
90
  - [ ] **2. Gather the real source of truth** — PR/ticket/investigation text plus existing user journeys, recipes, and evals in the target repo.
92
91
  - [ ] **3. Enumerate acceptance criteria** — write a numbered canonical list under `## Acceptance Criteria`.
93
92
  - [ ] **4. Extract proof targets** — split the work into the smallest executable claims.
@@ -99,7 +98,7 @@ It must include:
99
98
  - [ ] **10. Handle unavailable validation honestly** — if the repo has no validator or runner, write `validation unavailable` with the reason. Do not fake success.
100
99
  - [ ] **11. Write the learning artifact** — write `artifacts/recipe-cook-learning.json` with evidence verdict and proposed next delta.
101
100
  - [ ] **12. Audit unresolved targets** — update `## Resolved vs Unresolved` and `## Recipe Coverage Audit` after validation evidence exists.
102
- - [ ] **13. Final completion gate** — set `STATUS: done` only after recipe writing, validation evidence, learning artifact, and unresolved-target audit are all present.
101
+ - [ ] **13. Final completion gate** — reach the Success outcome only after recipe writing, validation evidence, learning artifact, and unresolved-target audit are all present.
103
102
 
104
103
  ## Failure Conditions
105
104
 
@@ -11,8 +11,7 @@ Do not edit the template in-place during a cooking run.
11
11
  Every time you complete a checklist step:
12
12
 
13
13
  1. run `./mark N` if present (1-based); otherwise immediately change `- [ ]` to `- [x]`
14
- 2. update `STATUS` if needed
15
- 3. only then move to the next step
14
+ 2. only then move to the next step
16
15
 
17
16
  Do not batch checkbox updates.
18
17
  Do not leave completed steps unchecked.
@@ -31,7 +30,6 @@ TASK_DIR: {{TASK_DIR}}
31
30
  ARTIFACT_DIR: {{ARTIFACT_DIR}}
32
31
  SOURCE_BUNDLE_FILE: {{SOURCE_BUNDLE_FILE}}
33
32
  VALIDATION_MODE: {{VALIDATION_MODE}}
34
- STATUS: pending
35
33
  ```
36
34
 
37
35
  ## Source Material
@@ -110,7 +108,7 @@ It must include:
110
108
 
111
109
  ## Checklist
112
110
 
113
- - [ ] **1. Fill the task block** — set `TARGET_REPO`, `SOURCE_KIND`, `SOURCE_REF`, `ARTIFACT_DIR`, `VALIDATION_MODE`, `STATUS: working`.
111
+ - [ ] **1. Fill the task block** — set `TARGET_REPO`, `SOURCE_KIND`, `SOURCE_REF`, `ARTIFACT_DIR`, `VALIDATION_MODE`.
114
112
  - [ ] **2. Gather the real source of truth** — PR/ticket/investigation text plus existing user journeys, recipes, and evals in the target repo.
115
113
  - [ ] **3. Enumerate acceptance criteria** — write a numbered canonical list under `## Acceptance Criteria`.
116
114
  - [ ] **4. Extract proof targets** — split the work into the smallest executable claims.
@@ -124,7 +122,7 @@ It must include:
124
122
  - [ ] **12. Handle unavailable validation honestly** — if the repo has no validator or runner, write `validation unavailable` with the reason. Do not fake success.
125
123
  - [ ] **13. Write the learning artifact** — write `artifacts/recipe-cook-learning.json` with evidence verdict and proposed next delta.
126
124
  - [ ] **14. Audit unresolved targets** — update `## Resolved vs Unresolved` and `## Recipe Coverage Audit` after validation evidence exists.
127
- - [ ] **15. Final completion gate** — set `STATUS: done` only after recipe writing, rewritten TASK.md, validation evidence, learning artifact, and unresolved-target audit are all present.
125
+ - [ ] **15. Final completion gate** — reach the Success outcome only after recipe writing, rewritten TASK.md, validation evidence, learning artifact, and unresolved-target audit are all present.
128
126
  - [ ] **16. Write terminal signal** — write exactly one terminal `SIGNAL.json` based on the outcome matrix below.
129
127
 
130
128
  ## Outcome Matrix
@@ -134,7 +132,6 @@ Write `SIGNAL.json` only after step 15 is evaluated.
134
132
  - **Success**:
135
133
  - every required validation passed or was explicitly non-applicable by contract
136
134
  - no required proof target remains `UNRESOLVED`
137
- - `STATUS: done`
138
135
  - signal:
139
136
  ```json
140
137
  { "status": "complete", "outcome": "success" }
@@ -143,7 +140,6 @@ Write `SIGNAL.json` only after step 15 is evaluated.
143
140
  - **Blocked**:
144
141
  - a concrete environment/runtime/tooling blocker prevented required validation
145
142
  - or a proof target remains `UNRESOLVED` because a required live/runtime dependency was unavailable
146
- - `STATUS: blocked`
147
143
  - signal:
148
144
  ```json
149
145
  { "status": "blocked", "outcome": "partial", "reason": "<concrete blocker>" }
@@ -152,7 +148,6 @@ Write `SIGNAL.json` only after step 15 is evaluated.
152
148
  - **Failed**:
153
149
  - a required validation actually ran and failed
154
150
  - or the recipe/proof contradicts the PR claim
155
- - `STATUS: failed`
156
151
  - signal:
157
152
  ```json
158
153
  { "status": "failed", "outcome": "failure", "reason": "<concrete failure>" }