bmad-module-skill-forge 1.9.0 → 2.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.
Files changed (118) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +10 -5
  3. package/docs/_data/pinned.yaml +2 -2
  4. package/docs/agents.md +11 -2
  5. package/docs/architecture.md +5 -4
  6. package/docs/bmad-synergy.md +30 -7
  7. package/docs/campaign.md +172 -0
  8. package/docs/examples.md +7 -3
  9. package/docs/forge-auto.md +90 -0
  10. package/docs/getting-started.md +9 -0
  11. package/docs/how-it-works.md +6 -4
  12. package/docs/index.md +4 -3
  13. package/docs/skill-model.md +1 -1
  14. package/docs/troubleshooting.md +17 -1
  15. package/docs/verifying-a-skill.md +2 -2
  16. package/docs/why-skf.md +1 -1
  17. package/docs/workflows.md +64 -24
  18. package/package.json +2 -2
  19. package/src/module-help.csv +2 -1
  20. package/src/shared/data/language-corpora.json +32 -0
  21. package/src/shared/references/pipeline-contracts.md +6 -3
  22. package/src/shared/scripts/schemas/skill-brief.v1.json +6 -1
  23. package/src/shared/scripts/skf-derive-assembly-shape.py +107 -0
  24. package/src/shared/scripts/skf-detect-docs.py +8 -3
  25. package/src/shared/scripts/skf-detect-language.py +5 -3
  26. package/src/shared/scripts/skf-emit-result-envelope.py +17 -0
  27. package/src/shared/scripts/skf-extract-public-api.py +53 -0
  28. package/src/shared/scripts/skf-language-corpora.py +100 -0
  29. package/src/shared/scripts/skf-merge-doc-urls.py +244 -0
  30. package/src/shared/scripts/skf-preapply.py +1 -1
  31. package/src/shared/scripts/skf-shape-detect.py +576 -25
  32. package/src/shared/scripts/skf-validate-brief-schema.py +2 -5
  33. package/src/shared/scripts/skf-validate-pins.py +2 -2
  34. package/src/shared/scripts/skf-write-skill-brief.py +21 -5
  35. package/src/skf-analyze-source/SKILL.md +1 -1
  36. package/src/skf-analyze-source/assets/skill-brief-schema.md +1 -1
  37. package/src/skf-analyze-source/references/step-auto-scope.md +196 -44
  38. package/src/skf-analyze-source/references/step-shape-detect.md +20 -13
  39. package/src/skf-audit-skill/references/init.md +1 -1
  40. package/src/skf-brief-skill/assets/skill-brief-schema.md +1 -1
  41. package/src/skf-brief-skill/references/analyze-target.md +7 -4
  42. package/src/skf-brief-skill/references/confirm-brief.md +0 -1
  43. package/src/skf-brief-skill/references/gather-intent.md +8 -2
  44. package/src/skf-brief-skill/references/scope-definition.md +2 -1
  45. package/src/skf-brief-skill/references/step-auto-brief.md +21 -4
  46. package/src/skf-brief-skill/references/step-auto-validate.md +2 -2
  47. package/src/skf-brief-skill/references/write-brief.md +2 -3
  48. package/src/skf-campaign/SKILL.md +190 -0
  49. package/src/skf-campaign/assets/campaign-state-schema.json +191 -0
  50. package/src/skf-campaign/customize.toml +73 -0
  51. package/src/skf-campaign/manifest.yaml +11 -0
  52. package/src/skf-campaign/references/campaign-directive-spec.md +121 -0
  53. package/src/skf-campaign/references/health-check.md +35 -0
  54. package/src/skf-campaign/references/step-01-setup.md +122 -0
  55. package/src/skf-campaign/references/step-02-strategy.md +97 -0
  56. package/src/skf-campaign/references/step-03-pins.md +56 -0
  57. package/src/skf-campaign/references/step-04-provenance.md +63 -0
  58. package/src/skf-campaign/references/step-05-skill-loop.md +103 -0
  59. package/src/skf-campaign/references/step-06-batch.md +87 -0
  60. package/src/skf-campaign/references/step-07-capstone.md +63 -0
  61. package/src/skf-campaign/references/step-08-verify.md +75 -0
  62. package/src/skf-campaign/references/step-09-refine.md +83 -0
  63. package/src/skf-campaign/references/step-10-export.md +106 -0
  64. package/src/skf-campaign/references/step-11-maintenance.md +84 -0
  65. package/src/skf-campaign/references/step-resume.md +114 -0
  66. package/src/skf-campaign/scripts/.gitkeep +0 -0
  67. package/src/skf-campaign/scripts/campaign-deps.py +235 -0
  68. package/src/skf-campaign/scripts/campaign-parse-manifest.py +119 -0
  69. package/src/skf-campaign/scripts/campaign-provenance.py +247 -0
  70. package/src/skf-campaign/scripts/campaign-render-kickoff.py +158 -0
  71. package/src/skf-campaign/scripts/campaign-report.py +249 -0
  72. package/src/skf-campaign/scripts/campaign-validate-pins.py +174 -0
  73. package/src/skf-campaign/scripts/campaign-validate-state.py +207 -0
  74. package/src/skf-campaign/templates/campaign-brief-template.yaml +34 -0
  75. package/src/skf-campaign/templates/campaign-report-template.md +54 -0
  76. package/src/skf-campaign/templates/kickoff-template.md +48 -0
  77. package/src/skf-create-skill/SKILL.md +1 -1
  78. package/src/skf-create-skill/assets/compile-assembly-rules.md +22 -0
  79. package/src/skf-create-skill/references/compile.md +4 -1
  80. package/src/skf-create-skill/references/extract.md +9 -1
  81. package/src/skf-create-skill/references/extraction-patterns.md +3 -1
  82. package/src/skf-create-skill/references/generate-artifacts.md +11 -2
  83. package/src/skf-create-skill/references/health-check.md +2 -2
  84. package/src/skf-create-skill/references/report.md +17 -1
  85. package/src/skf-create-skill/references/source-resolution-protocols.md +1 -1
  86. package/src/skf-create-skill/references/step-doc-rot.md +4 -4
  87. package/src/skf-create-skill/references/step-doc-sources.md +11 -2
  88. package/src/skf-create-skill/references/sub/fetch-docs.md +29 -0
  89. package/src/skf-create-skill/references/validate.md +12 -3
  90. package/src/skf-drop-skill/SKILL.md +2 -2
  91. package/src/skf-drop-skill/references/execute.md +20 -9
  92. package/src/skf-drop-skill/references/report.md +2 -0
  93. package/src/skf-drop-skill/references/select.md +7 -2
  94. package/src/skf-forger/SKILL.md +12 -7
  95. package/src/skf-refine-architecture/SKILL.md +2 -1
  96. package/src/skf-refine-architecture/references/compile.md +1 -1
  97. package/src/skf-refine-architecture/references/report.md +1 -1
  98. package/src/skf-rename-skill/SKILL.md +2 -2
  99. package/src/skf-rename-skill/references/execute.md +5 -4
  100. package/src/skf-rename-skill/references/rebuild-context.md +2 -2
  101. package/src/skf-rename-skill/references/select.md +3 -3
  102. package/src/skf-setup/SKILL.md +1 -1
  103. package/src/skf-setup/references/auto-index.md +3 -1
  104. package/src/skf-setup/references/detect-and-tier.md +4 -0
  105. package/src/skf-setup/references/report.md +4 -1
  106. package/src/skf-setup/references/tier-rules.md +1 -1
  107. package/src/skf-test-skill/references/coverage-check.md +10 -0
  108. package/src/skf-test-skill/references/init.md +1 -1
  109. package/src/skf-test-skill/references/source-access-protocol.md +13 -3
  110. package/src/skf-test-skill/scripts/compute-score.py +4 -3
  111. package/src/skf-update-skill/customize.toml +0 -9
  112. package/src/skf-update-skill/references/detect-changes.md +33 -3
  113. package/src/skf-update-skill/references/health-check.md +2 -2
  114. package/src/skf-update-skill/references/init.md +1 -0
  115. package/src/skf-update-skill/references/re-extract.md +15 -1
  116. package/src/skf-verify-stack/references/report.md +1 -1
  117. package/tools/cli/lib/ui.js +3 -2
  118. package/docs/deepwiki.md +0 -89
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  # `shared/health-check.md` resolves relative to the SKF module root
3
- # (`_bmad/skf/` when installed, `src/` during development), NOT relative
4
- # to this step file.
3
+ # (`{project-root}/_bmad/skf/` when installed, `{project-root}/src/` during
4
+ # development), NOT relative to this step file.
5
5
  nextStepFile: 'shared/health-check.md'
6
6
  ---
7
7
 
@@ -125,10 +125,26 @@ End workflow. No further steps.
125
125
 
126
126
  **Resolve the schema reference:** before writing, verify that `{project-root}/src/shared/references/output-contract-schema.md` exists and is readable. Try in order: `{project-root}/src/shared/references/output-contract-schema.md`, then `{project-root}/_bmad/skf/shared/references/output-contract-schema.md` (installed-forge path).
