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
|
@@ -17,7 +17,7 @@ To calculate approximate token counts for all exported artifacts and present a c
|
|
|
17
17
|
|
|
18
18
|
- Focus only on token counting and reporting — read-only measurement
|
|
19
19
|
- Auto-proceed when complete
|
|
20
|
-
- **Multi-skill mode:** when step 1 loaded more than one skill (`len(skill_batch) > 1`), run the helper once per skill, then present one aggregate table with one row per skill (context-snippet.md, SKILL.md, metadata.json, references/, package total). The `managed_section`
|
|
20
|
+
- **Multi-skill mode:** when step 1 loaded more than one skill (`len(skill_batch) > 1`), run the helper once per skill, then present one aggregate table with one row per skill (context-snippet.md, SKILL.md, metadata.json, references/, package total) and the Context Budget Impact per skill (its `context_budget.on_trigger`, `full_disclosure` and `snippet`). The `managed_section` and `context_budget.always_on` values are identical across runs (same target files): measure and report them once for the run. See step 1 §1c.
|
|
21
21
|
|
|
22
22
|
## MANDATORY SEQUENCE
|
|
23
23
|
|
|
@@ -37,44 +37,64 @@ The helper emits JSON:
|
|
|
37
37
|
{
|
|
38
38
|
"files": [{"path": "…", "role": "…", "exists": true, "words": 0, "tokens": 0}],
|
|
39
39
|
"references_total": {"count": 0, "words": 0, "tokens": 0},
|
|
40
|
-
"managed_section": {"present": true, "source_file": "…", "words": 0, "tokens": 0},
|
|
41
|
-
"package_total": {"words": 0, "tokens": 0}
|
|
40
|
+
"managed_section": {"present": true, "source_file": "…", "target_files": ["…"], "skills": 0, "words": 0, "tokens": 0},
|
|
41
|
+
"package_total": {"words": 0, "tokens": 0},
|
|
42
|
+
"context_budget": {"always_on": 0, "on_trigger": 0, "full_disclosure": 0, "snippet": 0}
|
|
42
43
|
}
|
|
43
44
|
```
|
|
44
45
|
|
|
45
46
|
**Read the JSON — do not re-count words in-prompt.** Each `tokens` value is a char-over-four estimate (`len(text)//4`, the SKF-wide convention shared with `skf-validate-output.py`); each `words` value is the whitespace-split count for the report's Words column. The `files` array carries one row per artifact — `role` is one of `context-snippet`, `skill-md`, `metadata`, `reference`. `package_total` already sums context-snippet.md + SKILL.md + metadata.json + `references_total` and **excludes** the `managed_section` row: the managed section is a shared all-skills cost (it bundles this skill's snippet plus every other skill's snippet), so folding it in would double-count this skill's snippet and pull in unrelated skills. It is reported separately under Context Budget Impact.
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
`managed_section.skills` counts the skill rows (each `[name v…]` row header, as the managed-section rebuild counts them) in the measured block. `managed_section.target_files` lists every target file that holds a block, each once, and `managed_section.source_file` names the one the helper measured, the first of them; a target file that cannot be read is skipped. `context_budget` gives the Context Budget Impact values, in tokens: `always_on` is the managed section's (null when no target file holds a block: render it as N/A), `on_trigger` SKILL.md's, `full_disclosure` SKILL.md's plus `references_total.tokens` (what an agent loads when it reads every reference too: neither the snippet nor metadata.json, so it is not `package_total`), and `snippet` the context-snippet.md row's.
|
|
49
|
+
|
|
50
|
+
**If `passive_context` was disabled:** the helper reports the `context-snippet.md` row with `exists: false` and `managed_section.present: false`: render both rows, the always-on cost and the benchmark as "N/A (disabled)".
|
|
48
51
|
|
|
49
52
|
### 2. Present Token Report
|
|
50
53
|
|
|
51
|
-
Render the
|
|
54
|
+
Render the report straight from the helper JSON, with no re-counting. In each table row, `{n}` ← the row's `words` and `{t}` ← its `tokens`:
|
|
52
55
|
|
|
53
56
|
- `context-snippet.md` row → `files[].tokens` / `.words` for `role: context-snippet`
|
|
54
|
-
- `Managed section` row → `managed_section.words` / `.tokens` (report once for the run)
|
|
57
|
+
- `Managed section` row → `managed_section.words` / `.tokens`; `{target-file-list}` ← `managed_section.target_files`, joined with commas; `{skill-count}` ← `managed_section.skills` (report the row once for the run)
|
|
55
58
|
- `SKILL.md` row → `files[]` for `role: skill-md`
|
|
56
59
|
- `metadata.json` row → `files[]` for `role: metadata`
|
|
57
|
-
- `references/` row → `references_total.words` / `.tokens`; `{count}`
|
|
60
|
+
- `references/` row → `references_total.words` / `.tokens`; `{count}` ← `references_total.count`
|
|
58
61
|
- `Package total` row → `package_total.words` / `.tokens`
|
|
59
62
|
|
|
63
|
+
The Context Budget Impact lines, per skill in multi-skill mode (the always-on cost once for the run):
|
|
64
|
+
|
|
65
|
+
- `{managed-section-tokens}` ← `context_budget.always_on`, the managed section's tokens (N/A when null)
|
|
66
|
+
- `{measured-file}` ← `managed_section.source_file`, the target file the helper measured
|
|
67
|
+
- `{skill-tokens}` ← `context_budget.on_trigger`, SKILL.md's tokens
|
|
68
|
+
- `{total-tokens}` ← `context_budget.full_disclosure`, SKILL.md's tokens plus `references_total.tokens` (never `package_total`, which also counts the snippet and metadata.json)
|
|
69
|
+
- `{snippet-tokens}` ← `context_budget.snippet`, the context-snippet.md row's tokens
|
|
70
|
+
- `{skill-name}` ← `skill_package`, its last folder (multi-skill mode only)
|
|
71
|
+
|
|
60
72
|
"**Token Report**
|
|
61
73
|
|
|
62
74
|
| Artifact | Words | Est. Tokens | Notes |
|
|
63
75
|
|----------|-------|-------------|-------|
|
|
64
76
|
| context-snippet.md | {n} | ~{t} | Passive context (always-on) |
|
|
65
|
-
| Managed section (all skills) | {n} | ~{t} | In {target-file-list}, all {count} skills |
|
|
77
|
+
| Managed section (all skills) | {n} | ~{t} | In {target-file-list}, all {skill-count} skills |
|
|
66
78
|
| SKILL.md | {n} | ~{t} | Active skill (on-trigger) |
|
|
67
79
|
| metadata.json | {n} | ~{t} | Machine-readable |
|
|
68
80
|
| references/ | {n} | ~{t} | {count} files |
|
|
69
81
|
| **Package total** | **{n}** | **~{t}** | **This skill's own artifacts (snippet + SKILL.md + metadata + references); excludes the shared Managed section row** |
|
|
70
82
|
|
|
71
83
|
**Context Budget Impact:**
|
|
72
|
-
- **Always-on cost:** ~{managed-section-tokens} tokens (managed section in {target-file-list})
|
|
84
|
+
- **Always-on cost:** ~{managed-section-tokens} tokens (measured in {measured-file}; the managed section is in {target-file-list})
|
|
73
85
|
- **On-trigger cost:** ~{skill-tokens} tokens (when SKILL.md is loaded)
|
|
74
86
|
- **Full disclosure cost:** ~{total-tokens} tokens (if references/ also loaded)
|
|
75
87
|
|
|
76
88
|
**Benchmark:** Target is ~80-120 tokens per skill in managed section. Current: ~{snippet-tokens} tokens."
|
|
77
89
|
|
|
90
|
+
**In multi-skill mode**, render the Context Budget Impact and the Benchmark this way instead: the always-on cost once for the run, then one line per skill, each from that skill's own run:
|
|
91
|
+
|
|
92
|
+
"**Context Budget Impact:**
|
|
93
|
+
- **Always-on cost:** ~{managed-section-tokens} tokens (measured in {measured-file}; the managed section is in {target-file-list})
|
|
94
|
+
- **{skill-name}:** on-trigger ~{skill-tokens} tokens, full disclosure ~{total-tokens} tokens, snippet ~{snippet-tokens} tokens
|
|
95
|
+
|
|
96
|
+
**Benchmark:** Target is ~80-120 tokens per skill in managed section."
|
|
97
|
+
|
|
78
98
|
### 3. Proceed to Summary
|
|
79
99
|
|
|
80
100
|
Display: "**Proceeding to export summary...**"
|
package/src/skf-forger/SKILL.md
CHANGED
|
@@ -86,10 +86,10 @@ Run these steps once, in order, before the first reply.
|
|
|
86
86
|
- No JSON object with a `status`: HARD HALT with what the call printed. When `uv` itself is missing, add that SKF runs its scripts with `uv`, which installs from <https://docs.astral.sh/uv/getting-started/installation/>.
|
|
87
87
|
- `status` `ok`: bind `{project_name}`, `{user_name}`, `{communication_language}` and `{document_output_language}` from `config`, and each folder from its absolute path: `{output_folder}` from `config.output_folder_resolved`, `{skills_output_folder}` from `config.skills_output_folder_resolved`, `{forge_data_folder}` from `config.forge_data_folder_resolved` and `{sidecar_path}` from `config.sidecar_path_resolved`. The greeting reads `derived`. `sidecar.preferences_error` and `sidecar.forge_tier_error` appear only for a file that is there but did not load: name that file in one line of the greeting and go on with its defaults.
|
|
88
88
|
|
|
89
|
-
3. **Resolve agent customization.**
|
|
89
|
+
3. **Resolve agent 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:
|
|
90
90
|
|
|
91
91
|
```bash
|
|
92
|
-
uv run {
|
|
92
|
+
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key agent
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-forger.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. Ferris keeps no run log of his own, so that line opens the greeting.
|
|
@@ -79,10 +79,10 @@ These rules apply to every step in this workflow:
|
|
|
79
79
|
|
|
80
80
|
2. **Resolve `{headless_mode}`**: true if `--headless` or `-H` was passed as an argument, or if `{sidecar_path}/preferences.yaml` sets `headless_mode: true`. Default: false.
|
|
81
81
|
|
|
82
|
-
3. **Resolve workflow customization.**
|
|
82
|
+
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:
|
|
83
83
|
|
|
84
84
|
```bash
|
|
85
|
-
uv run {
|
|
85
|
+
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-quick-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) and, when `{emitEnvelopeHelper}` has a path, record it in the run sink: write `customization_resolver_unavailable: {customization_resolver_unavailable}` to `{run_dir}/resolver-warning.txt` with a file write, never `echo` (the reason can hold quotes or `$( )`), then run `uv run {emitEnvelopeHelper} record --run-dir "{run_dir}" --warning "$(cat "{run_dir}/resolver-warning.txt")"`. Under `--batch`, `references/batch-mode.md` §2 records that file in each target's run folder.
|
|
@@ -24,8 +24,6 @@ You are an architecture refinement analyst in Ferris Architect mode; the user br
|
|
|
24
24
|
|
|
25
25
|
## Workflow Rules
|
|
26
26
|
|
|
27
|
-
These rules apply to every step in this workflow:
|
|
28
|
-
|
|
29
27
|
- Never speculate — every gap, issue, or improvement must cite specific APIs, types, or function signatures from the generated skills
|
|
30
28
|
- Only load one step file at a time — never preload future steps
|
|
31
29
|
- Always communicate in `{communication_language}`
|
|
@@ -67,15 +65,15 @@ When `{headless_mode}` is true, step 6 prints one `SKF_REFINE_ARCHITECTURE_RESUL
|
|
|
67
65
|
- `skills_output_folder`, `forge_data_folder`, `output_folder`, `sidecar_path`
|
|
68
66
|
|
|
69
67
|
2. **Compute run-scoped variables:**
|
|
70
|
-
- `timestamp` ← the output of `date -u +%Y%m%d-%H%M%S`, run once now
|
|
71
|
-
- `run_dir` ← `{project-root}/_bmad-output/.skf-run/skf-refine-architecture-{timestamp}
|
|
68
|
+
- `timestamp` ← the output of `date -u +%Y%m%d-%H%M%S`, run once now.
|
|
69
|
+
- `run_dir` ← `{project-root}/_bmad-output/.skf-run/skf-refine-architecture-{timestamp}`
|
|
72
70
|
|
|
73
71
|
3. **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.
|
|
74
72
|
|
|
75
|
-
4. **Resolve workflow customization.**
|
|
73
|
+
4. **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:
|
|
76
74
|
|
|
77
75
|
```bash
|
|
78
|
-
uv run {
|
|
76
|
+
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow
|
|
79
77
|
```
|
|
80
78
|
|
|
81
79
|
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-refine-architecture.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}` for §5 to record.
|
|
@@ -86,7 +84,7 @@ When `{headless_mode}` is true, step 6 prints one `SKF_REFINE_ARCHITECTURE_RESUL
|
|
|
86
84
|
- `{outputFolderPath}` ← `workflow.output_folder_path` if non-empty, else `{output_folder}`
|
|
87
85
|
- `{onCompleteCommand}` ← `workflow.on_complete` if non-empty, else empty
|
|
88
86
|
|
|
89
|
-
Then run `workflow.activation_steps_prepend` now, treat `workflow.persistent_facts` as standing context for the run (`file
|
|
87
|
+
Then run `workflow.activation_steps_prepend` now, treat `workflow.persistent_facts` as standing context for the run (`file:` entries load their files or globs as facts: the bundled default loads every `project-context.md` under `{project-root}`; an entry prefixed `!` drops each earlier entry it names and loads nothing itself, so an override's `"!file:{project-root}/**/project-context.md"` turns that default off), then run `workflow.activation_steps_append` once §5's pre-flight has passed, before §6 loads the first stage.
|
|
90
88
|
|
|
91
89
|
5. **Pre-flight: the emitter, config, write probe and run folder.** In that order, so every halt can print its envelope and an empty path halts as missing config (exit 3), not as a write failure.
|
|
92
90
|
|
|
@@ -114,7 +112,7 @@ When `{headless_mode}` is true, step 6 prints one `SKF_REFINE_ARCHITECTURE_RESUL
|
|
|
114
112
|
|
|
115
113
|
On any non-zero exit: HALT (exit code 4, `halt_reason: "write-failed"`) at phase `on-activation:write-probe`, with `"path"` set to the folder that failed.
|
|
116
114
|
|
|
117
|
-
**Run folder (exit 4).**
|
|
115
|
+
**Run folder (exit 4).**
|
|
118
116
|
|
|
119
117
|
```bash
|
|
120
118
|
mkdir -p "{project-root}/_bmad-output/.skf-run" && mkdir "{run_dir}"
|
|
@@ -63,10 +63,10 @@ Headless callers: the inputs, flags, gate map, outputs, concurrency rule and `SK
|
|
|
63
63
|
- `{dry_run}`: true if `--dry-run` was passed, else false.
|
|
64
64
|
- `{old_name_arg}` and `{new_name_arg}`: the current name and the new name the invocation supplied, as arguments or in a request such as "rename cognee to cognee-ai", each null when absent. select.md §4 and §5 take a supplied name in either mode instead of asking for it.
|
|
65
65
|
|
|
66
|
-
3. **Resolve workflow customization.**
|
|
66
|
+
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:
|
|
67
67
|
|
|
68
68
|
```bash
|
|
69
|
-
uv run {
|
|
69
|
+
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-rename-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 select.md §1 to record once it has created `{run_dir}`.
|
package/src/skf-setup/SKILL.md
CHANGED
|
@@ -65,10 +65,10 @@ You are a system executor: run each step in sequence, write the configuration fi
|
|
|
65
65
|
|
|
66
66
|
5. **Reconcile `{headless_mode}`**: OR it with `derived.headless_mode` (`headless_mode: true` in `preferences.yaml`). Then, if `{headless_mode}` is true, set `{quiet_mode}` to true.
|
|
67
67
|
|
|
68
|
-
6. **Resolve workflow customization.**
|
|
68
|
+
6. **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:
|
|
69
69
|
|
|
70
70
|
```bash
|
|
71
|
-
uv run {
|
|
71
|
+
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-setup.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. Bind `{customization_resolver_unavailable}` ← that reason (null when the resolver ran), which `references/report.md` and every later halt hand to the emitter; print the warning only when `{quiet_mode}` is false.
|
|
@@ -63,10 +63,10 @@ Headless callers: the full argument set (`Inputs`), the outputs, the exit codes
|
|
|
63
63
|
|
|
64
64
|
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.
|
|
65
65
|
|
|
66
|
-
3. **Resolve workflow customization.**
|
|
66
|
+
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:
|
|
67
67
|
|
|
68
68
|
```bash
|
|
69
|
-
uv run {
|
|
69
|
+
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-test-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}`: report.md §4c hands it to the result as a warning.
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
**Mode:** Naive (structural validation only)
|
|
9
9
|
**Coherence category:** Not scored (weight redistributed)
|
|
10
|
+
**Migration Section:** {the note migration-section-rules.md Case 4 records; leave this line out when it records none}
|
|
10
11
|
|
|
11
12
|
### Structural Findings
|
|
12
13
|
|
|
@@ -19,13 +20,13 @@
|
|
|
19
20
|
|
|
20
21
|
## Coherence Analysis — Naive Mode: Reference Consistency (split-body)
|
|
21
22
|
|
|
22
|
-
Only rendered when `references/` directory exists alongside SKILL.md.
|
|
23
|
+
Only rendered when `references/` directory exists alongside SKILL.md. Each row is a split-body gap coverage-check recorded, with its Source at `SKILL.md:{SKILL.md Line}` when the pinned source contradicts the body, else at `{Reference File}:{Reference Line}`. **Contradicted Side** is the side the source contradicts (`SKILL.md` or `reference`), `none` when both forms agree with it, or `not settled` when no source settled it.
|
|
23
24
|
|
|
24
25
|
```markdown
|
|
25
26
|
### Reference Consistency (split-body)
|
|
26
27
|
|
|
27
|
-
| # | Reference File | Export | Issue | SKILL.md Line | Reference Line |
|
|
28
|
-
|
|
28
|
+
| # | Reference File | Export | Issue | SKILL.md Line | Reference Line | Contradicted Side |
|
|
29
|
+
|---|---------------|--------|-------|---------------|---------------|-------------------|
|
|
29
30
|
| {per-mismatch rows} |
|
|
30
31
|
|
|
31
32
|
**Exports Cross-Checked:** {count}
|
|
@@ -41,6 +42,7 @@ Only rendered when `references/` directory exists alongside SKILL.md.
|
|
|
41
42
|
**References Found:** {count}
|
|
42
43
|
**References Valid:** {count}
|
|
43
44
|
**Broken References:** {count}
|
|
45
|
+
**Migration Section:** {the note migration-section-rules.md Case 4 records; leave this line out when it records none}
|
|
44
46
|
|
|
45
47
|
### Reference Validation
|
|
46
48
|
|
|
@@ -78,7 +80,7 @@ Every gap a stage finds is one JSON record in the run's gap ledger, `{forge_vers
|
|
|
78
80
|
```
|
|
79
81
|
|
|
80
82
|
- `severity` and `category` come from one row of the Gap Severity table in `references/scoring-rules.md`. The script refuses a category outside its vocabulary, which `uv run scripts/gap-ledger.py categories` lists.
|
|
81
|
-
- `source` is a `file:line` pair whenever a line applies, with nothing after it: update-skill reads the pair as the gap's citation. A gap inside the skill package cites `SKILL.md:{line}` or `references/{file}.md:{line}`.
|
|
83
|
+
- `source` is a `file:line` pair whenever a line applies, with nothing after it: update-skill reads the pair as the gap's citation. A gap inside the skill package cites `SKILL.md:{line}` or `references/{file}.md:{line}`. A split-body record, titled `Split-body mismatch: {export} ({reference_file})`, cites the side the pinned source contradicts: `SKILL.md:{skill_md_line}` when the reference agrees with the source declaration and the body does not, else `{reference_file}:{reference_line}`, also when no source settled it. Its remediation names the file to edit and the file and line whose form it takes, and update-skill changes only what it names as contradicting.
|
|
82
84
|
- `remediation` follows the Remediation Quality Rules below and names each file it touches.
|
|
83
85
|
- Leave out `id`, `group` and `stage`: the script assigns them (`GAP-{NNN}` in the order the gaps were recorded, the group from the category, the stage from `--stage`).
|
|
84
86
|
|
|
@@ -77,7 +77,7 @@ uv run {scanSkillMdStructureHelper} usage-scope "{resolved_skill_package}/SKILL.
|
|
|
77
77
|
The script decides split-body from the `references/` folder alone; whether the `## Full*` sections are stubs is your judgment, so pass `--body single` when a `references/` directory exists but those sections carry real content. Read `zero_usage[]` from the output:
|
|
78
78
|
- **Zero occurrences across the entire scope → Medium severity** finding, one per `zero_usage[]` entry: `naive-coherence: exported {kind} \`{name}\` is not referenced in any usage-family section or reference file`. This catches the "documented but unused" failure mode that trivially fails discovery testing. Like a missing export, it is a completeness gap, so it does not block the hard gate.
|
|
79
79
|
|
|
80
|
-
**2.5 Async/sync consistency.** `{locateExportSegmentsScript}` reports the facts: for each export the step 3 inventory lists with `kind` `function` or `method`, where the skill describes it (`descriptions[]`: the heading sections, table rows, list items and paragraphs that name it in code, and the signature lines that declare it) and each call to it in fenced code, with whether the call awaits it. It reads the names from the validated inventory file itself, keeping the `function` and `method` entries, so no name is copied by hand. Skip this check when the inventory has no such export; otherwise run the script (it resolves relative to the skill root):
|
|
80
|
+
**2.5 Async/sync consistency.** `{locateExportSegmentsScript}` reports the facts: for each export the step 3 inventory lists with `kind` `function` or `method`, where the skill describes it (`descriptions[]`: the heading sections, table rows, list items and paragraphs that name it in code, and the signature lines that declare it) and each call to it in fenced code, with whether the call awaits it (`awaited`) and how (`awaitedVia`): `direct` when the await applies to the call itself (`directlyAwaited`), `chain` when it applies to a member, subscript, call or group around the call, `consumer` for an async loop or context manager, and `argument` or `runner` when the call sits in the arguments of an awaited call or of a runner, whose callee `enclosingCallee` names. It reads the names from the validated inventory file itself, keeping the `function` and `method` entries, so no name is copied by hand. Skip this check when the inventory has no such export; otherwise run the script (it resolves relative to the skill root):
|
|
81
81
|
|
|
82
82
|
```bash
|
|
83
83
|
uv run {locateExportSegmentsScript} --inventory "{run_dir}/inventory.json" --skill-dir "{resolved_skill_package}"
|
|
@@ -92,7 +92,8 @@ The script judges no meaning. For each `exports[]` entry, read its `descriptions
|
|
|
92
92
|
Then, for each export:
|
|
93
93
|
|
|
94
94
|
- **async**, no call is awaited (`awaitedCount` is 0) and no unawaited call hands its result on → **High severity** finding: `naive-coherence: \`{name}\` described as async but example lacks \`await\`` (cite the first call's `file` and `line`). A call hands its result on when its `chainedMember` waits for it (`then`, `catch`, `join`, `get`), the example returns it (`return fetchData(x)` in an async function) or stores it and awaits it later: read the call lines before raising the finding.
|
|
95
|
-
- **sync** and `
|
|
95
|
+
- **sync** and `directlyAwaitedCount` is above 0 → **High severity** finding: `naive-coherence: \`{name}\` described as sync but example awaits it` (cite the first directly awaited call).
|
|
96
|
+
- **sync**, `directlyAwaitedCount` is 0 and a call sits in the arguments of a call that needs awaitable arguments → the same **High severity** finding (cite the first such call's `file` and `line`): its `awaitedVia` is `runner` (a runner such as Python `asyncio.run` or `loop.run_until_complete`, Rust `block_on`), or `argument` with an `enclosingCallee` of Python `asyncio.gather`, `asyncio.wait` or `asyncio.wait_for`. JavaScript `Promise.all`, `Promise.race` and `Promise.allSettled` accept plain values, so a call in their arguments raises nothing, and neither does any other call that is `awaited` but not `directlyAwaited` (`awaitedVia` `chain`, `consumer`, or `argument` of another callee).
|
|
96
97
|
- Otherwise no finding. The names in `notCalled[]` appear in no example, so they raise none.
|
|
97
98
|
|
|
98
99
|
**2.6 Table syntax.** Read `table_drift[]` from the second JSON blob (§2.0). The script normalizes escaped pipes (`\|`, used inside TypeScript union types such as `string \| undefined`) before splitting and compares each row against its header's column count, so a plain `split on |` false-positive cannot occur here. For each entry, emit a **Medium severity** finding: `naive-coherence: table row at line {entry.line} has {entry.actual_cols} columns; header has {entry.expected_cols}` (the `entry.section` and `entry.row` fields populate the detail when present).
|
|
@@ -136,7 +137,7 @@ SKILL.md's cross-references come in two kinds, and each has one owner:
|
|
|
136
137
|
uv run {scanSkillMdStructureHelper} reference-check "{resolved_skill_package}/SKILL.md" [--source-root "{source_path}"] [--skills-root "{skills_output_folder}"] > "{run_dir}/references.json"
|
|
137
138
|
```
|
|
138
139
|
|
|
139
|
-
Pass `--source-root` when metadata.json records the extraction tree (`{source_path}`), and `--skills-root` for a stack skill: a stack's constituent cross-references legitimately resolve to `skills/{name}/active` under `{skills_output_folder}`, which lies outside `{skillDir}`, and a stack's metadata.json records no
|
|
140
|
+
Pass `--source-root` when metadata.json records the extraction tree (`{source_path}`), and `--skills-root` for a stack skill: a stack's constituent cross-references legitimately resolve to `skills/{name}/active` under `{skills_output_folder}`, which lies outside `{skillDir}`, and a stack's metadata.json records no `source_root` to anchor them. The scanner resolves each target against the SKILL.md folder through every symlink (`os.path.realpath`) before the root check, so a symlink that points outside every root is still caught, and gives it a `status`: `ok` (inside a root, present), `missing` (inside a root, nothing there) or `escapes` (outside every root: `../../../etc/passwd`, an absolute path to an unrelated folder, a symlink redirection outside the skill, its source or a stack's skills tree). Do not check these targets again by hand or through subagents, and do not validate an escaping target's contents: the escape itself is the finding.
|
|
140
141
|
- **Skill references, type imports and integration-pattern references** (`See SKILL.md for {other-skill}`, `Integrates with {package}`, `import { Type } from './module'`, middleware chains, plugin hooks, shared state): whether one is accurate takes judgment. Delegate to a subagent that reads SKILL.md and returns only this JSON shape (no prose, no commentary, no markdown fences): `{"references_found": [{"line": N, "type": "skill|type-import|integration-pattern", "target": "..."}]}`. Parent strips wrapping markdown fences (if present) before parsing. If subagent unavailable, scan in main thread.
|
|
141
142
|
|
|
142
143
|
### 4. Contextual Mode: Validate Each Reference
|
|
@@ -238,7 +239,7 @@ Then record every coherence gap in the gap ledger `{ledgerFile}`: the hard gate
|
|
|
238
239
|
uv run {gapLedgerScript} append --ledger "{ledgerFile}" --stage coherence-check --from structure --input "{run_dir}/references.json"
|
|
239
240
|
```
|
|
240
241
|
|
|
241
|
-
Then write the records no script wrote, each in the Ledger Record Format of `{outputFormatsFile}`: the §2.5 findings, titled with their `naive-coherence:` text and with their Source at `
|
|
242
|
+
Then write the records no script wrote, each in the Ledger Record Format of `{outputFormatsFile}`: the §2.5 findings, titled with their `naive-coherence:` text and with their Source at the cited call's `{file}:{line}`; each §5 incomplete integration pattern; and in both modes a §2b / §5b migration section finding (Case 1 and Case 3 are Medium `migration-section` gaps, a Low `migration-section` gap when the reviewer downgraded Case 3 with an inline justification, and Case 2 is an Info `migration-section` gap). The split-body rows of the Reference Consistency table are not recorded here: coverage-check §1b recorded them. Write them as one JSON array on the lines between the two markers, exactly as they are: the quoted marker hands them to the script unchanged, quotes, apostrophes and `$` included. Run the command even when the array is empty (`[]`): the hard gate refuses to decide until every stage before it has recorded, with gaps or without.
|
|
242
243
|
|
|
243
244
|
```bash
|
|
244
245
|
uv run {gapLedgerScript} append --ledger "{ledgerFile}" --stage coherence-check <<'SKF_GAPS'
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Coverage Check: Branch Sections
|
|
2
|
+
|
|
3
|
+
Load this file only from the coverage-check.md sentence that names one of its sections, and follow that section alone: **Inventory Failures** from coverage-check.md §1a when the inventory check fails; **States 2 to 4** from coverage-check.md §2 when the source is not local, and only its State 2 command from coverage-check.md §2b when `rebuild` is `provenance-fold`; **Scalar and Stack Branches** from coverage-check.md §2c on the scalar or stack branch; **Denominator Candidates** from coverage-check.md §4 when `denominator.clause` is `stratified-scope` or `multi-entry`. Load it once: when another sentence names one of its sections, read that section from the copy already loaded. A bare § in a section is coverage-check.md's. coverage-check.md's frontmatter bindings, its run files, its per-file results and its §2 **Exits** apply here unchanged. When the section is done, continue in coverage-check.md right after the sentence that loaded it.
|
|
4
|
+
|
|
5
|
+
## Inventory Failures
|
|
6
|
+
|
|
7
|
+
- **`valid` false (exit 2):** delete the response `{validateInventoryScript}` refused, so a stale one is never validated twice:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
rm -f "{run_dir}/inventory-response.txt"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Re-dispatch the §1 subagent once, with the script's `violations[]` appended to its instructions (an absent name as a name the skill does not write): it writes its new response to that file, or returns it for you to save there, as §1 says. Run the §1a command again.
|
|
14
|
+
- **Exit 1** (no input, a file that cannot be read, or a package with no readable SKILL.md): correct the file and run it again.
|
|
15
|
+
|
|
16
|
+
## States 2 to 4
|
|
17
|
+
|
|
18
|
+
- **State 2** (`{forge_provenance_map}`, the provenance map init.md §2 bound): the union of its named exports and the `metadata.json` `exports[]` names, with `--fold`, `--keep` and `--fold-prefix` as the Source API Surface Definition's canonicalization says:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
uv run {coverageInputsScript} surface --provenance "{forge_provenance_map}" --metadata "{resolved_skill_package}/metadata.json" [--brief "{forge_data_folder}/{skill_name}/skill-brief.yaml"] [--fold] [--keep <variant>] [--fold-prefix <prefix>] [--monorepo] [--pattern-reference-intent] [--curated-subset] --output "{run_dir}/surface.json"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Step 5's scoring script reads its `state2` counts, and the report its fold summary, `canonical.summary`.
|
|
25
|
+
- **State 3**: the `metadata.json` `exports[]` names: `surface --metadata "{resolved_skill_package}/metadata.json" [--monorepo] [--pattern-reference-intent] [--curated-subset] --output "{run_dir}/surface.json"`.
|
|
26
|
+
- **State 4**: the names read remotely by eye, as per-file results: `surface --per-file "{run_dir}/per-file-<n>.json" [--brief "{forge_data_folder}/{skill_name}/skill-brief.yaml"] --metadata "{resolved_skill_package}/metadata.json" [--monorepo] [--pattern-reference-intent] [--curated-subset] [--barrel <state>] --output "{run_dir}/surface.json"`.
|
|
27
|
+
|
|
28
|
+
## Scalar and Stack Branches
|
|
29
|
+
|
|
30
|
+
Run the command of the branch §2b picked; §2c's exits apply to it:
|
|
31
|
+
|
|
32
|
+
- **Scalar**, over the `coverage-inputs.json` scalar and §2b's set (`denominator.surfaceSet`); `--verified` makes §2b's `verified` count the numerator when the count was inflated and the branch counts no set:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
uv run {reconcileScript} --denominator-source scalar --coverage-inputs "{run_dir}/coverage-inputs.json" --inventory "{run_dir}/inventory.json" --surface "{run_dir}/surface.json" --surface-set <set> --skill-dir "{resolved_skill_package}" --verified "{run_dir}/numerator.json" --output "{run_dir}/coverage.json"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- **Stack** (`metadata.json.skill_type == "stack"`):
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
uv run {reconcileScript} --denominator-source stack --coverage-inputs "{run_dir}/coverage-inputs.json" --skill-dir "{resolved_skill_package}" --output "{run_dir}/coverage.json"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**The scalar branch counts §2b's set** only when `surface.json` `denominator.setCounted` is true: a scope set (not `all`) whose every name sits at the file that defines it (a complete runner extraction, the fallback scan, or quick output that traces each name), read whole. Then `numeratorSource` is `surface`: `documented` counts the documented names the set holds (`documentedNames`), so a documented name the set lacks never offsets an undocumented one; `missing` lists the set's undocumented names; §5's render draws the per-export table; and the denominator stays the stored count, so when the set's size (`surfaceSetSize`) differs from it, `warnings` names both. Otherwise (`all`, a set read by eye or from an incomplete extraction, or an empty set, which warns) the branch looks each documented name up in the skill, `--verified` applies, and `missing` lists no name. On any surface read from the source (`guards.deflation.applicable` true), `stale` lists the documented names the whole surface (the set and the `all` set) lacks, with `staleApplicable` true, so §2c's Stale names bullet applies: a documented name outside the set that the source still exports is neither counted nor stale. §5's render lists each `warnings` entry.
|
|
45
|
+
|
|
46
|
+
**A surplus is a signal, not a pass.** Where the numerator and denominator count independent sets (the stack branch, and the scalar branch when it counts no set), `documented` can exceed `denominator`, as it can when a counted set holds more names than the stored count: `coverage.json` then reports `numeratorSurplus` above 0 and `coverageCapped: true`, holds `exportCoverage` at 100 and keeps the raw ratio in `coverageUncapped`, and §5's render states the surplus beside both counts. A large surplus is the deflated-denominator signature: check `guards.deflation`, whose `basis` names the set it measured the stored count against, before accepting the 100.
|
|
47
|
+
|
|
48
|
+
coverage-check.md §5b records this gap from the run files:
|
|
49
|
+
|
|
50
|
+
- **Missing count** (scalar or stack branch, `missingCount` above 0): one Medium `missing-export` gap for the count. A scalar result that counts its set records its `missing` names as §2c's Missing names bullet says instead, plus one gap for the rest when `missingCount` exceeds them. When §2b's numerator check found the count inflated and the scalar branch counted no set, its `absent[]` names replace this gap, one each: update-skill documents a named export, not a count.
|
|
51
|
+
|
|
52
|
+
## Denominator Candidates
|
|
53
|
+
|
|
54
|
+
```markdown
|
|
55
|
+
**Denominator Candidates** (stratified-scope audit trail):
|
|
56
|
+
- `stats.effective_denominator`: {statsEffectiveDenominator | absent} {← chosen on the scalar branch}
|
|
57
|
+
- `scope.tier_a_include` union: {tierAIncludeUnion | absent} {← chosen if the barrel branch's set}
|
|
58
|
+
- `scope.include` union: {scopeIncludeUnion | absent} {← chosen if the barrel branch's set}
|
|
59
|
+
- exports-map subpath union: {subpathUnion | absent} {← chosen if the barrel branch's set}
|
|
60
|
+
- root barrel: {rootBarrel | absent} {secondary candidate: root-barrel-vs-subpath-union audit}
|
|
61
|
+
```
|
|
@@ -4,7 +4,7 @@ Load this file only from coverage-check.md §2, and follow the one section §2 n
|
|
|
4
4
|
|
|
5
5
|
## Quick Tier
|
|
6
6
|
|
|
7
|
-
- **A skill quick-skill built** (`metadata.json` `generated_by` is `quick-skill`), **from local source** (State 1):
|
|
7
|
+
- **A skill quick-skill built** (`metadata.json` `generated_by` is `quick-skill`), or **any skill in Python, JavaScript or TypeScript**, **from local source** (State 1): parse the entry points with `{extractPublicApiHelper}` `--mode quick`. quick-skill built its export list with that parser, so both sides count one surface, and in those three languages it gives each name the file that defines it (`defined_in`), so the brief's globs place the name as the recipe runner does. Resolve it ← first existing path in `{extractPublicApiProbeOrder}` and run it once per package in scope, with its manifest and one `--entry-file` for each entry-point file the Source API Surface Definition makes its surface, each path relative to `{source_path}` (`<n>` counts the packages from 1):
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
uv run {extractPublicApiHelper} --mode quick --language <language> --source-root "{source_path}" --manifest-file <manifest path> --entry-file <entry path> > "{run_dir}/quick-<n>.json"
|
|
@@ -13,10 +13,10 @@ Load this file only from coverage-check.md §2, and follow the one section §2 n
|
|
|
13
13
|
Its `warnings` name each statement whose names the entry file alone cannot give. Read by eye only the statements its warnings name, save the names they give as a per-file result for the entry file that holds the statement, `{"file": "<entry path>", "exports_found": [<each name>], "signature_mismatches": []}`, and list those names in the Coverage Analysis section as read by eye. Give `surface` one `--quick` per package, one `--per-file` per saved result, the brief when the skill has one (its globs give the `scope.include` and `tier_a_include` sets), the metadata and, when init.md §2 bound one, the provenance map (the baselines of the §2b candidates and guards; they add no name):
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
uv run {coverageInputsScript} surface --quick "{run_dir}/quick-<n>.json" [--per-file "{run_dir}/per-file-<n>.json"] [--brief "{forge_data_folder}/{skill_name}/skill-brief.yaml"] --metadata "{resolved_skill_package}/metadata.json" [--provenance "{forge_provenance_map}"] --output "{run_dir}/surface.json"
|
|
16
|
+
uv run {coverageInputsScript} surface --quick "{run_dir}/quick-<n>.json" [--per-file "{run_dir}/per-file-<n>.json"] [--brief "{forge_data_folder}/{skill_name}/skill-brief.yaml"] --metadata "{resolved_skill_package}/metadata.json" [--provenance "{forge_provenance_map}"] [--monorepo] [--pattern-reference-intent] [--curated-subset] [--barrel <state>] --output "{run_dir}/surface.json"
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
- **Any other skill** (create-skill's Quick tier reads the entry points as source text), a source that is not local, or a helper that does not resolve or exits non-zero (it exits 1 on a language it does not parse): identify the exports by reading the entry points per the Source API Surface Definition, save one per-file result per entry file, run the `surface` command above with no `--quick`, and note in the Coverage Analysis section that the Quick-tier scan read them by eye.
|
|
19
|
+
- **Any other skill** (create-skill's Quick tier reads the entry points of another language as source text), a source that is not local, or a helper that does not resolve or exits non-zero (it exits 1 on a language it does not parse): identify the exports by reading the entry points per the Source API Surface Definition, save one per-file result per entry file, run the `surface` command above with no `--quick`, and note in the Coverage Analysis section that the Quick-tier scan read them by eye.
|
|
20
20
|
|
|
21
21
|
## Fallback Per-File Scan
|
|
22
22
|
|
|
@@ -26,10 +26,10 @@ The recipe runner cannot be used, so subagents read the files in scope one at a
|
|
|
26
26
|
uv run {scoreSignaturesScript} plan --inventory "{run_dir}/inventory.json" --output "{run_dir}/signature-plan.json"
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
For each source file that defines public API exports, delegate a subagent that gets that map with the file. It extracts the file
|
|
29
|
+
For each source file that defines public API exports, delegate a subagent that gets that map with the file. It extracts the symbols the file exports, with their full signatures, with ast-grep (or reads the file when `extraction.fallback.reason` says no ast-grep can run; list those files in the Coverage Analysis section as read by eye), leaves out a name the file only passes on from another module of the source being tested (`export { x } from './x'`, `export * from`, an `__init__.py` import, a Rust `pub use`), which that module's own result counts, keeps a name it passes on from another package (`export { z } from 'zod'`), which no other result counts, compares each with the map's entry (params, return type), gives each mismatch the `line` that defines the export, and returns only `{"file", "exports_found": [...], "types_found": [<the exported interfaces, type aliases, enums and classes>], "signature_mismatches": [...]}`, each mismatch in the shape §2's signature comparison returns. Tell it the overload rule of coverage-check.md §2: a declaration opening an overload set (Python `@overload` stubs, TS bodiless signatures) is read through its implementation, and a documented signature matching the implementation or any overload is no mismatch. Save each response to `{run_dir}/per-file-<n>.txt` with the Write tool and build the surface from them, one `--per-file` each and `--fallback-scan` (each names the file that defines its exports, so a `scope.tier_a_include` list that counts none of them does not turn the guards off), with the same baselines as at Quick tier, and `--extraction` when the runner's JSON sent you here (`extraction.fallback.needed`): it adds no name, only its scope and the globs it lists as matching no file, so the guards still run:
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
uv run {coverageInputsScript} surface --per-file "{run_dir}/per-file-<n>.txt" [--brief "{forge_data_folder}/{skill_name}/skill-brief.yaml"] --metadata "{resolved_skill_package}/metadata.json" [--provenance "{forge_provenance_map}"] --output "{run_dir}/surface.json"
|
|
32
|
+
uv run {coverageInputsScript} surface --per-file "{run_dir}/per-file-<n>.txt" --fallback-scan [--extraction "{run_dir}/extract-full.json"] [--brief "{forge_data_folder}/{skill_name}/skill-brief.yaml"] --metadata "{resolved_skill_package}/metadata.json" [--provenance "{forge_provenance_map}"] [--monorepo] [--pattern-reference-intent] [--curated-subset] [--barrel <state>] --output "{run_dir}/surface.json"
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
coverage-check.md §2b scores these responses once it has picked the denominator set.
|