bmad-module-skill-forge 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.nvmrc +1 -1
- package/docs/_data/pinned.yaml +1 -1
- package/package.json +2 -2
- package/src/knowledge/skill-lifecycle.md +1 -1
- package/src/module-help.csv +2 -2
- package/src/shared/scripts/skf-comention-pairs.py +350 -0
- package/src/shared/scripts/skf-count-tokens.py +222 -0
- package/src/shared/scripts/skf-detect-docs.py +232 -1
- package/src/shared/scripts/skf-detect-language.py +77 -0
- package/src/shared/scripts/skf-detect-tools.py +105 -2
- package/src/shared/scripts/skf-enumerate-stack-skills.py +111 -2
- package/src/shared/scripts/skf-find-cycles.py +246 -0
- package/src/shared/scripts/skf-hash-content.py +487 -2
- package/src/shared/scripts/skf-manifest-ops.py +67 -1
- package/src/shared/scripts/skf-rebuild-managed-sections.py +180 -0
- package/src/shared/scripts/skf-recommend-scope-type.py +1 -2
- package/src/shared/scripts/skf-render-metadata-stats.py +499 -0
- package/src/shared/scripts/skf-rewrite-skill-name.py +308 -0
- package/src/shared/scripts/skf-shard-body.py +461 -0
- package/src/shared/scripts/skf-skill-inventory.py +139 -3
- package/src/shared/scripts/skf-structural-diff.py +307 -67
- package/src/shared/scripts/skf-validate-brief-schema.py +39 -1
- package/src/shared/scripts/skf-validate-feasibility-report.py +234 -0
- package/src/shared/scripts/skf-validate-frontmatter.py +17 -12
- package/src/shared/scripts/skf-validate-output.py +443 -10
- package/src/shared/scripts/skf-validate-pins.py +1 -1
- package/src/shared/scripts/skf-verify-no-trace.py +214 -0
- package/src/shared/scripts/skf-verify-provenance-completeness.py +439 -0
- package/src/skf-analyze-source/SKILL.md +17 -32
- package/src/skf-analyze-source/customize.toml +2 -0
- package/src/skf-analyze-source/references/auto-docs-only.md +116 -0
- package/src/skf-analyze-source/references/continue.md +8 -13
- package/src/skf-analyze-source/references/generate-briefs.md +28 -12
- package/src/skf-analyze-source/references/headless-contract.md +33 -0
- package/src/skf-analyze-source/references/health-check.md +1 -2
- package/src/skf-analyze-source/references/identify-units.md +25 -36
- package/src/skf-analyze-source/references/init.md +12 -25
- package/src/skf-analyze-source/references/map-and-detect.md +8 -20
- package/src/skf-analyze-source/references/recommend.md +4 -14
- package/src/skf-analyze-source/references/scan-project.md +3 -11
- package/src/skf-analyze-source/references/step-auto-scope.md +42 -180
- package/src/skf-analyze-source/references/unit-detection-heuristics.md +2 -2
- package/src/skf-audit-skill/SKILL.md +9 -6
- package/src/skf-audit-skill/customize.toml +2 -0
- package/src/skf-audit-skill/references/init.md +58 -32
- package/src/skf-audit-skill/references/re-index.md +15 -34
- package/src/skf-audit-skill/references/report.md +11 -58
- package/src/skf-audit-skill/references/semantic-diff.md +4 -22
- package/src/skf-audit-skill/references/severity-classify.md +53 -87
- package/src/skf-audit-skill/references/step-doc-drift.md +49 -19
- package/src/skf-audit-skill/references/structural-diff.md +43 -68
- package/src/skf-brief-skill/SKILL.md +7 -36
- package/src/skf-brief-skill/assets/scope-templates.md +1 -1
- package/src/skf-brief-skill/customize.toml +14 -0
- package/src/skf-brief-skill/references/analyze-target.md +6 -14
- package/src/skf-brief-skill/references/confirm-brief.md +2 -14
- package/src/skf-brief-skill/references/draft-checkpoint.md +8 -8
- package/src/skf-brief-skill/references/gather-intent.md +19 -22
- package/src/skf-brief-skill/references/headless-args.md +1 -1
- package/src/skf-brief-skill/references/health-check.md +1 -22
- package/src/skf-brief-skill/references/invocation-contract.md +37 -0
- package/src/skf-brief-skill/references/scope-definition.md +4 -14
- package/src/skf-brief-skill/references/step-auto-brief.md +1 -5
- package/src/skf-brief-skill/references/step-auto-validate.md +1 -5
- package/src/skf-brief-skill/references/version-resolution.md +1 -1
- package/src/skf-brief-skill/references/write-brief.md +17 -10
- package/src/skf-campaign/SKILL.md +15 -84
- package/src/skf-campaign/customize.toml +2 -0
- package/src/skf-campaign/manifest.yaml +4 -0
- package/src/skf-campaign/references/campaign-contracts.md +67 -0
- package/src/skf-campaign/references/campaign-directive-spec.md +14 -103
- package/src/skf-campaign/references/step-01-setup.md +4 -5
- package/src/skf-campaign/references/step-02-strategy.md +8 -3
- package/src/skf-campaign/references/step-03-pins.md +1 -1
- package/src/skf-campaign/references/step-04-provenance.md +1 -1
- package/src/skf-campaign/references/step-05-skill-loop.md +1 -1
- package/src/skf-campaign/references/step-06-batch.md +7 -6
- package/src/skf-campaign/references/step-11-maintenance.md +2 -2
- package/src/skf-campaign/references/step-resume.md +9 -8
- package/src/skf-campaign/scripts/campaign-deps.py +10 -1
- package/src/skf-campaign/scripts/campaign-render-batch.py +199 -0
- package/src/skf-campaign/scripts/campaign-status.py +162 -0
- package/src/skf-campaign/templates/campaign-brief-template.yaml +1 -1
- package/src/skf-create-skill/SKILL.md +14 -3
- package/src/skf-create-skill/assets/compile-assembly-rules.md +17 -38
- package/src/skf-create-skill/assets/skill-sections.md +9 -100
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +4 -13
- package/src/skf-create-skill/customize.toml +13 -0
- package/src/skf-create-skill/references/authoritative-files-protocol.md +7 -7
- package/src/skf-create-skill/references/compile.md +34 -47
- package/src/skf-create-skill/references/component-extraction.md +8 -35
- package/src/skf-create-skill/references/ecosystem-check.md +9 -25
- package/src/skf-create-skill/references/enrich.md +4 -17
- package/src/skf-create-skill/references/extract.md +14 -29
- package/src/skf-create-skill/references/extraction-patterns-tracing.md +1 -1
- package/src/skf-create-skill/references/extraction-patterns.md +7 -7
- package/src/skf-create-skill/references/generate-artifacts.md +9 -23
- package/src/skf-create-skill/references/health-check.md +1 -1
- package/src/skf-create-skill/references/load-brief.md +16 -18
- package/src/skf-create-skill/references/report.md +12 -15
- package/src/skf-create-skill/references/source-resolution-protocols.md +3 -3
- package/src/skf-create-skill/references/step-auto-shard.md +52 -25
- package/src/skf-create-skill/references/step-doc-rot.md +30 -12
- package/src/skf-create-skill/references/step-doc-sources.md +2 -2
- package/src/skf-create-skill/references/sub/ccc-discover.md +2 -13
- package/src/skf-create-skill/references/sub/fetch-docs.md +8 -50
- package/src/skf-create-skill/references/sub/fetch-temporal.md +5 -20
- package/src/skf-create-skill/references/tier-degradation-rules.md +6 -8
- package/src/skf-create-skill/references/validate.md +59 -28
- package/src/skf-create-skill/scripts/scan-doc-rot.py +245 -0
- package/src/skf-create-stack-skill/SKILL.md +15 -9
- package/src/skf-create-stack-skill/assets/provenance-map-schema.md +1 -1
- package/src/skf-create-stack-skill/customize.toml +10 -0
- package/src/skf-create-stack-skill/references/compile-stack.md +7 -8
- package/src/skf-create-stack-skill/references/compose-mode-rules.md +2 -13
- package/src/skf-create-stack-skill/references/detect-integrations.md +49 -54
- package/src/skf-create-stack-skill/references/detect-manifests.md +37 -30
- package/src/skf-create-stack-skill/references/generate-output.md +24 -75
- package/src/skf-create-stack-skill/references/init.md +9 -81
- package/src/skf-create-stack-skill/references/manifest-patterns.md +5 -35
- package/src/skf-create-stack-skill/references/parallel-extract.md +12 -27
- package/src/skf-create-stack-skill/references/rank-and-confirm.md +6 -6
- package/src/skf-create-stack-skill/references/report.md +27 -62
- package/src/skf-create-stack-skill/references/validate.md +35 -31
- package/src/skf-drop-skill/SKILL.md +20 -23
- package/src/skf-drop-skill/customize.toml +10 -0
- package/src/skf-drop-skill/references/execute.md +51 -31
- package/src/skf-drop-skill/references/headless-contract.md +18 -0
- package/src/skf-drop-skill/references/health-check.md +1 -13
- package/src/skf-drop-skill/references/report.md +42 -10
- package/src/skf-drop-skill/references/select.md +88 -48
- package/src/skf-drop-skill/scripts/dir-sizes.py +116 -0
- package/src/skf-export-skill/SKILL.md +17 -14
- package/src/skf-export-skill/assets/managed-section-format.md +2 -5
- package/src/skf-export-skill/customize.toml +10 -6
- package/src/skf-export-skill/references/generate-snippet.md +21 -35
- package/src/skf-export-skill/references/load-skill.md +44 -42
- package/src/skf-export-skill/references/multi-skill-mode.md +1 -1
- package/src/skf-export-skill/references/orphan-context-detection.md +11 -3
- package/src/skf-export-skill/references/package.md +27 -33
- package/src/skf-export-skill/references/preflight-snippet-root-probe.md +24 -5
- package/src/skf-export-skill/references/result-envelope.md +28 -0
- package/src/skf-export-skill/references/summary.md +11 -5
- package/src/skf-export-skill/references/token-report.md +42 -24
- package/src/skf-export-skill/references/update-context.md +32 -33
- package/src/skf-forger/SKILL.md +27 -39
- package/src/skf-forger/references/pipeline-mode.md +34 -0
- package/src/skf-forger/scripts/parse-pipeline.py +270 -0
- package/src/skf-quick-skill/SKILL.md +10 -60
- package/src/skf-quick-skill/customize.toml +9 -0
- package/src/skf-quick-skill/references/batch-mode.md +6 -4
- package/src/skf-quick-skill/references/compile.md +15 -52
- package/src/skf-quick-skill/references/ecosystem-check.md +5 -20
- package/src/skf-quick-skill/references/finalize.md +12 -6
- package/src/skf-quick-skill/references/halt-contract.md +54 -0
- package/src/skf-quick-skill/references/health-check.md +3 -4
- package/src/skf-quick-skill/references/quick-extract.md +7 -18
- package/src/skf-quick-skill/references/registry-resolution.md +1 -23
- package/src/skf-quick-skill/references/resolve-target.md +27 -34
- package/src/skf-quick-skill/references/write-and-validate.md +10 -25
- package/src/skf-refine-architecture/SKILL.md +14 -7
- package/src/skf-refine-architecture/customize.toml +9 -0
- package/src/skf-refine-architecture/references/compile.md +9 -7
- package/src/skf-refine-architecture/references/gap-analysis.md +21 -50
- package/src/skf-refine-architecture/references/health-check.md +1 -1
- package/src/skf-refine-architecture/references/improvements.md +9 -25
- package/src/skf-refine-architecture/references/init.md +9 -16
- package/src/skf-refine-architecture/references/issue-detection.md +10 -25
- package/src/skf-refine-architecture/references/refinement-rules.md +4 -16
- package/src/skf-refine-architecture/references/report.md +13 -10
- package/src/skf-rename-skill/SKILL.md +11 -21
- package/src/skf-rename-skill/customize.toml +10 -0
- package/src/skf-rename-skill/references/execute.md +107 -78
- package/src/skf-rename-skill/references/exit-codes.md +12 -0
- package/src/skf-rename-skill/references/report.md +16 -8
- package/src/skf-rename-skill/references/select.md +69 -42
- package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -0
- package/src/skf-setup/SKILL.md +20 -12
- package/src/skf-setup/customize.toml +10 -0
- package/src/skf-setup/references/auto-index.md +9 -10
- package/src/skf-setup/references/ccc-index.md +14 -15
- package/src/skf-setup/references/detect-and-tier.md +6 -8
- package/src/skf-setup/references/report.md +22 -31
- package/src/skf-setup/references/tier-rules.md +2 -32
- package/src/skf-setup/references/write-config.md +9 -10
- package/src/skf-test-skill/SKILL.md +10 -8
- package/src/skf-test-skill/customize.toml +2 -0
- package/src/skf-test-skill/references/coherence-check.md +19 -31
- package/src/skf-test-skill/references/coverage-check.md +109 -75
- package/src/skf-test-skill/references/detect-mode.md +10 -41
- package/src/skf-test-skill/references/external-validators.md +11 -19
- package/src/skf-test-skill/references/init.md +38 -20
- package/src/skf-test-skill/references/migration-section-rules.md +4 -4
- package/src/skf-test-skill/references/report.md +60 -40
- package/src/skf-test-skill/references/score.md +32 -64
- package/src/skf-test-skill/references/scoring-rules.md +6 -62
- package/src/skf-test-skill/references/source-access-protocol.md +6 -6
- package/src/skf-test-skill/references/step-hard-gate.md +1 -3
- package/src/skf-test-skill/scripts/aggregate-coherence.py +276 -0
- package/src/skf-test-skill/scripts/check-metadata-coherence.py +359 -0
- package/src/skf-test-skill/scripts/combine-external-scores.py +190 -0
- package/src/skf-test-skill/scripts/compute-score.py +131 -1
- package/src/skf-test-skill/scripts/reconcile-coverage.py +327 -0
- package/src/skf-test-skill/scripts/validate-inventory.py +251 -0
- package/src/skf-test-skill/scripts/verify-declared-numerator.py +192 -0
- package/src/skf-update-skill/SKILL.md +17 -5
- package/src/skf-update-skill/customize.toml +14 -0
- package/src/skf-update-skill/references/detect-changes.md +19 -37
- package/src/skf-update-skill/references/health-check.md +3 -9
- package/src/skf-update-skill/references/init.md +39 -32
- package/src/skf-update-skill/references/manual-section-rules.md +1 -2
- package/src/skf-update-skill/references/merge-conflict-rules.md +1 -52
- package/src/skf-update-skill/references/merge.md +13 -57
- package/src/skf-update-skill/references/re-extract.md +12 -24
- package/src/skf-update-skill/references/report.md +14 -7
- package/src/skf-update-skill/references/validate.md +32 -30
- package/src/skf-update-skill/references/write.md +86 -51
- package/src/skf-update-skill/scripts/skf-new-file-diff.py +167 -0
- package/src/skf-verify-stack/SKILL.md +11 -21
- package/src/skf-verify-stack/customize.toml +12 -3
- package/src/skf-verify-stack/references/coverage-patterns.md +1 -29
- package/src/skf-verify-stack/references/coverage.md +21 -10
- package/src/skf-verify-stack/references/exit-codes.md +14 -0
- package/src/skf-verify-stack/references/health-check.md +1 -1
- package/src/skf-verify-stack/references/init.md +20 -25
- package/src/skf-verify-stack/references/integration-verification-rules.md +10 -19
- package/src/skf-verify-stack/references/integrations.md +44 -49
- package/src/skf-verify-stack/references/report.md +28 -34
- package/src/skf-verify-stack/references/requirements.md +3 -3
- package/src/skf-verify-stack/references/synthesize.md +36 -39
- package/src/skf-verify-stack/scripts/skf-coverage-tally.py +196 -0
- package/src/skf-verify-stack/scripts/skf-report-delta.py +256 -0
- package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +258 -0
- package/src/skf-rename-skill/references/rebuild-context.md +0 -110
|
@@ -24,6 +24,20 @@ descriptionGuardProbeOrder:
|
|
|
24
24
|
frontmatterValidatorProbeOrder:
|
|
25
25
|
- '{project-root}/_bmad/skf/shared/scripts/skf-validate-frontmatter.py'
|
|
26
26
|
- '{project-root}/src/shared/scripts/skf-validate-frontmatter.py'
|
|
27
|
+
# Resolve `{shardBodyHelper}` by probing `{shardBodyProbeOrder}` in order
|
|
28
|
+
# (installed SKF module path first, src/ dev-checkout fallback); first existing
|
|
29
|
+
# path wins. §4 uses it as the deterministic selective splitter and reads its
|
|
30
|
+
# `tier1_preserved` field instead of counting Tier-1 headings pre/post by hand.
|
|
31
|
+
shardBodyProbeOrder:
|
|
32
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-shard-body.py'
|
|
33
|
+
- '{project-root}/src/shared/scripts/skf-shard-body.py'
|
|
34
|
+
# Resolve `{renderMetadataStatsHelper}` by probing `{renderMetadataStatsProbeOrder}`
|
|
35
|
+
# in order (installed SKF module path first, src/ dev-checkout fallback); first
|
|
36
|
+
# existing path wins. §7 uses it in --check mode to re-derive the metadata
|
|
37
|
+
# `stats` / `confidence_distribution` instead of re-doing the arithmetic by hand.
|
|
38
|
+
renderMetadataStatsProbeOrder:
|
|
39
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-render-metadata-stats.py'
|
|
40
|
+
- '{project-root}/src/shared/scripts/skf-render-metadata-stats.py'
|
|
27
41
|
---
|
|
28
42
|
|
|
29
43
|
<!-- Config: communicate in {communication_language}. -->
|
|
@@ -111,14 +125,14 @@ If `restored: true` in the verify-restore output, apply §0's post-restore re-va
|
|
|
111
125
|
|
|
112
126
|
**If skill-check was available:** Skip — already validated in step 2.
|
|
113
127
|
|
|
114
|
-
**If skill-check
|
|
128
|
+
**If skill-check not available (fallback):** Perform manual frontmatter compliance check:
|
|
115
129
|
|
|
116
130
|
- [ ] Frontmatter present — file starts with `---` and has closing `---`
|
|
117
131
|
- [ ] `name` field — present, non-empty, lowercase alphanumeric + hyphens only, 1-64 chars
|
|
118
132
|
- [ ] `name` matches skill output directory name
|
|
119
133
|
- [ ] `description` field — present, non-empty, 1-1024 characters
|
|
120
134
|
- [ ] No unknown fields — only `name`, `description`, `license`, `compatibility`, `metadata`, `allowed-tools` permitted
|
|
121
|
-
- [ ] `version` and `author` are
|
|
135
|
+
- [ ] `version` and `author` are not in frontmatter (they belong in metadata.json)
|
|
122
136
|
|
|
123
137
|
If fails: auto-fix (deterministic), re-validate once, record result. If passes: record "Frontmatter: PASS".
|
|
124
138
|
|
|
@@ -144,13 +158,21 @@ If `restored: true` in the verify-restore output, apply §0's post-restore re-va
|
|
|
144
158
|
|
|
145
159
|
**Mandatory approach — selective split:** Identify Tier 2 sections by their `## Full` heading prefix (e.g., `## Full API Reference`, `## Full Type Definitions`, `## Full Integration Patterns`). Extract ONLY those sections to `references/`, starting with the largest. Keep ALL Tier 1 content and any smaller sections inline. Inline passive context achieves 100% task accuracy vs 79% for on-demand retrieval (per Vercel research).
|
|
146
160
|
|
|
147
|
-
|
|
161
|
+
This selective split is deterministic — run `{shardBodyHelper}` (the same splitter step 5b auto-shard uses) rather than counting and extracting by hand. **Resolve `{shardBodyHelper}`** from `{shardBodyProbeOrder}`; first existing path wins.
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
uv run {shardBodyHelper} <staging-skill-dir>/SKILL.md --budget 400
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
It extracts the largest `## Full` sections to `references/` until the body fits, rewrites each as a cross-reference blockquote through the atomic-write helper, and reports `sections_extracted`, `body_lines_after`, `tier1_preserved`, `xref_ok`, and `under_budget`.
|
|
148
168
|
|
|
149
|
-
**
|
|
169
|
+
**Do not run `npx skill-check split-body --write` before selective extraction.** It extracts every `##` section top-to-bottom, destroying the Tier 1 inline content the two-tier design depends on — a last resort used only after selective split has been attempted and proven insufficient.
|
|
150
170
|
|
|
151
|
-
**
|
|
171
|
+
**If selective split alone does not bring body under the limit** (the splitter reports `under_budget: false` — rare, typically only when Tier 1 itself exceeds 300 lines): reduce Tier 1 Key API Summary and Architecture at a Glance sections to fit within limits. Do not fall back to automated `split-body --write` to solve a Tier 1 sizing problem.
|
|
152
172
|
|
|
153
|
-
**
|
|
173
|
+
**Tier 1 preservation check:** After any split operation, verify that all of the following Tier 1 sections remain inline in SKILL.md (not moved to references/): Overview, Quick Start, Common Workflows, Key API Summary, Migration & Deprecation Warnings (if present), Key Types, Architecture at a Glance, CLI (if present), Scripts & Assets (if present), Manual Sections. If any was moved to references/, restore it immediately and re-split targeting only Tier 2 sections.
|
|
174
|
+
|
|
175
|
+
**Post-split Tier-1 count check (mandatory):** do not recount Tier-1 headings by hand — consume the splitter's `tier1_preserved` field. `{shardBodyHelper}` compares the Tier-1 headings inline before extraction against those inline afterward and reports `tier1_preserved` (with any pulled headings in `tier1_missing`). Read it from the invocation above, or re-check any split's result with `uv run {shardBodyHelper} <staging-skill-dir>/SKILL.md --dry-run`. **HALT** if `tier1_preserved` is false with: "Split reduced Tier-1 section count (missing {tier1_missing}). Tier-1 sections must remain inline. Restoring from staging backup and aborting body split — manual review required." Do not proceed past §4 — Tier-1 preservation is a hard invariant and a `tier1_preserved: false` result means the splitter pulled an inline section into references/ regardless of the section-list check above (e.g., heading-text variation, capitalization, or the splitter's own heuristics).
|
|
154
176
|
|
|
155
177
|
**Anchor validation and remediation:** After any split, verify that context-snippet section anchors (`#quick-start`, `#key-types`) still resolve to headings in SKILL.md. If an anchor no longer resolves (section was split out), restore that section to SKILL.md inline content — the context-snippet must always reference sections that exist in the main file.
|
|
156
178
|
|
|
@@ -189,8 +211,8 @@ Parse output for: `description_score`, `content_score`, `review_score`, `validat
|
|
|
189
211
|
**Apply dismissal rules** in this order:
|
|
190
212
|
|
|
191
213
|
1. **Check score thresholds** against the "Score Thresholds" table in `{tesslDismissalData}`. Most importantly:
|
|
192
|
-
- If tessl's output contains a `findings[]` entry with rule ID `description_field` (the deterministic angle-bracket / XML-tag validator): follow the **recover-then-halt** path defined by the `description-xml-tags-guarded-upstream` rule in `{tesslDismissalData}`. Re-apply step 5 §2a's `<`/`>` → `{`/`}` substitution in place on the staging SKILL.md frontmatter `description`, re-sync the in-context copy, and re-run `npx -y tessl skill review <staging-skill-dir>` once. **Re-run gate:** treat the `description_field` finding being **absent** from the re-run as the only successful recovery outcome. If the finding persists on the re-run — whether the re-substitution improved the description or not — that counts as recovery failure: halt with the original `description-xml-tags-guarded-upstream` failure message from `{tesslDismissalData}`, do
|
|
193
|
-
- If the LLM-judge `description_score` is below 100 **but no `description_field` finding is present** (the deterministic validator PASSED): this is a soft discoverability signal (jargon density, trigger-term phrasing) from the judge's sub-scores, not a sanitizer bypass. Record `description_judge_score: {n}% (deterministic description_field validator PASSED)` as a warning in the evidence report and continue — do
|
|
214
|
+
- If tessl's output contains a `findings[]` entry with rule ID `description_field` (the deterministic angle-bracket / XML-tag validator): follow the **recover-then-halt** path defined by the `description-xml-tags-guarded-upstream` rule in `{tesslDismissalData}`. Re-apply step 5 §2a's `<`/`>` → `{`/`}` substitution in place on the staging SKILL.md frontmatter `description`, re-sync the in-context copy, and re-run `npx -y tessl skill review <staging-skill-dir>` once. **Re-run gate:** treat the `description_field` finding being **absent** from the re-run as the only successful recovery outcome. If the finding persists on the re-run — whether the re-substitution improved the description or not — that counts as recovery failure: halt with the original `description-xml-tags-guarded-upstream` failure message from `{tesslDismissalData}`, do not proceed to §6b, and do not downgrade the recovery to a warning. On successful recovery (finding cleared), log `description-recovery: applied ({count} substitutions)` in the evidence report under "Dismissed tessl suggestions" and continue suggestion iteration against the rerun's `judge_suggestions[]`.
|
|
215
|
+
- If the LLM-judge `description_score` is below 100 **but no `description_field` finding is present** (the deterministic validator PASSED): this is a soft discoverability signal (jargon density, trigger-term phrasing) from the judge's sub-scores, not a sanitizer bypass. Record `description_judge_score: {n}% (deterministic description_field validator PASSED)` as a warning in the evidence report and continue — do not trigger the recover-then-halt path and do not halt.
|
|
194
216
|
- If `review_score < 60` or `content_score < 60`: record warnings in the evidence report, continue.
|
|
195
217
|
2. **Iterate `judge_suggestions[]`.** For each suggestion:
|
|
196
218
|
- Cross-reference against the rules in `{tesslDismissalData}` in order.
|
|
@@ -206,7 +228,7 @@ tessl installs automatically via `npx`. A missing tool is not an error — grace
|
|
|
206
228
|
|
|
207
229
|
**If §6 produced no novel suggestions (all dismissed via `{tesslDismissalData}`) OR tessl was unavailable:** Skip this gate — auto-proceed.
|
|
208
230
|
|
|
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
|
|
231
|
+
**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 and, the moment it lands, append the same object as a JSON line to the durable audit sink `{sidecar_path}/auto-decisions.jsonl` (the on-landing append established at step 1 §3). §8 below reconciles the sink into the evidence-report `## Auto-Decisions` table — this is the last gate to fire; the earlier gates' rows are already in the sink and were rendered into the staged report at step 5 §7. 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
232
|
|
|
211
233
|
**If §6 produced novel suggestions** (ones not matched by any dismissal rule) AND `{headless_mode}` is false, present them to the user:
|
|
212
234
|
|
|
@@ -234,14 +256,21 @@ tessl suggestions (novel — not matched by `{tesslDismissalData}`):
|
|
|
234
256
|
|
|
235
257
|
### 7. Validate metadata.json
|
|
236
258
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
-
|
|
242
|
-
|
|
259
|
+
**Re-derive the computed fields with `{renderMetadataStatsHelper}` in check mode** rather than re-doing the arithmetic by hand. Resolve `{renderMetadataStatsHelper}` from `{renderMetadataStatsProbeOrder}` (first existing path wins; HALT if neither resolves), then run it against the staged provenance-map and metadata.json:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
uv run {renderMetadataStatsHelper} <staging-skill-dir>/provenance-map.json \
|
|
263
|
+
--check <staging-skill-dir>/metadata.json
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
The helper re-bins `entries[]` by `signature_source`, recomputes `exports_documented`, `exports_total`, and `public_api_coverage` / `total_coverage` (null when the denominator is 0), and cross-checks `stats.scripts_count` / `stats.assets_count` against the `scripts[]` / `assets[]` array lengths and the provenance-map `file_entries` counts. It takes the judgment values (`exports_public_api`, `exports_internal`, `effective_denominator`) from `metadata.json` itself and infers the shape from `scope_type` / `skill_type` (pass `--shape` to override). Parse the emitted JSON (rely on the JSON, not the exit code):
|
|
243
267
|
|
|
244
|
-
|
|
268
|
+
- **`coherence.ok: true`** — the computed fields are internally consistent; record "Metadata: PASS".
|
|
269
|
+
- **`coherence.ok: false`** — each `violations[]` entry is `{field, expected, actual}` where `expected` is the correct value. **Auto-fix each computed-value violation** (`field` starting `stats.` or `confidence_distribution.`) by setting that field in `metadata.json` to `expected` (write via `python3 {atomicWriteHelper} write --target <staging-skill-dir>/metadata.json`), leaving every other stats field — e.g. `stats.notes` on a reference app — untouched. Record "Metadata: auto-fixed {N} computed-value discrepanc(y|ies)" listing the fields. These are computed values, so the helper is authoritative — a `confidence_distribution` violation is the per-entry mis-binning compile.md §4 describes (T2 annotations + T3 doc items counted on top of the per-export tiers); the helper's per-entry counts replace them. Carve-outs are handled by `--shape`: a **stack** distribution sums to the constituent count and a **reference-app** distribution to the per-citation count, so those are consistent states, not violations. A `provenance.file_entries.*` violation is not a computed metadata field — it means the provenance-map `file_entries` and metadata counts disagree; record it as a warning for manual reconciliation rather than auto-editing the count.
|
|
270
|
+
|
|
271
|
+
Then verify the two fields the helper does not own (genuine constants/contract):
|
|
272
|
+
- `spec_version` is `"1.3"`.
|
|
273
|
+
- `scope_type` is present and equals the brief's `scope.type` verbatim.
|
|
245
274
|
|
|
246
275
|
### 8. Update Evidence Report
|
|
247
276
|
|
|
@@ -272,6 +301,18 @@ Add validation results to evidence-report content in context:
|
|
|
272
301
|
- {warnings, security results, tessl scores and suggestions — or "skipped"}
|
|
273
302
|
```
|
|
274
303
|
|
|
304
|
+
**Auto-Decisions table (reconcile from the durable sink — idempotent):** all gates have now fired — every one appended its row to the on-disk sink `{sidecar_path}/auto-decisions.jsonl` as it landed (including §6b's tessl-suggestions row), step 5 §7 rendered the step 1–3d rows into the staged `<staging-skill-dir>/evidence-report.md`, and steps 7–9 add none. Reconcile: read the sink's JSON lines (the authoritative durable record — it survives any compaction of the in-context buffer), union them with both the `## Auto-Decisions` rows already in `<staging-skill-dir>/evidence-report.md` and the in-context `headless_decisions[]` buffer, keyed on `step`+`gate` so no decision is duplicated or dropped, and re-render the section from that union. Because the rows are recovered from the sink rather than from the possibly-compacted buffer, the audit table stays complete on a long headless run. Emit one row per entry:
|
|
305
|
+
|
|
306
|
+
```
|
|
307
|
+
## Auto-Decisions
|
|
308
|
+
|
|
309
|
+
| Step | Gate | Decision | Rationale | Timestamp |
|
|
310
|
+
|------|------|----------|-----------|-----------|
|
|
311
|
+
| {step} | {gate} | {decision}{value?} | {rationale} | {timestamp} |
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
If the sink, the on-disk rows, and `headless_decisions[]` are all empty, keep the single line step 5 §7 emitted: `No auto-decisions — workflow ran interactively (or all gates had no match to auto-resolve).` This keeps the section always present so reviewers can tell "zero auto-decisions" apart from "section missing", and keeps the row count equal to `summary.auto_decision_count`.
|
|
315
|
+
|
|
275
316
|
**Description Guard population:** if the §0 protocol fired during §2 (`skill-check --fix`) or §4 (`split-body`), fill the four Description Guard fields from context:
|
|
276
317
|
|
|
277
318
|
- `Restored: true` when `description_guard_restored == true`, otherwise `false`.
|
|
@@ -281,17 +322,7 @@ Add validation results to evidence-report content in context:
|
|
|
281
322
|
|
|
282
323
|
When `Restored: false`, the three follow-up fields are all `—` — this is the clean-run expected state.
|
|
283
324
|
|
|
284
|
-
### 9.
|
|
285
|
-
|
|
286
|
-
**Conditional interaction step.** If tessl produced suggestions, section 6b halts for user input. Otherwise, auto-proceed.
|
|
287
|
-
|
|
288
|
-
After validation completes (including any user decisions from section 6b), immediately load, read entire file, then execute `{nextStepFile}`.
|
|
289
|
-
|
|
290
|
-
- Tool unavailability is a skip, not a halt
|
|
291
|
-
- Validation failures are warnings — proceed to artifact generation
|
|
292
|
-
- tessl gate only triggers when suggestions exist — no gate for clean reviews or unavailable tools
|
|
293
|
-
|
|
294
|
-
## CRITICAL STEP COMPLETION NOTE
|
|
325
|
+
### 9. Auto-Proceed
|
|
295
326
|
|
|
296
|
-
|
|
327
|
+
Conditional interaction: §6b halts for user input only when tessl produced novel suggestions; otherwise the step auto-proceeds. After validation completes (including any §6b decisions), load `{nextStepFile}`, read it fully, then execute it. Tool unavailability and validation failures are skips and warnings, never halts.
|
|
297
328
|
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.10"
|
|
4
|
+
# ///
|
|
5
|
+
"""Deterministic doc-rot correction-indicator scan (step-doc-rot.md §2).
|
|
6
|
+
|
|
7
|
+
step 5c self-declares its correction scan "grep-based and deterministic — no AI
|
|
8
|
+
judgment is used for detection." This helper *is* that grep: it walks the
|
|
9
|
+
resolved feeder artifacts, matches every line against the fixed 13-row
|
|
10
|
+
correction-pattern table with case-insensitive substring containment (no regex,
|
|
11
|
+
no semantics), and emits the matches as JSON. It applies the one positional
|
|
12
|
+
filter the step documents — dropping matches that land inside the compiled
|
|
13
|
+
SKILL.md's own `## Migration & Deprecation Warnings` section (compile §4b
|
|
14
|
+
already authored those, so re-emitting them would be circular) — before it
|
|
15
|
+
returns. Running the scan here (instead of in-prompt) makes the "deterministic,
|
|
16
|
+
identical input → identical output" promise actually hold: the model no longer
|
|
17
|
+
hand-greps multi-KB artifacts.
|
|
18
|
+
|
|
19
|
+
The genuine judgment the step keeps in-prompt is untouched by this script:
|
|
20
|
+
enriching each match's `affected` symbol from surrounding context (§2) and
|
|
21
|
+
choosing where the `## CORRECTION` block goes (§3). The script emits every
|
|
22
|
+
deterministic field (`source`, `pattern`, `category`, `context_line`,
|
|
23
|
+
`line_number`); the prompt adds `affected`.
|
|
24
|
+
|
|
25
|
+
The pattern table and category labels are the contract in step-doc-rot.md §2 —
|
|
26
|
+
keep the two in lockstep.
|
|
27
|
+
|
|
28
|
+
CLI usage:
|
|
29
|
+
uv run scan-doc-rot.py --skill-md <staged SKILL.md> [FEEDER ...]
|
|
30
|
+
uv run scan-doc-rot.py --feeder evidence-report.md --feeder provenance-map.json
|
|
31
|
+
|
|
32
|
+
--skill-md the compiled/staged SKILL.md feeder (feeder #4); matches inside its
|
|
33
|
+
`## Migration & Deprecation Warnings` section are excluded. It is
|
|
34
|
+
also scanned like any other feeder.
|
|
35
|
+
FEEDER any other feeder artifact (evidence-report.md, provenance-map.json,
|
|
36
|
+
temporal-context files). Repeatable positionally or via --feeder.
|
|
37
|
+
|
|
38
|
+
Missing or empty files are skipped silently (not an error), matching §1's
|
|
39
|
+
"attempt to load; if it does not exist or is empty, skip it."
|
|
40
|
+
|
|
41
|
+
Output (stdout, one object):
|
|
42
|
+
{
|
|
43
|
+
"scanned": ["<path>", ...], # feeders that existed and were non-empty
|
|
44
|
+
"matches": [ # correction_matches[] (affected added in-prompt)
|
|
45
|
+
{"source": "<path>", "pattern": "deprecated", "category": "Deprecation",
|
|
46
|
+
"context_line": "<line text>", "line_number": <1-indexed int>},
|
|
47
|
+
...
|
|
48
|
+
],
|
|
49
|
+
"match_count": <int>,
|
|
50
|
+
"excluded_count": <int> # SKILL.md matches dropped by the §4b filter
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
Exit codes:
|
|
54
|
+
0 — scan emitted successfully (including the zero-match case)
|
|
55
|
+
1 — invalid arguments (no feeders supplied)
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
from __future__ import annotations
|
|
59
|
+
|
|
60
|
+
import argparse
|
|
61
|
+
import json
|
|
62
|
+
import re
|
|
63
|
+
import sys
|
|
64
|
+
from pathlib import Path
|
|
65
|
+
|
|
66
|
+
# Fixed correction-pattern table — mirrors step-doc-rot.md §2. (pattern, category)
|
|
67
|
+
# Order is the table order; scanning is per-pattern so overlapping patterns
|
|
68
|
+
# (e.g. "deprecated" ⊂ "@deprecated") each record their own hit, exactly as the
|
|
69
|
+
# 13-row table enumerates them.
|
|
70
|
+
PATTERN_TABLE: list[tuple[str, str]] = [
|
|
71
|
+
("deprecated", "Deprecation"),
|
|
72
|
+
("@deprecated", "Deprecation"),
|
|
73
|
+
("breaking change", "Breaking change"),
|
|
74
|
+
("BREAKING", "Breaking change"),
|
|
75
|
+
("removed in", "Removal"),
|
|
76
|
+
("was removed", "Removal"),
|
|
77
|
+
("renamed to", "Rename"),
|
|
78
|
+
("renamed from", "Rename"),
|
|
79
|
+
("superseded by", "Supersession"),
|
|
80
|
+
("replaced by", "Supersession"),
|
|
81
|
+
("no longer supported", "End of life"),
|
|
82
|
+
("migration required", "Migration"),
|
|
83
|
+
("signature changed", "Signature change"),
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
_MIGRATION_HEADING = re.compile(r"^\s*##\s+Migration\s*&\s*Deprecation Warnings", re.IGNORECASE)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def scan_text(text: str, source: str) -> list[dict]:
|
|
90
|
+
"""Case-insensitive substring scan of `text` against PATTERN_TABLE.
|
|
91
|
+
|
|
92
|
+
Returns one record per (line, pattern) hit, in file order then table order.
|
|
93
|
+
line_number is 1-indexed. Pure — no I/O.
|
|
94
|
+
"""
|
|
95
|
+
matches: list[dict] = []
|
|
96
|
+
for idx, line in enumerate(text.splitlines(), start=1):
|
|
97
|
+
lowered = line.lower()
|
|
98
|
+
for pattern, category in PATTERN_TABLE:
|
|
99
|
+
if pattern.lower() in lowered:
|
|
100
|
+
matches.append(
|
|
101
|
+
{
|
|
102
|
+
"source": source,
|
|
103
|
+
"pattern": pattern,
|
|
104
|
+
"category": category,
|
|
105
|
+
"context_line": line.strip(),
|
|
106
|
+
"line_number": idx,
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
return matches
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def migration_section_range(skill_md_text: str) -> tuple[int, int] | None:
|
|
113
|
+
"""1-indexed [start, end) line window of the `## Migration & Deprecation
|
|
114
|
+
Warnings` section, or None if the section is absent.
|
|
115
|
+
|
|
116
|
+
start = the heading line; end = the next level-2 (`## `) heading, or EOF.
|
|
117
|
+
A `### ` subsection inside the section does NOT close it (only a sibling
|
|
118
|
+
`## ` heading does), matching §2's "before the next `##` heading".
|
|
119
|
+
"""
|
|
120
|
+
lines = skill_md_text.splitlines()
|
|
121
|
+
start = None
|
|
122
|
+
for idx, line in enumerate(lines, start=1):
|
|
123
|
+
if _MIGRATION_HEADING.match(line):
|
|
124
|
+
start = idx
|
|
125
|
+
break
|
|
126
|
+
if start is None:
|
|
127
|
+
return None
|
|
128
|
+
end = len(lines) + 1
|
|
129
|
+
for idx in range(start + 1, len(lines) + 1):
|
|
130
|
+
if lines[idx - 1].lstrip().startswith("## "):
|
|
131
|
+
end = idx
|
|
132
|
+
break
|
|
133
|
+
return (start, end)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def apply_migration_exclusion(
|
|
137
|
+
matches: list[dict], skill_md_source: str | None, skill_md_text: str | None
|
|
138
|
+
) -> tuple[list[dict], int]:
|
|
139
|
+
"""Drop matches whose source is the compiled SKILL.md and whose line sits
|
|
140
|
+
inside its Migration & Deprecation Warnings section. Returns (kept, dropped)."""
|
|
141
|
+
if skill_md_source is None or skill_md_text is None:
|
|
142
|
+
return matches, 0
|
|
143
|
+
window = migration_section_range(skill_md_text)
|
|
144
|
+
if window is None:
|
|
145
|
+
return matches, 0
|
|
146
|
+
start, end = window
|
|
147
|
+
kept, dropped = [], 0
|
|
148
|
+
for m in matches:
|
|
149
|
+
if m["source"] == skill_md_source and start <= m["line_number"] < end:
|
|
150
|
+
dropped += 1
|
|
151
|
+
continue
|
|
152
|
+
kept.append(m)
|
|
153
|
+
return kept, dropped
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _load(path: str) -> str | None:
|
|
157
|
+
"""Read a feeder file as UTF-8. Returns None for missing/empty (skip, not
|
|
158
|
+
an error). UTF-8 avoids cp1252 mojibake on Windows."""
|
|
159
|
+
p = Path(path)
|
|
160
|
+
if not p.is_file():
|
|
161
|
+
return None
|
|
162
|
+
text = p.read_text(encoding="utf-8")
|
|
163
|
+
if not text.strip():
|
|
164
|
+
return None
|
|
165
|
+
return text
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def scan_files(feeders: list[str], skill_md: str | None) -> dict:
|
|
169
|
+
"""Scan every feeder + the skill-md feeder, apply the §4b exclusion, return
|
|
170
|
+
the result object. Deterministic: feeders scanned in the given order."""
|
|
171
|
+
scanned: list[str] = []
|
|
172
|
+
matches: list[dict] = []
|
|
173
|
+
skill_md_text = None
|
|
174
|
+
|
|
175
|
+
ordered = list(feeders)
|
|
176
|
+
if skill_md is not None:
|
|
177
|
+
ordered.append(skill_md)
|
|
178
|
+
|
|
179
|
+
for path in ordered:
|
|
180
|
+
text = _load(path)
|
|
181
|
+
if text is None:
|
|
182
|
+
continue
|
|
183
|
+
scanned.append(path)
|
|
184
|
+
if path == skill_md:
|
|
185
|
+
skill_md_text = text
|
|
186
|
+
matches.extend(scan_text(text, path))
|
|
187
|
+
|
|
188
|
+
matches, excluded = apply_migration_exclusion(matches, skill_md, skill_md_text)
|
|
189
|
+
return {
|
|
190
|
+
"scanned": scanned,
|
|
191
|
+
"matches": matches,
|
|
192
|
+
"match_count": len(matches),
|
|
193
|
+
"excluded_count": excluded,
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
198
|
+
parser = argparse.ArgumentParser(
|
|
199
|
+
prog="scan-doc-rot",
|
|
200
|
+
description=(
|
|
201
|
+
"Deterministic doc-rot correction-indicator scan (step-doc-rot.md §2): "
|
|
202
|
+
"case-insensitive substring match of feeder artifacts against the fixed "
|
|
203
|
+
"correction-pattern table, with the compiled SKILL.md's Migration & "
|
|
204
|
+
"Deprecation Warnings section excluded."
|
|
205
|
+
),
|
|
206
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
207
|
+
)
|
|
208
|
+
parser.add_argument(
|
|
209
|
+
"positional_feeders",
|
|
210
|
+
nargs="*",
|
|
211
|
+
metavar="FEEDER",
|
|
212
|
+
help="Feeder artifact paths (evidence-report.md, provenance-map.json, temporal files).",
|
|
213
|
+
)
|
|
214
|
+
parser.add_argument(
|
|
215
|
+
"--feeder",
|
|
216
|
+
action="append",
|
|
217
|
+
default=[],
|
|
218
|
+
dest="feeders",
|
|
219
|
+
help="Feeder artifact path (repeatable). Equivalent to a positional FEEDER.",
|
|
220
|
+
)
|
|
221
|
+
parser.add_argument(
|
|
222
|
+
"--skill-md",
|
|
223
|
+
dest="skill_md",
|
|
224
|
+
default=None,
|
|
225
|
+
help="Compiled/staged SKILL.md feeder; its Migration & Deprecation Warnings "
|
|
226
|
+
"section is excluded from matches.",
|
|
227
|
+
)
|
|
228
|
+
return parser
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def main(argv=None) -> int:
|
|
232
|
+
parser = _build_parser()
|
|
233
|
+
args = parser.parse_args(argv)
|
|
234
|
+
feeders = list(args.positional_feeders) + list(args.feeders)
|
|
235
|
+
if not feeders and args.skill_md is None:
|
|
236
|
+
parser.print_usage(file=sys.stderr)
|
|
237
|
+
print("error: supply at least one feeder path or --skill-md", file=sys.stderr)
|
|
238
|
+
return 1
|
|
239
|
+
result = scan_files(feeders, args.skill_md)
|
|
240
|
+
print(json.dumps(result, indent=2))
|
|
241
|
+
return 0
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
if __name__ == "__main__":
|
|
245
|
+
raise SystemExit(main())
|
|
@@ -12,6 +12,7 @@ Produces a consolidated stack skill documenting how libraries connect. **Code-mo
|
|
|
12
12
|
## Conventions
|
|
13
13
|
|
|
14
14
|
- Bare paths (e.g. `references/<name>.md`) resolve from the skill root.
|
|
15
|
+
- The `knowledge/` and `shared/` prefixes are the exception: they resolve from the **SKF module root** (`{project-root}/_bmad/skf/` when installed, `src/` during development), not the skill root — they point at module-shared reference docs (`knowledge/tool-resolution.md`, `knowledge/version-paths.md`) and scripts/schemas (`shared/references/…`) that live once at the module root, mirroring the resolution note `references/health-check.md` carries for `shared/health-check.md`.
|
|
15
16
|
- `references/` holds prompt content carved out of SKILL.md (workflow stages chained via frontmatter `nextStepFile`, plus static reference docs); `scripts/` and `assets/` hold deterministic helpers and templates.
|
|
16
17
|
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives, if present).
|
|
17
18
|
- `{project-root}`-prefixed paths resolve from the project working directory.
|
|
@@ -34,7 +35,9 @@ These rules apply to every step in this workflow:
|
|
|
34
35
|
|
|
35
36
|
## Workflow state contract
|
|
36
37
|
|
|
37
|
-
Every step that emits a warning
|
|
38
|
+
Every step that emits a warning appends a structured entry to a single in-memory list named `workflow_warnings[]` (the one accumulator for the whole workflow). Each entry has the shape `{step: "step-NN", severity: "info|warn|error", code: "<short-slug>", message: "<human text>", context: {<optional fields>}}`. Step 7 surfaces these in `evidence-report.md`; step 8 may add validation findings; step 9 §5 reads the accumulated list and renders the user-facing "Warnings" section.
|
|
39
|
+
|
|
40
|
+
**Single-pass — no mid-run checkpoint.** State lives in memory until step 7 commits `provenance-map.json`/`evidence-report.md`; the workflow keeps no resumable checkpoint and On Activation does not probe for a prior run — the analysis is deterministic and cheap to redo, and the two gates are trivially re-confirmed. If interrupted before that commit, restart from step 1.
|
|
38
41
|
|
|
39
42
|
## Stages
|
|
40
43
|
|
|
@@ -65,14 +68,13 @@ Every step that emits a warning ("log a warning", "record in workflow state for
|
|
|
65
68
|
|
|
66
69
|
Every HARD HALT in this workflow exits with a stable code so headless automators can branch on the failure class without grepping message text:
|
|
67
70
|
|
|
68
|
-
| Code | Meaning | Raised by
|
|
71
|
+
| Code | Meaning | Raised by (halt_reason) |
|
|
69
72
|
| ---- | -------------------- | ------------------------------------------------------------------------------------------ |
|
|
70
73
|
| 0 | success | step 10 (terminal handoff to shared health-check) |
|
|
71
|
-
| 2 | input
|
|
72
|
-
| 3 | resolution-failure | step 1 §1
|
|
73
|
-
| 4 | write-failure | step 7 §1 stage-dir / commit-dir failure; step 7 §1 group-dir collision when an existing non-stack skill occupies the target path |
|
|
74
|
-
|
|
|
75
|
-
| 6 | user-cancelled | any interactive menu in step 3 / step 6 (user selected `[X]` Cancel and exit) |
|
|
74
|
+
| 2 | input / precondition invalid | step 1 §0 `config.yaml` missing/malformed (`config-missing`); step 2 §2 headless with no manifests (`no-manifests`, S2); step 4 §3 all extractions failed (`all-extractions-failed`, B7); step 5 §2 feasibility-report `schemaVersion` mismatch (`schema-version-mismatch`) |
|
|
75
|
+
| 3 | resolution-failure | step 1 §1 `forge-tier.yaml` missing (`forge-tier-missing`); step 2 §0 compose-mode skill-resolution corruption (manifest + symlink both fail); step 2 §0 compose-mode zero qualifying skills (S1/B4); step 4 §0 compose-cycle — all `resolution-failure` |
|
|
76
|
+
| 4 | write-failure | step 7 §1 stage-dir / commit-dir failure; step 7 §1 group-dir collision when an existing non-stack skill occupies the target path — both `write-failure` |
|
|
77
|
+
| 6 | user-cancelled | any interactive menu in step 3 / step 6 when the user selects `[X]` Cancel and exit (`user-cancelled`) |
|
|
76
78
|
|
|
77
79
|
## Result Contract (Headless)
|
|
78
80
|
|
|
@@ -82,7 +84,7 @@ When `{headless_mode}` is true, step 9 emits a single-line JSON envelope on **st
|
|
|
82
84
|
SKF_STACK_RESULT_JSON: {"status":"success|error","skill_package":"…|null","skill_name":"…","stack_libraries":["…"],"mode":"code|compose","exit_code":0,"halt_reason":null}
|
|
83
85
|
```
|
|
84
86
|
|
|
85
|
-
`status` is `"success"` on the terminal happy path, `"error"` on any HALT. `skill_package` is the absolute path to the committed stack-skill directory (or `null` on error before commit). `skill_name` is the stack skill's published name (e.g. `{project_name}-stack`). `stack_libraries` is the array of library names included in the stack (constituent skill names in compose-mode, dependency names in code-mode). `mode` is `"code"` or `"compose"` per the run's resolved mode. `halt_reason` is one of: `null` (success), `"
|
|
87
|
+
`status` is `"success"` on the terminal happy path, `"error"` on any HALT. `skill_package` is the absolute path to the committed stack-skill directory (or `null` on error before commit). `skill_name` is the stack skill's published name (e.g. `{project_name}-stack`). `stack_libraries` is the array of library names included in the stack (constituent skill names in compose-mode, dependency names in code-mode). `mode` is `"code"` or `"compose"` per the run's resolved mode (`null` if the run halts before mode resolution). `halt_reason` is one of: `null` (success), `"config-missing"`, `"forge-tier-missing"`, `"no-manifests"`, `"all-extractions-failed"`, `"schema-version-mismatch"`, `"resolution-failure"`, `"write-failure"`, `"user-cancelled"`. `exit_code` matches the table above. Fields unknown at the halt point are `null` (`skill_name`) or `[]` (`stack_libraries`) — e.g. a `config-missing` halt precedes `project_name` resolution.
|
|
86
88
|
|
|
87
89
|
## On Activation
|
|
88
90
|
|
|
@@ -118,6 +120,10 @@ SKF_STACK_RESULT_JSON: {"status":"success|error","skill_package":"…|null","ski
|
|
|
118
120
|
- `{composeModeRulesPath}` ← `workflow.compose_mode_rules_path` if non-empty, else `references/compose-mode-rules.md`
|
|
119
121
|
- `{provenanceMapSchemaPath}` ← `workflow.provenance_map_schema_path` if non-empty, else `assets/provenance-map-schema.md`
|
|
120
122
|
|
|
121
|
-
|
|
123
|
+
Also resolve `{onCompleteCommand}` ← `workflow.on_complete` if non-empty, else empty string (no-op — `references/report.md` §6c skips the hook invocation entirely).
|
|
124
|
+
|
|
125
|
+
Stash all five paths plus `{onCompleteCommand}` as workflow-context variables. Stage files reference `{stackSkillTemplatePath}` / `{integrationPatternsPath}` / `{manifestPatternsPath}` / `{composeModeRulesPath}` / `{provenanceMapSchemaPath}` directly; empty-string overrides fall through to the bundled default.
|
|
126
|
+
|
|
127
|
+
Also apply the array surfaces: run `workflow.activation_steps_prepend` now, keep `workflow.persistent_facts` as standing context (`file:` entries load their contents), then run `workflow.activation_steps_append` after.
|
|
122
128
|
|
|
123
129
|
4. Load, read the full file, and then execute `references/init.md` to begin the workflow.
|
|
@@ -53,7 +53,7 @@ Used when the workflow ran in code-mode against an actual codebase. `source_repo
|
|
|
53
53
|
|
|
54
54
|
## Compose-mode variant
|
|
55
55
|
|
|
56
|
-
Used when the workflow ran in compose-mode against pre-generated constituent skills. Source-anchor fields (`source_repo`, `source_commit`, `source_ref`) are `null` because there is no codebase to anchor against — provenance traces back to the constituent skills instead, captured in the `constituents[]` array. Each entry's `extraction_method` is `"compose-from-skill"`; integrations have `detection_method` of `"architecture_co_mention"` (named in the architecture doc), `"constituent_documented_contract"` (a cross-library contract documented in a constituent skill's integration docs but not co-mentioned in the architecture document — e.g. a grep-verified upstream seam cited from a source skill), or `"inferred_from_shared_domain"` (synthesized inference from shared language/domain, no cited contract). `detection_method` records *how* an edge was discovered; it is orthogonal to `confidence`, which is inherited from the constituent skills per the Confidence Tier Inheritance matrix in `
|
|
56
|
+
Used when the workflow ran in compose-mode against pre-generated constituent skills. Source-anchor fields (`source_repo`, `source_commit`, `source_ref`) are `null` because there is no codebase to anchor against — provenance traces back to the constituent skills instead, captured in the `constituents[]` array. Each entry's `extraction_method` is `"compose-from-skill"`; integrations have `detection_method` of `"architecture_co_mention"` (named in the architecture doc), `"constituent_documented_contract"` (a cross-library contract documented in a constituent skill's integration docs but not co-mentioned in the architecture document — e.g. a grep-verified upstream seam cited from a source skill), or `"inferred_from_shared_domain"` (synthesized inference from shared language/domain, no cited contract). `detection_method` records *how* an edge was discovered; it is orthogonal to `confidence`, which is inherited from the constituent skills per the Confidence Tier Inheritance matrix in `{composeModeRulesPath}` (the integration tier is the weaker of the pair — never forced to a fixed band by detection method).
|
|
57
57
|
|
|
58
58
|
```json
|
|
59
59
|
{
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# DO NOT EDIT -- overwritten on every update.
|
|
2
2
|
#
|
|
3
3
|
# Workflow customization surface for skf-create-stack-skill.
|
|
4
|
+
# Team overrides: _bmad/custom/skf-create-stack-skill.toml (under {project-root})
|
|
5
|
+
# Personal overrides: _bmad/custom/skf-create-stack-skill.user.toml (under {project-root})
|
|
4
6
|
|
|
5
7
|
[workflow]
|
|
6
8
|
|
|
@@ -34,6 +36,14 @@ persistent_facts = [
|
|
|
34
36
|
"file:{project-root}/**/project-context.md",
|
|
35
37
|
]
|
|
36
38
|
|
|
39
|
+
# Command invoked once the workflow reaches its terminal stage — after the
|
|
40
|
+
# stack package is committed and the result contract is written (step 9,
|
|
41
|
+
# report.md §6c), before the health-check chain. Override wins. Use for a
|
|
42
|
+
# terminal action such as catalog registration or notifying a downstream
|
|
43
|
+
# pipeline. Empty string = no terminal action. The hook never fails the run.
|
|
44
|
+
|
|
45
|
+
on_complete = ""
|
|
46
|
+
|
|
37
47
|
# --- Optional asset overrides ---
|
|
38
48
|
#
|
|
39
49
|
# Lift the canonical asset paths so orgs can substitute house-style copies
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
---
|
|
2
2
|
nextStepFile: 'generate-output.md'
|
|
3
|
-
stackSkillTemplate: 'assets/stack-skill-template.md'
|
|
4
3
|
---
|
|
5
4
|
|
|
6
5
|
<!-- Config: communicate in {communication_language}. Artifact text in {document_output_language}. -->
|
|
@@ -13,7 +12,7 @@ Assemble the main SKILL.md by combining per-library extractions with the integra
|
|
|
13
12
|
|
|
14
13
|
## Rules
|
|
15
14
|
|
|
16
|
-
- Compile SKILL.md following the stack-skill-template structure — integration patterns go first
|
|
15
|
+
- Compile SKILL.md following the stack-skill-template structure — integration patterns go first
|
|
17
16
|
- Do not write output files (Step 07)
|
|
18
17
|
- Present compiled content for user review
|
|
19
18
|
|
|
@@ -21,7 +20,7 @@ Assemble the main SKILL.md by combining per-library extractions with the integra
|
|
|
21
20
|
|
|
22
21
|
### 1. Load Template Structure
|
|
23
22
|
|
|
24
|
-
Load `{
|
|
23
|
+
Load `{stackSkillTemplatePath}` and prepare SKILL.md section structure.
|
|
25
24
|
|
|
26
25
|
### 2. Generate Frontmatter
|
|
27
26
|
|
|
@@ -41,12 +40,12 @@ description: >
|
|
|
41
40
|
**Frontmatter rules:**
|
|
42
41
|
|
|
43
42
|
- `name`: lowercase alphanumeric + hyphens only, must match skill output directory name. **Stack skills MUST end in `-stack`** (e.g., `{project_name}-stack`) — this is how consumers (skf-verify-stack, skf-test-skill) detect stack vs individual skills.
|
|
44
|
-
- `description`: non-empty, max 1024 chars, trigger-optimized for agent discovery. MUST use third-person voice ("Processes..." not "I can..." or "You can..."). **Do NOT enumerate every library by name** — a 12+ library stack overruns 1024 chars. Keep the generic "{lib_count} libraries with {integration_count} integration patterns" form; if a per-library parenthetical is used, cap it to the top libraries by import/export count with a `+{N} more` suffix (full list lives in `metadata.json` `libraries[]`). See "Sizing Guidance for Large Stacks" in `{
|
|
43
|
+
- `description`: non-empty, max 1024 chars, trigger-optimized for agent discovery. MUST use third-person voice ("Processes..." not "I can..." or "You can..."). **Do NOT enumerate every library by name** — a 12+ library stack overruns 1024 chars. Keep the generic "{lib_count} libraries with {integration_count} integration patterns" form; if a per-library parenthetical is used, cap it to the top libraries by import/export count with a `+{N} more` suffix (full list lives in `metadata.json` `libraries[]`). See "Sizing Guidance for Large Stacks" in `{stackSkillTemplatePath}`.
|
|
45
44
|
- No other frontmatter fields — only `name`, `description`, `license`, `compatibility`, `metadata`, `allowed-tools` are permitted by spec
|
|
46
45
|
|
|
47
46
|
### 3. Compile Integration Layer
|
|
48
47
|
|
|
49
|
-
|
|
48
|
+
Compile in order:
|
|
50
49
|
|
|
51
50
|
**Zero-integration guard:** If the integration graph from step 05 has zero edges (no detected integration pairs), skip the integration layer compilation and note: "No integration patterns detected — stack skill will contain library summaries without an integration layer." Proceed directly to section 4 (Per-Library Sections).
|
|
52
51
|
|
|
@@ -72,7 +71,7 @@ description: >
|
|
|
72
71
|
`Per-Library Summaries` and `Library Reference Index` are the largest sections
|
|
73
72
|
and grow with the stack. For a **large stack** (heuristic: **> 6 libraries OR
|
|
74
73
|
> 6 integration patterns**), author both into `references/stack-catalog.md`
|
|
75
|
-
(structure in `{
|
|
74
|
+
(structure in `{stackSkillTemplatePath}`) and place only the inline pointer from the
|
|
76
75
|
template's "Sizing Guidance" in SKILL.md — this keeps the body under the 500-line
|
|
77
76
|
`body.max_lines` budget that step 08 enforces. For a **small stack**, keep both
|
|
78
77
|
inline (inline passive context yields higher task accuracy). Either way, step 08's
|
|
@@ -132,9 +131,9 @@ Display: **Select:** [C] Continue to Output Generation | [X] Cancel and exit
|
|
|
132
131
|
|
|
133
132
|
#### EXECUTION RULES:
|
|
134
133
|
|
|
135
|
-
-
|
|
134
|
+
- This is a review gate — advancing without the user's `C` would write output files from a compilation they never reviewed. Halt and wait for input after presenting the compiled SKILL.md.
|
|
136
135
|
- **GATE [default: C]** — If `{headless_mode}`: auto-proceed with [C] Continue, log: "headless: auto-approve stack compilation"
|
|
137
|
-
-
|
|
136
|
+
- Proceed to the next step only once the user approves by selecting `C`.
|
|
138
137
|
|
|
139
138
|
#### Menu Handling Logic:
|
|
140
139
|
|
|
@@ -6,18 +6,7 @@ Rules for synthesizing a stack skill from pre-generated individual skills and an
|
|
|
6
6
|
|
|
7
7
|
## Skill Loading
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
**Version-aware skill enumeration:**
|
|
12
|
-
|
|
13
|
-
1. **Primary: Export manifest** — Read `{skills_output_folder}/.export-manifest.json`. For each entry in `exports`, resolve the active version path: `{skills_output_folder}/{skill-name}/{active_version}/{skill-name}/` — this directory must contain both `SKILL.md` and `metadata.json`.
|
|
14
|
-
2. **Fallback: `active` symlinks** — If the manifest does not exist, is empty, or a manifest entry lacks an `active_version`, scan for `{skills_output_folder}/*/active/*/SKILL.md`. Each match resolves to a skill package at `{skills_output_folder}/{skill-name}/active/{skill-name}/` (the `{active_skill}` template).
|
|
15
|
-
3. **Filter:** Skip any skill named `{project_name}-stack` or any skill where `metadata.json` has `"skill_type": "stack"` — stack skills must not be loaded as source dependencies to avoid self-referencing loops
|
|
16
|
-
4. Skip any resolved skill package missing either `SKILL.md` or `metadata.json` — log a warning and skip
|
|
17
|
-
5. Load each `metadata.json` from the resolved version-aware path and extract: `name`, `language`, `confidence_tier`, `source_repo`, `exports` (count), `version`
|
|
18
|
-
6. Store the skill group directory name as `skill_dir` (the top-level name under `{skills_output_folder}`, distinct from `name` — the directory may differ from the metadata name)
|
|
19
|
-
7. Store the resolved package path as `skill_package_path` for use in later steps (extraction, integration detection)
|
|
20
|
-
8. Store loaded skills as `raw_dependencies` with `source: "existing_skill"`
|
|
9
|
+
The version-aware skill-enumeration protocol (export-manifest primary → active-symlink fallback → stack-skill filter → load metadata fields → store as `raw_dependencies`) is specified operationally in `detect-manifests.md` §0. That step runs at step 2 and has already enumerated and loaded every constituent skill into workflow state before this file is consulted at step 5, so the loading is not re-performed here — this file covers only the compose-mode composition rules below.
|
|
21
10
|
|
|
22
11
|
## Compose-mode Co-mention Precision
|
|
23
12
|
|
|
@@ -86,4 +75,4 @@ When no architecture document is available:
|
|
|
86
75
|
- Mark all inferred integrations: `[inferred from shared domain]`
|
|
87
76
|
- Inferred integrations default to lowest confidence of the pair with `[inferred from shared domain]` suffix (use this instead of `[composed]` for inferred integrations)
|
|
88
77
|
|
|
89
|
-
**Constituent-documented contracts (distinct from shared-domain inference):** When a constituent skill's own integration docs cite a verifiable cross-library contract (e.g. a grep-verified upstream seam) that the architecture document does not co-mention, record it with `detection_method: constituent_documented_contract` (see `
|
|
78
|
+
**Constituent-documented contracts (distinct from shared-domain inference):** When a constituent skill's own integration docs cite a verifiable cross-library contract (e.g. a grep-verified upstream seam) that the architecture document does not co-mention, record it with `detection_method: constituent_documented_contract` (see `{provenanceMapSchemaPath}`) — NOT `inferred_from_shared_domain`. It is a cited contract, not a synthesized guess. Its confidence still inherits the weaker tier of the pair per the matrix above — detection method is orthogonal to tier and never forces a fixed band.
|