127
127
 
128
- - **If resolved:** write the result contract per the schema — the per-run record at `{forge_version}/create-skill-result-{YYYYMMDD-HHmmss}.json` (UTC timestamp, resolution to seconds) and a copy at `{forge_version}/create-skill-result-latest.json` (stable path for pipeline consumers — copy, not symlink). Include `SKILL.md`, `context-snippet.md`, and `metadata.json` paths in `outputs` and confidence distribution in `summary`. Use `python3 {atomicWriteHelper} write --target {forge_version}/create-skill-result-{YYYYMMDD-HHmmss}.json` (stdin-piped JSON) for the per-run record, then the same helper for the `-latest.json` copy.
128
+ - **If resolved:** write the result contract per the schema — the per-run record at `{forge_version}/create-skill-result-{YYYYMMDD-HHmmss}.json` (UTC timestamp, resolution to seconds) and a copy at `{forge_version}/create-skill-result-latest.json` (stable path for pipeline consumers — copy, not symlink). Include `SKILL.md`, `context-snippet.md`, `metadata.json`, **and `{forge_version}/evidence-report.md`** paths in `outputs` (the evidence report carries the `## Auto-Decisions` audit table where every silent auto-decision is recorded — pipeline consumers follow this path to audit the run) and confidence distribution in `summary`. Also set `summary.auto_decision_count` to `len(headless_decisions[])` (0 when the run was interactive) so a consumer can tell from the result JSON alone whether any gate auto-resolved. Use `python3 {atomicWriteHelper} write --target {forge_version}/create-skill-result-{YYYYMMDD-HHmmss}.json` (stdin-piped JSON) for the per-run record, then the same helper for the `-latest.json` copy.
129
129
 
130
130
  - **If neither candidate path resolves:** skip the result-contract write entirely. Append a warning to `evidence-report.md`: "Result contract skipped — `shared/references/output-contract-schema.md` could not be resolved at either candidate path." Then set `validation_status: 'schema-unavailable'` in `metadata.json` (and re-write metadata.json via `skf-atomic-write.py write`). Pipeline consumers will observe the missing `-latest.json` and the metadata flag.
131
131
 
132
+ ### Result Contract on HARD HALT
133
+
134
+ The success-variant contract above is only reached at step 8. The ~10 HARD HALT conditions in steps 1–7 (forge-config missing, no brief, brief invalid, source not found, prerequisite failure in load-brief; atomic/detect/auth helper unresolved and the Tier-1 split-count drop in extract/validate; the non-symlink active-link refusal in generate-artifacts) otherwise print a human string and exit with **no machine-readable outcome** — a pipeline polling `create-skill-result-latest.json` cannot distinguish "halted at brief-invalid" from "still running" from "crashed". Mirror skf-quick-skill: **whenever `{headless_mode}` is true, every HARD HALT must surface an error-variant result before exiting.**
135
+
136
+ **Always (every HARD HALT under `{headless_mode}`, regardless of phase)** — emit a single line on **stderr** (one line, no pretty-print; matches the prefix-and-envelope convention used by `skf-emit-result-envelope.py`):
137
+
138
+ ```
139
+ SKF_CREATE_SKILL_RESULT_JSON: {"status":"failed","phase":"<step-slug>","outputs":{},"summary":{"halt_reason":"<short class>","evidence_report":"<path-or-null>"},"skill_package":"<path-or-null>"}
140
+ ```
141
+
142
+ Use `status: "partial"` instead of `"failed"` when artifacts were already staged or promoted before the HALT (i.e. the HALT fired at step 7 generate-artifacts after some files were written); use `"failed"` for HALTs before any artifact exists on disk (steps 1–6).
143
+
144
+ **Additionally, when `{forge_version}` is resolved** (HALT at step 7 onward, where the staging tree has been promoted) — write the same JSON object (without the `SKF_CREATE_SKILL_RESULT_JSON: ` prefix) to disk at `{forge_version}/create-skill-result-{YYYYMMDD-HHmmss}.json` and a copy at `{forge_version}/create-skill-result-latest.json` (copy, not symlink) via `python3 {atomicWriteHelper} write`, so consumers that hardcode the `-latest.json` path see a deterministic file even on failed runs. Set `summary.evidence_report` to `{forge_version}/evidence-report.md` whenever that file exists, so the consumer can still reach the `## Auto-Decisions` audit on a failed run. HALTs before step 7 cannot write to disk because `{forge_version}` is only created at step 7 §1; for those, the stderr envelope is the contract and `summary.evidence_report` is `null`.
145
+
146
+ When `{headless_mode}` is false, HARD HALTs display their human message only — no envelope is emitted.
147
+
132
148
  ### 6. Chain to Health Check
133
149
 
134
150
  **If not batch mode (or all batch briefs complete):**
@@ -154,7 +154,7 @@ If `source_repo` is a remote URL (GitHub URL or owner/repo format) AND tier is F
154
154
  - `remote_clone_path = {workspace_repo_path}`
155
155
  - `remote_clone_type = "workspace"`
156
156
 
157
- **Scope filtering:** Since the workspace uses a full checkout (no sparse-checkout), apply `include_patterns` and `exclude_patterns` from the brief as **file-level filters** when building the extraction file list. Always-included root files (`pyproject.toml`, `package.json`, `Cargo.toml`, `go.mod`, `setup.py`, `setup.cfg`, `VERSION`) are exempt from pattern filtering.
157
+ **Scope filtering:** Since the workspace uses a full checkout (no sparse-checkout), apply `include_patterns` and `exclude_patterns` from the brief as **file-level filters** when building the extraction file list. Always-included root files (`pyproject.toml`, `package.json`, `Cargo.toml`, `go.mod`, `pom.xml`, `build.gradle`, `build.gradle.kts`, `Package.swift`, `setup.py`, `setup.cfg`, `VERSION`) are exempt from pattern filtering.
158
158
 
159
159
  5. **Ephemeral fallback (on any workspace failure):**
160
160
 
@@ -23,12 +23,12 @@ Scan feeder artifacts for doc-rot correction indicators and annotate the compile
23
23
 
24
24
  ### §1. Locate Feeder Artifacts
25
25
 
26
- Identify the feeder artifacts in the staging directory for the current skill:
26
+ Identify the feeder artifacts in the **staging directory** for the current skill. This step (5c) runs **before** step 7 promotes the staging tree to `{forge_data_folder}/{skill-name}/{version}/`, so the feeder artifacts only exist under the staging path compile (step 5 §1a) wrote — reading the not-yet-promoted `{forge_data_folder}` path would make every match a no-op:
27
27
 
28
- 1. **Evidence report:** `{forge_data_folder}/{skill-name}/{version}/evidence-report.md`
29
- 2. **Provenance map:** `{forge_data_folder}/{skill-name}/{version}/provenance-map.json` — focus on T2/T3 entries with temporal annotations
28
+ 1. **Evidence report:** `_bmad-output/{skill-name}/evidence-report.md`
29
+ 2. **Provenance map:** `_bmad-output/{skill-name}/provenance-map.json` — focus on T2/T3 entries with temporal annotations
30
30
  3. **Temporal context:** changelogs, migration guides, and issue/PR data fetched by step 3b and enriched by step 4 (available in workflow context)
31
- 4. **Compiled SKILL.md:** the staged SKILL.md itself — check for `[QMD:...]` or `[DOC:...]` annotations referencing corrections
31
+ 4. **Compiled SKILL.md:** the staged `_bmad-output/{skill-name}/SKILL.md` itself — check for `[QMD:...]` or `[DOC:...]` annotations referencing corrections
32
32
 
33
33
  For each artifact, attempt to load its content. If an artifact does not exist or is empty, skip it — this is not an error.
34
34
 
@@ -1,5 +1,12 @@
1
1
  ---
2
2
  nextStepFile: 'step-auto-shard.md'
3
+ # Resolve `{detectDocsHelper}` by probing `{detectDocsProbeOrder}` in order
4
+ # (installed SKF module path first, src/ dev-checkout fallback); first existing
5
+ # path wins. HALT if neither resolves — §2 has no prose fallback for doc-source
6
+ # detection (Pages-API walk, docs/ folder scan, content hashing).
7
+ detectDocsProbeOrder:
8
+ - '{project-root}/_bmad/skf/shared/scripts/skf-detect-docs.py'
9
+ - '{project-root}/src/shared/scripts/skf-detect-docs.py'
3
10
  ---
4
11
 
5
12
  <!-- Config: communicate in {communication_language}. -->
@@ -21,7 +28,7 @@ Record detected documentation pages and README with content hashes in metadata.j
21
28
 
22
29
  ### 1. Check for Upstream Doc Detection Results
23
30
 
24
- Check if `doc_detection_results` is already populated in the workflow context (set by BS auto-brief in the deepwiki pipeline).
31
+ Check if `doc_detection_results` is already populated in the workflow context (set by BS auto-brief in the forge-auto pipeline).
25
32
 
