bmad-module-skill-forge 1.8.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 (136) 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 +42 -3
  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 +9 -3
  16. package/docs/why-skf.md +1 -1
  17. package/docs/workflows.md +73 -27
  18. package/package.json +2 -2
  19. package/src/module-help.csv +2 -1
  20. package/src/shared/_known-workarounds.yaml +155 -0
  21. package/src/shared/data/language-corpora.json +32 -0
  22. package/src/shared/references/pipeline-contracts.md +14 -2
  23. package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +6 -1
  24. package/src/shared/scripts/schemas/skill-brief.v1.json +6 -1
  25. package/src/shared/scripts/skf-derive-assembly-shape.py +107 -0
  26. package/src/shared/scripts/skf-detect-docs.py +417 -0
  27. package/src/shared/scripts/skf-detect-language.py +5 -3
  28. package/src/shared/scripts/skf-emit-brief-result-envelope.py +12 -1
  29. package/src/shared/scripts/skf-emit-result-envelope.py +17 -0
  30. package/src/shared/scripts/skf-extract-public-api.py +53 -0
  31. package/src/shared/scripts/skf-language-corpora.py +100 -0
  32. package/src/shared/scripts/skf-merge-doc-urls.py +244 -0
  33. package/src/shared/scripts/skf-preapply.py +192 -0
  34. package/src/shared/scripts/skf-shape-detect.py +1114 -0
  35. package/src/shared/scripts/skf-validate-brief-schema.py +2 -5
  36. package/src/shared/scripts/skf-validate-pins.py +368 -0
  37. package/src/shared/scripts/skf-write-skill-brief.py +21 -5
  38. package/src/skf-analyze-source/SKILL.md +26 -20
  39. package/src/skf-analyze-source/assets/skill-brief-schema.md +1 -1
  40. package/src/skf-analyze-source/references/init.md +30 -0
  41. package/src/skf-analyze-source/references/step-auto-scope.md +677 -0
  42. package/src/skf-analyze-source/references/step-shape-detect.md +86 -0
  43. package/src/skf-audit-skill/SKILL.md +1 -0
  44. package/src/skf-audit-skill/assets/drift-report-template.md +6 -0
  45. package/src/skf-audit-skill/references/init.md +1 -1
  46. package/src/skf-audit-skill/references/report.md +4 -0
  47. package/src/skf-audit-skill/references/severity-classify.md +1 -1
  48. package/src/skf-audit-skill/references/step-doc-drift.md +147 -0
  49. package/src/skf-brief-skill/SKILL.md +7 -3
  50. package/src/skf-brief-skill/assets/skill-brief-schema.md +1 -1
  51. package/src/skf-brief-skill/references/analyze-target.md +7 -4
  52. package/src/skf-brief-skill/references/confirm-brief.md +0 -1
  53. package/src/skf-brief-skill/references/gather-intent.md +22 -2
  54. package/src/skf-brief-skill/references/scope-definition.md +2 -1
  55. package/src/skf-brief-skill/references/step-auto-brief.md +188 -0
  56. package/src/skf-brief-skill/references/step-auto-validate.md +209 -0
  57. package/src/skf-brief-skill/references/write-brief.md +2 -3
  58. package/src/skf-campaign/SKILL.md +190 -0
  59. package/src/skf-campaign/assets/campaign-state-schema.json +191 -0
  60. package/src/skf-campaign/customize.toml +73 -0
  61. package/src/skf-campaign/manifest.yaml +11 -0
  62. package/src/skf-campaign/references/campaign-directive-spec.md +121 -0
  63. package/src/skf-campaign/references/health-check.md +35 -0
  64. package/src/skf-campaign/references/step-01-setup.md +122 -0
  65. package/src/skf-campaign/references/step-02-strategy.md +97 -0
  66. package/src/skf-campaign/references/step-03-pins.md +56 -0
  67. package/src/skf-campaign/references/step-04-provenance.md +63 -0
  68. package/src/skf-campaign/references/step-05-skill-loop.md +103 -0
  69. package/src/skf-campaign/references/step-06-batch.md +87 -0
  70. package/src/skf-campaign/references/step-07-capstone.md +63 -0
  71. package/src/skf-campaign/references/step-08-verify.md +75 -0
  72. package/src/skf-campaign/references/step-09-refine.md +83 -0
  73. package/src/skf-campaign/references/step-10-export.md +106 -0
  74. package/src/skf-campaign/references/step-11-maintenance.md +84 -0
  75. package/src/skf-campaign/references/step-resume.md +114 -0
  76. package/src/skf-campaign/scripts/.gitkeep +0 -0
  77. package/src/skf-campaign/scripts/campaign-deps.py +235 -0
  78. package/src/skf-campaign/scripts/campaign-parse-manifest.py +119 -0
  79. package/src/skf-campaign/scripts/campaign-provenance.py +247 -0
  80. package/src/skf-campaign/scripts/campaign-render-kickoff.py +158 -0
  81. package/src/skf-campaign/scripts/campaign-report.py +249 -0
  82. package/src/skf-campaign/scripts/campaign-validate-pins.py +174 -0
  83. package/src/skf-campaign/scripts/campaign-validate-state.py +207 -0
  84. package/src/skf-campaign/templates/campaign-brief-template.yaml +34 -0
  85. package/src/skf-campaign/templates/campaign-report-template.md +54 -0
  86. package/src/skf-campaign/templates/kickoff-template.md +48 -0
  87. package/src/skf-create-skill/SKILL.md +4 -1
  88. package/src/skf-create-skill/assets/compile-assembly-rules.md +22 -0
  89. package/src/skf-create-skill/assets/skill-sections.md +2 -0
  90. package/src/skf-create-skill/references/compile.md +5 -2
  91. package/src/skf-create-skill/references/extract.md +9 -1
  92. package/src/skf-create-skill/references/extraction-patterns.md +3 -1
  93. package/src/skf-create-skill/references/generate-artifacts.md +11 -2
  94. package/src/skf-create-skill/references/health-check.md +2 -2
  95. package/src/skf-create-skill/references/report.md +17 -1
  96. package/src/skf-create-skill/references/source-resolution-protocols.md +1 -1
  97. package/src/skf-create-skill/references/step-auto-shard.md +110 -0
  98. package/src/skf-create-skill/references/step-doc-rot.md +109 -0
  99. package/src/skf-create-skill/references/step-doc-sources.md +123 -0
  100. package/src/skf-create-skill/references/sub/fetch-docs.md +29 -0
  101. package/src/skf-create-skill/references/validate.md +12 -3
  102. package/src/skf-drop-skill/SKILL.md +2 -2
  103. package/src/skf-drop-skill/references/execute.md +20 -9
  104. package/src/skf-drop-skill/references/report.md +2 -0
  105. package/src/skf-drop-skill/references/select.md +7 -2
  106. package/src/skf-forger/SKILL.md +15 -4
  107. package/src/skf-refine-architecture/SKILL.md +2 -1
  108. package/src/skf-refine-architecture/references/compile.md +1 -1
  109. package/src/skf-refine-architecture/references/report.md +1 -1
  110. package/src/skf-rename-skill/SKILL.md +2 -2
  111. package/src/skf-rename-skill/references/execute.md +5 -4
  112. package/src/skf-rename-skill/references/rebuild-context.md +2 -2
  113. package/src/skf-rename-skill/references/select.md +3 -3
  114. package/src/skf-setup/SKILL.md +1 -1
  115. package/src/skf-setup/references/auto-index.md +3 -1
  116. package/src/skf-setup/references/detect-and-tier.md +4 -0
  117. package/src/skf-setup/references/report.md +4 -1
  118. package/src/skf-setup/references/tier-rules.md +1 -1
  119. package/src/skf-test-skill/SKILL.md +8 -7
  120. package/src/skf-test-skill/customize.toml +2 -1
  121. package/src/skf-test-skill/references/coverage-check.md +10 -0
  122. package/src/skf-test-skill/references/external-validators.md +1 -1
  123. package/src/skf-test-skill/references/init.md +16 -1
  124. package/src/skf-test-skill/references/report.md +5 -1
  125. package/src/skf-test-skill/references/score.md +95 -6
  126. package/src/skf-test-skill/references/source-access-protocol.md +13 -3
  127. package/src/skf-test-skill/references/step-hard-gate.md +73 -0
  128. package/src/skf-test-skill/scripts/compute-score.py +4 -3
  129. package/src/skf-test-skill/templates/test-report-template.md +1 -0
  130. package/src/skf-update-skill/customize.toml +0 -9
  131. package/src/skf-update-skill/references/detect-changes.md +33 -3
  132. package/src/skf-update-skill/references/health-check.md +2 -2
  133. package/src/skf-update-skill/references/init.md +1 -0
  134. package/src/skf-update-skill/references/re-extract.md +15 -1
  135. package/src/skf-verify-stack/references/report.md +1 -1
  136. package/tools/cli/lib/ui.js +3 -2
