bmad-module-skill-forge 3.0.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +3 -3
- package/docs/_data/pinned.yaml +19 -13
- package/docs/architecture.md +2 -2
- package/docs/concepts.md +4 -4
- package/docs/examples.md +33 -26
- package/docs/getting-started.md +3 -3
- package/docs/how-it-works.md +2 -2
- package/docs/index.md +1 -1
- package/docs/skill-model.md +41 -39
- package/docs/troubleshooting.md +8 -0
- package/docs/verifying-a-skill.md +46 -21
- package/docs/why-skf.md +1 -1
- package/docs/workflows.md +5 -5
- package/package.json +1 -1
- package/src/knowledge/qmd-registry.md +1 -1
- package/src/knowledge/version-paths.md +1 -1
- package/src/shared/health-check.md +2 -50
- package/src/shared/scripts/schemas/skf-audit-result-envelope.v1.json +2 -2
- package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +12 -4
- package/src/shared/scripts/schemas/skill-brief.v1.json +1 -1
- package/src/shared/scripts/skf-atomic-write.py +212 -59
- package/src/shared/scripts/skf-build-change-manifest.py +1437 -178
- package/src/shared/scripts/skf-classify-changed-files.py +78 -0
- package/src/shared/scripts/skf-compare-file-hashes.py +266 -104
- package/src/shared/scripts/skf-count-tokens.py +54 -10
- package/src/shared/scripts/skf-derive-assembly-shape.py +18 -1
- package/src/shared/scripts/skf-detect-scripts-assets.py +196 -54
- package/src/shared/scripts/skf-detect-workspaces.py +67 -0
- package/src/shared/scripts/skf-emit-result-envelope.py +6 -5
- package/src/shared/scripts/skf-extract-public-api.py +1673 -189
- package/src/shared/scripts/skf-extraction-inventory.py +584 -46
- package/src/shared/scripts/skf-extraction-snapshot.py +158 -33
- package/src/shared/scripts/skf-forge-tier-rw.py +26 -3
- package/src/shared/scripts/skf-github-probe.py +47 -8
- package/src/shared/scripts/skf-hash-content.py +14 -8
- package/src/shared/scripts/skf-load-provenance.py +3 -1
- package/src/shared/scripts/skf-merge-doc-urls.py +142 -19
- package/src/shared/scripts/skf-parse-gaps.py +135 -26
- package/src/shared/scripts/skf-preflight.py +15 -1
- package/src/shared/scripts/skf-provenance-gap-dispatch.py +25 -1
- package/src/shared/scripts/skf-render-metadata-stats.py +1 -1
- package/src/shared/scripts/skf-resolve-authoritative-files.py +29 -3
- package/src/shared/scripts/skf-resolve-customization.py +197 -0
- package/src/shared/scripts/skf-shard-body.py +239 -41
- package/src/shared/scripts/skf-structural-diff.py +133 -18
- package/src/shared/scripts/skf-update-active-symlink.py +115 -62
- package/src/shared/scripts/skf-validate-brief-schema.py +19 -1
- package/src/shared/scripts/skf-validate-output.py +12 -3
- package/src/shared/scripts/skf-verify-provenance-completeness.py +1439 -79
- package/src/shared/scripts/skf-write-skill-brief.py +41 -8
- package/src/shared/tool-requirements.yaml +8 -2
- package/src/skf-analyze-source/SKILL.md +2 -2
- package/src/skf-analyze-source/assets/skill-brief-schema.md +1 -1
- package/src/skf-analyze-source/references/map-unit-exports.md +1 -1
- package/src/skf-audit-skill/SKILL.md +2 -2
- package/src/skf-audit-skill/assets/drift-report-template.md +1 -0
- package/src/skf-audit-skill/references/headless-contract.md +1 -1
- package/src/skf-audit-skill/references/health-check.md +2 -0
- package/src/skf-audit-skill/references/init.md +2 -1
- package/src/skf-audit-skill/references/re-index.md +14 -9
- package/src/skf-audit-skill/references/report.md +2 -2
- package/src/skf-audit-skill/references/semantic-diff.md +15 -15
- package/src/skf-audit-skill/references/structural-diff.md +10 -5
- package/src/skf-audit-skill/scripts/render-drift-tables.py +29 -2
- package/src/skf-brief-skill/SKILL.md +3 -3
- package/src/skf-brief-skill/assets/scope-templates.md +1 -1
- package/src/skf-brief-skill/assets/skill-brief-schema.md +4 -11
- package/src/skf-brief-skill/references/analyze-target.md +6 -6
- package/src/skf-brief-skill/references/confirm-brief.md +1 -1
- package/src/skf-brief-skill/references/scope-definition.md +4 -4
- package/src/skf-brief-skill/references/step-auto-brief.md +11 -29
- package/src/skf-brief-skill/references/version-resolution.md +5 -9
- package/src/skf-campaign/SKILL.md +2 -2
- package/src/skf-campaign/references/step-05-skill-loop.md +6 -1
- package/src/skf-create-skill/SKILL.md +2 -2
- package/src/skf-create-skill/assets/compile-assembly-rules.md +5 -3
- package/src/skf-create-skill/assets/skill-sections.md +7 -7
- package/src/skf-create-skill/references/authoritative-files-protocol.md +2 -23
- package/src/skf-create-skill/references/compile.md +31 -60
- package/src/skf-create-skill/references/component-extraction.md +3 -3
- package/src/skf-create-skill/references/enrich.md +2 -2
- package/src/skf-create-skill/references/entry-points-by-hand.md +24 -7
- package/src/skf-create-skill/references/extract.md +23 -19
- package/src/skf-create-skill/references/extraction-patterns-tracing.md +9 -9
- package/src/skf-create-skill/references/extraction-patterns.md +10 -11
- package/src/skf-create-skill/references/generate-artifacts.md +1 -1
- package/src/skf-create-skill/references/report.md +21 -5
- package/src/skf-create-skill/references/source-resolution-protocols.md +12 -17
- package/src/skf-create-skill/references/sub/fetch-docs.md +1 -1
- package/src/skf-create-stack-skill/SKILL.md +2 -2
- package/src/skf-create-stack-skill/references/validate.md +1 -1
- package/src/skf-drop-skill/SKILL.md +2 -2
- package/src/skf-drop-skill/references/execute.md +5 -1
- package/src/skf-drop-skill/references/health-check.md +2 -0
- package/src/skf-drop-skill/references/invocation-contract.md +1 -1
- package/src/skf-drop-skill/references/report.md +2 -2
- package/src/skf-drop-skill/references/select.md +7 -3
- package/src/skf-export-skill/SKILL.md +2 -2
- package/src/skf-export-skill/assets/snippet-format.md +5 -5
- package/src/skf-export-skill/references/generate-snippet.md +22 -15
- package/src/skf-export-skill/references/load-skill.md +1 -1
- package/src/skf-export-skill/references/token-report.md +29 -9
- package/src/skf-forger/SKILL.md +2 -2
- package/src/skf-quick-skill/SKILL.md +2 -2
- package/src/skf-refine-architecture/SKILL.md +6 -8
- package/src/skf-rename-skill/SKILL.md +2 -2
- package/src/skf-setup/SKILL.md +2 -2
- package/src/skf-test-skill/SKILL.md +2 -2
- package/src/skf-test-skill/assets/output-section-formats.md +6 -4
- package/src/skf-test-skill/references/coherence-check.md +5 -4
- package/src/skf-test-skill/references/coverage-check-branches.md +61 -0
- package/src/skf-test-skill/references/coverage-check-tiers.md +5 -5
- package/src/skf-test-skill/references/coverage-check.md +69 -111
- package/src/skf-test-skill/references/init.md +5 -6
- package/src/skf-test-skill/references/migration-section-rules.md +26 -14
- package/src/skf-test-skill/references/report.md +11 -9
- package/src/skf-test-skill/references/scoring-rules.md +8 -6
- package/src/skf-test-skill/references/source-access-protocol.md +15 -18
- package/src/skf-test-skill/scripts/build-result-context.py +114 -21
- package/src/skf-test-skill/scripts/gap-ledger.py +580 -80
- package/src/skf-test-skill/scripts/load-coverage-inputs.py +646 -107
- package/src/skf-test-skill/scripts/locate-export-segments.py +101 -20
- package/src/skf-test-skill/scripts/reconcile-coverage.py +450 -23
- package/src/skf-test-skill/scripts/score-signatures.py +356 -61
- package/src/skf-test-skill/scripts/stage-helper-payload.py +64 -13
- package/src/skf-test-skill/scripts/validate-inventory.py +108 -9
- package/src/skf-test-skill/scripts/verify-declared-numerator.py +7 -5
- package/src/skf-test-skill/templates/test-report-template.md +1 -0
- package/src/skf-update-skill/SKILL.md +2 -2
- package/src/skf-update-skill/references/detect-changes.md +32 -13
- package/src/skf-update-skill/references/gap-driven.md +37 -22
- package/src/skf-update-skill/references/init.md +6 -2
- package/src/skf-update-skill/references/invocation-contract.md +1 -1
- package/src/skf-update-skill/references/merge.md +17 -12
- package/src/skf-update-skill/references/re-extract.md +4 -2
- package/src/skf-update-skill/references/report.md +7 -4
- package/src/skf-update-skill/references/write.md +77 -36
- package/src/skf-update-skill/scripts/skf-new-file-diff.py +157 -42
- package/src/skf-verify-stack/SKILL.md +2 -2
- package/tools/cli/lib/tool-check.js +158 -2
|
@@ -32,7 +32,7 @@ You are a skill scoping architect collaborating with a developer who wants to cr
|
|
|
32
32
|
These rules apply to every step in this workflow:
|
|
33
33
|
|
|
34
34
|
- Only load one step file at a time — never preload future steps
|
|
35
|
-
- **Lazy-load references and assets:** `references/*.md` and `assets/*.md` files are loaded inside the section that needs them, not at step entry. If a section is skipped (e.g. `version-resolution.md` when
|
|
35
|
+
- **Lazy-load references and assets:** `references/*.md` and `assets/*.md` files are loaded inside the section that needs them, not at step entry. If a section is skipped (e.g. `version-resolution.md` when step 2's extractor ran, `scope-templates.md` for the `docs-only` branch that bypasses §2c), do not load that file. Each unnecessary load costs context (~5-10 KB per reference) and biases the LLM toward consulting material the current path does not need.
|
|
36
36
|
- Always communicate in `{communication_language}` (the language for user-facing prose). Written artifact text — the `description`, `notes`, and other free-form fields persisted into `skill-brief.yaml` — is in `{document_output_language}`; per-step rules call this out where it applies (see step 5). The two values may be the same.
|
|
37
37
|
- If `{headless_mode}` is true, auto-proceed through confirmation gates with their default action and log each auto-decision
|
|
38
38
|
- Keep one list of the run's warnings, `workflow_warnings[]`, in the order they are raised: each `warn:` line a step logs, each entry of a helper's `warnings[]` that a step surfaces, and each degraded signal the run goes on past (a truncated tree listing, a failed doc detection or QMD step), one line of text each. A helper warning that is an object, such as the `{field, message}` entries skf-validate-brief-inputs.py and skf-validate-brief-schema.py return, goes in as one line, `<field>: <message>`: the emitter takes only strings, and one object in `warnings` makes it exit non-zero and print no line. The run's `SKF_BRIEF_RESULT_JSON` envelope carries the list as `warnings`, at the end of a successful run and at a halt
|
|
@@ -58,10 +58,10 @@ Give the halt's own `halt_reason`: the helper derives `exit_code` from it. `skil
|
|
|
58
58
|
|
|
59
59
|
**Resolve `{auto_mode}`**: true when the invocation carries the `[auto]` flag (a pipeline's `BS[auto]`, which step 1 §1b routes to the auto stages), else false.
|
|
60
60
|
|
|
61
|
-
3. **Resolve workflow customization.**
|
|
61
|
+
3. **Resolve workflow customization.** Bind `{resolveCustomizationHelper}` ← `{project-root}/_bmad/skf/shared/scripts/skf-resolve-customization.py`, else `{project-root}/src/shared/scripts/skf-resolve-customization.py` (the first that exists), and run:
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
|
-
uv run {
|
|
64
|
+
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-brief-skill.toml` (team overrides, committed) and `.user.toml` (personal overrides, gitignored). When it exits non-zero, prints no JSON or is missing, print one line, `[activation/warn] customization_resolver_unavailable: <reason>` (`<reason>`: its first stderr line, `not found` when the script is missing, `no JSON` when it printed none). If the resolver cannot run, read `{skill-root}/customize.toml` alone and use its bundled defaults: the `{project-root}/_bmad/custom/` overrides do not apply to this run. In that case add `customization_resolver_unavailable: <reason>` to `workflow_warnings[]` as the run's first warning.
|
|
@@ -133,4 +133,4 @@ Prompt: "Does this component library scope look right? Adjust before continuing.
|
|
|
133
133
|
|
|
134
134
|
## Scripts & Assets Detection (Optional Refinement)
|
|
135
135
|
|
|
136
|
-
When `scripts_intent` or `assets_intent` is `detect` (default), SKF auto-detects from source directories matching: `scripts/`, `bin/`, `tools/`, `cli/` (for scripts) and `assets/`, `templates/`, `schemas/`, `configs/`, `examples/` (for assets). Detection applies to all scope types except `docs-only`.
|
|
136
|
+
When `scripts_intent` or `assets_intent` is `detect` (default), SKF auto-detects from source directories matching: `scripts/`, `bin/`, `tools/`, `cli/` (for scripts; when one of these four folders is a Python package, holding `__init__.py`, only its `__main__.py` and the modules with a shebang or a `__main__` block count) and `assets/`, `templates/`, `schemas/`, `configs/`, `examples/` (for assets). Detection applies to all scope types except `docs-only`.
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
| `target_ref` | string | Git ref (tag or branch) | Optional. Explicit git ref used verbatim as the resolved `source_ref`, bypassing version-to-tag matching. Escape hatch for monorepo crate tags whose prefix differs from the skill name (e.g. tag `livekit/v0.7.42` for skill `livekit-rust`). Remote sources only. |
|
|
27
27
|
| `source_authority` | string | `official` / `community` / `internal` | Default `community`. Set to `official` only when the skill creator is the library maintainer. Forced to `community` when `source_type: "docs-only"`. |
|
|
28
28
|
| `source_ref` | string | Git ref (tag/branch/HEAD) | Resolved git ref used for source access. Set automatically during tag resolution — do not set manually. |
|
|
29
|
-
| `scope.tier_a_include` | array | Glob patterns (min 1, non-empty) | Optional. Narrower tier-A include list for a curated-subset skill
|
|
29
|
+
| `scope.tier_a_include` | array | Glob patterns (min 1, non-empty) | Optional. Narrower tier-A include list for a curated-subset skill: stratified monorepo packages, single-crate `specific-modules` scopes, and multi-entry exports-map packages. When present, `skf-test-skill` re-derives the coverage denominator from this list instead of the coarse `scope.include`, so the denominator reflects the authoring surface rather than incidentally-matched internal infrastructure. **With it, the denominator deflation guard measures the stored `stats.effective_denominator` against this list's set, in either direction, instead of against the coarse `scope.include` union**, so supply it only when it genuinely narrows the surface, never as an empty list, and never as a way to widen (listing files outside `scope.include` inverts its meaning). See `skf-test-skill/references/source-access-protocol.md` stratified-scope resolution. |
|
|
30
30
|
| `scope.registry_path` | string | Path from the source root (non-empty) | Optional, for `scope.type: "component-library"`. The component registry file. `skf-create-skill` step 3d reads it instead of detecting a registry, and writes it back, with a `demo-and-registry` amendment, when the user confirms a detected registry or gives its path. |
|
|
31
31
|
| `scope.ui_variants` | array | `{name, package?}` objects (`name` non-empty) | Optional, for `scope.type: "component-library"`. The design system variants, the first one primary. |
|
|
32
32
|
| `scope.demo_patterns` | array | Glob patterns (non-empty strings) | Optional, for `scope.type: "component-library"`. The demo and example files `skf-create-skill` step 3d leaves out. Step 3d reads them instead of detecting demo files, and writes them back, with a `demo-and-registry` amendment per glob, when the user confirms or adjusts the detected ones. |
|
|
@@ -39,16 +39,9 @@ When `source_type: "docs-only"`:
|
|
|
39
39
|
|
|
40
40
|
## Version Detection
|
|
41
41
|
|
|
42
|
-
During brief creation,
|
|
42
|
+
During brief creation, step 2 auto-detects the source version (`references/version-resolution.md`, Detection Algorithm) before defaulting to `"1.0.0"`.
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
- **JavaScript/TypeScript:** root `package.json` (`"version"`) → if root has `"private": true` with a `"workspaces"` array or lacks a `"version"` field, fall back to a primary workspace package's `package.json` (e.g., `code/core/package.json`, or the first matching `packages/*/package.json`). For GitHub sources, prefer `gh api repos/{owner}/{repo}/releases/latest` → `tag_name` when a non-pre-release tag exists, over a default-branch pre-release. Treat a version containing `-alpha`, `-beta`, `-rc`, `-next`, or `-canary` as a pre-release.
|
|
46
|
-
- **Rust:** `Cargo.toml` `[package] version` (static) → if `version = { workspace = true }`, resolve from workspace root `Cargo.toml` → `git describe --tags --abbrev=0`
|
|
47
|
-
- **Go:** version tag from `go.mod` or `git describe --tags --abbrev=0`
|
|
48
|
-
|
|
49
|
-
If the source is a remote GitHub repo, read the version file step 2 fetched into the run folder with `skf-github-fetch.py`, which needs `gh` only for a private repository. If the source is local, read the file directly.
|
|
50
|
-
|
|
51
|
-
If detection succeeds, use the detected version. If it fails or returns a non-semver value, fall back to `"1.0.0"`.
|
|
44
|
+
If detection succeeds, use the detected version. If it fails or returns a non-semver value, fall back to `"1.0.0"`; the brief writer's result then carries a warning that says so.
|
|
52
45
|
|
|
53
46
|
The create-skill workflow (extract) also performs version reconciliation at extraction time — if the source version has changed since the brief was created, the extraction step warns and uses the source version.
|
|
54
47
|
|
|
@@ -163,7 +156,7 @@ scope:
|
|
|
163
156
|
|
|
164
157
|
**Demotion (scope-expansion only):** `demoted-include` removes a previously-promoted path from `scope.include` — used when a prior `[P]` decision is reversed. `demoted-exclude` removes a path from `scope.exclude` — used when a previously excluded path needs to be re-evaluated. Both write the structural change and append the amendment so future runs see the rationale. Demotion is not valid for `category: "auth-doc"`: auth-doc skips already prevent re-prompting without scope mutation.
|
|
165
158
|
|
|
166
|
-
**Exclusion (scope-expansion only):** `excluded` adds a path to `scope.exclude`, written by `skf-update-skill` gap-driven rescope (gap-driven.md §1 rule R1) when a coverage gap's remediation is removal (the export is internal, `#[doc(hidden)]`, or out of scope). The amendment is the audit trail; the `scope.exclude` write is what shrinks the source barrel, so the legitimate scope reduction is expressed in the brief rather than by editing `metadata.stats`. This keeps the reduction visible to `skf-test-skill`'s denominator-deflation check, which re-derives the barrel from `scope.include` filtered by `scope.exclude
|
|
159
|
+
**Exclusion (scope-expansion only):** `excluded` adds a path to `scope.exclude`, written by `skf-update-skill` gap-driven rescope (gap-driven.md §1 rule R1) when a coverage gap's remediation is removal (the export is internal, `#[doc(hidden)]`, or out of scope). The amendment is the audit trail; the `scope.exclude` write is what shrinks the source barrel, so the legitimate scope reduction is expressed in the brief rather than by editing `metadata.stats`. This keeps the reduction visible to `skf-test-skill`'s denominator-deflation check, which re-derives the barrel from `scope.include` filtered by `scope.exclude` (with a `scope.tier_a_include` list, the tier A set within it). Not valid for `category: "auth-doc"`.
|
|
167
160
|
|
|
168
161
|
**Backward compatibility:** `scope.amendments` is optional. Briefs without this field validate unchanged. Treat missing as an empty list. Existing entries without `category` are equivalent to `category: "auth-doc"` — readers must default the field when absent.
|
|
169
162
|
|
|
@@ -180,7 +180,7 @@ If `confidence` is `low` (or `unknown` is returned for `language`) and no `langu
|
|
|
180
180
|
|
|
181
181
|
**Resolve `{extractPublicApiHelper}`** from `{extractPublicApiProbeOrder}`; first existing path wins. HALT if no candidate exists.
|
|
182
182
|
|
|
183
|
-
Identify the public API surface. **Delegate the parsing to `{extractPublicApiHelper}` whenever the detected language is supported:** it finds the entry files, parses the manifest
|
|
183
|
+
Identify the public API surface. **Delegate the parsing to `{extractPublicApiHelper}` whenever the detected language is supported:** it finds the entry files, parses the manifest, reads the exports and finds the version, so none of them is derived in prose.
|
|
184
184
|
|
|
185
185
|
**Script-supported languages** (use the script): `js`, `ts`, `javascript`, `typescript`, `python`, `rust`, `go`, `java`, `kotlin`.
|
|
186
186
|
|
|
@@ -203,14 +203,14 @@ Display `Listing the exports...`. This section runs exactly one of §4.1 (script
|
|
|
203
203
|
2. **Parse them and follow their re-exports** by path: the `manifest` (no `--manifest-file` when it is null) and one `--entry-file` per entry file (for a GitHub source, each one the fetch `fetched`):
|
|
204
204
|
|
|
205
205
|
```bash
|
|
206
|
-
uv run {extractPublicApiHelper} --mode quick --language "<language>" --source-root "<source root>" --tree-file "{run_dir}/tree.json" --manifest-file "<manifest path>" --entry-file "<entry path>" --follow [--repo "{owner}/{repo}" --ref "{analysis_ref}"] > "{run_dir}/extract.json"
|
|
206
|
+
uv run {extractPublicApiHelper} --mode quick --language "<language>" --source-root "<source root>" --tree-file "{run_dir}/tree.json" --manifest-file "<manifest path>" --entry-file "<entry path>" --follow --resolve-version [--repo "{owner}/{repo}" --ref "{analysis_ref}"] > "{run_dir}/extract.json"
|
|
207
207
|
```
|
|
208
208
|
|
|
209
|
-
`--follow` reads each module an `export *` or a star import passes names on from, round after round, so a chain is read to its end, and lists them in `followed`; for a GitHub source, `--repo` and `--ref` fetch each into `{run_dir}/files` first. A module it could not read stays in `unlisted[]`, and a warning names it.
|
|
209
|
+
`--follow` reads each module an `export *` or a star import passes names on from, round after round, so a chain is read to its end, and lists them in `followed`; for a GitHub source, `--repo` and `--ref` fetch each into `{run_dir}/files` first. A module it could not read stays in `unlisted[]`, and a warning names it. `--resolve-version` looks up a version the manifest does not state.
|
|
210
210
|
|
|
211
211
|
3. On a non-zero exit of the extractor (codes 1 or 2 per its docstring), log its stderr and fall through to §4.2 (the prose-fallback path), which writes over `{run_dir}/extract.json`: never HALT just because the script choked on an unusual manifest.
|
|
212
212
|
|
|
213
|
-
4. Keep `{run_dir}/extract.json`'s `package_name`, `exports` (each entry's `name`/`type`/`source_file`), `dependencies` and `warnings` for §5, which shows the package, its dependency count, the exports and the warnings. Its `version` feeds §4b
|
|
213
|
+
4. Keep `{run_dir}/extract.json`'s `package_name`, `exports` (each entry's `name`/`type`/`source_file`), `dependencies` and `warnings` for §5, which shows the package, its dependency count, the exports and the warnings. Its `version` feeds §4b, and step 3 counts its exports.
|
|
214
214
|
|
|
215
215
|
#### 4.2 Procedure — fallback (not script-supported)
|
|
216
216
|
|
|
@@ -251,9 +251,9 @@ If CCC is unavailable or returns no results: skip this subsection silently.
|
|
|
251
251
|
|
|
252
252
|
### 4b. Detect Source Version
|
|
253
253
|
|
|
254
|
-
**When
|
|
254
|
+
**When §4.1 ran:** `{run_dir}/extract.json`'s `version` is the detected version, null when the extractor found none. Do not derive it again or load `{versionResolutionFile}`.
|
|
255
255
|
|
|
256
|
-
**When
|
|
256
|
+
**When §4.2 ran:** load `{versionResolutionFile}` and run its Detection Algorithm.
|
|
257
257
|
|
|
258
258
|
Whichever path produced it, keep the detected version for §5, which shows it beside `target_version`. The actual write happens in step 05.
|
|
259
259
|
|
|
@@ -108,7 +108,7 @@ Glosses (substitute the matching one-liner for `{tier_gloss}`, `{scripts_gloss}`
|
|
|
108
108
|
- `Deep`: full pipeline — AST + ccc + qmd portfolio search + LLM re-ranking
|
|
109
109
|
|
|
110
110
|
- **`scripts_intent` / `assets_intent` glosses** —
|
|
111
|
-
- `detect`: SKF will scan source for the standard `scripts/`/`bin/`/`tools/`/`cli/` (or `assets/`/`templates/`/`schemas/`/`configs/`) directories during create-skill and decide automatically
|
|
111
|
+
- `detect`: SKF will scan source for the standard `scripts/`/`bin/`/`tools/`/`cli/` (or `assets/`/`templates/`/`schemas/`/`configs/`) directories during create-skill and decide automatically (when one of the four script folders is a Python package, holding `__init__.py`, only its `__main__.py` and the modules with a shebang or a `__main__` block count as scripts)
|
|
112
112
|
- `none`: no script/asset packaging — create-skill will skip the detection pass
|
|
113
113
|
- free-text (anything else): a description of what to package; create-skill treats it as the user's spec
|
|
114
114
|
|
|
@@ -173,10 +173,10 @@ Using the boundary definitions from `{scopeTemplatesPath}`, present the appropri
|
|
|
173
173
|
|
|
174
174
|
- **A module include** is `<path>/**`, where `<path>` is the module's `path` in `{run_dir}/snapshot.json` (its `workspaces` or `module_candidates` entry, or the entry of `{run_dir}/package-snapshot.json` when §4.3 picked from it): repo-relative, with any workspace prefix already in it. For Maven and Gradle it is the §4.1 `modules` entry, prefixed with `{monorepo_workspace}/` when step 2 picked a workspace. A `named_module_subset` name maps to the §4.3 module it names. With no module picked, the include is `{monorepo_workspace}/**` when step 2 picked a workspace (§3b), else `**`.
|
|
175
175
|
- **The default exclusions** are the Full Library template's test globs (`**/*.test.*`, `**/*.spec.*`, `**/test/**`, `**/tests/**`) and build globs (`**/dist/**`, `**/build/**`, `**/target/**`). The template's configuration and documentation exclusions name no glob, and the module includes already leave the root configuration files and the docs folders out.
|
|
176
|
-
- **The public API files** are the files that define the names the package's entry points export. Run §3c's recipe runner (its helper, source folder, clone and clone removal) with the `full-library` default as its globs and `-o "{run_dir}/public-api.json"`, then print the file of each public name, an entry point's own definitions
|
|
176
|
+
- **The public API files** are the files that define the names the package's entry points export. Run §3c's recipe runner (its helper, source folder, clone and clone removal) with the `full-library` default as its globs and `-o "{run_dir}/public-api.json"`, then print the file of each public name, an entry point's own definitions, the files its re-exports lead to, the files that define a namespace's `members` (the names a submodule the package exports passes on) and each other in-scope file that defines a public name (`entry_point_diff.other_definitions`, such as a name two packages of a monorepo each define):
|
|
177
177
|
|
|
178
178
|
```bash
|
|
179
|
-
uv run python -c 'import json, sys; d = json.load(open(sys.argv[1], encoding="utf-8")); print("\n".join(sorted({
|
|
179
|
+
uv run python -c 'import json, sys; d = json.load(open(sys.argv[1], encoding="utf-8")); print("\n".join(sorted({f for p in d["entry_point_diff"]["public"] + (d["entry_point_diff"].get("other_definitions") or []) for f in [p.get("file")] + [m.get("file") for m in p.get("members") or []] if f})))' "{run_dir}/public-api.json"
|
|
180
180
|
```
|
|
181
181
|
|
|
182
182
|
At Quick tier, when the runner exits 1 to 3 or the clone fails, or when it prints no file, take the `full-library` default instead and give the reason in the warn line: `warn: headless boundary default public-api: full-library boundaries ({the reason})`.
|
|
@@ -215,10 +215,10 @@ Head this off by capturing the authoring surface as **`scope.tier_a_include`**:
|
|
|
215
215
|
uv run {extractPublicApiHelper} --mode full --source-root "<source folder>" --include "<include glob>" --exclude "<exclude glob>" --language "{language}" -o "{run_dir}/tier-a.json"
|
|
216
216
|
```
|
|
217
217
|
|
|
218
|
-
The candidates are the files that define a name an entry point passes on (`reexport_targets`), less the entry points themselves (`entry_points.files[].file`). Print them, one per line:
|
|
218
|
+
The candidates are the files that define a name an entry point passes on (`reexport_targets`, the `members` of a namespace in `entry_point_diff.public`, and `entry_point_diff.other_definitions`, each other in-scope file that defines a public name), less the entry points themselves (`entry_points.files[].file`) and the files of the names the runner lists outside the scope (`entry_point_diff.outside_scope`), since a tier A name counts only in a file `scope.include` matches. A file outside `scope.include` that a named re-export (`export { x } from`) or a barrel reaches has no `outside_scope` row and stays a candidate, which Create Skill's runner then warns about. Print them, one per line:
|
|
219
219
|
|
|
220
220
|
```bash
|
|
221
|
-
uv run python -c 'import json, sys; d = json.load(open(sys.argv[1], encoding="utf-8")); entries = {f["file"] for f in d["entry_points"]["files"]}; print("\n".join(sorted({t["file"] for t in d["reexport_targets"]
|
|
221
|
+
uv run python -c 'import json, sys; d = json.load(open(sys.argv[1], encoding="utf-8")); entries = {f["file"] for f in d["entry_points"]["files"]}; diff = d.get("entry_point_diff") or {}; members = {m.get("file") for p in diff.get("public") or [] for m in p.get("members") or []}; others = {o.get("file") for o in diff.get("other_definitions") or []}; outside = {o.get("file") for o in diff.get("outside_scope") or []}; print("\n".join(sorted(({t["file"] for t in d["reexport_targets"]} | members | others) - {None} - entries - outside)))' "{run_dir}/tier-a.json"
|
|
222
222
|
```
|
|
223
223
|
|
|
224
224
|
Once the runner has run, whatever its exit, remove a clone this section made (`rm -rf "{run_dir}/clone"`).
|
|
@@ -60,50 +60,31 @@ Nothing the upstream brief carries is rebuilt or dropped: §4's writer starts fr
|
|
|
60
60
|
|
|
61
61
|
**Resolve `{detectDocsHelper}`** from `{detectDocsProbeOrder}`; first existing path wins. HALT if no candidate exists.
|
|
62
62
|
|
|
63
|
-
Invoke doc detection to discover documentation URLs for the source repo:
|
|
63
|
+
Invoke doc detection to discover documentation URLs for the source repo, keeping its JSON in the run folder:
|
|
64
64
|
|
|
65
65
|
```bash
|
|
66
|
-
uv run {detectDocsHelper} --repo-url {source_repo}
|
|
66
|
+
uv run {detectDocsHelper} --repo-url "{source_repo}" > "{run_dir}/detected.json"
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
`--repo-url` is always required (the script uses it for GitHub API calls). If a local clone is also available at `{local_clone_path}`, add `--local-path {local_clone_path}` to enable docs-folder scanning in addition to API-based detection.
|
|
69
|
+
`--repo-url` is always required (the script uses it for GitHub API calls). If a local clone is also available at `{local_clone_path}`, add `--local-path "{local_clone_path}"` to enable docs-folder scanning in addition to API-based detection.
|
|
70
70
|
|
|
71
71
|
**Handle exit codes:**
|
|
72
72
|
|
|
73
|
-
- **Exit 0 (found docs):**
|
|
73
|
+
- **Exit 0 (found docs):** Proceed to §3, which reads `{run_dir}/detected.json`.
|
|
74
74
|
- **Exit 1 (none found):** Log: "No external documentation found — brief generated from source analysis only." Proceed to §4 with no doc enrichment.
|
|
75
75
|
- **Exit 2 (error):** Log the warning "Doc detection failed: proceeding without doc enrichment." and add it to `workflow_warnings[]`. Do NOT halt: doc enrichment is best-effort. Proceed to §4 with no doc enrichment.
|
|
76
76
|
|
|
77
77
|
### 3. Enrich Brief with Detected Docs
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
- `url` ← `url` (direct copy)
|
|
82
|
-
- `label` ← derive from `content_type` if available:
|
|
83
|
-
- `"api-docs"` → `"API Documentation"`
|
|
84
|
-
- `"guide"` → `"Guide"`
|
|
85
|
-
- `"reference"` → `"Reference"`
|
|
86
|
-
- Otherwise derive from `detected_via`:
|
|
87
|
-
- `"homepageUrl"` → `"Homepage"`
|
|
88
|
-
- `"readme_link"` → `"README Link"`
|
|
89
|
-
- `"pages_api"` → `"GitHub Pages"`
|
|
90
|
-
- `"docs_folder"` → `"Docs Folder"`
|
|
91
|
-
- `source` ← coarse provenance derived from `detected_via` (per the `skill-brief.v1.json` `doc_urls[].source` enum): `homepageUrl` → `homepage`, `readme_link` → `readme-detection`, `pages_api` → `pages-api`, `docs_folder` → `docs-folder`. This marks the entry as opportunistically detected, distinct from a registry-guaranteed corpus.
|
|
92
|
-
|
|
93
|
-
**Merge via the canonical helper.** Resolve `{mergeDocUrlsHelper}` from `{mergeDocUrlsProbeOrder}` (first existing path wins; HALT if neither exists). Stage its input in the run folder, the upstream brief's `doc_urls` as `existing` and the entries just mapped above as `detected`, then run it on the file:
|
|
79
|
+
**Merge via the canonical helper.** Resolve `{mergeDocUrlsHelper}` from `{mergeDocUrlsProbeOrder}` (first existing path wins; HALT if neither exists). It maps each detected entry to the brief's `{url, label, source}`, reads the upstream brief's `doc_urls` itself and merges the two, so no entry is typed here:
|
|
94
80
|
|
|
95
81
|
```bash
|
|
96
|
-
|
|
97
|
-
{"scope_type": "<scope_type>", "existing": <the upstream brief's doc_urls, [] if none>, "detected": <the mapped detected entries>}
|
|
98
|
-
SKF_JSON
|
|
99
|
-
uv run {mergeDocUrlsHelper} < "{run_dir}/doc-urls.json" > "{run_dir}/doc-urls-merged.json"
|
|
82
|
+
uv run {mergeDocUrlsHelper} --scope-type "{scope_type}" --detected-file "{run_dir}/detected.json" --existing-brief "{brief_path}" > "{run_dir}/doc-urls-merged.json"
|
|
100
83
|
```
|
|
101
84
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
The helper returns `{"doc_urls": [...], "suppressed": [...]}`. It deduplicates by **normalized** URL (lowercase host, strip a trailing `/index.html` and any trailing `/`), so a seeded `…/book/` and a README's `…/book/index.html` collapse to one entry; existing/corpora-seeded entries always win and keep their `source: language-registry`, so the registry-vs-detected distinction survives the merge. For a **whole-language reference** (`scope_type == "full-library"` AND ≥1 `existing` entry has `source: language-registry`) it additionally suppresses README noise on a corpus host: non-corpus path segments (`/whatsnew/`, `/contribute`, `/wiki/`) and non-primary-locale duplicates of a kept page (`/ja/master/` when `/en/master/` is kept). Ordinary skills (any other `scope_type`, or no registry corpora) pass through with dedup only — no suppression. Use the returned `doc_urls` as the brief's merged list.
|
|
85
|
+
§4 hands the writer that file: its `doc_urls` are the merged list, every upstream entry kept with its `source`. On a non-zero exit, add `Doc merge failed: proceeding without doc enrichment.` to `workflow_warnings[]` and proceed to §4 with no doc enrichment.
|
|
105
86
|
|
|
106
|
-
**Log suppressed entries.** When `suppressed` is non-empty, log one line per entry
|
|
87
|
+
**Log suppressed entries.** When its `suppressed` is non-empty, log one line per entry, `"info: suppressed {url} ({reason})"`, so the operator sees each entry the merge dropped and why (never drop silently).
|
|
107
88
|
|
|
108
89
|
### 4. Write Enriched Brief
|
|
109
90
|
|
|
@@ -115,10 +96,11 @@ Write the enriched brief through the canonical writer. It starts from the upstre
|
|
|
115
96
|
uv run {writeSkillBriefHelper} write --target {forge_data_folder}/{skill_name}/skill-brief.yaml --base-brief "{brief_path}" --doc-urls-file "{run_dir}/doc-urls-merged.json"
|
|
116
97
|
```
|
|
117
98
|
|
|
118
|
-
Leave `--doc-urls-file` out when §2 and §3 were skipped or found nothing.
|
|
99
|
+
Leave `--doc-urls-file` out when §2 and §3 were skipped or found nothing, or the merge failed.
|
|
119
100
|
|
|
120
101
|
**On script failure (non-zero exit):**
|
|
121
|
-
- Exit 1 (
|
|
102
|
+
- Exit 1 of a call with `--doc-urls-file` whose stderr `field` is `doc_urls` (a merged entry the brief schema refuses): doc enrichment is best-effort, so add `Doc enrichment dropped: {its message}` to `workflow_warnings[]`, run the call once more without `--doc-urls-file`, and handle that result.
|
|
103
|
+
- Any other exit 1 (validation/invariant): emit the halt envelope first, `uv run {emitBriefEnvelopeHelper} emit --target stderr` with `halt_reason: "input-invalid"` (SKILL.md Halt Contract), then HARD HALT (exit code 2).
|
|
122
104
|
- Exit 2 (I/O failure): emit the halt envelope first, `uv run {emitBriefEnvelopeHelper} emit --target stderr` with `halt_reason: "write-failed"` (SKILL.md Halt Contract), then HARD HALT (exit code 4).
|
|
123
105
|
|
|
124
106
|
**On success:** Capture `brief_path` and `version` from the response envelope for step-auto-validate's envelope emission, and add each entry of its `warnings[]` to `workflow_warnings[]`.
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
# Version Resolution
|
|
2
2
|
|
|
3
|
-
Single source of truth for how brief-skill resolves the `version` field of `skill-brief.yaml`. Loaded by step 2 §4b (auto-detect,
|
|
4
|
-
|
|
5
|
-
**Aligned with** `assets/skill-brief-schema.md` "Version Detection" section. If you change one, change the other.
|
|
3
|
+
Single source of truth for how brief-skill resolves the `version` field of `skill-brief.yaml`. Loaded by step 2 §4b (auto-detect, when §4.2's fallback read the source) and step 5 §3 (resolve & write) so both operate on the same precedence rules and invariant. Step-01 §3b references this file in prose for human-readable rationale but does not load it: that step only collects `target_version` and checks its shape with `skf-validate-brief-inputs.py --only target_version`.
|
|
6
4
|
|
|
7
5
|
## Detection Algorithm
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
Step 2 §4.1's extractor detects the version of the languages it reads, with `--resolve-version`: the manifest's literal version, then a version file or a Cargo workspace root, then the release tag (`skf-extract-public-api.py --help` gives the order). When §4.2 read the source instead, attempt these lookups in order and stop at the first match:
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- **Rust:** `Cargo.toml` `[package] version` (static). If `version = { workspace = true }`, resolve from workspace root `Cargo.toml` → `git describe --tags --abbrev=0`.
|
|
14
|
-
- **Go:** version tag from `go.mod`, or `git describe --tags --abbrev=0`.
|
|
9
|
+
1. The version the manifest or a version file writes as a literal: `package.json`'s `"version"`, `pyproject.toml`'s `[project] version` or a package's `__version__`, a `.gemspec` or `version.rb` `VERSION`, a `.csproj` `<Version>`, `composer.json`'s `"version"`.
|
|
10
|
+
2. The tag lookup. For a GitHub source, `uv run {githubProbeHelper} tags --repo "{owner}/{repo}" --ref "{analysis_ref}" --limit 1`: its `release.version` is the version (the newest release tag at `HEAD`, the pinned tag itself at a tag, none at a branch or a commit), and a null `release` or a non-zero exit gives none. For a local source, `git -C "{source_path}" describe --tags --abbrev=0`: a tag whose name is a version gives it, a leading `v` dropped.
|
|
15
11
|
|
|
16
12
|
For remote GitHub sources, read version-bearing files from `{run_dir}/files/`, where step 2 stages the files it fetches; fetch one that is not there yet with step 2's fetch helper, naming it: `uv run {githubFetchHelper} --repo "{owner}/{repo}" --ref "{analysis_ref}" --tree-file "{run_dir}/tree.json" --dest "{run_dir}/files" "{file}"`. `{analysis_ref}` is the ref resolved in step 02 §1, defaulting to `HEAD` when no `target_ref`/`target_version` was pinned; reading at the pinned ref keeps the "Detected version" consistent with the version being skilled. For local sources, read the file directly.
|
|
17
13
|
|
|
@@ -43,5 +39,5 @@ Step-05 §3 enforces this by setting both fields to the same string when `target
|
|
|
43
39
|
|------|----------------|
|
|
44
40
|
| 01 §3b | Collect `target_version` (interactive prompt, or headless arg). Do not auto-detect — that is step 02's job. |
|
|
45
41
|
| 02 §1 | Resolve `target_version` to the git tag step 2 reads, through `skf-validate-pins.py`. With no match, analyze `HEAD` and warn. |
|
|
46
|
-
| 02 §4b |
|
|
42
|
+
| 02 §4b | Detect the version (§4.1's extractor, or the detection algorithm after §4.2) regardless of whether `target_version` is set. If `target_version` is set and the detected version differs, surface the disagreement to the user; the precedence above is unchanged, so `target_version` wins. |
|
|
47
43
|
| 05 §3 | Apply the precedence rules and write `version`. If `target_version` is set, also write the `target_version` field with the identical value. Enforce the invariant. |
|
|
@@ -31,10 +31,10 @@ Run these steps once, in order, before dispatching to Mode Routing.
|
|
|
31
31
|
|
|
32
32
|
2. **Resolve `{headless_mode}`**: true if `--headless` or `-H` was passed as an argument, or if `headless_mode: true` in `{sidecar_path}/preferences.yaml`. Default: false.
|
|
33
33
|
|
|
34
|
-
3. **Resolve workflow customization.**
|
|
34
|
+
3. **Resolve workflow customization.** Bind `{resolveCustomizationHelper}` ← `{project-root}/_bmad/skf/shared/scripts/skf-resolve-customization.py`, else `{project-root}/src/shared/scripts/skf-resolve-customization.py` (the first that exists), and run:
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
uv run {
|
|
37
|
+
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-campaign.toml` (team overrides, committed) and `.user.toml` (personal overrides, gitignored). When it exits non-zero, prints no JSON or is missing, print one line, `[activation/warn] customization_resolver_unavailable: <reason>` (`<reason>`: its first stderr line, `not found` when the script is missing, `no JSON` when it printed none). If the resolver cannot run, read `{skill-root}/customize.toml` alone and use its bundled defaults: the `{project-root}/_bmad/custom/` overrides do not apply to this run. In that case, unless the invocation is `campaign status` (which writes nothing), log `customization_resolver_unavailable: <reason>` as an `event` in the decision log once `{campaignWorkspacePath}` is bound below (`references/campaign-contracts.md`, Decision Log).
|
|
@@ -12,6 +12,10 @@ kickoffScript: 'scripts/campaign-render-kickoff.py'
|
|
|
12
12
|
gateScript: 'scripts/campaign-quality-gate.py'
|
|
13
13
|
stateScript: 'scripts/campaign-state.py'
|
|
14
14
|
validateScript: 'scripts/campaign-validate-state.py'
|
|
15
|
+
# The customization helper the kickoff pipe runs (§5).
|
|
16
|
+
resolveCustomizationProbeOrder:
|
|
17
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-resolve-customization.py'
|
|
18
|
+
- '{project-root}/src/shared/scripts/skf-resolve-customization.py'
|
|
15
19
|
---
|
|
16
20
|
|
|
17
21
|
<!-- Config: communicate in {communication_language}. -->
|
|
@@ -86,8 +90,9 @@ For each ready Tier A skill:
|
|
|
86
90
|
(During development, with no `{project-root}/_bmad/skf/shared/scripts/skf-preapply.py` installed, the helper lives at `{project-root}/src/shared/scripts/skf-preapply.py`.) It writes its `applied[]` workarounds to `{preapplyLogFile}`, which the kickoff script reads. Pre-apply is best-effort: if the helper is missing or exits non-zero, log a warning and proceed (it is not a gate), and leave `--workarounds-file` out of the kickoff call below, so an earlier skill's log is never listed for this one.
|
|
87
91
|
- **Kickoff emit:** render the whole kickoff message with the kickoff script, which fills every placeholder of the template. The customization resolver pipes the persistent facts into it:
|
|
88
92
|
```
|
|
89
|
-
uv run {
|
|
93
|
+
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow.persistent_facts | uv run {kickoffScript} --state-file {stateFile} --brief-file {briefFile} --skill {skill_name} --template {kickoffTemplate} [--workarounds-file {preapplyLogFile}] --facts-json - --project-root {project-root} [--directive-file <campaign.directive_path>]
|
|
90
94
|
```
|
|
95
|
+
- `{resolveCustomizationHelper}` is resolved from `{resolveCustomizationProbeOrder}` when it is not bound.
|
|
91
96
|
- `--facts-json -` takes `workflow.persistent_facts` from the resolver unchanged. When the resolver is missing or fails, write the `persistent_facts` list of `{skill-root}/customize.toml`, as written, to `{factsFile}` as a JSON list with your file-writing tool, and replace the pipe with `< {factsFile}`: no fact or workaround ever passes through shell quoting.
|
|
92
97
|
- `--workarounds-file` is passed when pre-apply exited 0 for this skill; the script lists each workaround it applied (the file, the fingerprint and its fix, the severity).
|
|
93
98
|
- `--directive-file` is passed when state sets `campaign.directive_path`.
|
|
@@ -75,10 +75,10 @@ These rules apply to every step in this workflow:
|
|
|
75
75
|
|
|
76
76
|
2. **Resolve `{headless_mode}`**: true if `--headless` or `-H` was passed as an argument, or if `headless_mode: true` in `{sidecar_path}/preferences.yaml`. Default: false.
|
|
77
77
|
|
|
78
|
-
3. **Resolve workflow customization.**
|
|
78
|
+
3. **Resolve workflow customization.** Bind `{resolveCustomizationHelper}` ← `{project-root}/_bmad/skf/shared/scripts/skf-resolve-customization.py`, else `{project-root}/src/shared/scripts/skf-resolve-customization.py` (the first that exists), and run:
|
|
79
79
|
|
|
80
80
|
```bash
|
|
81
|
-
uv run {
|
|
81
|
+
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow
|
|
82
82
|
```
|
|
83
83
|
|
|
84
84
|
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-create-skill.toml` (team overrides, committed) and `.user.toml` (personal overrides, gitignored). When it exits non-zero, prints no JSON or is missing, print one line, `[activation/warn] customization_resolver_unavailable: <reason>` (`<reason>`: its first stderr line, `not found` when the script is missing, `no JSON` when it printed none). If the resolver cannot run, read `{skill-root}/customize.toml` alone and use its bundled defaults: the `{project-root}/_bmad/custom/` overrides do not apply to this run. In that case keep the reason as `{customization_resolver_unavailable}` (unset when the resolver ran) for step 1 §0 to record in each brief's run folder.
|
|
@@ -29,6 +29,8 @@ description: >
|
|
|
29
29
|
|
|
30
30
|
**Two-tier assembly.** SKILL.md must retain actionable inline content that survives `split-body` extraction. Assemble Tier 1 sections first (always inline), then Tier 2 sections (reference-eligible, may be extracted by split-body).
|
|
31
31
|
|
|
32
|
+
**Public surface (`public-api` scope).** An export the extraction inventory marks `public: false` is off the package's public surface: no section of SKILL.md or `references/` names or documents it, as the provenance map and `metadata.json`'s `exports[]` hold no entry for it. Every other export, and every export of any other scope type, is assembled as below.
|
|
33
|
+
|
|
32
34
|
### Tier 1 — Always Inline (must survive split-body)
|
|
33
35
|
|
|
34
36
|
These sections form the essential standalone body. Target: **under 300 lines total** for Tier 1. An agent loading only SKILL.md (without references) must get enough to act.
|
|
@@ -57,7 +59,7 @@ These sections form the essential standalone body. Target: **under 300 lines tot
|
|
|
57
59
|
```
|
|
58
60
|
|
|
59
61
|
**Section 4 — Key API Summary (~20 lines):**
|
|
60
|
-
- Table of top 10-15 functions: name, purpose, key parameters
|
|
62
|
+
- Table of top 10-15 functions: name, purpose, key parameters, none marked `public: false`
|
|
61
63
|
- One row per function — no full signatures, just enough for discovery
|
|
62
64
|
- Provenance citation per function
|
|
63
65
|
|
|
@@ -104,7 +106,7 @@ These sections form the essential standalone body. Target: **under 300 lines tot
|
|
|
104
106
|
|
|
105
107
|
### Tier 2 — Reference-Eligible (can be extracted by split-body)
|
|
106
108
|
|
|
107
|
-
Assemble Sections 9-11 (Full API Reference, Full Type Definitions, Full Integration Patterns) as defined in the skill-sections data file. These contain full detail and are split into `references/` when the body exceeds 500 lines. Include T2 annotations from enrichment in the Full API Reference (Deep tier only).
|
|
109
|
+
Assemble Sections 9-11 (Full API Reference, Full Type Definitions, Full Integration Patterns) as defined in the skill-sections data file, for the exports the provenance map holds (never one marked `public: false`). These contain full detail and are split into `references/` when the body exceeds 500 lines. Include T2 annotations from enrichment in the Full API Reference (Deep tier only).
|
|
108
110
|
|
|
109
111
|
**Tier 2 differentiation from Tier 1:** Tier 2 Full API Reference must contain content that is not present in Tier 1's Key API Summary. Specifically:
|
|
110
112
|
|
|
@@ -270,7 +272,7 @@ Replace per-function subsections with `references/pattern-*.md` groupings: one r
|
|
|
270
272
|
- `stats.exports_documented` MAY be set to the Pattern Surface row count as a proxy, but its semantics change: it counts authored pattern surfaces, not public exports. Emit an adjacent `stats.pattern_surfaces_documented` with the same integer so downstream consumers (test-skill, feasibility, discovery) can discriminate.
|
|
271
273
|
- `exports_public_api` / `exports_internal` / `exports_total` are not meaningful for a reference app — omit them, or set all three to the Pattern Surface count with a note in `stats.notes`: `"reference-app: export counts are pattern-surface proxies, not library exports"`.
|
|
272
274
|
- Do not fabricate signature / type-coverage data from pattern surfaces. skf-test-skill skips those categories when metadata flags a reference-app (`referenceApp`, the same skip path as `stackSkill`; its `scripts/compute-score.py` redistributes their weights).
|
|
273
|
-
- Do not emit `effective_denominator` for reference-app scope
|
|
275
|
+
- Do not emit `effective_denominator` for reference-app scope (its `effective_denominator_decision` never stores one): `effective_denominator` counts public exports from the authoring-surface barrel, a library concept that `pattern_surfaces_documented` already replaces here: pattern-surface coverage is the reference-app basis, and skf-test-skill skips library-export coverage for it.
|
|
274
276
|
|
|
275
277
|
**Language / spec-reference sub-shape:** A reference app that documents an engine- or spec-versioned **language** — a query language, grammar, or DSL (e.g. SurrealQL @ SurrealDB) whose value is construct idioms rather than wiring or exports — is a recognized reference-app sub-shape. Keep `scope.type: "reference-app"` (there is no separate enum value), so every carve-out above applies unchanged (`pattern_surfaces_documented` proxy, no `effective_denominator`, test-skill signature/type skip). Adapt the three overrides to the language's surface instead of app wiring:
|
|
276
278
|
|
|
@@ -117,7 +117,7 @@ Indexed pipe-delimited format for CLAUDE.md managed section (~80-120 tokens per
|
|
|
117
117
|
"tool_versions": {
|
|
118
118
|
"ast_grep": "{version-or-null}",
|
|
119
119
|
"qmd": "{version-or-null}",
|
|
120
|
-
"skf": "{
|
|
120
|
+
"skf": "{installed-skf-version-or-unknown}"
|
|
121
121
|
},
|
|
122
122
|
"confidence_distribution": {
|
|
123
123
|
"t1": 0,
|
|
@@ -134,7 +134,7 @@ Indexed pipe-delimited format for CLAUDE.md managed section (~80-120 tokens per
|
|
|
134
134
|
"total_coverage": 0.0,
|
|
135
135
|
"scripts_count": 0,
|
|
136
136
|
"assets_count": 0
|
|
137
|
-
// "effective_denominator": 0 // optional
|
|
137
|
+
// "effective_denominator": 0 // optional: emitted when compile.md §4 passes it (counts.effective_denominator_decision.store and a stratified scope.notes); test-skill uses this as the coverage denominator when present.
|
|
138
138
|
},
|
|
139
139
|
// scripts[] and assets[] — include ONLY when inventories are non-empty; omit entirely otherwise
|
|
140
140
|
// "scripts": [{ "file": "scripts/{name}", "purpose": "{description}", "source_file": "{source-path}", "confidence": "T1-low" }],
|
|
@@ -144,7 +144,7 @@ Indexed pipe-delimited format for CLAUDE.md managed section (~80-120 tokens per
|
|
|
144
144
|
// detected_via "brief_doc_urls" = docs-only skills: the brief's doc_urls (plus subpages step 3c fetched), hashed by `skf-detect-docs.py hash-urls`
|
|
145
145
|
"generated_by": "{quick-skill|create-skill}",
|
|
146
146
|
"dependencies": [],
|
|
147
|
-
"compatibility": "
|
|
147
|
+
"compatibility": ""
|
|
148
148
|
}
|
|
149
149
|
```
|
|
150
150
|
|
|
@@ -270,9 +270,9 @@ t2_future_count: {N}
|
|
|
270
270
|
**Source:** {repo} @ {commit}
|
|
271
271
|
|
|
272
272
|
## Tool Versions
|
|
273
|
-
- ast-grep: {
|
|
274
|
-
- QMD: {
|
|
275
|
-
- SKF: {
|
|
273
|
+
- ast-grep: {tool_versions.ast_grep}
|
|
274
|
+
- QMD: {tool_versions.qmd}
|
|
275
|
+
- SKF: {tool_versions.skf}
|
|
276
276
|
|
|
277
277
|
## Extraction Summary
|
|
278
278
|
- Files scanned: {count}
|
|
@@ -315,4 +315,4 @@ t2_future_count: {N}
|
|
|
315
315
|
|
|
316
316
|
**Frontmatter — pinned detection contract:** the `t2_future_count` field is the authoritative forward-looking-annotation count for downstream gate checks (e.g. skf-test-skill §2b migration-section rule). Emit **always**, even when 0 — omission is indistinguishable from "no T2-future data" and silently flips the gate into Case 2/3 for a Case-1 skill. `generated` and `forge_tier` mirror the narrative header for consumers that read only the frontmatter. Downstream gate rules parse `t2_future_count` from frontmatter, not prose — prose drift (heading renames, alternate phrasings like "forward-looking annotations") silently breaks grep-based detection.
|
|
317
317
|
|
|
318
|
-
**Description Guard slot:** populated by step 6 §0 (create-skill) and §0 (update-skill) when the guard protocol fires. `Restored: true` indicates that an external tool (typically `skill-check --fix
|
|
318
|
+
**Description Guard slot:** populated by step 6 §0 (create-skill) and §0 (update-skill) when the guard protocol fires. `Restored: true` indicates that an external tool (typically `skill-check --fix` or the `skf-shard-body.py` body split, which create-skill and update-skill both run; update-skill runs `split-body` only as its fallback when that helper is missing) rewrote the frontmatter `description` and the guard restored the pre-tool value. When `Restored: false`, leave `Triggering tool`, `Original description preserved`, and `Notes` as the no-fire dash the template above shows. When `Restored: true`, fill all four fields: tool name, whether the original was successfully written back, and a one-sentence note describing what the tool had changed (e.g., "replaced with generic summary", "truncated at 80 chars", "angle-bracket tokens re-introduced"). Downstream test-skill assertions can grep for `Restored: true` to detect unintended tool rewrites without parsing free-form warning prose.
|
|
@@ -12,11 +12,9 @@ Once §2b has resolved source access, scan the resolved source tree (the local s
|
|
|
12
12
|
|
|
13
13
|
This protocol detects such files, prompts the user, and records the decision in the brief so future runs (re-create, update, audit) honor it.
|
|
14
14
|
|
|
15
|
-
**Heuristic scan list (handled by the helper — listed here for reference):** case-insensitive basename match, any directory depth, on `llms.txt`, `llms-full.txt`, `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, `COPILOT.md`, `.cursorrules`, `.windsurfrules`, `.clinerules`.
|
|
16
|
-
|
|
17
15
|
## Procedure
|
|
18
16
|
|
|
19
|
-
1. **Resolve, scan, classify, and load previews in one call.** Resolve `{resolveAuthoritativeFilesHelper}` ← first existing path in `{resolveAuthoritativeFilesProbeOrder}` (step 3's frontmatter); if neither exists, **HARD HALT** (exit code 3, `helper-missing`, phase `extract`; stage `{run_dir}/halt.json` per the Workflow Rules and run `uv run {emitEnvelopeHelper} emit-halt --workflow skf-create-skill --run-dir "{run_dir}" --target stderr < "{run_dir}/halt.json"`): "Cannot scan for authoritative files: skf-resolve-authoritative-files.py is missing. Re-install SKF, then re-run create-skill." The helper
|
|
17
|
+
1. **Resolve, scan, classify, and load previews in one call.** Resolve `{resolveAuthoritativeFilesHelper}` ← first existing path in `{resolveAuthoritativeFilesProbeOrder}` (step 3's frontmatter); if neither exists, **HARD HALT** (exit code 3, `helper-missing`, phase `extract`; stage `{run_dir}/halt.json` per the Workflow Rules and run `uv run {emitEnvelopeHelper} emit-halt --workflow skf-create-skill --run-dir "{run_dir}" --target stderr < "{run_dir}/halt.json"`): "Cannot scan for authoritative files: skf-resolve-authoritative-files.py is missing. Re-install SKF, then re-run create-skill." The helper walks the tree, classifies each candidate and hashes it:
|
|
20
18
|
|
|
21
19
|
```bash
|
|
22
20
|
uv run {resolveAuthoritativeFilesHelper} resolve \
|
|
@@ -57,7 +55,7 @@ This protocol detects such files, prompts the user, and records the decision in
|
|
|
57
55
|
|
|
58
56
|
2. **Apply the helper's classification:**
|
|
59
57
|
|
|
60
|
-
- **`already_in_scope[]`**: the file is in scope and not skipped. Append the record to in-context `promoted_docs[]`, and remove the path from §2's filtered file list when §2 built one
|
|
58
|
+
- **`already_in_scope[]`**: the file is in scope and not skipped. Append the record to in-context `promoted_docs[]`, and remove the path from §2's filtered file list when §2 built one. No prompt: authoritative docs must never reach §4 code extraction even when scope.include matches.
|
|
61
59
|
- **`pre_decided[]` with `prior_action: "promoted"`** — amendment says promoted; **append to `promoted_docs[]`** using the helper-supplied hash/size/lines. Deterministic replay path.
|
|
62
60
|
- **`pre_decided[]` with `prior_action: "skipped"`** — user previously declined. Do nothing. Move on.
|
|
63
61
|
- **`unresolved[]`**: proceed to step 3 below (user prompt). A `prior_action` of `deferred-headless` means an earlier headless run met the file and left the decision to the next interactive run.
|
|
@@ -120,22 +118,3 @@ This protocol detects such files, prompts the user, and records the decision in
|
|
|
120
118
|
**Record for evidence report:** `authoritative_files_scan: {candidates: N, promoted: P, skipped: S, deferred: D, pre_decided: A, decisions: [{path, action, heuristic, reason}]}`. Step 3 §5 writes it and `promoted_docs[]` into the extraction inventory, and step 5 writes it into `evidence-report.md`.
|
|
121
119
|
|
|
122
120
|
When step 3 §2a's remote source guard skipped the scan, the record is `authoritative_files_scan: {not_scanned: "remote source not cloned"}` instead (its own key, apart from the `skipped` count of candidates above), and step 6 lists that skip under the evidence report's Remaining Warnings.
|
|
123
|
-
|
|
124
|
-
## How promoted docs reach the provenance map
|
|
125
|
-
|
|
126
|
-
Promoted docs do not flow through §4 code extraction. Instead:
|
|
127
|
-
|
|
128
|
-
1. §2a populates the `promoted_docs[]` list with content hashes, which step 3 §5 writes into the extraction inventory.
|
|
129
|
-
2. **Step-05 §6** (provenance-map assembly) reads `promoted_docs[]` from the inventory and emits one `file_entries[]` entry per promoted doc with `file_type: "doc"`, `extraction_method: "promoted-authoritative"`, `confidence: "T1-low"`, and the pre-computed `content_hash`.
|
|
130
|
-
3. **Step 7's promotion** does not copy doc files into the skill package (unlike scripts and assets). The source file remains at its original path; only the provenance map tracks it. Future audit and update workflows compare against this tracking entry via content hash. No file copy is required, because the intent is drift detection on the *source*, not bundling documentation into the skill output.
|
|
131
|
-
|
|
132
|
-
**Re-running `skf-create-skill`** reads the amended brief. Files with `action: "promoted"` amendments already appear in `scope.include`, but §2a still runs — it detects the file is in scope AND has an existing amendment, and takes the "pre-decided" silent path. The `promoted_docs[]` list is rebuilt on each run by scanning amendments with `action: "promoted"` (this is the deterministic replay path).
|
|
133
|
-
|
|
134
|
-
## Downstream workflow consumption
|
|
135
|
-
|
|
136
|
-
Zero code changes required in consumer workflows:
|
|
137
|
-
|
|
138
|
-
- **`skf-update-skill`** reads `provenance-map.json`. Promoted docs appear as `file_entries[]` entries. Update-skill Category D (script/asset file changes) iterates `file_entries` and compares content hashes — this works identically for `file_type: "doc"` entries, giving drift detection for free.
|
|
139
|
-
- **`skf-audit-skill`** scans files from `provenance-map.json`. The re-index builds its list from `entries[].source_file ∪ file_entries[].source_file`, so promoted doc paths are naturally included in the audit scan.
|
|
140
|
-
|
|
141
|
-
The brief is the single source of truth for authored scope intent. The provenance map is the single source of truth for extracted state. `scope.amendments[]` is the bridge that records when those two intentionally diverged. `promoted_docs[]` is the handoff from §2a to step 5 §6, through the extraction inventory; the published form is the `file_entries[]` list in provenance-map.json.
|