26
33
  - **If upstream results exist:** use them directly, skip to step 3.
27
34
  - **If no upstream results:** continue to step 2.
@@ -37,10 +44,12 @@ Check that `{source_repo}` is available from the skill brief.
37
44
 
38
45
  **If `{source_repo}` is available:**
39
46
 
47
+ **Resolve `{detectDocsHelper}`** from `{detectDocsProbeOrder}`; first existing path wins. HALT if no candidate exists.
48
+
40
49
  Invoke the detect-docs script:
41
50
 
42
51
  ```bash
43
- uv run python src/shared/scripts/skf-detect-docs.py \
52
+ uv run {detectDocsHelper} \
44
53
  --repo-url {source_repo} \
45
54
  [--local-path {source_path}] \
46
55
  [--skip-pages-api]
@@ -6,6 +6,12 @@ nextStepFile: '../enrich.md'
6
6
  atomicWriteProbeOrder:
7
7
  - '{project-root}/_bmad/skf/shared/scripts/skf-atomic-write.py'
8
8
  - '{project-root}/src/shared/scripts/skf-atomic-write.py'
9
+ # Resolve `{deriveAssemblyShapeHelper}` the same way — used in §1 to decide
10
+ # whether this is a whole-language reference (registry-corpora prose retained
11
+ # as a Language Guide) or a standard skill (unchanged behaviour).
12
+ deriveAssemblyShapeProbeOrder:
13
+ - '{project-root}/_bmad/skf/shared/scripts/skf-derive-assembly-shape.py'
14
+ - '{project-root}/src/shared/scripts/skf-derive-assembly-shape.py'
9
15
  ---
10
16
 
11
17
  <!-- Config: communicate in {communication_language}. -->
@@ -33,6 +39,14 @@ Evaluate the following conditions. **If the condition fails, skip silently to se
33
39
 
34
40
  No tier gate — if `doc_urls` are present, this step runs at Quick, Forge, and Deep tiers alike.
35
41
 
42
+ **Determine assembly shape (whole-language gate).** Resolve `{deriveAssemblyShapeHelper}` from `{deriveAssemblyShapeProbeOrder}` (first existing path wins) and run it on the brief:
43
+
44
+ ```bash
45
+ uv run {deriveAssemblyShapeHelper} {brief_path}
46
+ ```
47
+
48
+ If the result's `assembly_shape` is `whole-language-reference` (the brief carries ≥1 `doc_urls` entry with `source: language-registry` — a compiler/interpreter repo enriched with the language's canonical prose), set the in-context flag `whole_language_reference: true`. This changes ONLY how the registry-sourced corpora are handled below (§4a): their prose is retained as a **Language Guide** rather than shredded into per-export items. For every other brief the flag is false and this step behaves exactly as before — no change to ordinary skills.
49
+
36
50
  ### 2. Security Notice
37
51
 
38
52
  Display an informational notice (not a gate — the user already approved these URLs in the brief):
@@ -122,6 +136,19 @@ Parse the successfully fetched markdown for:
122
136
 
123
137
  **No hallucination:** If information cannot be found in the fetched content, exclude it. Do not infer or fabricate API details.
124
138
 
139
+ **Whole-language references — retain prose, do NOT shred (`whole_language_reference: true`):** For a whole-language reference the registry-sourced corpora (the guide/Book, the standard/library docs) ARE the product, not the compiler's internal exports. Reducing that prose to per-export signature items and then discarding it under the §5 "T3 never overrides T1" rule (the compiler's AST already owns names like `Vec`, `Option`, `HashMap`) would gut exactly the content the skill exists to teach. So for these briefs, skip §4a below for the registry corpora.
140
+
141
+ ### 4a. Retain the Language Guide (whole-language references only)
142
+
143
+ **Skip this section entirely unless `whole_language_reference: true`.** When it is true, for each `doc_urls` entry whose `source` is `language-registry`:
144
+
145
+ - Do NOT reduce its fetched markdown to per-export items. Instead retain the cleaned prose as a Language-Guide entry `{url, label, prose}`, where `prose` is the substantive body (narrative, idioms, usage examples, conceptual reference) lightly trimmed of navigation/boilerplate, each block cited `[EXT:{url}]`.
146
+ - Collect these into a `language_guide[]` context artifact, in `doc_urls` order.
147
+
148
+ This artifact is a **distinct** carrier — it is NOT merged into the extraction inventory and is NOT subject to the §5 conflict rule, so the canonical prose survives intact into step 5 (compile), which foregrounds it as the skill's Language Guide. Non-registry docs (README-detected, homepage, Pages, docs-folder) still flow through §4's normal per-export extraction and the §5 merge unchanged.
149
+
150
+ **If a registry corpus could not be fetched** (network failure), record it in `language_guide[]` as `{url, label, prose: null}` and warn — step 5 surfaces the gap rather than emitting a thin guide silently.
151
+
125
152
  ### 5. Build Doc-Fetch Inventory
126
153
 
127
154
  **Mode determines merge behavior:**
@@ -131,6 +158,8 @@ Parse the successfully fetched markdown for:
131
158
 
132
159
  **Conflict rule:** T3 items NEVER override existing T1, T1-low, or T2 items for the same export. When an export already has a higher-confidence entry, the T3 item is discarded. T3 has the lowest priority.
133
160
 
161
+ **Language-Guide carve-out:** the `language_guide[]` artifact from §4a (whole-language references) is NOT part of the export inventory and is therefore NOT subject to this conflict rule — it carries no export key, so it cannot collide with a T1 compiler export and can never be pruned. It is passed separately into step 5, which renders it as the foregrounded Language Guide section. Only the per-export T3 items participate in the T1/T2/T3 merge.
162
+
134
163
  **Edge case — T1-zero supplemental mode:** If T1 extraction produced zero results and `doc_urls` are present in supplemental mode, T3 items should be used as the primary inventory since no T1 data exists to conflict with.
135
164
 
136
165
  **Aggregate totals for reporting:**
@@ -17,6 +17,13 @@ atomicWriteProbeOrder:
17
17
  descriptionGuardProbeOrder:
18
18
  - '{project-root}/_bmad/skf/shared/scripts/skf-description-guard.py'
19
19
  - '{project-root}/src/shared/scripts/skf-description-guard.py'
20
+ # Resolve `{frontmatterValidator}` by probing `{frontmatterValidatorProbeOrder}`
21
+ # in order (installed SKF module path first, src/ dev-checkout fallback); first
22
+ # existing path wins. §0's post-restore re-validation hook uses it; an installed
23
+ # module has no src/ tree, so a bare src/ path would silently fail the hook.
24
+ frontmatterValidatorProbeOrder:
25
+ - '{project-root}/_bmad/skf/shared/scripts/skf-validate-frontmatter.py'
26
+ - '{project-root}/src/shared/scripts/skf-validate-frontmatter.py'
20
27
  ---
21
28
 
22
29
  <!-- Config: communicate in {communication_language}. -->
@@ -43,7 +50,7 @@ To validate the compiled SKILL.md content against the agentskills.io specificati
43
50
 
44
51
  Load `{descriptionGuardProtocol}` for the full prose explanation of the four-phase guard (why it exists, what counts as divergence, why token-stream comparison is the right shape). The deterministic phases are executed via `{descriptionGuardHelper}` — the calling sections (§2 and §4) invoke the helper at the capture and verify-restore points.
45
52
 
46
- **This skill's post-restore re-validation hook:** after `{descriptionGuardHelper}` reports `restored: true`, run `uv run {project-root}/src/shared/scripts/skf-validate-frontmatter.py <staging-skill-dir>/SKILL.md` and capture `schema_revalidation_result` in context. If the validator exits non-zero OR reports failure for the `description` field, flip the Schema result back to `FAIL` in the evidence report (overriding any prior PASS/WARN from §2), record `description_guard_revalidation: FAIL` with the validator's diagnostic message, and continue — do not halt (step 9 health-check and result contract still need to run so the failure is surfaced through the normal artifact path).
53
+ **This skill's post-restore re-validation hook:** after `{descriptionGuardHelper}` reports `restored: true`, resolve `{frontmatterValidator}` from `{frontmatterValidatorProbeOrder}` (first existing path wins), run `uv run {frontmatterValidator} <staging-skill-dir>/SKILL.md` and capture `schema_revalidation_result` in context. If the validator exits non-zero OR reports failure for the `description` field, flip the Schema result back to `FAIL` in the evidence report (overriding any prior PASS/WARN from §2), record `description_guard_revalidation: FAIL` with the validator's diagnostic message, and continue — do not halt (step 9 health-check and result contract still need to run so the failure is surfaced through the normal artifact path).
47
54
 
48
55
  ### 1. Check Tool Availability
49
56
 
@@ -199,7 +206,9 @@ tessl installs automatically via `npx`. A missing tool is not an error — grace
199
206
 
200
207
  **If §6 produced no novel suggestions (all dismissed via `{tesslDismissalData}`) OR tessl was unavailable:** Skip this gate — auto-proceed.
201
208
 
202
- **If §6 produced novel suggestions** (ones not matched by any dismissal rule), present them to the user:
209
+ **GATE [default: S]** — If `{headless_mode}` is true AND §6 produced novel suggestions: auto-select [S] Skip (a headless run has no human to triage novel suggestions), record `"tessl suggestions: {N} novel suggestion(s) auto-skipped (headless)"` in the evidence report under "Dismissed tessl suggestions", log `"headless: auto-skip {N} novel tessl suggestion(s)"`, and append `{step: "validate", gate: "tessl-suggestions", decision: "S", value: "{N} novel auto-skipped", rationale: "headless mode — no human to triage novel tessl suggestions", timestamp: {ISO}}` to the in-context `headless_decisions[]` list (step 5 §7 reads it into the evidence-report `## Auto-Decisions` table). This is the one consequential auto-decision that drops human-relevant feedback, so it must leave an audit row rather than vanishing. Then auto-proceed to §7 — do NOT present the menu below.
210
+
211
+ **If §6 produced novel suggestions** (ones not matched by any dismissal rule) AND `{headless_mode}` is false, present them to the user:
203
212
 
