bmad-module-skill-forge 5.0.0 → 7.0.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/.claude-plugin/marketplace.json +1 -1
- package/docs/_data/pinned.yaml +1 -1
- package/docs/agents.md +1 -1
- package/docs/architecture.md +3 -3
- package/docs/campaign.md +1 -1
- package/docs/troubleshooting.md +9 -3
- package/docs/verifying-a-skill.md +9 -9
- package/docs/workflows.md +7 -7
- package/package.json +1 -1
- package/src/knowledge/tool-resolution.md +6 -6
- package/src/shared/references/headless-gate-convention.md +4 -1
- package/src/shared/references/output-contract-schema.md +3 -3
- package/src/shared/references/pipeline-contracts.md +2 -1
- package/src/shared/scripts/schemas/skf-test-result-envelope.v1.json +1 -1
- package/src/shared/scripts/skf-build-change-manifest.py +332 -54
- package/src/shared/scripts/skf-classify-changed-files.py +15 -10
- package/src/shared/scripts/skf-emit-result-envelope.py +58 -15
- package/src/shared/scripts/skf-extract-public-api.py +33 -13
- package/src/shared/scripts/skf-parse-gaps.py +3 -2
- package/src/shared/scripts/skf-quick-batch.py +16 -3
- package/src/shared/scripts/skf-verify-provenance-completeness.py +317 -44
- package/src/skf-analyze-source/SKILL.md +1 -0
- package/src/skf-analyze-source/customize.toml +3 -2
- package/src/skf-analyze-source/references/auto-docs-only.md +2 -2
- package/src/skf-analyze-source/references/generate-briefs.md +5 -3
- package/src/skf-analyze-source/references/headless-contract.md +3 -3
- package/src/skf-analyze-source/references/step-auto-scope.md +2 -2
- package/src/skf-audit-skill/SKILL.md +2 -1
- package/src/skf-audit-skill/references/report.md +1 -1
- package/src/skf-brief-skill/SKILL.md +1 -0
- package/src/skf-brief-skill/references/invocation-contract.md +2 -2
- package/src/skf-campaign/SKILL.md +1 -0
- package/src/skf-campaign/references/campaign-contracts.md +3 -1
- package/src/skf-campaign/references/step-05-skill-loop.md +2 -2
- package/src/skf-campaign/references/step-07-capstone.md +3 -3
- package/src/skf-campaign/references/step-08-verify.md +3 -3
- package/src/skf-campaign/references/step-09-refine.md +3 -3
- package/src/skf-campaign/references/step-10-export.md +3 -2
- package/src/skf-campaign/scripts/campaign-state.py +22 -5
- package/src/skf-create-skill/SKILL.md +3 -2
- package/src/skf-create-skill/customize.toml +4 -4
- package/src/skf-create-skill/references/batch-mode.md +3 -1
- package/src/skf-create-skill/references/ccc-index-check.md +1 -1
- package/src/skf-create-skill/references/component-extraction.md +1 -1
- package/src/skf-create-skill/references/extract.md +1 -1
- package/src/skf-create-skill/references/generate-artifacts.md +1 -1
- package/src/skf-create-skill/references/report.md +9 -4
- package/src/skf-create-skill/references/sub/ccc-discover.md +1 -1
- package/src/skf-create-skill/references/validate.md +2 -2
- package/src/skf-create-stack-skill/SKILL.md +1 -0
- package/src/skf-create-stack-skill/references/generate-output.md +1 -1
- package/src/skf-create-stack-skill/references/invocation-contract.md +1 -1
- package/src/skf-create-stack-skill/references/parallel-extract.md +2 -2
- package/src/skf-create-stack-skill/references/report.md +1 -1
- package/src/skf-create-stack-skill/references/validate.md +12 -4
- package/src/skf-drop-skill/SKILL.md +1 -0
- package/src/skf-drop-skill/references/invocation-contract.md +1 -1
- package/src/skf-drop-skill/references/report.md +1 -1
- package/src/skf-export-skill/SKILL.md +1 -0
- package/src/skf-export-skill/references/invocation-contract.md +1 -1
- package/src/skf-export-skill/references/summary.md +1 -1
- package/src/skf-forger/SKILL.md +1 -0
- package/src/skf-forger/references/pipeline-mode.md +2 -2
- package/src/skf-quick-skill/SKILL.md +2 -1
- package/src/skf-quick-skill/references/batch-mode.md +2 -0
- package/src/skf-quick-skill/references/finalize.md +1 -1
- package/src/skf-quick-skill/references/halt-contract.md +2 -2
- package/src/skf-quick-skill/references/write-and-validate.md +20 -10
- package/src/skf-refine-architecture/SKILL.md +2 -1
- package/src/skf-refine-architecture/customize.toml +1 -1
- package/src/skf-refine-architecture/references/report.md +1 -1
- package/src/skf-rename-skill/SKILL.md +1 -0
- package/src/skf-rename-skill/references/invocation-contract.md +2 -2
- package/src/skf-setup/SKILL.md +1 -0
- package/src/skf-setup/references/ccc-index.md +1 -1
- package/src/skf-setup/references/invocation-contract.md +1 -1
- package/src/skf-test-skill/SKILL.md +1 -0
- package/src/skf-test-skill/assets/output-section-formats.md +4 -4
- package/src/skf-test-skill/references/coherence-check.md +8 -6
- package/src/skf-test-skill/references/coverage-check-branches.md +23 -3
- package/src/skf-test-skill/references/coverage-check-tiers.md +3 -3
- package/src/skf-test-skill/references/coverage-check.md +30 -43
- package/src/skf-test-skill/references/init.md +1 -1
- package/src/skf-test-skill/references/inventory-contract.md +27 -0
- package/src/skf-test-skill/references/invocation-contract.md +3 -3
- package/src/skf-test-skill/references/report.md +3 -3
- package/src/skf-test-skill/references/scoring-rules.md +7 -7
- package/src/skf-test-skill/references/signature-comparison-rules.md +74 -0
- package/src/skf-test-skill/references/source-access-protocol.md +12 -8
- package/src/skf-test-skill/references/step-hard-gate.md +1 -0
- package/src/skf-test-skill/scripts/build-result-context.py +20 -3
- package/src/skf-test-skill/scripts/gap-ledger.py +78 -13
- package/src/skf-test-skill/scripts/load-coverage-inputs.py +575 -9
- package/src/skf-test-skill/scripts/locate-export-segments.py +591 -63
- package/src/skf-test-skill/scripts/score-signatures.py +937 -76
- package/src/skf-test-skill/scripts/validate-inventory.py +325 -30
- package/src/skf-update-skill/SKILL.md +1 -0
- package/src/skf-update-skill/customize.toml +1 -1
- package/src/skf-update-skill/references/gap-driven.md +28 -22
- package/src/skf-update-skill/references/invocation-contract.md +1 -1
- package/src/skf-update-skill/references/merge.md +4 -4
- package/src/skf-update-skill/references/report.md +11 -4
- package/src/skf-update-skill/references/write.md +6 -5
- package/src/skf-verify-stack/SKILL.md +2 -1
|
@@ -30,6 +30,7 @@ These rules apply to every step in this workflow:
|
|
|
30
30
|
- Zero hallucination — all extracted content must trace to actual source code (compose-mode inferences must be labeled)
|
|
31
31
|
- Only load one step file at a time — never preload future steps
|
|
32
32
|
- If any instruction references a subprocess or tool you lack, achieve the outcome in your main context thread, with two exceptions: never decide by hand whether SKF generated a folder (the ownership check), and never work out by hand what a shared helper computes where its step names a `helper-missing` HALT: that helper resolving to no path stops the run with exit 3 `helper-missing` (`references/invocation-contract.md` lists every such halt). Where a step says what to do without its helper (an advisory check, a default value), do as it says
|
|
33
|
+
- Run every subagent in the foreground (parallel ones as several calls in one message, never with `run_in_background`) and wait for every long-running command to exit before the next instruction; in a headless run, never end a turn while either still runs, since under `claude -p` a run stops when its turn ends.
|
|
33
34
|
- Never write into a skill folder SKF did not generate — generate-output §1 runs the inventory's write check before any prior metadata is read and again before staging
|
|
34
35
|
- Always communicate in `{communication_language}`
|
|
35
36
|
- If `{headless_mode}` is true, auto-proceed through confirmation gates with their default action, and record each auto-decision in the run sink the moment the gate takes it, with the `record --decision` command the gate shows
|
|
@@ -188,7 +188,7 @@ uv run {verifyProvenanceCompletenessHelper} fix \
|
|
|
188
188
|
--skill-dir {skill_staging}
|
|
189
189
|
```
|
|
190
190
|
|
|
191
|
-
Read the JSON, not the exit code, ignoring `missing` and `orphaned` (a stack's `exports` is `[]`). Record a `workflow_warnings[]` entry (`severity: "info"`, `code: "provenance-lines-fixed"`) naming the lines `fix` moved, and one (`code: "provenance-line-unverified"`, `message`: the item's `export_name`, `source_file`, `source_line`, `reason` and `definition_lines`) per `stale[]` item of the last `verify`. The check is advisory (`provenance-lines-unchecked`) and ends at a `summary.stale_check` of `skipped-no-source-root`. Delete `{forge_version}/provenance-verify.skf-tmp` at the end.
|
|
191
|
+
Read the JSON, not the exit code, ignoring `missing` and `orphaned` (a stack's `exports` is `[]`). Record a `workflow_warnings[]` entry (`severity: "info"`, `code: "provenance-lines-fixed"`) naming the lines `fix` moved, and one (`code: "provenance-line-unverified"`, `message`: the item's `export_name`, `source_file`, `source_line`, `reason` and `definition_lines`, and, when it carries `folded_to`, that its line was checked against that base name) per `stale[]` item of the last `verify`. The check is advisory (`provenance-lines-unchecked`) and ends at a `summary.stale_check` of `skipped-no-source-root`. Delete `{forge_version}/provenance-verify.skf-tmp` at the end.
|
|
192
192
|
|
|
193
193
|
### 8. Pre-Commit Gate
|
|
194
194
|
|
|
@@ -7,7 +7,7 @@ The headless contract of Stack Skill: its inputs, gates and outputs, the exit co
|
|
|
7
7
|
| **Inputs** | All optional; step 1 binds and checks each one, and halts with `input-invalid` on a value it cannot use. `project_path`: the folder code mode scans (default: the project root; an interactive run asks once when the manifests sit in several folders). `skills`: the libraries to rank, or the constituent skills in compose mode. `stack_name`: the stack's skill name (default `{project_name}-stack`). `scope_overrides`: step 3 scope overrides. `architecture_doc_path`: the document compose mode reads. `mode`: `code` or `compose` (default: step 1 detects it) |
|
|
8
8
|
| **Gates** | step 1: compose suggestion [accept] and scan scope [A] (each asked only in an interactive run; headless takes the default and records it); step 2: no manifest found (headless: HALT `no-manifests`, exit 2); step 3: scope gate [C]; step 6: review gate [C] |
|
|
9
9
|
| **Outputs** | SKILL.md (stack), context-snippet.md, metadata.json; `provenance-map.json` and `evidence-report.md` in `{forge_version}`; the result contract, `create-stack-skill-result-{YYYYMMDD-HHmmss}.json` and its `create-stack-skill-result-latest.json` copy in `{forge_version}`, with the `-latest` copy also at the stack group root `{forge_data_folder}/{stack_name}/`; a run folder under `{project-root}/_bmad-output/.skf-run/`, deleted when the run finishes or the user cancels and kept after any other HALT |
|
|
10
|
-
| **Headless** | All gates auto-resolve with their default action when `{headless_mode}` is true, and each records its decision in the run sink, so the envelope's `headless_decisions` lists every one |
|
|
10
|
+
| **Headless** | All gates auto-resolve with their default action when `{headless_mode}` is true, and each records its decision in the run sink, so the envelope's `headless_decisions` lists every one. A caller reads the run's own `SKF_STACK_RESULT_JSON` line. A run that printed no envelope line has no result to read, whatever the process exit code: it was cut off, or could not write its result, and each `create-stack-skill-result-latest.json` copy may then describe an earlier run, so neither is read in its place. |
|
|
11
11
|
| **Exit codes** | See "Exit Codes" below |
|
|
12
12
|
|
|
13
13
|
## Exit Codes
|
|
@@ -213,7 +213,7 @@ Each subprocess:
|
|
|
213
213
|
|
|
214
214
|
**If parallel subprocess unavailable:** Process libraries sequentially in main thread. Report progress after each library.
|
|
215
215
|
|
|
216
|
-
**Per-subprocess
|
|
216
|
+
**Per-subprocess budget (S6):** Tell each library's extraction subprocess to stop and return what it has after about 20 tool calls, adding `"extraction budget reached"` to its `warnings` when it stops there, and wait for it in the foreground. A library whose subprocess returned partial data is a `partial-failure`: store that data and continue with the remaining libraries. One whose subprocess returned no usable data is a complete failure (§3): it goes in `failed[]` with an `extraction-failed` warning and counts toward the `all-extractions-failed` halt. Do NOT abort the batch on a single library that reached its budget or failed.
|
|
217
217
|
|
|
218
218
|
### 3. Handle Extraction Failures
|
|
219
219
|
|
|
@@ -221,7 +221,7 @@ For each library extraction:
|
|
|
221
221
|
|
|
222
222
|
**Success:** Store the extraction result as the library's `per_library_extractions[]` entry.
|
|
223
223
|
|
|
224
|
-
**Partial failure:** Store partial result with warnings, continue with other libraries.
|
|
224
|
+
**Partial failure:** Store partial result with warnings, append a `workflow_warnings[]` entry (`step: "step-04"`, `severity: "warn"`, `code: "extraction-partial"`, `message`: the library and its warnings, `extraction budget reached` among them when it stopped on its budget), and continue with other libraries.
|
|
225
225
|
|
|
226
226
|
**Complete failure:** Log failure reason, exclude from stack skill, note in report.
|
|
227
227
|
|
|
@@ -71,7 +71,7 @@ uv run {emitEnvelopeHelper} emit --workflow skf-create-stack-skill --run-dir "{r
|
|
|
71
71
|
|
|
72
72
|
The emitter stamps `timestamp`, `run_id`, the run's auto-decisions and its warnings into the record, writes `create-stack-skill-result-<YYYYMMDD-HHmmss>.json` (it picks the name, with `-2` for a run that ends in the same second as another) and its `create-stack-skill-result-latest.json` copy in `{forge_version}`, and prints the success line on stdout: display it when `{headless_mode}` is true. A file it cannot write becomes a `result_file_write_failed` warning in the envelope, with `result_path` `null` when the per-run record is the one that failed. If the emitter exits non-zero, fix the payload its stderr names and run it once more; if it fails again, print its error and continue: the result contract is advisory and never halts the report.
|
|
73
73
|
|
|
74
|
-
**Stable latest pointer at the stack group root.**
|
|
74
|
+
**Stable latest pointer at the stack group root.** `{forge_data_folder}/{stack_name}/create-stack-skill-result-latest.json`, above the version folder, is a copy of the version folder's `-latest` copy, at a path that does not name the current version; a caller reads its own run's `SKF_STACK_RESULT_JSON` line. Copy the record there (a copy, not a symlink, so no consumer chases a link across version folders):
|
|
75
75
|
|
|
76
76
|
```bash
|
|
77
77
|
python3 {atomicWriteHelper} write --target {forge_data_folder}/{stack_name}/create-stack-skill-result-latest.json < {forge_version}/create-stack-skill-result-latest.json
|
|
@@ -120,15 +120,23 @@ Record format violations as **WARNING** findings.
|
|
|
120
120
|
|
|
121
121
|
### 9. Security Scan (if skill-check available)
|
|
122
122
|
|
|
123
|
-
Run: `npx skill-check check <skill-dir> --format json` (security scan enabled by default).
|
|
123
|
+
Run: `npx skill-check check <skill-dir> --format json > "{run_dir}/skill-check.json" 2> "{run_dir}/skill-check.stderr"; echo "skill-check exited $?"` (security scan enabled by default), so stdout, stderr and the exit code are read apart.
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
**If `{run_dir}/skill-check.json` does not start with a JSON object**, skill-check stopped before it validated (exit 2 on a config, option or plugin error): record no finding, only the §10 note "security scan skipped: skill-check failed ({the stderr line that names the cause, or its last line})". Otherwise stdout can go on after the JSON (a `Wrote <path>` line when the project config sets a report path), so read only the JSON object at its start.
|
|
126
126
|
|
|
127
|
-
**
|
|
127
|
+
**Read the JSON, not the exit code.** skill-check exits 1 when `summary.errorCount` is above 0, when `--strict` or `--fail-on-warning` is passed and `summary.warningCount` is above 0 (this call passes neither), or when the scan exits non-zero, and 2 when no scan runner starts, so a non-zero exit is not a security finding. skill-check 1.2.0 puts no security key in the JSON. The scan exited non-zero, which without `--ci` means it did not finish, when skill-check exits 1 although `summary.errorCount` is 0: stderr is then the scan's last lines (its tail), even when blank. On exit 0, stderr is never a scan tail (npm and Node warnings can be there); when exit 1 has another cause, stderr is a scan tail only when it holds the scanner's own lines, such as its token message or a finding code. A finding code is `[`, a capital letter, three digits and `]` (such as `[E001]`), the codes skill-check's own scan summary counts. Read the scan from stderr and the exit code, the first that applies:
|
|
128
|
+
|
|
129
|
+
- **Could not run:** skill-check exits 2, the call stopped before it exited, or the scan exited non-zero and its tail carries no finding code. Record no finding, only a note in the §10 validation results: "security scan skipped: SNYK_TOKEN not configured" when the tail says to set the `SNYK_TOKEN` environment variable (the scanner prints "To use Agent Scan, set the SNYK_TOKEN environment variable."), otherwise "security scan skipped: scanner could not run ({the stderr line that names the cause, or its last line})", or "security scan skipped: scanner exited non-zero with no output" when stderr is blank.
|
|
130
|
+
- **Findings:** `security[]` holds findings, or the tail carries finding codes.
|
|
131
|
+
- **Neither:** record the §10 note "security scan exited 0; skill-check 1.2.0 does not pass the scan's findings on". Without `--ci`, agent-scan also exits 0 when its analysis fails (a 401 from a bad token, a 429, a network error), so this does not show that the scan found nothing (#892).
|
|
132
|
+
|
|
133
|
+
Record security findings as advisory **WARNING** findings: they do not block the report.
|
|
134
|
+
|
|
135
|
+
**If unavailable:** Skip, with the note "security scan skipped: skill-check unavailable" in the §10 validation results.
|
|
128
136
|
|
|
129
137
|
### 10. Display Validation Results
|
|
130
138
|
|
|
131
|
-
Report the validation outcome. If all checks passed, state so and name what was verified: file presence (`{count}/{count}`), SKILL.md structure, metadata.json fields, the `{lib_count}` library + `{pair_count}` integration reference files, and complete confidence-tier coverage. If there were findings, report the `{warning_count}` finding(s)
|
|
139
|
+
Report the validation outcome. If all checks passed, state so and name what was verified: file presence (`{count}/{count}`), SKILL.md structure, metadata.json fields, the `{lib_count}` library + `{pair_count}` integration reference files, and complete confidence-tier coverage. If there were findings, report the `{warning_count}` finding(s), each with severity, description, and file path, plus files present/expected and warning/error counts; when errors include missing files, note this may indicate a write failure in step 07. In both cases, list each §9 security scan note.
|
|
132
140
|
|
|
133
141
|
### 11. Auto-Proceed to Next Step
|
|
134
142
|
|
|
@@ -33,6 +33,7 @@ These rules apply to every step in this workflow:
|
|
|
33
33
|
- Never purge content SKF did not generate: select.md §8b takes the purge verdict for the skill folder and its forge folder from the inventory helper; a forge folder SKF did not generate is left in place, and a purge SKF cannot check is refused
|
|
34
34
|
- Only load one step file at a time — never preload future steps
|
|
35
35
|
- If any instruction references a subprocess or tool you lack, achieve the outcome in your main context thread, with two exceptions. A helper On-Activation §4 resolves is never replaced by hand (a missing one halts the run before the first prompt): never type an envelope or edit the manifest or a context file yourself. And never decide by hand whether SKF generated a folder: without the inventory helper, select.md offers manifest skills only and refuses every purge
|
|
36
|
+
- Run every subagent in the foreground (parallel ones as several calls in one message, never with `run_in_background`) and wait for every long-running command to exit before the next instruction; in a headless run, never end a turn while either still runs, since under `claude -p` a run stops when its turn ends.
|
|
36
37
|
- Always communicate in `{communication_language}`
|
|
37
38
|
- At any interactive prompt, the inputs `cancel`, `exit`, `[X]`, `q`, or `:q` exit cleanly with exit code 6 (`halt_reason: "user-cancelled"`)
|
|
38
39
|
- If `{headless_mode}` is true, auto-proceed through confirmation gates with their default action and log each auto-decision
|
|
@@ -10,7 +10,7 @@ The arguments, gates, exit codes and headless result envelope of `skf-drop-skill
|
|
|
10
10
|
| **Flags** | `--headless` / `-H` (auto-resolve all gates); `--dry-run` (run selection and display the §10 confirmation block, then exit with `status="dry-run"`: no manifest mutation, no file deletion) |
|
|
11
11
|
| **Gates** | step 1: Input Gate [use args] (§4 skill), Scope Gate [use args] (§6 version), Mode Gate [use args] (§8 mode), Confirm Gate [Y] |
|
|
12
12
|
| **Outputs** | Updated manifest, rebuilt context files, (purge: deleted directories), `drop-skill-result-{YYYYMMDD-HHmmss}.json` (UTC) and `drop-skill-result-latest.json` in `{skills_output_folder}` (none for a dry run or a HALT); a run folder under `{project-root}/_bmad-output/.skf-run/`, kept only after a HALT, except an interactive HALT in step 1 or 2, which deletes it |
|
|
13
|
-
| **Headless** | Gates auto-resolve with their default action (SKILL.md Workflow Rules); select.md §6 and §8 halt when the `version` or `mode` argument a headless run must pass is missing. Any non-empty `forbid_purge_in_headless`, set under `[workflow]` in a team or personal override (never in the bundled `customize.toml`, which every update overwrites), stops a headless `mode=purge` at On-Activation §4 (exit code 6, `halt_reason: "headless-purge-forbidden"`) when the customization resolver runs. When it cannot, including an override file that fails to parse, the run warns `customization_resolver_unavailable` and the bundled empty value leaves the guard off. A key written without the `[workflow]` line is ignored with no warning, and the guard stays off. |
|
|
13
|
+
| **Headless** | Gates auto-resolve with their default action (SKILL.md Workflow Rules); select.md §6 and §8 halt when the `version` or `mode` argument a headless run must pass is missing. Any non-empty `forbid_purge_in_headless`, set under `[workflow]` in a team or personal override (never in the bundled `customize.toml`, which every update overwrites), stops a headless `mode=purge` at On-Activation §4 (exit code 6, `halt_reason: "headless-purge-forbidden"`) when the customization resolver runs. When it cannot, including an override file that fails to parse, the run warns `customization_resolver_unavailable` and the bundled empty value leaves the guard off. A key written without the `[workflow]` line is ignored with no warning, and the guard stays off. A caller reads the run's own `SKF_DROP_SKILL_RESULT_JSON` line. A run that printed no envelope line has no result to read, whatever the process exit code: it was cut off, or could not write its result, and `drop-skill-result-latest.json` may then describe an earlier run, so it is never read in its place. |
|
|
14
14
|
| **Exit codes** | See "Exit Codes" below |
|
|
15
15
|
|
|
16
16
|
## Exit Codes
|
|
@@ -121,7 +121,7 @@ Then run, in every mode:
|
|
|
121
121
|
uv run {emitEnvelopeHelper} emit --workflow skf-drop-skill --run-dir "{run_dir}" --result-dir "{skills_output_folder}" < "{run_dir}/result-context.json"
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
-
The emitter stamps the UTC time, the run id, the run's warnings and the auto-decisions select.md recorded (`headless_decisions`) into the record, writes it as `{skills_output_folder}/drop-skill-result-{YYYYMMDD-HHmmss}.json`, copies it to `{skills_output_folder}/drop-skill-result-latest.json` (the stable path
|
|
124
|
+
The emitter stamps the UTC time, the run id, the run's warnings and the auto-decisions select.md recorded (`headless_decisions`) into the record, writes it as `{skills_output_folder}/drop-skill-result-{YYYYMMDD-HHmmss}.json`, copies it to `{skills_output_folder}/drop-skill-result-latest.json` (the record of the last run whose emitter wrote this copy, at a stable path; a caller reads its own run's `SKF_DROP_SKILL_RESULT_JSON` line), and prints the `SKF_DROP_SKILL_RESULT_JSON:` line on stdout (field rules in `references/invocation-contract.md`). Bind `{result_json_path}` ← that line's `result_path`: null when the record could not be written, and the line's `warnings` then say why. When `{headless_mode}` is true, bind `{result_envelope_line}` ← the line and do not display it here: the shared health check displays it verbatim as the run's last line, so a `claude -p` caller reads the envelope as the final message. If the helper exits non-zero, correct `result-context.json` from the `message` of its stderr JSON and run it once more; if it fails again, leave `{result_envelope_line}` empty, display "The drop result record could not be written: {message}" and go on, since the drop itself is complete.
|
|
125
125
|
|
|
126
126
|
### Post-drop hook (optional)
|
|
127
127
|
|
|
@@ -27,6 +27,7 @@ You are Ferris in Delivery mode, a packaging specialist: you check a developer's
|
|
|
27
27
|
## Workflow Rules
|
|
28
28
|
|
|
29
29
|
- Only load one step file at a time — never preload future steps
|
|
30
|
+
- Run every subagent in the foreground (parallel ones as several calls in one message, never with `run_in_background`) and wait for every long-running command to exit before the next instruction; in a headless run, never end a turn while either still runs, since under `claude -p` a run stops when its turn ends.
|
|
30
31
|
- Always communicate in `{communication_language}`
|
|
31
32
|
- At any interactive prompt, the inputs `cancel`, `exit`, `[X]`, `q`, or `:q` exit cleanly with exit code 6 (`halt_reason: "user-cancelled"`)
|
|
32
33
|
- If `{headless_mode}` is true, auto-proceed through confirmation gates with their default action and record each auto-decision in the run sink (`references/result-envelope.md`)
|
|
@@ -11,5 +11,5 @@ The inputs, flags, gates and outputs of `skf-export-skill`, for headless callers
|
|
|
11
11
|
| **Gates** | step 1 §1b, only when config.yaml sets no `snippet_skill_root_override`: the layout question when no earlier export chose a snippet root (headless: [I], the IDE skill folder), or the mismatch gate when an earlier export's root differs from the IDE mapping (headless: checks the disk, and takes (d), the earlier root for this run, when only that folder holds the skills, else (b), the IDE mapping); step 1 §6: one Confirm Gate [C] for the whole batch (headless: [C]); step 4 §3b orphaned context files (headless: keep) and §4c.1 orphaned managed-section rows (headless: preserve); step 4 §8: one Confirm Gate [C] for the whole batch (headless: [C]) |
|
|
12
12
|
| **Outputs** | Updated `.export-manifest.json` (every skill in the batch, also when `passive_context` is off), updated context files (CLAUDE.md/AGENTS.md/.cursorrules), per-skill `context-snippet.md` (written last, after the step-4 gate), per-run result contract `export-skill-result-{YYYYMMDD-HHmmss}.json` and `export-skill-result-latest.json` (none on a dry run); in a headless run, a scratch run folder under `{project-root}/_bmad-output/.skf-run/`, which step 6 deletes (a HARD HALT keeps it) |
|
|
13
13
|
| **Multi-skill mode** | Activated when more than one skill is selected (via `--all`, multi-selection, or multi-argument invocation). See `references/load-skill.md` §1c for the per-step iteration map. |
|
|
14
|
-
| **Headless** | Every gate takes its default action and records it as it decides (SKILL.md Workflow Rules), and the run ends with one `SKF_EXPORT_RESULT_JSON` line: `references/result-envelope.md` |
|
|
14
|
+
| **Headless** | Every gate takes its default action and records it as it decides (SKILL.md Workflow Rules), and the run ends with one `SKF_EXPORT_RESULT_JSON` line: `references/result-envelope.md`. A caller reads that line. A run that printed no envelope line has no result to read, whatever the process exit code: it was cut off, or could not write its result, and `export-skill-result-latest.json` may then describe an earlier run, so it is never read in its place. |
|
|
15
15
|
| **Exit codes** | 0 on success and on a dry run. Each HARD HALT names its exit code and `halt_reason` where it occurs (3 and `not-skf-output` for a flat skill SKF did not generate, for example); `references/result-envelope.md` maps every halt site to its code |
|
|
@@ -139,7 +139,7 @@ SKF_JSON
|
|
|
139
139
|
```
|
|
140
140
|
|
|
141
141
|
- Pass `--run-dir` in a headless run: the emitter folds the auto-decisions the gates recorded there into `headless_decisions`, in the line and in the result file.
|
|
142
|
-
- **Without `--dry-run`:** pass `--result-dir`, and give `status` `"success"`, `skills` (every skill in `skill_batch`), `context_files_updated` (the files step 4 §9 wrote; `[]` with passive context off), `manifest_path` (the `{manifest_path}` step 4 §9b bound) and `result_contract` (below). The emitter writes the per-run record `export-skill-result-{YYYYMMDD-HHmmss}.json` in `{skills_output_folder}` (its name from the clock, with `-2`, `-3` and so on when a run already took that second), then its copy `export-skill-result-latest.json`, the stable path
|
|
142
|
+
- **Without `--dry-run`:** pass `--result-dir`, and give `status` `"success"`, `skills` (every skill in `skill_batch`), `context_files_updated` (the files step 4 §9 wrote; `[]` with passive context off), `manifest_path` (the `{manifest_path}` step 4 §9b bound) and `result_contract` (below). The emitter writes the per-run record `export-skill-result-{YYYYMMDD-HHmmss}.json` in `{skills_output_folder}` (its name from the clock, with `-2`, `-3` and so on when a run already took that second), then its copy `export-skill-result-latest.json`, the record of the last run whose emitter wrote this copy, at a stable path; a caller reads its own run's `SKF_EXPORT_RESULT_JSON` line.
|
|
143
143
|
- **With `--dry-run`:** leave out `--result-dir` and `result_contract`, and give `status` `"dry-run"`, `skills`, `context_files_updated: []` and `manifest_path: null`. The emitter writes no file, so a dry run leaves `export-skill-result-latest.json` as the last real export wrote it.
|
|
144
144
|
|
|
145
145
|
The emitter derives `exit_code` (0) and `halt_reason` (null) and sets `result_path`: leave them out. Bind `{result_path}` ← the line's `result_path` (null on a dry run). When `{headless_mode}` is true, bind `{result_envelope_line}` ← the line and do not display it here: the shared health check displays it verbatim as the run's last line, so a `claude -p` caller reads the envelope as the final message. If the emitter exits non-zero, correct the payload field its stderr message names and run it once more; if it fails again, leave `{result_envelope_line}` empty, display "**Result contract not written:** {message}" and continue with `{result_path}` null.
|
package/src/skf-forger/SKILL.md
CHANGED
|
@@ -67,6 +67,7 @@ Say "dismiss" or "exit persona" to leave Ferris at any time.
|
|
|
67
67
|
- Write state files only to `{sidecar_path}` and, for a pipeline, to its own run folder under `{project-root}/_bmad-output/.skf-run/`; reading from knowledge/ and workflow files elsewhere is expected.
|
|
68
68
|
- When a workflow step directs knowledge consultation, consult `{project-root}/_bmad/skf/knowledge/skf-knowledge-index.csv` to select the relevant fragment(s) and load only those files. If the CSV is missing or empty, inform the user and continue without knowledge augmentation
|
|
69
69
|
- Load the referenced fragment(s) from `{project-root}/_bmad/skf/` using the path in the `fragment_file` column (e.g., `knowledge/overview.md` resolves to `{project-root}/_bmad/skf/knowledge/overview.md`) before giving recommendations on the topic the step directed
|
|
70
|
+
- Run every subagent in the foreground (parallel ones as several calls in one message, never with `run_in_background`) and wait for every long-running command to exit before the next instruction; in a headless run, never end a turn while either still runs, since under `claude -p` a run stops when its turn ends.
|
|
70
71
|
|
|
71
72
|
## On Activation
|
|
72
73
|
|
|
@@ -55,7 +55,7 @@ Paths here resolve as the skf-forger SKILL.md **Conventions** say: each `uv run
|
|
|
55
55
|
SKF_GATE
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
After any other workflow, or when the gate printed no JSON, give the step's status: `completed`, or `halted` with the workflow's own halt reason (or what the gate printed):
|
|
58
|
+
After any other workflow, or when the gate printed no JSON, give the step's status: `completed`, or `halted` with the workflow's own halt reason (or what the gate printed). An ungated workflow (CS, BS, QS, US, EX, SS, RA, DS, RS) that printed no envelope line has no result to read, whatever the process exit code: it was cut off, or could not write its result, so record it `halted` with `--reason no-result`, as the gate halts a gated one:
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
61
|
uv run scripts/pipeline-journal.py step --journal "<journal>" --code <code> --status <completed|halted> [--reason '<reason>'] [--set "<name>=<value>"] [--output "<path>"]
|
|
@@ -86,4 +86,4 @@ uv run scripts/pipeline-journal.py reopen --journal "<journal>"
|
|
|
86
86
|
|
|
87
87
|
## Special behaviors
|
|
88
88
|
|
|
89
|
-
- **`SF` in a sequence:** SF writes no result file; the one `SKF_SETUP_RESULT_JSON` envelope line it displays is its output. Continue on `status` `success`. Any other status halts the pipeline, with that status and any `error.reason` as the halt reason; so does a run that displays no envelope line, with the line it did display as the reason.
|
|
89
|
+
- **`SF` in a sequence:** SF writes no result file; the one `SKF_SETUP_RESULT_JSON` envelope line it displays is its output. Continue on `status` `success`. Any other status halts the pipeline, with that status and any `error.reason` as the halt reason; so does a run that displays no envelope line, with the line it did display as the reason, or `no-result` when it displayed none.
|
|
@@ -27,6 +27,7 @@ These rules apply to every step in this workflow:
|
|
|
27
27
|
- Never fabricate content — all data must come from source extraction or user input
|
|
28
28
|
- Never write into a skill folder SKF did not generate
|
|
29
29
|
- Only load one step file at a time — never preload future steps
|
|
30
|
+
- Run every subagent in the foreground (parallel ones as several calls in one message, never with `run_in_background`) and wait for every long-running command to exit before the next instruction; in a headless run, never end a turn while either still runs, since under `claude -p` a run stops when its turn ends.
|
|
30
31
|
- Always communicate in `{communication_language}`
|
|
31
32
|
- **Universal cancel-line affordance**: at any interactive prompt the user may type `cancel`, `exit`, `:q`, or select the `[X] Cancel and exit` menu option (where surfaced) to leave cleanly. HARD HALT with **exit code 6 (user-cancelled)**: stage `{"phase": "<the step's slug>", "halt_reason": "user-cancelled", "reason": "Cancelled. No files were written.", "skill_package": null}` as `{run_dir}/halt.json` and run `uv run {emitEnvelopeHelper} emit-halt --workflow skf-quick-skill --run-dir "{run_dir}" --target stderr < "{run_dir}/halt.json"` (`references/halt-contract.md`). In step 4 §6 the equivalent affordance is `[Q] Quit without writing`, with the same exit code and envelope.
|
|
32
33
|
- If `{headless_mode}` is true, auto-proceed through confirmation gates with their default action, and record each auto-decision in the run folder the moment its gate decides, so the envelope's `headless_decisions` carries it: stage `{"gate": "<the gate's name>", "default_action": "<X>", "taken_action": "<X>", "reason": "headless: <why>"}` as `{run_dir}/decision.json` and run `uv run {emitEnvelopeHelper} record --workflow skf-quick-skill --run-dir "{run_dir}" --decision < "{run_dir}/decision.json"`. Each gate names its gate and default where it decides.
|
|
@@ -52,7 +53,7 @@ These rules apply to every step in this workflow:
|
|
|
52
53
|
| **Overrides** | `--language-hint`, `--scope-hint`, `--description`, `--exports`, `--skip-snippet`, `--no-active-pointer`, `--batch <file>`, `--fail-fast`: see On Activation step 4 |
|
|
53
54
|
| **Gates** | step 1: target input, ambiguous package name [C/n/U/X] (if another registry also holds the name; headless keeps the first registry's pick), multi-language disambiguation [C/n/A] (pick any detected language); step 2: ecosystem match [P/I/A] (none until agentskills.io has a registry API); step 3: repo-shape [C/A] + zero-exports rescue [R/P/A]; step 4: review [C/E/S/Q]; step 5: overwrite [Y/N] |
|
|
54
55
|
| **Outputs** | SKILL.md, context-snippet.md, metadata.json, active pointer, result contract (timestamped + `-latest` copy), one `SKF_QUICK_SKILL_RESULT_JSON` line (stdout when the run finishes, stderr at a HARD HALT), and under `--batch` the batch summary. Snippet and active pointer can be skipped per overrides. |
|
|
55
|
-
| **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true; each auto-decision is recorded in the envelope's `headless_decisions` |
|
|
56
|
+
| **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true; each auto-decision is recorded in the envelope's `headless_decisions`. A caller reads the run's own `SKF_QUICK_SKILL_RESULT_JSON` line. A run that printed no envelope line has no result to read, whatever the process exit code: it was cut off, or could not write its result, and the result contract's `-latest` copy may then describe an earlier run, so it is never read in its place. Under `--batch`, a caller reads the batch's `batch_summary` event and the summary file its `summary_path` names, never the batch summary's `-latest` copy in its place: that copy holds the last batch summary written, which may be another batch's. A printed `batch_summary` event stays the batch's result even if a halt line follows it. A batch that printed no `batch_summary` event has no batch result, whatever the process exit code: a halt line of its own (phase `batch-mode`, or `on-activation`) says why it stopped, and with no halt line it was cut off or could not print its halt line. A target it printed a `done` or `fail` event for ended as that event says; a target with a `start` event and no end event has no result, and a target with no event did not run in this session. |
|
|
56
57
|
| **Exit codes** | See `references/halt-contract.md`: the exit-code map and the envelope every HARD HALT emits, except On Activation step 1's check for `python3` and `uv`, which prints none |
|
|
57
58
|
|
|
58
59
|
## On Activation
|
|
@@ -93,6 +93,8 @@ uv run {quickBatchHelper} summarize --run-dir "{batch_dir}" --output-dir "{batch
|
|
|
93
93
|
|
|
94
94
|
The helper counts the targets, sets the batch status and exit code, writes the summary files below, appends the final status to the batch file (and removes the batch run folder when no target failed), and prints the `batch_summary` event on stderr: display it verbatim. Then print step 7's `start` event and load, read in full and execute `{healthCheckStepFile}`: the health check runs once per batch, here. The batch ends with the event's `exit_code`.
|
|
95
95
|
|
|
96
|
+
**Reading a batch.** Under `--batch`, a caller reads the batch's `batch_summary` event and the summary file its `summary_path` names, never `quick-skill-batch-latest.json` in its place: that copy holds the last batch summary written, which may be another batch's. The event comes before the health check, so a caller reads it from the session's output stream (`claude -p --output-format stream-json`) or transcript, not from the final message. A printed `batch_summary` event stays the batch's result even if a halt line follows it. A batch that printed no `batch_summary` event has no batch result, whatever the process exit code: a halt line of its own (phase `batch-mode`, or `on-activation`) says why it stopped, and with no halt line it was cut off or could not print its halt line. A target it printed a `done` or `fail` event for ended as that event says; a target with a `start` event and no end event has no result, and a target with no event did not run in this session. When the event's `summary_path` is null, the per-batch file was not written (its `summary_write_failed` warning says why): the event and `{batch_dir}/batch.jsonl`, which the helper then keeps, are the batch's result, never `quick-skill-batch-latest.json`. A `summary_write_failed` warning beside a set `summary_path` means only the `-latest` copy failed: the file it names is still the batch's result. After a cut-off batch, run the batch again: each run takes a new run folder (SKILL.md On Activation step 1), so it starts over from the first target.
|
|
97
|
+
|
|
96
98
|
## Batch summary contract
|
|
97
99
|
|
|
98
100
|
`summarize` writes the batch summary at:
|
|
@@ -113,7 +113,7 @@ uv run {emitEnvelopeHelper} emit --workflow skf-quick-skill --run-dir "{run_dir}
|
|
|
113
113
|
|
|
114
114
|
This list is the summary, once: `skill_name`, `version`, `language`, `exports_documented` (the export count), `quality_score` and `validation_issues` (step 5, the object `{"skill_md": <n>, "context_snippet": <n>, "metadata": <n>, "security": <n>}`), `confidence` and `repo_shape` (step 3, the shape null when it recorded none), `language_resolution` and `detected_languages` (step 1), `zero_exports_rescue` (step 3 §4.5, else null) and `active_pointer` (§1). Write each value as JSON: a string in double quotes with any `"` or `\` escaped, every path absolute with `/`, and `null` for a value the run did not record. Leave the snippet out of both `outputs` under `--skip-snippet`, and `outputs.active_pointer` out when §1 flipped no pointer.
|
|
115
115
|
|
|
116
|
-
The emitter writes the result contract from `result_contract`, with the payload's own `status` and `summary`, so neither is typed twice (the per-run record `{skill_package}/quick-skill-result-{YYYYMMDD-HHmmss}.json` and its copy `quick-skill-result-latest.json`, the stable path
|
|
116
|
+
The emitter writes the result contract from `result_contract`, with the payload's own `status` and `summary`, so neither is typed twice (the per-run record `{skill_package}/quick-skill-result-{YYYYMMDD-HHmmss}.json` and its copy `quick-skill-result-latest.json`, the record of the last run whose emitter wrote this copy, at a stable path; a caller reads its own run's `SKF_QUICK_SKILL_RESULT_JSON` line) and prints one line on stdout, `SKF_QUICK_SKILL_RESULT_JSON: {...}`. In a headless single-target run, bind `{result_envelope_line}` ← that line and do not display it here: the shared health check displays it verbatim as the run's last line, so a `claude -p` caller reads the envelope as the final message. Otherwise (an interactive run, or a `--batch` target) display it verbatim as its own line. If it exits non-zero, fix `result-context.json` once (its `message` names the problem) and run it again; if it still fails, or no path resolved for `{emitEnvelopeHelper}`, leave `{result_envelope_line}` empty, say that the result contract was not written and why, and go on.
|
|
117
117
|
|
|
118
118
|
**Post-completion hook (optional).** If `{onCompleteCommand}` is non-empty (resolved at SKILL.md On Activation §3 from `workflow.on_complete`) and the emitter wrote the result contract (the line it printed has a non-null `result_path` and no `result_file_write_failed` warning naming `quick-skill-result-latest.json`), invoke it after the result contract is finalized:
|
|
119
119
|
|
|
@@ -22,7 +22,7 @@ Every HARD HALT in this workflow exits with a stable, documented code so headles
|
|
|
22
22
|
|
|
23
23
|
## Result Contract on HARD HALT
|
|
24
24
|
|
|
25
|
-
Every HARD HALT emits an **error envelope** through the shared emitter, `{emitEnvelopeHelper}`, so
|
|
25
|
+
Every HARD HALT emits an **error envelope** through the shared emitter, `{emitEnvelopeHelper}`, so no HARD HALT leaves a headless automator without a result line, except SKILL.md On Activation's halt for a missing `python3` or `uv`, which the emitter needs: that halt displays its message alone. SKILL.md On Activation step 1 resolved the emitter; when a compaction lost it, it is the first existing path of `{project-root}/_bmad/skf/shared/scripts/skf-emit-result-envelope.py` and `{project-root}/src/shared/scripts/skf-emit-result-envelope.py`. Stage the halt payload as `{run_dir}/halt.json` through a quoted heredoc, then run the emitter:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
28
|
cat > "{run_dir}/halt.json" <<'SKF_JSON'
|
|
@@ -47,7 +47,7 @@ The emitter prints one line on stderr, `SKF_QUICK_SKILL_RESULT_JSON: {...}`: dis
|
|
|
47
47
|
{skill_package}/quick-skill-result-latest.json (copy, not symlink)
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
so
|
|
50
|
+
so the halt's record also becomes the `-latest.json` copy; a caller still reads the run's own `SKF_QUICK_SKILL_RESULT_JSON` line, never the copy in its place. Before step 5 §1 computes `{skill_package}`, the stderr line and the exit code are the whole contract. A HALT while `{skill_package}` has no `metadata.json` (a failed first write in step 5 §2, for example) writes nothing on disk either: a package holding only result files is not SKF output, so the next run's ownership check would refuse it.
|
|
51
51
|
|
|
52
52
|
**Schema:** `shared/scripts/schemas/skf-quick-skill-result-envelope.v1.json` (installed under `{project-root}/_bmad/skf/`).
|
|
53
53
|
|
|
@@ -72,7 +72,7 @@ If `{skill_package}/metadata.json` exists, confirm with user before overwriting:
|
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
Proceed to section 2.
|
|
75
|
-
- **If user selects N:** HARD HALT with **exit code 5 (overwrite-cancelled)**: "Overwrite cancelled. Existing skill preserved. Quick Skill names a skill after its target, so to create it under another name, brief it with `@Ferris BS` and compile it with `@Ferris CS`; or remove the existing directory yourself and re-run." Stage `{"phase": "write-and-validate", "halt_reason": "overwrite-cancelled", "reason": "Overwrite cancelled. Existing skill preserved.", "skill_package": "{skill_package}", "details": {"existing_generator": "{existing_generator}"}}` as `{run_dir}/halt.json` (`existing_generator` null when none names it) and run `uv run {emitEnvelopeHelper} emit-halt --workflow skf-quick-skill --run-dir "{run_dir}" --result-dir "{skill_package}" --target stderr < "{run_dir}/halt.json"`: `{skill_package}` holds `metadata.json`, so the emitter also writes the `-latest.json` envelope there
|
|
75
|
+
- **If user selects N:** HARD HALT with **exit code 5 (overwrite-cancelled)**: "Overwrite cancelled. Existing skill preserved. Quick Skill names a skill after its target, so to create it under another name, brief it with `@Ferris BS` and compile it with `@Ferris CS`; or remove the existing directory yourself and re-run." Stage `{"phase": "write-and-validate", "halt_reason": "overwrite-cancelled", "reason": "Overwrite cancelled. Existing skill preserved.", "skill_package": "{skill_package}", "details": {"existing_generator": "{existing_generator}"}}` as `{run_dir}/halt.json` (`existing_generator` null when none names it) and run `uv run {emitEnvelopeHelper} emit-halt --workflow skf-quick-skill --run-dir "{run_dir}" --result-dir "{skill_package}" --target stderr < "{run_dir}/halt.json"`: `{skill_package}` holds `metadata.json`, so the emitter also writes the `-latest.json` envelope there; a caller still reads the run's own `SKF_QUICK_SKILL_RESULT_JSON` line, never that copy in its place.
|
|
76
76
|
|
|
77
77
|
**GATE [default: Y]** when `{existing_generator}` is `quick-skill` or null: if `{headless_mode}` is true, auto-proceed with Y, log "headless: overwriting existing `{skill_package}`", and record the decision: stage `{"gate": "write-and-validate.overwrite", "default_action": "Y", "taken_action": "Y", "reason": "headless: overwrote the existing package", "evidence": {"skill_package": "{skill_package}"}}` as `{run_dir}/decision.json` and run `uv run {emitEnvelopeHelper} record --workflow skf-quick-skill --run-dir "{run_dir}" --decision < "{run_dir}/decision.json"`.
|
|
78
78
|
|
|
@@ -112,23 +112,27 @@ Check:
|
|
|
112
112
|
Run: `npx skill-check -h`
|
|
113
113
|
|
|
114
114
|
- If succeeds (returns usage information): Continue to automated validation (section 4)
|
|
115
|
-
- If fails (command not found or error): skip to the frontmatter validator in section 4, and log "security scan skipped: skill-check unavailable" in the validation results
|
|
115
|
+
- If fails (command not found or error): skip to the frontmatter validator in section 4, bind `{security_result}` ← `skipped`, and log "security scan skipped: skill-check unavailable" in the validation results
|
|
116
116
|
|
|
117
117
|
### 4. Validate SKILL.md via skill-check (if available)
|
|
118
118
|
|
|
119
|
-
**If `npx skill-check` is available**, run validation and its security scan in one call against the skill package written in section 2. `--fix` can also rewrite the frontmatter `description` the user approved in step 4, which would leave SKILL.md out of step with `metadata.json`, so the call runs inside the description guard. Resolve `{descriptionGuardHelper}` ← first existing path in `{descriptionGuardProbeOrder}`, then run the three commands in order:
|
|
119
|
+
**If `npx skill-check` is available**, run validation and its security scan in one call against the skill package written in section 2. `--fix` can also rewrite the frontmatter `description` the user approved in step 4, which would leave SKILL.md out of step with `metadata.json`, so the call runs inside the description guard. Resolve `{descriptionGuardHelper}` ← first existing path in `{descriptionGuardProbeOrder}`, then run the three commands in order. `verify-restore` runs whatever skill-check exits; note that exit code:
|
|
120
120
|
|
|
121
121
|
```bash
|
|
122
122
|
uv run {descriptionGuardHelper} capture "{skill_package}/SKILL.md" > "{run_dir}/description-guard.json"
|
|
123
|
-
npx skill-check check "{skill_package}" --fix --format json
|
|
123
|
+
npx skill-check check "{skill_package}" --fix --format json > "{run_dir}/skill-check.json" 2> "{run_dir}/skill-check.stderr"; echo "skill-check exited $?"
|
|
124
124
|
uv run {descriptionGuardHelper} verify-restore "{skill_package}/SKILL.md" --captured-file "{run_dir}/description-guard.json"
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
+
**If no "skill-check exited" line came back**, the shell tool stopped the block before skill-check finished (a cold `uvx mcp-scan` install can outlast its timeout): run the `verify-restore` command on its own before reading `{run_dir}/skill-check.json`, so a description `--fix` rewrote is restored.
|
|
128
|
+
|
|
127
129
|
**Guard outputs.** `capture` stages the approved description in `{run_dir}/description-guard.json` and `verify-restore` reads it there, so it never passes through a shell string. Bind `{guard_restored}` ← `restored` and `{guard_diff_kind}` ← `diff_kind` from `verify-restore`. When `{guard_restored}` is true, the approved description is back in SKILL.md, matching `metadata.json`: record the validation note "description restored after `skill-check --fix` ({guard_diff_kind})", which §6 lists with the auto-fixed issues. `verify-restore` refuses an empty or whitespace-only captured description, and a file it cannot read (exit 1, SKILL.md untouched): re-run it with `--captured-file "{skill_package}/metadata.json"`, whose `description` step 4 rendered, never with an empty value, and if that exits 1 too, record a high-severity issue: the guard could not check the description. When it exits 2 with JSON on stdout (the JSON carries `restore_error`), the restore could not be written, so record a high-severity issue: SKILL.md's description no longer matches `metadata.json`.
|
|
128
130
|
|
|
129
|
-
**If no `{descriptionGuardProbeOrder}` path exists**, never run `--fix` unguarded. Run `npx skill-check check "{skill_package}" --format json` instead, which validates and scans without fixing, and log the validation issue "description guard unavailable (`skf-description-guard.py` missing): skill-check ran without `--fix`".
|
|
131
|
+
**If no `{descriptionGuardProbeOrder}` path exists**, never run `--fix` unguarded. Run `npx skill-check check "{skill_package}" --format json` instead, which validates and scans without fixing, with the same capture (`> "{run_dir}/skill-check.json" 2> "{run_dir}/skill-check.stderr"`, its exit code noted), and log the validation issue "description guard unavailable (`skf-description-guard.py` missing): skill-check ran without `--fix`".
|
|
132
|
+
|
|
133
|
+
**If `{run_dir}/skill-check.json` does not start with a JSON object**, skill-check stopped before it validated (exit 2 on a config, option or plugin error): log "skill-check failed: {the stderr line that names the cause, or its last line}" and "security scan skipped: skill-check failed ({that line})", keep `{quality_score}` null, bind `{security_result}` ← `skipped`, and run the frontmatter validator below, as when skill-check is not available. Otherwise stdout can go on after the JSON (a `Wrote <path>` line when the project config sets a report path), so read only the JSON object at its start.
|
|
130
134
|
|
|
131
|
-
**Parse JSON
|
|
135
|
+
**Parse the JSON in `{run_dir}/skill-check.json`** to extract:
|
|
132
136
|
- `scores[].score` — overall score (0-100); match the entry by `relativePath`/`skillId`
|
|
133
137
|
- `diagnostics[]` — remaining issues after auto-fix
|
|
134
138
|
- `fixed[]` — issues automatically corrected
|
|
@@ -136,6 +140,12 @@ uv run {descriptionGuardHelper} verify-restore "{skill_package}/SKILL.md" --capt
|
|
|
136
140
|
|
|
137
141
|
Bind `{quality_score}` ← that score, and record the remaining diagnostics and security findings as validation issues. `{quality_score}` is null when skill-check did not run.
|
|
138
142
|
|
|
143
|
+
**Read the JSON, not the exit code.** skill-check prints its JSON before the security scan runs, then exits 1 when `summary.errorCount` is above 0, when `--strict` or `--fail-on-warning` is passed and `summary.warningCount` is above 0 (this call passes neither), or when the scan exits non-zero, and 2 when no scan runner starts, so `summary.errorCount` alone is the validation verdict. skill-check 1.2.0 puts no security key in the JSON. The scan exited non-zero, which without `--ci` means it did not finish, when skill-check exits 1 although `summary.errorCount` is 0: stderr is then the scan's last lines (its tail), even when blank. On exit 0, stderr is never a scan tail (npm and Node warnings can be there); when exit 1 has another cause, stderr is a scan tail only when it holds the scanner's own lines, such as its token message or a finding code. A finding code is `[`, a capital letter, three digits and `]` (such as `[E001]`), the codes skill-check's own scan summary counts. Bind `{security_result}` from `{run_dir}/skill-check.stderr` and the exit code, the first that applies:
|
|
144
|
+
|
|
145
|
+
- `skipped`: §3 found no skill-check (§3's log is the note), skill-check exits 2, the call stopped before it exited, or the scan exited non-zero and its tail carries no finding code. Log in the validation results "security scan skipped: SNYK_TOKEN not configured" when the tail says to set the `SNYK_TOKEN` environment variable (the scanner prints "To use Agent Scan, set the SNYK_TOKEN environment variable."), otherwise "security scan skipped: scanner could not run ({the stderr line that names the cause, or its last line})", or "security scan skipped: scanner exited non-zero with no output" when stderr is blank.
|
|
146
|
+
- `warn`: `security[]` holds findings, or the tail carries finding codes. Record each finding as a validation issue.
|
|
147
|
+
- `pass`: otherwise. The scan exited 0 and skill-check reported no finding. Without `--ci`, agent-scan also exits 0 when its analysis fails (a 401 from a bad token, a 429, a network error), so this does not show that the scan found nothing (#892).
|
|
148
|
+
|
|
139
149
|
**If skill-check is not available**, run the shared frontmatter validator. Resolve `{frontmatterValidator}` from `{frontmatterValidatorProbeOrder}`; first existing path wins. If no candidate exists, nothing can check the frontmatter: HARD HALT with **exit code 3 (resolution-failure)**, in interactive mode too: "**`{skill_package}` was written but not validated.** skill-check is unavailable and SKF's frontmatter validator (`skf-validate-frontmatter.py`) is missing from `{project-root}/_bmad/skf/shared/scripts/`, so re-install SKF, then re-run." Stage `{"phase": "write-and-validate", "halt_reason": "resolution-failure", "reason": "The skill was written but not validated: the frontmatter validator is missing.", "skill_package": "{skill_package}", "outputs": {<each deliverable §2 wrote, as "metadata", "skill_md" or "context_snippet" with its path>}, "details": {"cause": "frontmatter-validator-missing"}}` as `{run_dir}/halt.json` and run `uv run {emitEnvelopeHelper} emit-halt --workflow skf-quick-skill --run-dir "{run_dir}" --result-dir "{skill_package}" --target stderr < "{run_dir}/halt.json"`.
|
|
140
150
|
|
|
141
151
|
```bash
|
|
@@ -160,7 +170,7 @@ The validator emits JSON with `result` (PASS/FAIL), `validation.skill_md.body[]`
|
|
|
160
170
|
|
|
161
171
|
"**Validation complete:**
|
|
162
172
|
|
|
163
|
-
**SKILL.md:** {pass/issues found} (quality score: {
|
|
173
|
+
**SKILL.md:** {pass/issues found}{ (quality score: {quality_score}/100), only when `{quality_score}` is not null}
|
|
164
174
|
{list any issues}
|
|
165
175
|
{list any auto-fixed issues, and the §4 description-guard note when the guard restored the description}
|
|
166
176
|
|
|
@@ -170,8 +180,8 @@ The validator emits JSON with `result` (PASS/FAIL), `validation.skill_md.body[]`
|
|
|
170
180
|
**metadata.json:** {pass/issues found}
|
|
171
181
|
{list any issues}
|
|
172
182
|
|
|
173
|
-
**Security:** {
|
|
174
|
-
{list any security findings}
|
|
183
|
+
**Security:** {security_result}
|
|
184
|
+
{list any security findings, or the §4 skipped note; for `pass`: "scan exited 0; skill-check 1.2.0 does not pass the scan's findings on"}
|
|
175
185
|
|
|
176
186
|
**Overall:** {pass / N issues found}
|
|
177
187
|
|
|
@@ -180,7 +190,7 @@ These issues are advisory for community-tier skills: fix them in the written fil
|
|
|
180
190
|
|
|
181
191
|
**Proceeding to finalize...**"
|
|
182
192
|
|
|
183
|
-
Set `validation_result` with pass/fail status, quality score, and issues list, and `{validation_issues}` ← `{"skill_md": <n>, "context_snippet": <n>, "metadata": <n>, "security": <n>}`, each `<n>` the number of entries the validators returned for that heading: `skill_md` counts skill-check's `diagnostics[]` (without skill-check, the frontmatter validator's `issues[]`) plus `validation.skill_md.body[]`; `context_snippet` counts `validation.context_snippet.issues[]`; `metadata` counts `validation.metadata.issues[]`; `security` counts skill-check's `security[]
|
|
193
|
+
Set `validation_result` with pass/fail status, quality score, and issues list, and `{validation_issues}` ← `{"skill_md": <n>, "context_snippet": <n>, "metadata": <n>, "security": <n>}`, each `<n>` the number of entries the validators returned for that heading: `skill_md` counts skill-check's `diagnostics[]` (without skill-check, the frontmatter validator's `issues[]`) plus `validation.skill_md.body[]`; `context_snippet` counts `validation.context_snippet.issues[]`; `metadata` counts `validation.metadata.issues[]`; `security` counts skill-check's `security[]` (or, without one, the finding codes §4 read from the tail, each occurrence one finding, as skill-check's own scan summary counts them). A check that did not run counts 0, and `fixed[]` and the guard note count nothing. Step 6 §3's summary carries `{quality_score}` and `{validation_issues}`.
|
|
184
194
|
|
|
185
195
|
### 7. Auto-Proceed to Finalize
|
|
186
196
|
|
|
@@ -26,6 +26,7 @@ You are an architecture refinement analyst in Ferris Architect mode; the user br
|
|
|
26
26
|
|
|
27
27
|
- Never speculate — every gap, issue, or improvement must cite specific APIs, types, or function signatures from the generated skills
|
|
28
28
|
- Only load one step file at a time — never preload future steps
|
|
29
|
+
- Run every subagent in the foreground (parallel ones as several calls in one message, never with `run_in_background`) and wait for every long-running command to exit before the next instruction; in a headless run, never end a turn while either still runs, since under `claude -p` a run stops when its turn ends.
|
|
29
30
|
- Always communicate in `{communication_language}`
|
|
30
31
|
- At any interactive prompt, the inputs `cancel`, `exit`, `[X]`, `q`, or `:q` exit cleanly with exit code 6 (`halt_reason: "user-cancelled"`)
|
|
31
32
|
- If `{headless_mode}` is true, auto-proceed through confirmation gates with their default action; a gate that picks its default records that decision in the run sink the moment it decides
|
|
@@ -52,7 +53,7 @@ You are an architecture refinement analyst in Ferris Architect mode; the user br
|
|
|
52
53
|
| **Flags** | `--headless` / `-H`; `--architecture-doc <path>` and `--vs-report-path <path>` answer the step 1 prompts (`--vs-report-path none` refines without a report and skips the search for one); `--scope-skills <names>` (comma-separated skill names that replace the derived scope, so step 2 asks no scope confirmation) |
|
|
53
54
|
| **Gates** | step 1: Input Gate [use args] | step 2: Scope Confirm Gate [C] continue / [X] cancel (only when a derived scope drops skills or keeps an ambiguous one) | step 5: Review Gate [R] review each refinement / [C] approve and replace the refined document / [X] cancel |
|
|
54
55
|
| **Outputs** | `refined-architecture-{arch_project_name}.md` at `{outputFolderPath}` (the document's frontmatter `project_name`, else config's), promoted from a draft once the step 5 review approves it; an earlier one is first renamed to `refined-architecture-{arch_project_name}-{timestamp}.md`. Also `refine-architecture-result-{YYYYMMDD-HHmmss}.json`, its `-latest.json` copy and, once an approved review drops a finding, `.ra-dismissed-{arch_project_name}.json` |
|
|
55
|
-
| **Headless** | Every gate takes its default when `{headless_mode}` is true: `--scope-skills` skips the step 2 scope confirmation, and without `--vs-report-path` step 1 uses the [VS] report it finds. |
|
|
56
|
+
| **Headless** | Every gate takes its default when `{headless_mode}` is true: `--scope-skills` skips the step 2 scope confirmation, and without `--vs-report-path` step 1 uses the [VS] report it finds. A caller reads the run's own `SKF_REFINE_ARCHITECTURE_RESULT_JSON` line. A run that printed no envelope line has no result to read, whatever the process exit code: it was cut off, or could not write its result, and `refine-architecture-result-latest.json` may then describe an earlier run, so it is never read in its place. |
|
|
56
57
|
|
|
57
58
|
## Result Contract (Headless)
|
|
58
59
|
|
|
@@ -71,7 +71,7 @@ refinement_rules_path = ""
|
|
|
71
71
|
# Override the destination directory for the refined architecture document
|
|
72
72
|
# and the files beside it: the earlier refined document a promotion renames,
|
|
73
73
|
# the result files (refine-architecture-result-<YYYYMMDD-HHmmss>.json and its
|
|
74
|
-
# -latest.json copy, the
|
|
74
|
+
# -latest.json copy, the record of the last run whose emitter wrote it) and the record of
|
|
75
75
|
# findings a review dropped (.ra-dismissed-<project>.json). Empty = use
|
|
76
76
|
# {output_folder} from config.yaml. Useful for orgs that want
|
|
77
77
|
# refined-architecture artifacts to land under a curated docs tree.
|
|
@@ -95,7 +95,7 @@ The shared emitter writes the result contract (per `shared/references/output-con
|
|
|
95
95
|
uv run {emitEnvelopeHelper} emit --workflow skf-refine-architecture --run-dir "{run_dir}" --result-dir "{outputFolderPath}" < "{run_dir}/result-context.json"
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
-
It writes `{outputFolderPath}/refine-architecture-result-{YYYYMMDD-HHmmss}.json` and its `refine-architecture-result-latest.json` copy (a copy, not a symlink: the stable path
|
|
98
|
+
It writes `{outputFolderPath}/refine-architecture-result-{YYYYMMDD-HHmmss}.json` and its `refine-architecture-result-latest.json` copy (a copy, not a symlink: the record of the last run whose emitter wrote this copy, at a stable path; a caller reads its own run's `SKF_REFINE_ARCHITECTURE_RESULT_JSON` line), stamping the time, the run id, the run's warnings and the auto-decisions the gates recorded, and prints the `SKF_REFINE_ARCHITECTURE_RESULT_JSON:` line on stdout. When `{headless_mode}` is true, display the line verbatim. Bind `{result_path}` ← the line's `result_path`: a result file that could not be written leaves it null and a `result_file_write_failed` warning in the line, and the run still finishes. If the emitter exits non-zero, run §1's `context` command and the emitter once more; if it fails again, HALT (exit code 4, `halt_reason: "write-failed"`) at phase `report:result-contract`: the run's result contract could not be written.
|
|
99
99
|
|
|
100
100
|
### 5. Post-Completion Hook (optional)
|
|
101
101
|
|
|
@@ -32,6 +32,7 @@ These rules apply to every step in this workflow:
|
|
|
32
32
|
- Never allow a rename to collide with an existing skill name
|
|
33
33
|
- Never rename a folder SKF did not generate: select.md §4a takes that verdict from the inventory helper
|
|
34
34
|
- Only load one step file at a time — never preload future steps
|
|
35
|
+
- Run every subagent in the foreground (parallel ones as several calls in one message, never with `run_in_background`) and wait for every long-running command to exit before the next instruction; in a headless run, never end a turn while either still runs, since under `claude -p` a run stops when its turn ends.
|
|
35
36
|
- Every helper is required: select.md §1, §5's validator call and execute.md §0 halt (exit 4) without one, and no stage does a helper's write, scan or check by hand
|
|
36
37
|
- Always communicate in `{communication_language}`
|
|
37
38
|
- At any interactive prompt, the inputs `cancel`, `exit`, `[X]`, `q`, or `:q` exit cleanly with exit code 6 (`halt_reason: "user-cancelled"`)
|
|
@@ -10,8 +10,8 @@ The inputs, flags, gate map, outputs and headless result envelope of `skf-rename
|
|
|
10
10
|
| **Flags** | `--headless` / `-H` (auto-resolve all gates); `--dry-run` (run selection + validation + display the step 1 §8 confirmation block, then exit with `status="dry-run"`: no run lock, no copy, no manifest re-key, no delete, no result file); `--acknowledge-official` (answers the step 1 §6 source-authority warning for this run only, so a headless run may rename a skill whose `source_authority` is `"official"`; the decision is recorded in `headless_decisions[]`). |
|
|
11
11
|
| **Gates** | step 1: Input Gate [use args] x2 (§4 old name, §5 new name); Source-Authority Gate [Y/N] (§6, only for an `"official"` skill; headless default: HALT `source-authority-blocked` unless `--acknowledge-official` or the `force_source_authority_in_headless` setting); Confirm Gate [Y] (§8). Step 2 asks nothing. |
|
|
12
12
|
| **Outputs** | Renamed skill directories, updated manifest, updated context files, `{new_name}/rename-skill-result-{YYYYMMDD-HHmmss}.json` (UTC) and `{new_name}/rename-skill-result-latest.json`; a halt keeps its run folder under `{project-root}/_bmad-output/.skf-run/` |
|
|
13
|
-
| **Concurrency** | step 1 §4b takes the run lock `{forge_data_folder}/.skf-rename-{old_name}.lock` through `skf-run-lock.py`. While another rename of the same `old_name` holds a lock that has not gone stale (60 minutes after it was taken or renewed), the run HALTs with `halt_reason: "halted-for-concurrent-run"` (exit 5). Every exit releases it; `--dry-run` takes none. |
|
|
14
|
-
| **Headless** | All gates auto-resolve with their default action when `{headless_mode}` is true. The §6 source-authority warning HALTs by default when `source_authority="official"`: pass `--acknowledge-official` to proceed for one run. The `force_source_authority_in_headless = "true"` setting, in a team or personal override of this skill (SKILL.md On Activation step 3 names the files), is a standing approval for every headless run. Either way the decision is recorded in `headless_decisions[]`. |
|
|
13
|
+
| **Concurrency** | step 1 §4b takes the run lock `{forge_data_folder}/.skf-rename-{old_name}.lock` through `skf-run-lock.py`. While another rename of the same `old_name` holds a lock that has not gone stale (60 minutes after it was taken or renewed), the run HALTs with `halt_reason: "halted-for-concurrent-run"` (exit 5). Every exit releases it, except a run cut off before it ends, whose lock stays until it goes stale; `--dry-run` takes none. |
|
|
14
|
+
| **Headless** | All gates auto-resolve with their default action when `{headless_mode}` is true. The §6 source-authority warning HALTs by default when `source_authority="official"`: pass `--acknowledge-official` to proceed for one run. The `force_source_authority_in_headless = "true"` setting, in a team or personal override of this skill (SKILL.md On Activation step 3 names the files), is a standing approval for every headless run. Either way the decision is recorded in `headless_decisions[]`. A caller reads the run's own `SKF_RENAME_SKILL_RESULT_JSON` line. A run that printed no envelope line has no result to read, whatever the process exit code: it was cut off, or could not write its result, and `rename-skill-result-latest.json` may then describe an earlier run, so it is never read in its place. |
|
|
15
15
|
| **Exit codes** | Stable per-failure-class codes: see `references/exit-codes.md` |
|
|
16
16
|
|
|
17
17
|
## Result Contract (Headless)
|
package/src/skf-setup/SKILL.md
CHANGED
|
@@ -27,6 +27,7 @@ You are a system executor: run each step in sequence, write the configuration fi
|
|
|
27
27
|
## Workflow Rules
|
|
28
28
|
|
|
29
29
|
- Only load one step file at a time — never preload future steps.
|
|
30
|
+
- Run every subagent in the foreground (parallel ones as several calls in one message, never with `run_in_background`) and wait for every long-running command to exit before the next instruction; in a headless run, never end a turn while either still runs, since under `claude -p` a run stops when its turn ends.
|
|
30
31
|
- Communicate in `{communication_language}`.
|
|
31
32
|
- If `{orphan_action}` is non-null or `{quiet_mode}` is true, resolve the step 3 orphan-removal gate without prompting; step 3 records the decision for the envelope's `warnings`.
|
|
32
33
|
- When `{quiet_mode}` is true, the one line setup displays is the `SKF_SETUP_RESULT_JSON: {…}` envelope that step 4 builds, or the blocked envelope from a halt. In a standalone run that line is the run's final message, so it is all `claude -p` prints (step 4 holds it until then).
|
|
@@ -39,7 +39,7 @@ If `{ccc}` (from step 1) is false, go to section 3 with no output. Otherwise con
|
|
|
39
39
|
|
|
40
40
|
Unless `{quiet_mode}` is true, display: "**Preparing the ccc settings, then the semantic index if it needs building: a first index can take several minutes on large codebases (1000+ files). Run `ccc status` in another terminal to monitor progress.**"
|
|
41
41
|
|
|
42
|
-
Pass `{ccc_index_fresh}` (from step 1) and `{ccc_skip_index}` as `true` or `false`, and add `--defer-index` only when `{require_tier_satisfied}` (from step 1) is `false`: a run that ends `tier_failure` then builds no index that is due, while a fresh index record still carries forward.
|
|
42
|
+
Pass `{ccc_index_fresh}` (from step 1) and `{ccc_skip_index}` as `true` or `false`, and add `--defer-index` only when `{require_tier_satisfied}` (from step 1) is `false`: a run that ends `tier_failure` then builds no index that is due, while a fresh index record still carries forward. Run the call in the foreground with the shell's longest timeout (in an interactive run, an index that can outlast that timeout may run in the background instead, the step waiting for it to exit before it goes on; a headless run keeps it in the foreground); a call the shell stops at that timeout goes on as the paragraph after the command says:
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
45
|
uv run {mergeCccExclusionsHelper} \
|
|
@@ -10,5 +10,5 @@ The flags, gate, outputs, headless contract and failure modes of `skf-setup`, fo
|
|
|
10
10
|
| **Flags** | `--headless` / `-H` (skip prompts, auto-resolve gates to defaults); `--quiet` (an alias of `--headless`; its step 3 orphan default is recorded as `quiet-default`); `--require-tier=<Quick\|Forge\|Forge+\|Deep>` or `--require-tier <tier>` (halt with failure if calculated tier does not satisfy the requirement; any other value halts the run `blocked`, see Failure modes); `--orphan-action=<keep\|remove>` (resolve the orphan-removal gate non-interactively, even outside `--headless`; any other value halts at activation); `--ccc-skip-index` (skip building the CCC index: settings.yml is still prepared and its SKF exclusions kept current, and the envelope's `ccc_index.status` becomes `"skipped"`; the fast re-probe lane for an expert re-running only to refresh the detected tier without paying the full ccc re-index cost) |
|
|
11
11
|
| **Gates** | One optional: orphaned QMD collection removal (step 3, Deep tier only; default: Keep, or whatever `--orphan-action` set; resolves without prompting under `--headless` or `--quiet`). Only a collection with a forge suffix that the registry does not list and whose QMD path lies inside this project is offered: another project's collections never are |
|
|
12
12
|
| **Outputs** | `{sidecar_path}/forge-tier.yaml` and `{forge_data_folder}/` (setup never writes `preferences.yaml`: the installer creates it, and setup reads it when it is there); when ccc is available, `.cocoindex_code/settings.yml` (created by `ccc init` when missing; SKF exclusion patterns merged and stale ones removed), a `/.cocoindex_code/` line in `.gitignore` when `ccc init` adds one, and the project ccc index; for the run itself, a scratch folder under `{project-root}/_bmad-output/.skf-run/` holding the helpers' outputs, which step 4 deletes (a halt that names a phase leaves it in place) |
|
|
13
|
-
| **Headless** | All gates auto-resolve with their default action under `--headless` or its alias `--quiet`, under `headless_mode: true` in `{sidecar_path}/preferences.yaml` (known once activation has loaded it, so the halts before that emit an envelope only under a flag or in a pipeline), and in a forger pipeline. Under `--headless`, `--quiet` or `headless_mode: true`, a standalone run's final message, and so everything `claude -p` prints, is exactly one line: the single-line `SKF_SETUP_RESULT_JSON: {…}` envelope from step 4 (displayed once the health check has finished, or at the tier-miss halt), or a blocked envelope from a halt, or the bare halt reason when no envelope helper can run (see Failure modes). Setup displays no FORGE STATUS banner or health-check output, and no halt diagnostic beyond that one line; the health check still runs and saves any findings to the local improvement queue. Inside a forger pipeline (`{pipeline_mode}` true), setup displays the same line and returns control to the forger. Branch on the envelope's top-level `status` field: `success`, `tier_failure` (require-tier miss), or `blocked` (any pre-report halt, whose `error.phase` names the cause). `skf-emit-result-envelope.py` derives `status` from the run's staged outputs, so pipelines never compose it from `require_tier_satisfied` + `error`. Schema: `shared/scripts/schemas/skf-setup-result-envelope.v1.json`. |
|
|
13
|
+
| **Headless** | All gates auto-resolve with their default action under `--headless` or its alias `--quiet`, under `headless_mode: true` in `{sidecar_path}/preferences.yaml` (known once activation has loaded it, so the halts before that emit an envelope only under a flag or in a pipeline), and in a forger pipeline. Under `--headless`, `--quiet` or `headless_mode: true`, a standalone run's final message, and so everything `claude -p` prints, is exactly one line: the single-line `SKF_SETUP_RESULT_JSON: {…}` envelope from step 4 (displayed once the health check has finished, or at the tier-miss halt), or a blocked envelope from a halt, or the bare halt reason when no envelope helper can run (see Failure modes). Setup displays no FORGE STATUS banner or health-check output, and no halt diagnostic beyond that one line; the health check still runs and saves any findings to the local improvement queue. Inside a forger pipeline (`{pipeline_mode}` true), setup displays the same line and returns control to the forger. Branch on the envelope's top-level `status` field: `success`, `tier_failure` (require-tier miss), or `blocked` (any pre-report halt, whose `error.phase` names the cause). `skf-emit-result-envelope.py` derives `status` from the run's staged outputs, so pipelines never compose it from `require_tier_satisfied` + `error`. Schema: `shared/scripts/schemas/skf-setup-result-envelope.v1.json`. A run that printed no envelope line has no result to read, whatever the process exit code: it was cut off, or could not write its result, as when no helper can build one (Failure modes), and a caller treats it as a failure. |
|
|
14
14
|
| **Failure modes** | `--require-tier` not satisfied → status `tier_failure`, the envelope sets `"require_tier_satisfied": false`, and the workflow halts before step 5 (interactive runs also print a "REQUIRED TIER NOT MET" block). Such a run fails fast: it builds no ccc index that is due (`ccc_index.status` `"skipped"`, while a fresh index record still carries forward) and runs no registry hygiene, so it removes no QMD collection, even under `--orphan-action=remove`; it still writes `forge-tier.yaml` with the detected tier. A `--require-tier` value that is not exactly one of the four tiers is no miss: the detector rejects it, and the run ends `blocked` at `step 1:detect-tools` with a reason that names the valid tiers (with a did-you-mean hint for a wrong-case tier). A write failure (forge-tier.yaml or the forge data folder could not be written) halts step 2 with a `blocked` envelope whose `error.phase` (`step 2:write-tools` or `step 2:forge-data-dir`) and `error` name the path and reason. The other halts (uv missing, config missing, malformed or naming no usable `sidecar_path`, an `--orphan-action` other than `keep` or `remove`, a run folder that cannot be created, a helper that no probe path resolves, a failed tool detection) end the same way under `--headless` or `--quiet`: `error.phase` names the halt and `error.reason` carries the diagnostic. The exception is a run where the envelope helper itself cannot run (SKF's scripts are not installed in the project, as in a directory that is not an SKF project, or an On Activation halt on a machine where none of `uv`, `python3`, `python` and `py -3` can run it): its one line is the bare halt reason with no envelope, and a pipeline treats a missing envelope as a failure. |
|
|
@@ -29,6 +29,7 @@ These rules apply to every step in this workflow:
|
|
|
29
29
|
|
|
30
30
|
- Zero hallucination — every finding must trace to actual code with file:line citations
|
|
31
31
|
- Only load one step file at a time — never preload future steps
|
|
32
|
+
- Run every subagent in the foreground (parallel ones as several calls in one message, never with `run_in_background`) and wait for every long-running command to exit before the next instruction; in a headless run, never end a turn while either still runs, since under `claude -p` a run stops when its turn ends.
|
|
32
33
|
- Update `stepsCompleted` in output file frontmatter before loading next step
|
|
33
34
|
- Always communicate in `{communication_language}`
|
|
34
35
|
- No stage waits for an answer in headless mode: the one question, the skill name, halts `input-missing` there
|
|
@@ -20,13 +20,13 @@
|
|
|
20
20
|
|
|
21
21
|
## Coherence Analysis — Naive Mode: Reference Consistency (split-body)
|
|
22
22
|
|
|
23
|
-
Only rendered when `references/` directory exists alongside SKILL.md. Each row is a split-body gap coverage-check recorded
|
|
23
|
+
Only rendered when `references/` directory exists alongside SKILL.md. Each row is a split-body gap coverage-check recorded. **Other Side** is the other file of its pair, at its line: `SKILL.md:{skill_md_line}`, or `{other_file}:{other_line}` for two references. Its Source is at **Other Side** when the pinned source contradicts that side and agrees with **Reference File**, else at `{Reference File}:{Reference Line}`. **Contradicted Side** is the path of the one file the source contradicts (`SKILL.md` or a `references/` file's path), `none` when both forms agree with it, or `not settled` when it contradicts both or no source settled it.
|
|
24
24
|
|
|
25
25
|
```markdown
|
|
26
26
|
### Reference Consistency (split-body)
|
|
27
27
|
|
|
28
|
-
| # | Reference File | Export | Issue |
|
|
29
|
-
|
|
28
|
+
| # | Reference File | Export | Issue | Other Side | Reference Line | Contradicted Side |
|
|
29
|
+
|---|---------------|--------|-------|------------|---------------|-------------------|
|
|
30
30
|
| {per-mismatch rows} |
|
|
31
31
|
|
|
32
32
|
**Exports Cross-Checked:** {count}
|
|
@@ -80,7 +80,7 @@ Every gap a stage finds is one JSON record in the run's gap ledger, `{forge_vers
|
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
- `severity` and `category` come from one row of the Gap Severity table in `references/scoring-rules.md`. The script refuses a category outside its vocabulary, which `uv run scripts/gap-ledger.py categories` lists.
|
|
83
|
-
- `source` is a `file:line` pair whenever a line applies, with nothing after it: update-skill reads the pair as the gap's citation. A gap inside the skill package cites `SKILL.md:{line}` or `references/{file}.md:{line}`. A split-body record, titled `Split-body mismatch: {export} ({reference_file})`, cites the side the pinned source contradicts: `SKILL.md:{skill_md_line}` when
|
|
83
|
+
- `source` is a `file:line` pair whenever a line applies, with nothing after it: update-skill reads the pair as the gap's citation. A gap inside the skill package cites `SKILL.md:{line}` or `references/{file}.md:{line}`. A split-body record, titled `Split-body mismatch: {export} ({reference_file})`, or `Split-body mismatch: {export} ({other_file}, {reference_file})` for two references, cites the side the pinned source contradicts: the other side, `SKILL.md:{skill_md_line}` or `{other_file}:{other_line}`, when `{reference_file}` agrees with the source declaration and the other side does not, else `{reference_file}:{reference_line}`, also when no source settled it. Its remediation names the file to edit and the file and line whose form it takes, and update-skill changes only what it names as contradicting.
|
|
84
84
|
- `remediation` follows the Remediation Quality Rules below and names each file it touches.
|
|
85
85
|
- Leave out `id`, `group` and `stage`: the script assigns them (`GAP-{NNN}` in the order the gaps were recorded, the group from the category, the stage from `--stage`).
|
|
86
86
|
|