@@ -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,13 +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 (`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
+ 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:
90
+
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>`.
96
+
88
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.
89
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.
90
99
  4. **Execute left to right** — for each workflow in the sequence:
91
100
  - a. **Report start**: "Pipeline [{current}/{total}]: Starting {code} ({description})..."
92
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.
93
- - c. **Invoke the workflow** with `{headless_mode}` = true 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.
94
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.
95
104
  - e. **Report completion**: "Pipeline [{current}/{total}]: {code} complete — {brief summary of output}."
96
105
  5. **Pipeline summary** — after all workflows complete (or on halt), present a summary:
@@ -100,6 +109,8 @@ When the user provides multiple workflow codes (e.g., `BS CS TS EX`, `QS TS EX`,
100
109
  - Next steps recommendation
101
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`.
102
111
 
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.
113
+
103
114
  **Special pipeline behaviors:**
104
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.
105
116
  - `AS` followed by `US`: if `summary.severity` in `audit-skill-result-latest.json` is CLEAN, skip US and report "No drift detected — skipping update."
@@ -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.)
@@ -91,6 +91,7 @@ Load and read {tierRulesData} for the tier capability descriptions and re-run me
91
91
  CCC Index:
92
92
  {if ccc_index_result is "fresh": up to date — semantic discovery ready}
93
93
  {if ccc_index_result is "created": indexed this run — semantic discovery ready}
94
+ {if ccc_index_result is "skipped": skipped (--ccc-skip-index) — run `/skf-setup` without --ccc-skip-index to build the index when you're ready}
94
95
  {if ccc_index_result is "failed": indexing failed — semantic discovery unavailable this session}
95
96
  {end if}
96
97
 
@@ -126,6 +127,7 @@ Load and read {tierRulesData} for the tier capability descriptions and re-run me
126
127
 
127
128
  {if {tier_changed} is false and {tools_added} is empty and {tools_removed} is empty and {previous_tier} is non-null:}
128
129
  {same-tier message from tier-rules.md}
130
+ {if preferences_yaml_created is false and (ccc_index_result is "fresh" or ccc_index_result is "none" or ccc_index_result is "skipped"): Nothing changed — your preferences were left untouched and the index was already current. You're good.}
129
131
 
130
132
  {if {tier_changed} is false and ({tools_added} or {tools_removed} is non-empty):}
131
133
  Tier unchanged: {calculated_tier}.
@@ -137,7 +139,7 @@ Load and read {tierRulesData} for the tier capability descriptions and re-run me
137
139
  ═══════════════════════════════════════
138
140
 
139
141
  {if {headless_mode} is false:}
140
- Next: try `/skf-brief-skill` to scope your first compilation target, or `/skf-quick-skill` for a fast template-driven path. Already have a skill? `/skf-audit-skill` drift-checks an existing skill against current sources.
142
+ Next: the fastest start is `@Ferris forge-auto <repo-or-doc-url>` — one command auto-scopes, briefs, compiles, tests at a 90% quality gate, and exports a verified skill with zero configuration. Prefer to scope by hand? `/skf-brief-skill` scopes your first compilation target, or `/skf-quick-skill` is a fast template-driven path. Already have a skill? `/skf-audit-skill` drift-checks an existing skill against current sources.
141
143
  ```
142
144
 
143
145
  All re-run-delta context flags (`{tools_added}`, `{tools_removed}`, `{tier_changed}`) come from the detector's `deltas` block bound in stage 1 — no LLM-side recomputation, no set arithmetic in prose.
@@ -203,6 +205,7 @@ echo '{
203
205
  "ccc_exclusion_warnings": {ccc_exclusion_warnings_list},
204
206
  "ccc_registry_stale_removed": {ccc_registry_stale_removed_paths_list},
205
207
  "ccc_indexing_failed_reason": {ccc_indexing_failed_reason_or_null},
208
+ "orphan_auto_resolution": {orphan_auto_resolution_or_null},
206
209
  "error": {error_object_or_null}
207
210
  }' | uv run {emitEnvelopeHelper} emit
208
211
  ```
@@ -7,7 +7,7 @@
7
7
  | ast-grep | `ast-grep --version` | Returns version string without error |
8
8
  | gh | `gh --version` | Returns version string without error |
9
9
  | qmd | `qmd status` | Returns status indicating initialized and operational |
10
- | ccc | Step A: `ccc --help` Step B: `ccc doctor` | Step A: exits 0 AND help output identifies the binary as cocoindex-code. Step B: daemon healthy. See `references/detect-and-tier.md` §7 for the full identity-marker procedure. |
10
+ | ccc | Step A: `ccc --help` Step B: `ccc doctor` | Step A: exits 0 AND help output identifies the binary as cocoindex-code. Step B: daemon healthy. The detection script (`skf-detect-tools.py`) performs the full identity-marker check (the `CocoIndex Code` substring that rejects PATH-shadowing aliases); see `references/detect-and-tier.md` §2. |
11
11
 
12
12
  **Important:** Use verification commands, not existence checks (`which`, `command -v`). A tool must be functional, not just present on PATH. For daemon-based tools (ccc), verify both binary identity and daemon health — a binary with the right name but the wrong implementation is a false positive, not a tool.
13
13
 
@@ -41,6 +41,7 @@ These rules apply to every step in this workflow:
41
41
  | 3 | Coverage Check | references/coverage-check.md | Yes |
42
42
  | 4 | Coherence Check | references/coherence-check.md | Yes |
43
43
  | 4b | External Validators | references/external-validators.md | Yes |
44
+ | 4c | Hard Gate | references/step-hard-gate.md | Yes |
44
45
  | 5 | Score | references/score.md | Yes |
45
46
  | 6 | Report | references/report.md | No (confirm) |
46
47
  | 7 | Workflow Health Check | references/health-check.md | Yes |
@@ -49,9 +50,9 @@ These rules apply to every step in this workflow:
49
50
 
50
51
  | Aspect | Detail |
51
52
  |--------|--------|
52
- | **Inputs** | skill_name [required]; optional flags: `--allow-workspace-drift`, `--no-discovery` (skip §4b Discovery Testing), `--no-health-check` (skip §7 health-check dispatch), `--tier=<Quick\|Forge\|Forge+\|Deep>` (bypass forge-tier.yaml sidecar requirement), `--threshold=<N>` (override pass threshold; CLI wins over `workflow.default_threshold` scalar) |
53
+ | **Inputs** | skill_name [required]; optional flags: `--allow-workspace-drift`, `--no-discovery` (skip §4b Discovery Testing), `--no-health-check` (skip §7 health-check dispatch), `--tier=<Quick\|Forge\|Forge+\|Deep>` (bypass forge-tier.yaml sidecar requirement), `--threshold=<N>` (override pass threshold; CLI wins over per-pipeline defaults and `workflow.default_threshold` scalar) |
53
54
  | **Gates** | step 6: Confirm Gate [C] |
54
- | **Outputs** | per-run `test-report-{skill_name}-{run_id}.md` with completeness score and result (PASS/FAIL); per-run `skf-test-skill-result-{run_id}.json` and `skf-test-skill-result-latest.json` written atomically under `{forge_version}/` — downstream consumers (export-skill, update-skill `--from-test-report`) glob `test-report-{skill_name}-*.md` and pick newest by parsed ISO timestamp |
55
+ | **Outputs** | per-run `test-report-{skill_name}-{run_id}.md` with completeness score and result (PASS/FAIL); per-run `skf-test-skill-result-{run_id}.json` and `skf-test-skill-result-latest.json` written atomically under `{forge_version}/`; `evidence-report-fallback.md` written under `{forge_version}/` when threshold fallback occurs (score between 80% and target threshold) — downstream consumers (export-skill, update-skill `--from-test-report`) glob `test-report-{skill_name}-*.md` and pick newest by parsed ISO timestamp |
55
56
  | **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true |
56
57
  | **Exit codes** | See "Exit Codes" below |
57
58
 
@@ -62,7 +63,7 @@ Every terminal state in this workflow exits with a stable code so headless autom
62
63
  | Code | Meaning | Raised by |
63
64
  | ---- | -------------------- | ------------------------------------------------------------------------------------------ |
64
65
  | 0 | success / PASS | step 6 §6b — `testResult: 'pass'` (after the result contract is written in §4c) |
65
- | 2 | fail / FAIL | step 6 §6b — `testResult: 'fail'` (after the result contract is written in §4c) |
66
+ | 2 | fail / FAIL | step 4c §3 — hard gate blocked (`halt_reason: "hard-gate-blocked"`); step 6 §6b — `testResult: 'fail'` (after the result contract is written in §4c) |
66
67
  | 3 | inconclusive | step 6 §6b — `testResult: 'inconclusive'` (distinct from fail so orchestrators can route to manual-review queues) |
67
68
  | 4 | pass-with-drift | step 6 §6b — `testResult: 'pass-with-drift'` (distinct from clean pass — `--allow-workspace-drift` was in effect; orchestrators MUST route to re-test-against-pinned-commit queues and refuse export; never exit 0 under drift override) |
68
69
 
@@ -71,10 +72,10 @@ Every terminal state in this workflow exits with a stable code so headless autom
71
72
  When `{headless_mode}` is true, step 6 emits a single-line JSON envelope on **stdout** before chaining to step 7, and every HARD HALT emits the same envelope shape on **stderr** with `status: "error"`:
72
73
 
73
74
  ```
74
- SKF_TEST_RESULT_JSON: {"status":"success|error","skill_name":"…","verdict":"PASS|FAIL|INCONCLUSIVE|pass-with-drift","score":N,"threshold":N,"report_path":"…|null","next_workflow":"export-skill|update-skill|null","exit_code":0,"halt_reason":null}
75
+ SKF_TEST_RESULT_JSON: {"status":"success|error","skill_name":"…","verdict":"PASS|FAIL|INCONCLUSIVE|pass-with-drift","score":N,"threshold":N,"report_path":"…|null","next_workflow":"export-skill|update-skill|null","exit_code":0,"halt_reason":null,"threshold_fallback":true,"original_threshold":90}
75
76
  ```
76
77
 
77
- `status` is `"success"` on the terminal happy path (any of PASS / FAIL / INCONCLUSIVE / pass-with-drift — the workflow completed), `"error"` on any HARD HALT before §4c. `verdict` is the canonical result string. `next_workflow` is `"export-skill"` only when `verdict == "PASS"`; `"update-skill"` for `FAIL` or `pass-with-drift`; `null` for `INCONCLUSIVE` (manual review). `halt_reason` is `null` on the terminal path or one of the workflow-defined halt strings (`"forge-tier-missing"`, `"target-inaccessible"`, `"write-failed"`, `"step-completeness-violation"`, `"report-anchor-missing"`, `"health-check-missing"`, `"atomic-writer-missing"`). `exit_code` matches the table above.
78
+ `status` is `"success"` on the terminal happy path (any of PASS / FAIL / INCONCLUSIVE / pass-with-drift — the workflow completed), `"error"` on any HARD HALT before §4c. `verdict` is the canonical result string. `next_workflow` is `"export-skill"` only when `verdict == "PASS"`; `"update-skill"` for `FAIL` or `pass-with-drift`; `null` for `INCONCLUSIVE` (manual review). `halt_reason` is `null` on the terminal path or one of the workflow-defined halt strings (`"forge-tier-missing"`, `"target-inaccessible"`, `"write-failed"`, `"step-completeness-violation"`, `"report-anchor-missing"`, `"health-check-missing"`, `"atomic-writer-missing"`, `"hard-gate-blocked"`). `exit_code` matches the table above. When threshold fallback occurred, the envelope includes `"threshold_fallback":true` and `"original_threshold":N`; these fields are omitted when no fallback occurred.
78
79
 
79
80
  The same payload is persisted to disk by step 6 §4c (atomic write) at two locations under `{forge_version}/`:
80
81
 
@@ -83,7 +84,7 @@ The same payload is persisted to disk by step 6 §4c (atomic write) at two locat
83
84
  | `skf-test-skill-result-{run_id}.json` | Per-run record. `{run_id}` carries UTC timestamp + PID + random suffix. |
84
85
  | `skf-test-skill-result-latest.json` | Latest copy — stable path for pipeline consumers (copy, not symlink). |
85
86
 
86
- The on-disk payload is the richer form: it adds `outputs[]` (report-path entries), `summary` (`score`, `threshold`, `result`, `testMode`, `activeCategories[]`, `inconclusiveReasons[]` when present), `runId`, and `healthCheckDispatched`. The stdout envelope is the compact subset documented above.
87
+ The on-disk payload is the richer form: it adds `outputs[]` (report-path entries), `summary` (`score`, `threshold`, `result`, `testMode`, `activeCategories[]`, `inconclusiveReasons[]` when present, `threshold_fallback`, `original_threshold`, `evidence_report_path` when threshold fallback occurred), `runId`, and `healthCheckDispatched`. The stdout envelope is the compact subset documented above.
87
88
 
88
89
  ## On Activation
89
90
 
@@ -113,7 +114,7 @@ The on-disk payload is the richer form: it adds `outputs[]` (report-path entries
113
114
  - `{testReportTemplatePath}` ← `workflow.test_report_template_path` if non-empty, else `templates/test-report-template.md`
114
115
  - `{outputFormatsPath}` ← `workflow.output_formats_path` if non-empty, else `assets/output-section-formats.md`
115
116
  - `{scoringRulesPath}` ← `workflow.scoring_rules_path` if non-empty, else `references/scoring-rules.md`
116
- - `{defaultThreshold}` ← `workflow.default_threshold` if non-empty/non-null, else `80`. CLI `--threshold=<N>` (when present) wins over the scalar at the usage site in `references/score.md`.
117
+ - `{defaultThreshold}` ← `workflow.default_threshold` if non-empty/non-null, else `80`. CLI `--threshold=<N>` wins over per-pipeline defaults (from init.md §1b) which win over this scalar at the usage site in `references/score.md`.
117
118
  - `{onCompleteCommand}` ← `workflow.on_complete` if non-empty, else empty string. When empty, the post-finalization hook in `references/report.md` is a no-op.
118
119
 
119
120
  Stash all five as workflow-context variables. Stage files reference `{testReportTemplatePath}` / `{outputFormatsPath}` / `{scoringRulesPath}` / `{defaultThreshold}` / `{onCompleteCommand}` directly — no conditional at the usage site. Empty-string overrides cleanly fall through to the bundled default; non-empty values let orgs swap in house-style copies without forking the skill.
@@ -43,7 +43,8 @@ test_report_template_path = ""
43
43
  output_formats_path = ""
44
44
  scoring_rules_path = ""
45
45
 
46
- # Pass threshold (percent). CLI `--threshold=<N>` wins over this scalar.
46
+ # Pass threshold (percent). Precedence: CLI `--threshold=<N>` > per-pipeline
47
+ # defaults (from init.md §1b lookup table) > this scalar > bundled fallback 80.
47
48
  # Override here to change the org-wide default without forking the skill.
48
49
  default_threshold = 80
49
50
 
@@ -260,12 +260,22 @@ as-observed (or `absent` when the candidate was not present for this skill):
260
260
  - `stats.effective_denominator`: {N | absent} {← chosen if priority (1) applied}
261
261
  - `scope.tier_a_include` union: {N | absent} {← chosen if priority (2) applied}
262
262
  - `scope.include` union: {N | absent} {← chosen if priority (3) applied}
263
+ - exports-map subpath union: {N | absent} {← chosen if the multi-entry clause applied}
264
+ - root barrel: {N} {secondary candidate — root-barrel-vs-subpath-union audit}
263
265
  ```
264
266
 
265
267
  Readers can then spot-check whether the chosen denominator is reasonable
266
268
  against the other two without re-running the extraction. A future reviewer who
267
269
  suspects denominator gaming has the evidence inline.
268
270
 
271
+ **Multi-entry (exports-map) denominator (single-package multi-subpath libraries):** Before computing Export Coverage, check whether the Source Access Protocol's multi-entry clause applies to this skill (see `{sourceAccessProtocol}` §Source API Surface Definition — "Multi-entry (exports-map) packages"). It applies when the in-scope `package.json` declares an `exports` map with multiple non-root subpath entries, no monorepo markers — covering `scope.type: "full-library"` AND `scope.type: "public-api"` for such packages. When it applies:
272
+
273
+ 1. **Prefer `metadata.json.stats.effective_denominator`** when present. Use it directly as `total_exports` — subject to the same **denominator deflation guard** the stratified-scope branch applies.
274
+ 2. **Otherwise prefer `scope.tier_a_include`** (filtered by `scope.exclude`, excluding umbrella barrel files per the protocol's umbrella-barrel note) when the brief supplies it; **else** re-derive the **union of named exports across the files each NON-WILDCARD `exports` subpath resolves to** per the protocol (resolving committed `.d.ts` / `.d.mts` targets, applying the multi-line brace-accumulation and `export *` star-resolution rules, and excluding `"./*"` wildcard subpaths). Use the resulting count as `total_exports`. When the `exports` map has only a root `"."` entry or only wildcards, fall back to the standard root-barrel rule instead.
275
+ 3. **Report the root-barrel named-export count as a secondary candidate** in the Denominator Candidates block so the root-barrel-vs-subpath-union choice is auditable.
276
+
277
+ Record the denominator source in the Coverage Analysis section as `Denominator: multi-entry ({effective_denominator | tier_a_include union | subpath union}, {N} subpaths resolved; root barrel: {R})`.
278
+
269
279
  **State 2 denominator validation:** When using provenance-map as the baseline (State 2), cross-reference the provenance-map entry count against `metadata.json`'s `exports[]` array before computing Export Coverage. If they diverge, use the union as the denominator per the source-access-protocol rules. Log the gap size if any. The stratified-scope rule above takes precedence when both conditions apply — compute the stratified denominator first, then validate the provenance-map entry count against it.
270
280
 
271
281
  ### 4b. Metadata Export-Count Coherence Cross-Check
@@ -1,5 +1,5 @@
1
1
  ---
2
- nextStepFile: 'score.md'
2
+ nextStepFile: 'step-hard-gate.md'
3
3
  outputFile: '{forge_version}/test-report-{skill_name}-{run_id}.md'
4
4
  ---
5
5
 
@@ -30,7 +30,7 @@ If skill path was provided as workflow argument, use it directly.
30
30
  - `--no-discovery` — skip the §4b Discovery Testing block in step 6 (report). Store `no_discovery: true` in workflow context when present.
31
31
  - `--no-health-check` — skip the §7 health-check dispatch in step 6 (report). Store `no_health_check: true` in workflow context when present.
32
32
  - `--tier=<Quick|Forge|Forge+|Deep>` — bypass the §4 forge-tier.yaml sidecar HALT. Store `tier_flag: '<value>'` in workflow context when present; §4 will set `detected_tier` directly from this value and skip the sidecar probe.
33
- - `--threshold=<N>` — override the pass threshold for this run. Consumed by `references/score.md` §1; CLI wins over the `workflow.default_threshold` scalar.
33
+ - `--threshold=<N>` — override the pass threshold for this run. Consumed by `references/score.md` §1; CLI wins over per-pipeline defaults (§1b) and the `workflow.default_threshold` scalar.
34
34
 
35
35
  If no path provided, ask:
36
36
 
@@ -40,6 +40,21 @@ Provide the skill path or name. I'll search in `{skillsOutputFolder}`.
40
40
 
41
41
  **Path or name:**"
42
42
 
43
+ ### 1b. Resolve Per-Pipeline Quality Threshold
44
+
45
+ If `{pipeline_alias}` is set in the workflow data context (forwarded by the forger when TS runs inside a pipeline — see `shared/references/pipeline-contracts.md` Pipeline State), look up the alias in the per-pipeline threshold defaults table:
46
+
47
+ | Pipeline Alias | Default Threshold |
48
+ |----------------|-------------------|
49
+ | `forge-auto` | 90 |
50
+ | `forge` | 80 |
51
+ | `forge-quick` | 80 |
52
+ | `campaign` | 90 |
53
+
54
+ - **If `{pipeline_alias}` is present AND found in the table:** store the corresponding value as `{pipeline_default_threshold}` in workflow context. This variable is consumed by `references/score.md` §1 as a precedence layer between CLI `--threshold` and `{defaultThreshold}`.
55
+ - **If `{pipeline_alias}` is present but NOT in the table:** `{pipeline_default_threshold}` remains unset. Score.md falls through to `{defaultThreshold}`.
56
+ - **If `{pipeline_alias}` is absent** (standalone TS invocation, not running inside a pipeline): `{pipeline_default_threshold}` remains unset. Score.md falls through to `{defaultThreshold}`.
57
+
43
58
  ### 2. Validate Skill Exists (version-aware)
44
59
 
45
60
  Resolve the skill path using version-aware resolution (see `{versionPathsKnowledge}`):
@@ -149,7 +149,7 @@ cat payload.json | python3 {atomicWriteHelper} write --target {forge_version}/sk
149
149
 
150
150
  Payload contents:
151
151
  - `outputs[]` — include the test report path at `{outputFile}` with its `{run_id}` suffix
152
- - `summary` — `score`, `threshold`, `result` (`"PASS"`, `"PASS_WITH_DRIFT"`, `"FAIL"`, or **`"INCONCLUSIVE"`**), `testMode` (naive/contextual), `activeCategories[]`, `inconclusiveReasons[]` (when present). `PASS_WITH_DRIFT` is set when the workflow observed workspace drift and the user passed `--allow-workspace-drift` — see step 5 §5 drift override. Downstream consumers MUST treat `PASS_WITH_DRIFT` as a non-exportable result: re-run against the pinned commit before export.
152
+ - `summary` — `score`, `threshold`, `result` (`"PASS"`, `"PASS_WITH_DRIFT"`, `"FAIL"`, or **`"INCONCLUSIVE"`**), `testMode` (naive/contextual), `activeCategories[]`, `inconclusiveReasons[]` (when present). `PASS_WITH_DRIFT` is set when the workflow observed workspace drift and the user passed `--allow-workspace-drift` — see step 5 §5 drift override. Downstream consumers MUST treat `PASS_WITH_DRIFT` as a non-exportable result: re-run against the pinned commit before export. When threshold fallback occurred, add `threshold_fallback: true`, `original_threshold: {N}`, and `evidence_report_path: '{path}'` to the summary — these fields are absent (not `false`/`null`) when no fallback occurred.
153
153
  - `runId` — the workflow's `{run_id}` for downstream correlation
154
154
  - `healthCheckDispatched` — boolean, set by §7 after the dispatch decision
155
155
 
@@ -173,6 +173,7 @@ Run it with a bounded timeout (default 60s). On success: log Info note "on_compl
173
173
  'coverage-check',
174
174
  'coherence-check',
175
175
  'external-validators',
176
+ 'hard-gate',
176
177
  'score',
177
178
  'report']
178
179
  ```
@@ -211,6 +212,9 @@ skipped its append.
211
212
 
212
213
  **Result:** **{PASS|PASS_WITH_DRIFT|FAIL|INCONCLUSIVE}** — **{score}%** (threshold: {threshold}%)
213
214
 
215
+ {If `thresholdFallback` is present in output frontmatter:}
216
+ **Threshold fallback:** scored {score}% against {originalThreshold} target — accepted at 80% floor. Evidence report: {evidenceReportPath}
217
+
214
218
  **Gaps Found:** {total_gaps}
215
219
  - Critical: {N}
216
220
  - High: {N}