204
213
  "**Content quality review: {score}%**
205
214
 
@@ -228,7 +237,7 @@ tessl suggestions (novel — not matched by `{tesslDismissalData}`):
228
237
  Cross-check metadata.json against extraction inventory:
229
238
  - `stats.exports_documented` / `stats.exports_public_api` / `stats.exports_internal` / `stats.exports_total` are accurate
230
239
  - `stats.public_api_coverage` and `stats.total_coverage` are correctly computed (null when denominator is 0)
231
- - `confidence_distribution.t1`, `confidence_distribution.t1_low`, `confidence_distribution.t2`, `confidence_distribution.t3` match actual counts
240
+ - `confidence_distribution.{t1, t1_low, t2, t3}` bin **per-export signature tiers** — each documented export lands in exactly one tier, so the four bins must **sum to `stats.exports_documented`**. Do NOT fold T2 annotation counts or T3 doc-item counts into the bins (a common error: binning ~8 annotations + ~80 doc items on top of 59 exports → sum 147 ≠ 59). `skf-test-skill` coverage-check §4b enforces this sum as an internal-consistency defect. Carve-outs mirror that consumer: for a **stack** skill the distribution bins constituents (sums to the constituent count, not `exports_documented`); for a **reference-app** it bins per-citation provenance entries (sums to the citation count, not `pattern_surfaces_documented`)
232
241
  - `spec_version` is "1.3"
233
242
  - If `scripts[]` or `assets[]` arrays present: verify `stats.scripts_count`/`stats.assets_count` match array lengths; verify `file_entries` count in provenance-map.json matches
234
243
 
@@ -62,9 +62,9 @@ Every HARD HALT in this workflow exits with a stable code so headless automators
62
62
  | Code | Meaning | Raised by |
63
63
  | ---- | -------------------- | -------------------------------------------------------------------------------------------- |
64
64
  | 0 | success | step 4 (terminal) |
65
- | 2 | input-missing / input-invalid | step 1 §4 (headless missing `skill_name` arg) → `input-missing`; non-existent skill / unparseable version → `input-invalid` |
65
+ | 2 | input-missing / input-invalid | step 1 §4 (headless missing `skill_name` arg) → `input-missing`; step 1 §4 (headless skill arg matches no listed skill) / step 1 §6 (headless version arg absent from the `versions` map) → `input-invalid` |
66
66
  | 3 | resolution-failure | step 1 §2 (manifest is malformed JSON); step 1 §3 (no skills found anywhere) |
67
- | 4 | write-failure | On-Activation §3 pre-flight write probe (skills_output_folder unwritable); step 2 §2 (manifest write); step 2 §3 (context-file rewrite); step 2 §4 (directory delete) |
67
+ | 4 | write-failure | On-Activation §3 pre-flight write probe (skills_output_folder unwritable); step 2 §2 (manifest write → `manifest-write-failed`); step 2 §3 (context-file rewrite → `context-rebuild-failed`); step 2 §4 (full purge failure — every target directory failed to delete → `delete-failed`) |
68
68
  | 5 | state-conflict | step 1 §7 (active-version-guard refuses to drop the active version while non-deprecated peers remain) |
69
69
  | 6 | user-cancelled | step 1 §10 confirmation gate `[N]`; any prompt that accepted `cancel`/`exit`/`:q`; On-Activation §4 (`headless-purge-forbidden`) |
70
70
 
@@ -152,13 +152,15 @@ Report: "**Rebuilt managed sections in:** {list of updated files}. {if any faile
152
152
 
153
153
  ### 4. Delete Files (Purge Mode Only)
154
154
 
155
- **If `drop_mode != "purge"`**, skip this section entirely. Set `files_deleted = []` and `disk_freed = "N/A (soft drop)"`, then jump to section 5.
155
+ **If `drop_mode != "purge"`**, skip this section entirely. Set `files_deleted = []`, `disk_freed = "N/A (soft drop)"`, `delete_failures = []`, and `purge_status = "success"`, then jump to section 5.
156
156
 
157
157
  **If `drop_mode == "purge"`:**
158
158
 
159
159
  1. Initialize `files_deleted = []` and `bytes_freed = 0`.
160
160
 
161
- 2. For each directory path in `affected_directories`:
161
+ 2. Also initialize `delete_failures = []` to track paths whose deletion was attempted but did not succeed.
162
+
163
+ 3. For each directory path in `affected_directories`:
162
164
  a. Verify the path is inside either `{skills_output_folder}` or `{forge_data_folder}` (defense in depth against accidental deletion of unrelated paths)
163
165
  b. If the directory does not exist, record it as "(already absent)" and continue
164
166
  c. Compute the directory size in bytes before deletion (recursive sum)
@@ -166,23 +168,29 @@ Report: "**Rebuilt managed sections in:** {list of updated files}. {if any faile
166
168
  e. Verify deletion succeeded (the path no longer exists)
167
169
  f. Append the path to `files_deleted` and add its byte size to `bytes_freed`
168
170
 
169
- 3. **Version-level purge, single version:**
171
+ 4. **Version-level purge, single version:**
170
172
  - `{skills_output_folder}/{target_skill}/{version}/` is deleted, but `{skills_output_folder}/{target_skill}/` remains (it still contains other versions or the `active` symlink)
171
173
  - If the `active` symlink pointed to the just-deleted version, update or remove it:
172
174
  - If other versions remain in the manifest for `{target_skill}`, repoint `active` to the manifest's current `active_version` (skipping deprecated)
173
175
  - If no non-deprecated versions remain, remove the `active` symlink (reachable only when dropping the sole surviving version, which in step 1 was permitted because no other non-deprecated versions existed)
174
176
 
175
- 4. **Skill-level purge:**
177
+ 5. **Skill-level purge:**
176
178
  - `{skills_output_folder}/{target_skill}/` and `{forge_data_folder}/{target_skill}/` are deleted in full — the `active` symlink disappears with the parent directory
177
179
 
178
- 5. Convert `bytes_freed` to a human-readable string for the final report (e.g. `"4.2 MB"`). Store as `disk_freed`.
180
+ 6. Convert `bytes_freed` to a human-readable string for the final report (e.g. `"4.2 MB"`). Store as `disk_freed`.
179
181
 
180
- **On deletion error:**
182
+ **On deletion error (per path):**
181
183
 
182
- - Record which path failed and the error message
183
- - Continue attempting the remaining paths — partial purge is still better than no purge
184
+ - Append the path and its error message to `delete_failures`
185
+ - Continue attempting the remaining paths — a partial purge is still better than no purge
184
186
  - Report all failures at the end of this section
185
187
 
188
+ **After the loop — classify the deletion outcome.** Let `attempted` be the number of paths in `affected_directories` that existed on disk (i.e. were not recorded as "(already absent)"):
189
+
190
+ - **Full purge failure** — `attempted > 0` AND every attempted path is in `delete_failures` (nothing was deleted): the purge accomplished none of its destructive intent, so it must NOT report success. HALT (exit code 4, `halt_reason: "delete-failed"`): "**Purge failed** — none of the {attempted} target director(ies) could be deleted: {list each path with its error}. The manifest and context files were already updated in sections 2–3; the on-disk files remain and can be removed manually (`rm -rf {path}`)." In headless mode, emit the error envelope per SKILL.md "Result Contract (Headless)" with the resolved `skill`, `drop_mode`, `versions_affected`, `files_deleted: []`, and `manifest_updated` from section 2. Do not proceed to section 5.
191
+ - **Partial purge failure** — `delete_failures` is non-empty but at least one path was deleted: keep record-and-continue. Set `purge_status = "partial"` so step 3's on-disk result record reflects it (the `output-contract-schema.md` `status` enum supports `"partial"`); proceed to section 5. The headless single-line envelope has no `"partial"` value in its enum, so it stays `"success"` while `context_files_failed`/`verification_errors`/the report surface the unfreed paths.
192
+ - **No failures** — `delete_failures` is empty: set `purge_status = "success"` and proceed to section 5.
193
+
186
194
  ### 5. Verify Final State
187
195
 
188
196
  Run these verification checks:
@@ -203,6 +211,8 @@ Store the following for step 3:
203
211
 
204
212
  - `files_deleted` — list of directory paths actually deleted (purge mode) or `[]` (soft drop)
205
213
  - `disk_freed` — human-readable size (purge mode) or `"N/A (soft drop)"`
214
+ - `delete_failures` — list of `{path, error}` for paths whose deletion was attempted but failed (empty if none; a *full* purge failure already HALTed in section 4 and never reaches this step)
215
+ - `purge_status` — `"success"`, `"partial"` (some paths failed to delete), or `"success"` for soft drops; step 3 maps this to the on-disk result record's `status` field
206
216
  - `manifest_updated` — boolean (true if section 2 succeeded)
207
217
  - `context_files_updated` — list of successfully rebuilt files
208
218
  - `context_files_failed` — list of files that failed to rebuild (empty if none)
@@ -220,7 +230,8 @@ If any stage fails, record which stage failed and provide recovery guidance in t
220
230
  |--------------|-------------------|
221
231
  | Manifest update | "Manifest is in pre-drop state. Re-run the workflow once the underlying I/O issue is resolved. No files were deleted." |
222
232
  | Context file rebuild | "Manifest is already updated. Re-run `[EX] Export Skill` against any still-valid skill to regenerate the affected managed sections, or rerun the drop workflow." |
223
- | File deletion (purge) | "Manifest and context files are consistent. Remaining directories listed in the report can be deleted manually: `rm -rf {path}`." |
233
+ | File deletion (purge — partial) | "Manifest and context files are consistent. Remaining directories listed in the report can be deleted manually: `rm -rf {path}`." (status `partial`) |
234
+ | File deletion (purge — full) | "No target directory could be deleted. Manifest and context files are already updated; the on-disk files remain. HALT with `delete-failed` (exit 4) — re-run once the I/O issue is resolved, or remove the listed directories manually." |
224
235
  | Verification | "Execution completed but post-write checks found drift. See the report for specific paths requiring manual review." |
225
236
 
226
237
  ## CRITICAL STEP COMPLETION NOTE
@@ -73,6 +73,8 @@ These require manual review — see the error-handling guidance in step 2.
73
73
 
74
74
  Write the result contract per `shared/references/output-contract-schema.md`: the per-run record at `{skills_output_folder}/drop-skill-result-{YYYYMMDD-HHmmss}.json` (UTC timestamp, resolution to seconds) and a copy at `{skills_output_folder}/drop-skill-result-latest.json` (stable path for pipeline consumers — copy, not symlink). Include all purged file paths in `outputs`; include `target_skill`, `drop_mode`, and `versions_affected` in `summary`.
75
75
 
76
+ Set the record's `status` from step 2's `purge_status`: `"partial"` when some (but not all) purge-mode directories failed to delete — surface the failing paths from `delete_failures` in `summary.delete_failures` — otherwise `"success"`. A *full* purge failure never reaches this step: step 2 §4 HALTs with `halt_reason: "delete-failed"` and the error-envelope path below handles it.
77
+
76
78
  When `{headless_mode}` is true, also emit the single-line envelope on **stdout** before chaining to step 4 (matches the SKILL.md "Result Contract (Headless)" shape):
77
79
 
78
80
  ```
@@ -78,7 +78,9 @@ Enter the skill name or its number from the list above, or `cancel` / `exit` / `
78
78
  Wait for user input. Accept either the numeric index or the skill name (exact match). **GATE [default: use args]** — If `{headless_mode}` and skill name was provided as argument: select that skill and auto-proceed. If not provided, HALT (exit code 2, `halt_reason: "input-missing"`): "headless mode requires skill name argument." In headless mode, emit the error envelope with `skill: null`, `drop_mode: null`.
79
79
 
80
80
  - If the user enters `cancel`, `exit`, `[X]`, `q`, or `:q`: Display "Cancelled — no changes were made." and HALT (exit code 6, `halt_reason: "user-cancelled"`).
81
- - **If the user's input does not match any listed skill:** Re-display the list and ask again.
81
+ - **If the user's input does not match any listed skill:**
82
+ - **Interactive:** Re-display the list and ask again.
83
+ - **Headless (`{headless_mode}` is true):** the supplied `skill_name` argument resolves to no skill in the combined list — there is no further input to re-prompt for. HALT (exit code 2, `halt_reason: "input-invalid"`): "headless mode: skill argument `{supplied value}` does not match any listed skill." Emit the error envelope per SKILL.md "Result Contract (Headless)" with `skill: null`, `drop_mode: null`, `versions_affected: []`.
82
84
 
83
85
  Store the selection as `target_skill`. Also store `target_in_manifest = true` if the selected skill has an entry in the manifest, `false` otherwise — subsequent sections use this flag to restrict the available drop options.
84
86
 
@@ -126,7 +128,10 @@ Wait for user selection.
126
128
 
127
129
  "**Which version?** Enter the version string (e.g. `0.5.0`)."
128
130
 
129
- Wait for user input. Validate that the version exists in the manifest's `versions` map for `target_skill`. If not, repeat the prompt.
131
+ Wait for user input. Validate that the version exists in the manifest's `versions` map for `target_skill`.
132
+
133
+ - **If it does not match (interactive):** repeat the prompt.
134
+ - **If it does not match (headless — `{headless_mode}` is true):** the supplied `version` argument is unparseable or absent from the `versions` map — there is no further input to re-prompt for. HALT (exit code 2, `halt_reason: "input-invalid"`): "headless mode: version argument `{supplied value}` does not exist in `{target_skill}`'s versions." Emit the error envelope per SKILL.md "Result Contract (Headless)" with `skill: "{target_skill}"`, `drop_mode: null`, `versions_affected: []`.
130
135
 
131
136
  Set `target_versions = [<selected version>]` and `is_skill_level = false`.
132
137
 
@@ -43,8 +43,9 @@ Structured reports with inline AST citations during work — no metaphor, no com
43
43
  | 12 | EX | Package for distribution and inject context into CLAUDE.md/AGENTS.md/.cursorrules | skf-export-skill |
44
44
  | 13 | RS | Rename a skill across all its versions (transactional) | skf-rename-skill |
45
45
  | 14 | DS | Drop a skill — deprecate (soft) or purge (hard) | skf-drop-skill |
46
- | 15 | KI | List available knowledge fragments | (inline action) |
47
- | 16 | WS | Show current lifecycle position and forge tier status | (inline action) |
46
+ | 15 | | Orchestrate multi-library skill campaigns with dependency tracking | skf-campaign |
47
+ | 16 | KI | List available knowledge fragments | (inline action) |
48
+ | 17 | WS | Show current lifecycle position and forge tier status | (inline action) |
48
49
 
49
50
  Say "dismiss" or "exit persona" to leave Ferris at any time.
50
51
 
@@ -84,17 +85,21 @@ When the user provides multiple workflow codes (e.g., `BS CS TS EX`, `QS TS EX`,
84
85
 
85
86
  **Pipeline activation:**
86
87
 
87
- 1. **Parse the sequence** — split codes, expand aliases (`deepwiki` → `AN[auto] BS[auto] CS TS[min:90] EX`, `forge` → `BS CS TS EX`, `forge-quick` → `QS TS EX`, `onboard` → `AN CS TS EX`, `maintain` → `AS US TS EX`), extract any bracket arguments (`CS[cocoindex]`, `TS[min:80]`)
88
- **Alias lifecycle:** When the parsed alias is `onboard`, emit a deprecation notice **before** expanding the alias and **before** pipeline progress reporting begins. Emit on **every** invocation — this is not a one-time warning. After emitting the notice, expand `onboard` → `AN CS TS EX` as normal (no behavioral change — existing 80% default threshold applies):
88
+ 1. **Parse the sequence** — split codes, expand aliases (`forge-auto` → `AN[auto] BS[auto] CS TS[min:90] EX`, `forge` → `BS CS TS EX`, `forge-quick` → `QS TS EX`, `maintain` → `AS US TS EX`), extract any bracket arguments (`CS[cocoindex]`, `TS[min:80]`)
89
+ **Deprecated aliases:** If the parsed alias is `deepwiki`, expand it exactly as `forge-auto` and set `{pipeline_alias}` to `forge-auto`, but first emit a one-time notice:
89
90
 
90
- > ⚠️ **onboard is deprecated use deepwiki instead.** The onboard pipeline (AN CS TS EX at 80% threshold) still runs, but will be removed in v2.0. To migrate: replace `onboard` with `deepwiki <repo-url>`. deepwiki adds auto-brief generation (BS[auto]) and a 90% quality threshold.
91
+ > ⚠️ **`deepwiki` is now `forge-auto`.** The alias was renamed to avoid confusion with the DeepWiki MCP this pipeline auto-forges a verified skill from source and does **not** call that MCP. `deepwiki` still works as a deprecated alias; prefer `forge-auto <repo-url>` going forward.
92
+
93
+ **Removed aliases:** If the parsed alias is `onboard`, do NOT expand it. Instead, HALT with:
94
+
95
+ > 🚫 **onboard has been removed.** Use `forge-auto <repo-url>` instead. forge-auto auto-scopes, auto-briefs, and tests at 90% quality. Run `forge-auto` with any GitHub URL, doc URL, or `--pin <version>`.
91
96
 
92
97
  2. **Validate the sequence** — check for anti-patterns (EX before TS, CS without BS, duplicates). If found, warn the user and ask to confirm or adjust. In `{headless_mode}`, warn but proceed.
93
98
  3. **Set `{headless_mode}` = true** — pipelines auto-activate headless mode for all workflows in the chain. The user committed to the sequence by providing it.
94
99
  4. **Execute left to right** — for each workflow in the sequence:
95
100
  - a. **Report start**: "Pipeline [{current}/{total}]: Starting {code} ({description})..."
96
101
  - b. **Resolve inputs** from the previous workflow's output using the Data Flow table in pipeline-contracts.md. If the previous workflow produced a `skill_name`, `brief_path`, or other handoff data, pass it as the input argument.
97
- - c. **Invoke the workflow** with `{headless_mode}` = true, `{pipeline_alias}` set to the alias name (`deepwiki`, `forge`, `forge-quick`, `onboard`, `maintain`, or `null` for ad-hoc sequences), and any resolved arguments.
102
+ - c. **Invoke the workflow** with `{headless_mode}` = true, `{pipeline_alias}` set to the alias name (`forge-auto`, `forge`, `forge-quick`, `maintain`, or `null` for ad-hoc sequences; a `deepwiki` invocation resolves to `forge-auto`), and any resolved arguments.
98
103
  - d. **Check circuit breaker** after completion. Load the output artifact and validate against the threshold (default or user-specified via `[min:N]`). If the check fails: halt the pipeline, report what completed and what remains.
99
104
  - e. **Report completion**: "Pipeline [{current}/{total}]: {code} complete — {brief summary of output}."
100
105
  5. **Pipeline summary** — after all workflows complete (or on halt), present a summary:
@@ -104,7 +109,7 @@ When the user provides multiple workflow codes (e.g., `BS CS TS EX`, `QS TS EX`,
104
109
  - Next steps recommendation
105
110
  6. **Result Contract** — write the pipeline result contract per `shared/references/output-contract-schema.md`: the per-run record at `{sidecar_path}/pipeline-result-{YYYYMMDD-HHmmss}.json` (UTC timestamp, resolution to seconds) and a copy at `{sidecar_path}/pipeline-result-latest.json` (stable path for pipeline consumers — copy, not symlink). Include one entry per completed workflow in `outputs` (referencing each workflow's own `-latest.json` result record); include per-step status and the overall pipeline status in `summary`.
106
111
 
107
- **deepwiki pipeline note:** `deepwiki <repo-url> --pin <version>` — the `--pin` argument is passed to AN's pipeline data context alongside the `[auto]` flag. AN's `step-auto-scope.md §0b` consumes it for pin resolution.
112
+ **forge-auto pipeline note:** `forge-auto <repo-url> --pin <version>` — the `--pin` argument is passed to AN's pipeline data context alongside the `[auto]` flag. AN's `step-auto-scope.md §0b` consumes it for pin resolution.
108
113
 
109
114
  **Special pipeline behaviors:**
110
115
  - `AN` in a pipeline with `CS`: if AN produces multiple recommended briefs, auto-select all and process sequentially in batch mode. If only one unit found, auto-select it.
@@ -68,6 +68,7 @@ Every HARD HALT in this workflow exits with a stable code so headless automators
68
68
  | 5 | state-conflict | step 1 §3 (no skills found — refinement requires ≥1 skill) |
69
69
  | 6 | user-cancelled | step 1 §1 prompt cancelled; any prompt that accepted `cancel`/`exit`/`:q`; step 5 review gate `[X]` |
70
70
  | 7 | inventory-unreliable | step 1 §2 (>20% skill-inventory warnings exceed budget) |
71
+ | 8 | recovery-failed | step 5 §1 (durability state insufficient to reconstruct Step 02-04 findings); step 6 §1 (`## Refinement Summary` absent from the compiled document) |
71
72
 
72
73
  ## Result Contract (Headless)
73
74
 
@@ -77,7 +78,7 @@ When `{headless_mode}` is true, step 6 emits a single-line JSON envelope on **st
77
78
  SKF_REFINE_ARCHITECTURE_RESULT_JSON: {"status":"success|error","refined_path":"…|null","gap_count":0,"issue_count":0,"improvement_count":0,"exit_code":0,"halt_reason":null}
78
79
  ```
79
80
 
80
- `status` is `"success"` on the terminal happy path, `"error"` on any HALT. `halt_reason` is one of: `null` (success), `"input-missing"`, `"input-invalid"`, `"insufficient-skills"`, `"output-folder-unconfigured"`, `"forge-folder-unconfigured"`, `"inventory-unreliable"`, `"write-failed"`, `"user-cancelled"`. `exit_code` matches the table above.
81
+ `status` is `"success"` on the terminal happy path, `"error"` on any HALT. `halt_reason` is one of: `null` (success), `"input-missing"`, `"input-invalid"`, `"insufficient-skills"`, `"output-folder-unconfigured"`, `"forge-folder-unconfigured"`, `"inventory-unreliable"`, `"write-failed"`, `"recovery-failed"`, `"user-cancelled"`. `exit_code` matches the table above.
81
82
 
82
83
  ## On Activation
83
84
 
@@ -25,7 +25,7 @@ Load the complete original architecture document.
25
25
 
26
26
  This is the base. Every line of the original MUST appear in the refined document, unmodified.
27
27
 
28
- **Context recovery check:** If gap, issue, or improvement findings from Steps 02-04 are not available in context (e.g., due to context degradation in long runs), attempt to read the durability state from `{forge_data_folder}/ra-state-{project_name}.md`. Parse the `<!-- [RA-GAPS] -->`, `<!-- [RA-ISSUES] -->`, and `<!-- [RA-IMPROVEMENTS] -->` comment blocks to recover the complete formatted findings (each block contains full citation text with evidence, not just counts). If a section is still missing or contains only summary counts after recovery, HALT: "⚠️ Context for the [Gaps|Issues|Improvements] analysis was lost and the durability state is insufficient to reconstruct findings. Re-run [RA] from the beginning — step 01 will reset the state file, then steps 02-04 will rebuild all findings."
28
+ **Context recovery check:** If gap, issue, or improvement findings from Steps 02-04 are not available in context (e.g., due to context degradation in long runs), attempt to read the durability state from `{forge_data_folder}/ra-state-{project_name}.md`. Parse the `<!-- [RA-GAPS] -->`, `<!-- [RA-ISSUES] -->`, and `<!-- [RA-IMPROVEMENTS] -->` comment blocks to recover the complete formatted findings (each block contains full citation text with evidence, not just counts). If a section is still missing or contains only summary counts after recovery, HALT (exit code 8, `halt_reason: "recovery-failed"`): "⚠️ Context for the [Gaps|Issues|Improvements] analysis was lost and the durability state is insufficient to reconstruct findings. Re-run [RA] from the beginning — step 01 will reset the state file, then steps 02-04 will rebuild all findings." In headless, emit the error envelope per SKILL.md "Result Contract (Headless)" with `refined_path: null`.
29
29
 
30
30
  ### 2. Insert Gap-Fill Subsections
31
31
 
@@ -26,7 +26,7 @@ Present the complete refinement summary to the user. Display counts of gaps fill
26
26
 
27
27
  Read the `{outputFile}` to have all data available for presentation.
28
28
 
29
- Verify the `## Refinement Summary` section is present. If it is absent, HALT: "⚠️ Refinement Summary not found in `{outputFile}`. Step 05 may not have completed successfully. Re-run [RA] from the beginning."
29
+ Verify the `## Refinement Summary` section is present. If it is absent, HALT (exit code 8, `halt_reason: "recovery-failed"`): "⚠️ Refinement Summary not found in `{outputFile}`. Step 05 may not have completed successfully. Re-run [RA] from the beginning." In headless, emit the error envelope per SKILL.md "Result Contract (Headless)" with `refined_path: null`.
30
30
 
31
31
  **Extract metrics from the Refinement Summary section:** Parse `gap_count`, `issue_count`, `improvement_count`, `critical_count`, `major_count`, `minor_count`, `high_count`, `medium_count`, `low_count`, and `skill_count` from the Changes Made table and Evidence Sources table. Use these extracted values in the summary table and next-steps sections below.
32
32
 
@@ -66,7 +66,7 @@ Every HARD HALT in this workflow exits with a stable code so headless automators
66
66
  | 0 | success | step 4 (terminal) |
67
67
  | 2 | input-missing / input-invalid | step 1 §4/§5 (headless missing `old_name`/`new_name` arg) → `input-missing`; new name fails kebab-case / length / same-as-old → `input-invalid` |
68
68
  | 3 | resolution-failure | step 1 §2 (manifest is malformed JSON); step 1 §3 (no skills found anywhere) |
69
- | 4 | write-failure | On-Activation §3 pre-flight write probe (skills_output_folder / forge_data_folder unwritable); step 2 §1 (copy); step 2 §3 (file content update); step 2 §6 (manifest write); step 2 §7 (context-file rewrite) |
69
+ | 4 | write-failure | On-Activation §3 pre-flight write probe (skills_output_folder / forge_data_folder unwritable); step 2 §1 (copy → `copy-failed`); step 2 §2 (inner-dir rename) / §3 (file content update) / §4 (symlink repair) → `write-failed`; step 2 §6 (manifest write → `manifest-write-failed`). step 2 §7 (context-file rewrite) is **best-effort and never halts** — per-file failures are recorded in `context_files_failed` and the rename still succeeds (manifest + disk are canonical; re-run `[EX] Export Skill` to retry). |
70
70
  | 5 | state-conflict | step 1 §5 (name-collision check fails: target name already in use); step 1 §6 (source-authority="official" headless without `force_source_authority_in_headless`); concurrency lock collision (`halted-for-concurrent-run`) |
71
71
  | 6 | user-cancelled | step 1 §6 (source-authority warning [N]); step 1 §8 confirmation gate `[N]`; any prompt that accepted `cancel`/`exit`/`:q` |
72
72
 
@@ -78,7 +78,7 @@ When `{headless_mode}` is true, step 3 emits a single-line JSON envelope on **st
78
78
  SKF_RENAME_SKILL_RESULT_JSON: {"status":"success|error|dry-run","old_name":"…|null","new_name":"…|null","versions_renamed":[],"manifest_rekeyed":false,"context_files_updated":[],"exit_code":0,"halt_reason":null}
79
79
  ```
80
80
 
81
- `status` is `"success"` on the terminal happy path, `"dry-run"` when `--dry-run` was set and the workflow exited before §9 stores decisions, `"error"` on any HALT. `halt_reason` is one of: `null` (success), `"input-missing"`, `"input-invalid"`, `"manifest-corrupt"`, `"nothing-to-rename"`, `"name-collision"`, `"source-authority-blocked"`, `"halted-for-concurrent-run"`, `"copy-failed"`, `"verify-failed"`, `"manifest-write-failed"`, `"context-rebuild-failed"`, `"write-failed"`, `"user-cancelled"`. `exit_code` matches the table above.
81
+ `status` is `"success"` on the terminal happy path, `"dry-run"` when `--dry-run` was set and the workflow exited before §9 stores decisions, `"error"` on any HALT. `halt_reason` is one of: `null` (success), `"input-missing"`, `"input-invalid"`, `"manifest-corrupt"`, `"nothing-to-rename"`, `"name-collision"`, `"source-authority-blocked"`, `"halted-for-concurrent-run"`, `"copy-failed"`, `"verify-failed"`, `"manifest-write-failed"`, `"write-failed"`, `"user-cancelled"`. (§7 context-file rebuild is best-effort and never halts, so it has no `halt_reason`.) `exit_code` matches the table above.
82
82
 
83
83
  ## On Activation
84
84
 
@@ -70,15 +70,15 @@ If any helper has no existing candidate, release the lock and HALT (exit code 4,
70
70
 
71
71
  **Precondition:** Both `{new_skill_group}` and `{new_forge_group}` must NOT exist (step 1 validated this in the collision check, but verify again before copying).
72
72
 
73
- 1. If `{new_skill_group}` or `{new_forge_group}` exists on disk, halt with: "**Collision detected at execution time.** `{new_skill_group}` or `{new_forge_group}` now exists on disk — it did not exist during step 1 selection. Aborting before any files are touched."
73
+ 1. If `{new_skill_group}` or `{new_forge_group}` exists on disk: release the lock (`rm -f "{forge_data_folder}/{old_name}/.skf-rename.lock"`) and halt with "**Collision detected at execution time.** `{new_skill_group}` or `{new_forge_group}` now exists on disk — it did not exist during step 1 selection. Aborting before any files are touched." HALT (exit code 4, `halt_reason: "copy-failed"`). In headless, emit the error envelope.
74
74
 
75
75
  2. Copy `{old_skill_group}` to `{new_skill_group}` recursively:
76
76
  - Preserve file permissions, timestamps, and symlinks
77
77
  - Equivalent to `cp -a {old_skill_group} {new_skill_group}` (preserves symlinks) or `cp -r` followed by explicit symlink re-creation in section 4
78
- - If the copy fails: halt with "**Copy failed:** `{old_skill_group}` → `{new_skill_group}`: {error}. No files were modified. Old skill is intact."
78
+ - If the copy fails: release the lock (`rm -f "{forge_data_folder}/{old_name}/.skf-rename.lock"`) and halt with "**Copy failed:** `{old_skill_group}` → `{new_skill_group}`: {error}. No files were modified. Old skill is intact." HALT (exit code 4, `halt_reason: "copy-failed"`). In headless, emit the error envelope.
79
79
 
80
80
  3. Copy `{old_forge_group}` to `{new_forge_group}` the same way:
81
- - If the copy fails: **rollback** by deleting `{new_skill_group}` (just created in step 2), then halt with "**Copy failed:** `{old_forge_group}` → `{new_forge_group}`: {error}. Rolled back new skill_group. Old skill is intact."
81
+ - If the copy fails: **rollback** by deleting `{new_skill_group}` (just created in step 2), release the lock (`rm -f "{forge_data_folder}/{old_name}/.skf-rename.lock"`), then halt with "**Copy failed:** `{old_forge_group}` → `{new_forge_group}`: {error}. Rolled back new skill_group. Old skill is intact." HALT (exit code 4, `halt_reason: "copy-failed"`). In headless, emit the error envelope.
82
82
 
83
83
  **Rollback procedure for this section:** `rm -rf {new_skill_group}` and `rm -rf {new_forge_group}` (whichever exist). Old skill is untouched.
84
84
 
@@ -96,7 +96,8 @@ For each version `v` in `affected_versions`:
96
96
  **Rollback on any rename failure:**
97
97
 
98
98
  - `rm -rf {new_skill_group}` and `rm -rf {new_forge_group}`
99
- - Halt with: "**Inner directory rename failed** at `{v}/{old_name}`: {error}. Rolled back both new directories. Old skill is intact."
99
+ - Release the lock: `rm -f "{forge_data_folder}/{old_name}/.skf-rename.lock"`
100
+ - Halt with: "**Inner directory rename failed** at `{v}/{old_name}`: {error}. Rolled back both new directories. Old skill is intact." HALT (exit code 4, `halt_reason: "write-failed"`). In headless, emit the error envelope.
100
101
 
101
102
  Report: "**Renamed {count} inner directories** to `{new_name}/`."
102
103
 
@@ -22,9 +22,9 @@ Loaded by `execute.md` §7 once `manifest_updated == true` (§6 succeeded) and t
22
22
  Load the `ides` list from `config.yaml`. The installer writes IDE identifiers — these must be mapped to context files and skill roots using the "IDE → Context File Mapping" table in `{managedSectionLogic}`:
23
23
 
24
24
  1. For each entry in `config.yaml.ides`, look up its `context_file` and `skill_root` from the mapping table.
25
- 2. For any entry not found in the table, default to AGENTS.md / `.agents/skills/` and emit a warning: `Unknown IDE '{value}' in config.yaml — defaulting to AGENTS.md`.
25
+ 2. For any entry not found in the table, default to `{unknownIdeDefaultContextFile}` / `{unknownIdeDefaultSkillRoot}` (resolved by SKILL.md On-Activation §3 from `customize.toml`; bundled defaults `AGENTS.md` / `.agents/skills/`) and emit a warning: `Unknown IDE '{value}' in config.yaml — defaulting to {unknownIdeDefaultContextFile}`.
26
26
  3. Deduplicate by `context_file` — when multiple IDEs map to the same context file, use the first configured IDE's `skill_root`.
27
- 4. If `config.yaml.ides` is absent or the mapping yields an empty list, fall back to `[{context_file: "AGENTS.md", skill_root: ".agents/skills/"}]` and emit a note: `No IDEs configured in config.yaml — defaulting to AGENTS.md`.
27
+ 4. If `config.yaml.ides` is absent or the mapping yields an empty list, fall back to `[{context_file: "{unknownIdeDefaultContextFile}", skill_root: "{unknownIdeDefaultSkillRoot}"}]` and emit a note: `No IDEs configured in config.yaml — defaulting to {unknownIdeDefaultContextFile}`.
28
28
 
29
29
  Store the result as `target_context_files`.
30
30
 
@@ -124,15 +124,15 @@ Apply the following validations in order:
124
124
 
125
125
  1. **Kebab-case format:** Must match `^[a-z][a-z0-9-]*[a-z0-9]$` (or `^[a-z0-9]$` for the single-character case). If it fails:
126
126
  "**Invalid name format.** The new name must be lowercase alphanumeric with hyphens, starting with a letter and ending with a letter or digit. Try again."
127
- Re-ask.
127
+ Re-ask. In headless mode, instead of re-asking, release the lock (`rm -f {forge_data_folder}/{old_name}/.skf-rename.lock`) and HALT (exit code 2, `halt_reason: "input-invalid"`) and emit the error envelope.
128
128
 
129
129
  2. **Length:** Must be 1-64 characters per the agentskills.io spec. If it fails:
130
130
  "**Invalid name length.** The new name must be 1-64 characters. Try again."
131
- Re-ask.
131
+ Re-ask. In headless mode, instead of re-asking, release the lock (`rm -f {forge_data_folder}/{old_name}/.skf-rename.lock`) and HALT (exit code 2, `halt_reason: "input-invalid"`) and emit the error envelope.
132
132
 
133
133
  3. **Same as old name:** If the new name equals `old_name`:
134
134
  "**The new name is identical to the current name.** Nothing to rename. Try again or abort the workflow."
135
- Re-ask.
135
+ Re-ask. In headless mode, instead of re-asking, release the lock (`rm -f {forge_data_folder}/{old_name}/.skf-rename.lock`) and HALT (exit code 2, `halt_reason: "input-invalid"`) and emit the error envelope.
136
136
 
137
137
  4. **Collision check:** The new name MUST NOT collide with any existing skill:
138
138
  - It must not appear as a key in `exports` in the manifest
@@ -46,7 +46,7 @@ All stages auto-proceed except the optional orphan-removal gate in step 3.
46
46
  | Aspect | Detail |
47
47
  |--------|--------|
48
48
  | **Inputs** | (none) |
49
- | **Flags** | `--headless` / `-H` (skip prompts, auto-resolve gates to defaults); `--require-tier=<Quick\|Forge\|Forge+\|Deep>` (halt with failure if calculated tier does not satisfy the requirement); `--orphan-action=<keep\|remove>` (resolve the orphan-removal gate non-interactively, even outside `--headless`); `--ccc-skip-index` (skip CCC indexing; envelope `ccc_index.status` becomes `"skipped"`); `--quiet` (suppress the human-readable FORGE STATUS banner — pipelines and expert re-runners get the envelope only) |
49
+ | **Flags** | `--headless` / `-H` (skip prompts, auto-resolve gates to defaults); `--require-tier=<Quick\|Forge\|Forge+\|Deep>` (halt with failure if calculated tier does not satisfy the requirement); `--orphan-action=<keep\|remove>` (resolve the orphan-removal gate non-interactively, even outside `--headless`); `--ccc-skip-index` (skip CCC indexing; envelope `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); `--quiet` (suppress the human-readable FORGE STATUS banner — pipelines and expert re-runners get the envelope only) |
50
50
  | **Gates** | One optional: orphaned QMD collection removal (step 3, Deep tier only; default: Keep, or whatever `--orphan-action` set) |
51
51
  | **Outputs** | `forger-sidecar/forge-tier.yaml`, `forger-sidecar/preferences.yaml`, `{forge_data_folder}/`; when ccc is available, `.cocoindex_code/settings.yml` (exclusion patterns merged) and the project ccc index |
52
52
  | **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true. Under `--headless` or `--quiet`, step 4 emits a single-line `SKF_SETUP_RESULT_JSON: {…}` envelope as the only stdout line — the FORGE STATUS banner is suppressed. Branch on the envelope's top-level `status` field (one of `success`, `tier_failure`, `write_failure`, `blocked`) — `skf-emit-result-envelope.py` derives it from the payload, so pipelines never compose it from `require_tier_satisfied` + `error`. Schema in `references/report.md` §4. |
@@ -37,6 +37,8 @@ For Quick and Forge tiers, skip silently and proceed (QMD is not available; ccc
37
37
 
38
38
  Read `{calculated_tier}` and `{ccc}` from context (set by step 1).
39
39
 
40
+ Default `{orphan_auto_resolution}` to null at the top of this step; only the non-interactive branch in section 3 overrides it.
41
+
40
42
  **If `{calculated_tier}` is Quick or Forge AND `{ccc}` is false:** No registry hygiene needed. Set `{hygiene_result: "skipped", hygiene_healthy: 0, hygiene_orphaned_removed: 0, hygiene_orphaned_kept: 0, hygiene_stale_cleaned: 0, ccc_registry_stale_cleaned: 0, ccc_registry_stale_removed_paths: []}`. Proceed directly to section 5 (Auto-Proceed) — no output, no messaging.
41
43
 
42
44
  **If `{calculated_tier}` is Quick or Forge AND `{ccc}` is true:** No QMD work, but ccc registry needs pruning. Set QMD-related flags to defaults (`hygiene_result: "skipped"`, all hygiene_* counts = 0). Skip directly to section 4 (Stale Registry Cleanup), running it with the ccc-prune flag only.
@@ -72,7 +74,7 @@ Set `{hygiene_result: "completed"}`.
72
74
 
73
75
  **If `{orphaned_collections}` is empty:** Set `{hygiene_orphaned_removed: 0, hygiene_orphaned_kept: 0}` and skip to section 4.
74
76
 
75
- **Non-interactive resolution.** If `{orphan_action}` is non-null, resolve the gate without prompting using that value: log `"Auto-decision (--orphan-action={value}): kept|removed {len(orphaned_collections)} orphaned forge collection(s)"`. If `{orphan_action}` is `"keep"`, set `{hygiene_orphaned_removed: 0, hygiene_orphaned_kept: len(orphaned_collections)}` and skip to section 4. If `"remove"`, fall through to the removal block below (still no user prompt). Independently, if `{headless_mode}` is true and `{orphan_action}` is null, auto-resolve to the default **Keep** with the equivalent log line and skip to section 4. The two paths compose: `--orphan-action` overrides the headless default; `--headless` without `--orphan-action` keeps backward-compatible behavior.
77
+ **Non-interactive resolution.** If `{orphan_action}` is non-null, resolve the gate without prompting using that value: log `"Auto-decision (--orphan-action={value}): kept|removed {len(orphaned_collections)} orphaned forge collection(s)"` and set `{orphan_auto_resolution: {action: "{orphan_action}", count: len(orphaned_collections), source: "orphan-action-flag"}}` so step 4 can fold it into the envelope warnings (the audit trail matters most when `remove` deletes collections headlessly). If `{orphan_action}` is `"keep"`, set `{hygiene_orphaned_removed: 0, hygiene_orphaned_kept: len(orphaned_collections)}` and skip to section 4. If `"remove"`, fall through to the removal block below (still no user prompt). Independently, if `{headless_mode}` is true and `{orphan_action}` is null, auto-resolve to the default **Keep** with the equivalent log line, set `{orphan_auto_resolution: {action: "keep", count: len(orphaned_collections), source: "headless-default"}}`, and skip to section 4. The two paths compose: `--orphan-action` overrides the headless default; `--headless` without `--orphan-action` keeps backward-compatible behavior. (On an interactive resolution — section 3's prompt below — leave `{orphan_auto_resolution}` null; the human chose, so there is no auto-decision to audit.)
76
78
 
77
79
  **If `{headless_mode}` is false**, display to the user:
78
80
 
@@ -48,6 +48,10 @@ Prior state lives in two files; the detector helper (§2) reads forge-tier.yaml
48
48
 
49
49
  Press Esc or Ctrl+C now if this isn't the right project — no files have been written yet."
50
50
 
51
+ **Re-run notice** — when the prior-state read (§2 below, `prior.previous_tier`) returns a non-null value AND `{headless_mode}` is `false`, display this notice instead of the first-run preamble, before continuing, so a user who re-ran in the wrong/sibling repo can abort before any config rewrite or re-index. (When `{headless_mode}` is true, skip it — pipelines re-run intentionally.)
52
+
53
+ "**Forge already set up here:** {previous_tier} tier, detected {previous_detection_date}. Re-running re-probes the tools and refreshes config/index in this project. To refresh the tier without paying the ccc re-index cost, re-run with `--ccc-skip-index`. Press Esc or Ctrl+C now if this isn't the project you meant — nothing has been rewritten yet."
54
+
51
55
  ### 2. Run Detection Helper
52
56
 
53
57
  Build the Bash invocation: `uv run {detectToolsHelper} --prior-state-from "{project-root}/_bmad/_memory/forger-sidecar/forge-tier.yaml"`. If `{tier_override}` is non-null, append `--tier-override "{tier_override}"`. If `{require_tier}` is non-null, append `--require-tier "{require_tier}"`. Then execute. (The script's PEP 723 metadata is why `uv run` is required — bare `python3` skips dependency resolution.)