bmad-module-skill-forge 8.0.0 → 8.1.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/docs/_data/pinned.yaml +7 -7
- package/docs/verifying-a-skill.md +7 -7
- package/docs/workflows.md +1 -1
- package/package.json +1 -1
- package/src/shared/scripts/skf-verify-provenance-completeness.py +331 -105
- package/src/skf-analyze-source/SKILL.md +1 -1
- package/src/skf-audit-skill/SKILL.md +1 -1
- package/src/skf-brief-skill/SKILL.md +1 -1
- package/src/skf-campaign/SKILL.md +1 -1
- package/src/skf-create-skill/SKILL.md +1 -1
- package/src/skf-create-stack-skill/SKILL.md +1 -1
- package/src/skf-drop-skill/SKILL.md +1 -1
- package/src/skf-export-skill/SKILL.md +1 -1
- package/src/skf-forger/SKILL.md +1 -1
- package/src/skf-quick-skill/SKILL.md +1 -1
- package/src/skf-refine-architecture/SKILL.md +1 -1
- package/src/skf-rename-skill/SKILL.md +1 -1
- package/src/skf-setup/SKILL.md +1 -1
- package/src/skf-test-skill/SKILL.md +1 -1
- package/src/skf-test-skill/customize.toml +10 -0
- package/src/skf-test-skill/references/init.md +1 -1
- package/src/skf-test-skill/references/report.md +2 -1
- package/src/skf-test-skill/references/scoring-rules.md +2 -2
- package/src/skf-test-skill/scripts/gap-ledger.py +61 -39
- package/src/skf-test-skill/templates/test-report-template.md +4 -3
- package/src/skf-update-skill/SKILL.md +1 -1
- package/src/skf-verify-stack/SKILL.md +1 -1
|
@@ -87,7 +87,7 @@ These rules apply to every step in this workflow:
|
|
|
87
87
|
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-analyze-source.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 5 to record in the run sink.
|
|
90
|
+
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-analyze-source.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, `SKF helper skf-resolve-customization.py 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 5 to record in the run sink.
|
|
91
91
|
|
|
92
92
|
Then bind, so stage files use the variable with no conditional at the usage site:
|
|
93
93
|
|
|
@@ -79,7 +79,7 @@ Stage 1b returns to init.md at §5b's **Record for report**, then §6. Step 1 §
|
|
|
79
79
|
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-audit-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. Then keep the reason as `{customization_resolver_unavailable}`, which step 6 hands to the emitter as a warning.
|
|
82
|
+
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-audit-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, `SKF helper skf-resolve-customization.py 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. Then keep the reason as `{customization_resolver_unavailable}`, which step 6 hands to the emitter as a warning.
|
|
83
83
|
|
|
84
84
|
Bind the two scalars the stages read, each to its bundled default when the merged value is empty or absent:
|
|
85
85
|
|
|
@@ -65,7 +65,7 @@ Give the halt's own `halt_reason`: the helper derives `exit_code` from it. `skil
|
|
|
65
65
|
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
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.
|
|
68
|
+
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, `SKF helper skf-resolve-customization.py 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.
|
|
69
69
|
|
|
70
70
|
Bind the values the stage files use, taking the bundled default when the merged value is empty or absent:
|
|
71
71
|
|
|
@@ -37,7 +37,7 @@ Run these steps once, in order, before dispatching to Mode Routing.
|
|
|
37
37
|
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow
|
|
38
38
|
```
|
|
39
39
|
|
|
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).
|
|
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, `SKF helper skf-resolve-customization.py 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).
|
|
41
41
|
|
|
42
42
|
Resolve each scalar now (so step files never repeat the conditional) and stash as workflow-context variables:
|
|
43
43
|
|
|
@@ -82,7 +82,7 @@ These rules apply to every step in this workflow:
|
|
|
82
82
|
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
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.
|
|
85
|
+
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, `SKF helper skf-resolve-customization.py 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.
|
|
86
86
|
|
|
87
87
|
Apply the resolved values so the surface is not a silent no-op: execute each entry in `workflow.activation_steps_prepend` in order now; treat every entry in `workflow.persistent_facts` as standing context for the whole run (entries prefixed `file:` are paths or globs whose contents load as facts), except a `!` entry and the entry it names (`!file:{project-root}/**/project-context.md` drops the bundled project-context default); and stash `{onCompleteCommand}` ← `workflow.on_complete` (empty string = no-op) for step 8 to invoke once each brief's result contract is written. After activation completes, execute each entry in `workflow.activation_steps_append` in order.
|
|
88
88
|
|
|
@@ -87,7 +87,7 @@ The sink, `{run_dir}/warnings.jsonl`, is the list: step 7 §8b lists it in `evid
|
|
|
87
87
|
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-create-stack-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): `references/init.md` records it as the run's first warning once its pre-flight has created `{run_dir}`.
|
|
90
|
+
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-create-stack-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, `SKF helper skf-resolve-customization.py 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): `references/init.md` records it as the run's first warning once its pre-flight has created `{run_dir}`.
|
|
91
91
|
|
|
92
92
|
Apply the path-scalar fallback now so stage files don't have to repeat the conditional logic. For each of the two scalars, if the merged value is empty or absent, use the bundled default:
|
|
93
93
|
|
|
@@ -66,7 +66,7 @@ Headless callers and pipelines: the inputs, flags, gate map, exit codes and `SKF
|
|
|
66
66
|
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-drop-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. Keep the reason as `{customization_resolver_unavailable}` (unset when the resolver ran): the run folder does not exist yet, so step 4 records it.
|
|
69
|
+
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-drop-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, `SKF helper skf-resolve-customization.py 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. Keep the reason as `{customization_resolver_unavailable}` (unset when the resolver ran): the run folder does not exist yet, so step 4 records it.
|
|
70
70
|
|
|
71
71
|
Bind each scalar to its bundled default when the merged value is empty or absent:
|
|
72
72
|
|
|
@@ -65,7 +65,7 @@ Headless callers and pipelines: the inputs, flags, gates, outputs and exit codes
|
|
|
65
65
|
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-export-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. Keep the reason as `{customization_resolver_unavailable}` (unset when the resolver ran): a headless run records it once step 4 creates its run folder, and an interactive run only prints it.
|
|
68
|
+
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-export-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, `SKF helper skf-resolve-customization.py 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. Keep the reason as `{customization_resolver_unavailable}` (unset when the resolver ran): a headless run records it once step 4 creates its run folder, and an interactive run only prints it.
|
|
69
69
|
|
|
70
70
|
Bind each scalar to its bundled default when the merged value is empty or absent:
|
|
71
71
|
|
package/src/skf-forger/SKILL.md
CHANGED
|
@@ -93,7 +93,7 @@ Run these steps once, in order, before the first reply.
|
|
|
93
93
|
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key agent
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
-
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.
|
|
96
|
+
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, `SKF helper skf-resolve-customization.py 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.
|
|
97
97
|
|
|
98
98
|
The bundled arrays are empty and the roster values stay as shipped: the persona and the menu are this file's. An override may add to the arrays, and each one applies: run `agent.activation_steps_prepend` in order now; treat each `agent.persistent_facts` entry as standing context for the session (a `file:` entry loads its file or glob contents as facts; an entry prefixed `!` drops each earlier entry it names and loads nothing itself); then run `agent.activation_steps_append` once step 6 has greeted, before it dispatches a pick.
|
|
99
99
|
|
|
@@ -86,7 +86,7 @@ These rules apply to every step in this workflow:
|
|
|
86
86
|
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
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.
|
|
89
|
+
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, `SKF helper skf-resolve-customization.py 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.
|
|
90
90
|
|
|
91
91
|
Bind these three as workflow-context variables, taking the default when the merged value is empty or absent:
|
|
92
92
|
|
|
@@ -77,7 +77,7 @@ When `{headless_mode}` is true, step 6 prints one `SKF_REFINE_ARCHITECTURE_RESUL
|
|
|
77
77
|
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
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.
|
|
80
|
+
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, `SKF helper skf-resolve-customization.py 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.
|
|
81
81
|
|
|
82
82
|
Bind each path scalar, taking the bundled default when the merged value is empty or absent:
|
|
83
83
|
|
|
@@ -70,7 +70,7 @@ Headless callers: the inputs, flags, gate map, outputs, concurrency rule and `SK
|
|
|
70
70
|
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
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}`.
|
|
73
|
+
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, `SKF helper skf-resolve-customization.py 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}`.
|
|
74
74
|
|
|
75
75
|
Apply the scalar fallback now so stage files don't have to repeat the conditional logic. For each of the two scalars, if the merged value is empty or absent, the bundled default applies:
|
|
76
76
|
|
package/src/skf-setup/SKILL.md
CHANGED
|
@@ -72,7 +72,7 @@ You are a system executor: run each step in sequence, write the configuration fi
|
|
|
72
72
|
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
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.
|
|
75
|
+
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, `SKF helper skf-resolve-customization.py 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.
|
|
76
76
|
|
|
77
77
|
Apply the resolved values, with no narration of your own when `{quiet_mode}` is true: run each `workflow.activation_steps_prepend` entry now; treat each `workflow.persistent_facts` entry as standing context (a `file:` entry loads its file or glob contents); bind `{onCompleteCommand}` ← `workflow.on_complete` (empty: no hook) for `references/report.md` §5; then run each `workflow.activation_steps_append` entry, before the first stage.
|
|
78
78
|
|
|
@@ -70,7 +70,7 @@ Headless callers: the full argument set (`Inputs`), the outputs, the exit codes
|
|
|
70
70
|
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
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.
|
|
73
|
+
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, `SKF helper skf-resolve-customization.py 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.
|
|
74
74
|
|
|
75
75
|
Apply the scalar fallbacks now so stage files don't have to repeat the conditional logic. For each scalar, if the merged value is empty or absent, use the bundled default:
|
|
76
76
|
|
|
@@ -48,6 +48,16 @@ persistent_facts = [
|
|
|
48
48
|
#
|
|
49
49
|
# Lift the canonical asset paths so orgs can substitute house-style copies
|
|
50
50
|
# without forking the skill. Empty string = use the bundled default.
|
|
51
|
+
#
|
|
52
|
+
# The test report template init copies to the run's report. Default:
|
|
53
|
+
# templates/test-report-template.md. A house-style copy may reword the prose,
|
|
54
|
+
# and must keep:
|
|
55
|
+
# - the title line `# Test Report: {{skillName}}`, which init fills with
|
|
56
|
+
# the skill name;
|
|
57
|
+
# - the six `## ...` section headings, each with the `<!-- Populated by`
|
|
58
|
+
# comment under it, since each stage replaces its own placeholder.
|
|
59
|
+
# The report check halts the run (`report-anchor-missing`) on a report
|
|
60
|
+
# without them.
|
|
51
61
|
|
|
52
62
|
test_report_template_path = ""
|
|
53
63
|
|
|
@@ -320,7 +320,7 @@ nextWorkflow: ''
|
|
|
320
320
|
---
|
|
321
321
|
```
|
|
322
322
|
|
|
323
|
-
Replace the template's frontmatter with this block (the two hold the same keys). `toolingStatus` is the `tooling_status` §3b set: score.md §3a reads it back for Cap 1. `threshold` and `thresholdSource` are the pass threshold §1b resolved and its source: score.md §1 reads them back, and score.md §7 rewrites `threshold` with the threshold the verdict was settled against. `analysisConfidence` stays empty until score.md §7, or step-hard-gate.md §3 on a run the hard gate blocks, writes the source access coverage-check resolved: `full`, `provenance-map`, `metadata-only`, `remote-only` or `docs-only`. report.md §4c halts on a report that holds none of them.
|
|
323
|
+
Replace the template's frontmatter with this block (the two hold the same keys). Then replace `{{skillName}}` in the template's H1 with `{skill_name}`, so the report's title is `# Test Report: {skill_name}`: report.md §4c halts on a report without that line. `toolingStatus` is the `tooling_status` §3b set: score.md §3a reads it back for Cap 1. `threshold` and `thresholdSource` are the pass threshold §1b resolved and its source: score.md §1 reads them back, and score.md §7 rewrites `threshold` with the threshold the verdict was settled against. `analysisConfidence` stays empty until score.md §7, or step-hard-gate.md §3 on a run the hard gate blocks, writes the source access coverage-check resolved: `full`, `provenance-map`, `metadata-only`, `remote-only` or `docs-only`. report.md §4c halts on a report that holds none of them.
|
|
324
324
|
|
|
325
325
|
### 7. Report Initialization Status
|
|
326
326
|
|
|
@@ -186,9 +186,10 @@ for heading in '## Test Summary' '## Coverage Analysis' '## Coherence Analysis'
|
|
|
186
186
|
printf '%s: ' "$heading"; grep -cxF "$heading" "{outputFile}"
|
|
187
187
|
done
|
|
188
188
|
printf 'placeholders: '; grep -c '^<!-- Populated by' "{outputFile}"
|
|
189
|
+
printf 'title: '; grep -cxF '# Test Report: {skill_name}' "{outputFile}"
|
|
189
190
|
```
|
|
190
191
|
|
|
191
|
-
Each heading must print 1, standing alone on exactly one line, and `
|
|
192
|
+
Each heading must print 1, standing alone on exactly one line, `placeholders` must print 0, and `title` must print 1 (the H1 init.md §6c filled). A run the hard gate blocked has all six: step 4c §3 wrote the Completeness Score section in scoring's place. On a heading whose count is not 1, a placeholder left, or a `title` count other than 1, HALT (`halt_reason: "report-anchor-missing"`, phase `report:anchors`) with "report anchor missing: {each heading whose count is not 1, with its count}, {placeholder_count} placeholder comments left, or a `title` count of {title_count} (the report does not hold `# Test Report: {skill_name}` once); the section or title was not written once by its owning step".
|
|
192
193
|
|
|
193
194
|
**Find the health check** unless `no_health_check` is true (the `--no-health-check` flag): resolve `{healthCheckFile}` ← first existing path in `{healthCheckProbeOrder}`. It ends the run (§7), so check it now, while a HALT still leaves the previous run's report and result files in place. If neither candidate exists, HALT (`halt_reason: "health-check-missing"`, phase `report:health-check`):
|
|
194
195
|
|
|
@@ -19,7 +19,7 @@ This table is the single source of truth for classifying a gap. Every stage reco
|
|
|
19
19
|
| Severity | Category | Criteria |
|
|
20
20
|
|----------|-----------------------------|----------|
|
|
21
21
|
| Critical | `signature-mismatch` | Wrong signature: a documented signature that differs from the source in its parameters (name, type, order, optionality) or its return type, as signature-comparison-rules.md reads each form (a component by its props; an annotation either side leaves out is not compared), in any way but those the Low `signature-mismatch` row and the Info `observation` row grade, such as a renamed Python parameter (the script cannot see which parameters a caller must pass by position), an optional parameter left out of a form the skill does not label partial, or a mismatch with no `difference` (coverage-check §2 and §2b) |
|
|
22
|
-
| Critical | `fabricated-signature` | Fabricated signature: a documented export absent at the source line the skill cites for it. Decided only where ast-grep enumerated the exports at the pinned commit: the name is outside that surface, and the cited file is missing or defines no such name, and no item of an export list (`export { L as NAME }`, `export type { ... }`) or of a Python `from X import` clause elsewhere renames to it a name that file declares
|
|
22
|
+
| Critical | `fabricated-signature` | Fabricated signature: a documented export absent at the source line the skill cites for it. Decided only where ast-grep enumerated the exports at the pinned commit: the name is outside that surface, and the cited file is missing or defines no such name, and no item of an export list (`export { L as NAME }`, `export type { ... }`) or of a Python `from X import` clause elsewhere renames to it a name that file declares, nor does any other rename the `aliased` clause of the Info `observation` row clears, directly or through a chain of renames; a rename that clause does not clear stays fabricated (coverage-check §2c) |
|
|
23
23
|
| Critical | `broken-reference` | Broken reference (contextual mode): a reference whose target does not exist, such as a file not at the stated path, a skill not in the skills output folder, a type no module declares, or a `scripts/` or `assets/` file the skill names (coherence-check §4) |
|
|
24
24
|
| High | `inaccurate-reference` | Inaccurate reference (contextual mode): the target exists but does not match, such as a type the cited module does not export, a signature that differs, or an integration pattern that does not match the implementation (coherence-check §4) |
|
|
25
25
|
| High | `reference-escape` | A reference whose real path leaves the skill, its source and, for a stack, the skills output folder (coherence-check §4) |
|
|
@@ -45,7 +45,7 @@ This table is the single source of truth for classifying a gap. Every stage reco
|
|
|
45
45
|
| Low | `metadata` | Missing optional metadata or examples |
|
|
46
46
|
| Low | `description` | Description trigger optimization recommended (third-person voice, negative triggers, or keyword coverage gaps), a Tessl Review description suggestion included |
|
|
47
47
|
| Low | `external-validator` | A skill-check diagnostic or a Tessl Review validation finding the run left unresolved (external-validators §5b) |
|
|
48
|
-
| Info | `observation` | Style suggestions, non-blocking observations, and a documented extra: a documented name outside the enumerated surface that is not a fabricated signature and that the source still declares at column 0 (a module so named counts, unless the inventory gives the name a `function`, `hook`, `class`, `interface`, `enum` or `type` kind, no provenance map entry of the name types it a `module` or `package` and the module is not a TS/JS file that is itself the export, with an `export default` or a `module.exports =`; among several declaring files, the one the skill cites on a line naming it; an `aliased` name, which a line elsewhere renames from the declaration its cited file holds (an export-list item `L as NAME` (`export { default as NAME } from` included, a whole-string side unquoted, a comment allowed before `export`), a plain `export { NAME }` of an import that renames (`import { L as NAME }`, `import NAME from`), a `const`, `let`, `var` or `type NAME = L` with a bare identifier `L`, a Python `from X import L as NAME` at column 0
|
|
48
|
+
| Info | `observation` | Style suggestions, non-blocking observations, and a documented extra: a documented name outside the enumerated surface that is not a fabricated signature and that the source still declares at column 0 (a module so named counts, unless the inventory gives the name a `function`, `hook`, `class`, `interface`, `enum` or `type` kind, no provenance map entry of the name types it a `module` or `package` and the module is not a TS/JS file that is itself the export, with an `export default` or a `module.exports =`; among several declaring files, the one the skill cites on a line naming it; an `aliased` name, which a line elsewhere renames from the declaration its cited file holds, directly or through a chain of renames (an export-list item `L as NAME` (`export { default as NAME } from` included, a whole-string side unquoted, a comment allowed before `export`), a plain `export { NAME }` of an import that renames (`import { L as NAME }`, `import NAME from`), a `const`, `let`, `var` or `type NAME = L` with a bare identifier `L` (a `const`, `let` or `var` annotated with a one-line type that holds no `;` and no `=` but in `=>` included, and a generic `type NAME<T> = L<T>` that passes its bare parameters on in the same order), an `export default L` at column 0 (a rename of `L` to `default`) whose bare identifier `L` an import or a declaration of the same file binds, the statement ending on its line, a Python `from X import L as NAME` at column 0, one level in a module-level `try`, `except`, `else`, `if`, `elif`, `with` or `finally` block or as such a block header's one-line body (`try: from X import L as NAME`), or a Python `NAME = L` or `NAME: <type> = L` whose type holds no `;` or `=`), at that line, a multi-line list's item at its own line; when the rename's relative or in-package `from` module, or the import that binds `L`, resolves to a file that declares `L`, that file must be the cited one or one that renames `L` in turn, by one of these forms or by a Python `L = M.L2` whose module `M` a column-0 `from X import M` binds, a chain classify-stale follows file by file until it reaches the cited declaration, so a rename of another file's same-named declaration stays a fabricated signature, as do a chain that leaves the files classify-stale resolves, loops or ends at another declaration, a generic alias whose parameters are constrained, defaulted, swapped or instantiated, a type assertion, any other expression (`NAME = a.b` included) and an import inside a function or a class), found only in a Python or TS/JS source where classify-stale ran (the Forge, Forge+ or Deep tier, `analysis_confidence` `full`, `workspaceDrift` not `overridden` and a bound provenance map) and `surface.json` has a complete `extraction`: `Documented extra: {name}`, at that line, which update-skill never routes (coverage-check §2c). Also a partial signature: a mismatch the signature judge tags `omitted-optional` (defined in signature-comparison-rules.md, Difference tags) on a form the skill labels partial (simplified, key parameters, a `...` that stands in for omitted members, not rest or variadic syntax), which the inventory marks `signature_form: "partial"`, as score-signatures.py checks: `Partial signature: {name}`, at the declaration, counted as matching in Signature Accuracy (coverage-check §2 and §2b). Also a return annotation the body contradicts: a mismatch the signature judge tags `annotation-contradicted` (defined in signature-comparison-rules.md, Difference tags) whose documented signature states a return type and writes the source's parameters, and whose `evidence_line`, the body line that returns the documented type, comes after the declaration and before the next one and, with the local source, starts with `return` (or is a one-line declaration's own expression body), as score-signatures.py checks: `Annotation contradicted: {name}`, at the declaration, counted as matching in Signature Accuracy, with no change to the skill (the annotation is reported upstream) (coverage-check §2 and §2b) Also a documentation error a Deep-tier run sees that no script checks (an import path whose module does not export the name, a type member that differs from its declaration): `Not checked by script: {name}`, at the documenting line, written by hand (coverage-check §2 and §5b) |
|
|
49
49
|
| Info | `provenance-unverified` | Provenance line not verified: the line-check rules found no definition line, or the recorded line is not a line number (`line-invalid`) (coverage-check §4c) |
|
|
50
50
|
| Info | `migration-section` | Historical migration content in Section 4b with no T2-future annotation (Case 2 of `migration-section-rules.md`) |
|
|
51
51
|
| Info | `multi-denominator` | Multi-denominator reporting: barrel and documented-surface clusters diverge by design (>10% cross-cluster) |
|
|
@@ -126,23 +126,28 @@ cites (never read; default `metadata.json`):
|
|
|
126
126
|
first that writes it as an export, the first tier that holds
|
|
127
127
|
the name winning: (1) its first origin in the documented
|
|
128
128
|
inventory's floor (load-coverage-inputs.py floor); (2) a
|
|
129
|
-
|
|
130
|
-
floor's
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
129
|
+
heading, or the first cell of a table whose header's first
|
|
130
|
+
cell names exports (floor's table headers), whose text is the
|
|
131
|
+
name or holds a code span floor reads as the name; (3) a line
|
|
132
|
+
whose inline code span is the name, or the name directly
|
|
133
|
+
followed by `(`; (4) a column-0 declaration of it in a fenced
|
|
134
|
+
block of a language floor's `_declared_names` reads (JS/TS,
|
|
135
|
+
Python, Rust, Go), cited or not, so a def an example writes
|
|
136
|
+
under the name is cited only when nothing above documents it;
|
|
137
|
+
(5) a line of a fenced block that holds the name as a whole
|
|
138
|
+
quoted string (`"name"` or `'name'`), such as an `__all__` item.
|
|
139
|
+
Tiers 2 to 5 read SKILL.md, then every .md file under
|
|
140
|
+
references/, subfolders included, in path order, outside the
|
|
141
|
+
frontmatter (2 and 3 outside fenced code and HTML comments, 4
|
|
142
|
+
and 5 inside a fence outside an HTML comment and a data block
|
|
143
|
+
(json, yaml...)); within a tier the first file, then its lowest
|
|
144
|
+
line, wins. A bare word, a span that holds the name among other
|
|
145
|
+
words (`uitripled add`), or a quoted string outside a fence or
|
|
146
|
+
holding other words too (`"add x"`) documents nothing; with no
|
|
147
|
+
such line, or without --skill-dir, the Source is `SKILL.md`.
|
|
148
|
+
Floor's InputError (no SKILL.md, or a top-level file it cannot
|
|
149
|
+
read or decode) leaves tier 1 empty, and a skill file that
|
|
150
|
+
cannot be read leaves tiers 2 to 5 empty. One whose
|
|
146
151
|
`excluded.outsideScope` row says the brief
|
|
147
152
|
scopes its file out (`excludeGlob`, the `scope.exclude` glob
|
|
148
153
|
that matches it, or `outsideInclude`, no `scope.include` glob
|
|
@@ -406,6 +411,9 @@ EFFORT = {
|
|
|
406
411
|
_ID_RE = re.compile(r"^GAP-(\d{3,})$")
|
|
407
412
|
_STAGE_RE = re.compile(r"^[a-z][a-z0-9-]*$")
|
|
408
413
|
_RUN_ID_FROM_NAME = re.compile(r"^test-findings-(.+)\.json$")
|
|
414
|
+
# A whole quoted string, `"name"` or `'name'`. The lookahead lets matches overlap, so an apostrophe earlier on the
|
|
415
|
+
# line (`don't 'add'`) does not swallow the next string; the text between two strings is a harmless extra key.
|
|
416
|
+
_QUOTED_RE = re.compile(r"""(?=(["'])([^"'\n]+)\1)""")
|
|
409
417
|
|
|
410
418
|
|
|
411
419
|
class LedgerError(Exception):
|
|
@@ -913,26 +921,33 @@ def _documenting_lines(skill_dir: Path) -> dict[str, str]:
|
|
|
913
921
|
1. its first origin in the documented inventory's floor
|
|
914
922
|
(load-coverage-inputs.py `floor`: SKILL.md and the top-level
|
|
915
923
|
references/*.md);
|
|
916
|
-
2. a
|
|
917
|
-
`_declared_names`), cited or not, outside an HTML comment and a data
|
|
918
|
-
block;
|
|
919
|
-
3. a heading, or the first cell of a table whose header's first cell is
|
|
924
|
+
2. a heading, or the first cell of a table whose header's first cell is
|
|
920
925
|
one of floor's `FLOOR_TABLE_HEADERS`, whose stripped text is the name
|
|
921
926
|
or holds a code span floor's `_floor_name` reads as the name;
|
|
922
|
-
|
|
923
|
-
directly followed by `(
|
|
924
|
-
|
|
925
|
-
|
|
927
|
+
3. a line whose inline code span, stripped, is the name, or the name
|
|
928
|
+
directly followed by `(`;
|
|
929
|
+
4. a column-0 public declaration of it in a fenced code block (floor's
|
|
930
|
+
`_declared_names`), cited or not, so a def an example writes under
|
|
931
|
+
the name (a connector author's own `mount_table_target`) is cited
|
|
932
|
+
only when nothing above documents it;
|
|
933
|
+
5. a raw (unmasked) line of a fenced code block that holds the name as a
|
|
934
|
+
whole quoted string (`"name"` or `'name'`), such as an `__all__`
|
|
935
|
+
item; it only replaces the bare `SKILL.md` fallback.
|
|
936
|
+
|
|
937
|
+
Tiers 2 to 5 read SKILL.md, then every .md file under references/,
|
|
926
938
|
subfolders included, in path order (locate-export-segments.py
|
|
927
|
-
`skill_files`), never the frontmatter
|
|
928
|
-
and HTML comments
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
939
|
+
`skill_files`), never the frontmatter: tiers 2 and 3 outside fenced code
|
|
940
|
+
and HTML comments, tiers 4 and 5 inside a fence outside an HTML comment
|
|
941
|
+
and a data block (json, yaml...). Within a tier the first file, then its
|
|
942
|
+
lowest line, wins. A bare word, a span that holds the name among other
|
|
943
|
+
words (`uitripled add`), or a quoted string outside a fence or holding
|
|
944
|
+
other words too (`"add x"`) documents nothing. An input a tier cannot
|
|
945
|
+
read is skipped: floor's InputError (no SKILL.md, a file that is not
|
|
946
|
+
UTF-8) leaves tier 1 empty, an unreadable file tiers 2 to 5."""
|
|
933
947
|
coverage = _sibling("load-coverage-inputs.py")
|
|
934
948
|
segments = _sibling("locate-export-segments.py")
|
|
935
|
-
|
|
949
|
+
# In merge order: floor, heading or export-table cell, code span, fenced declaration, quoted string.
|
|
950
|
+
tiers: list[dict[str, str]] = [{}, {}, {}, {}, {}]
|
|
936
951
|
try:
|
|
937
952
|
for entry in coverage.floor(str(skill_dir))["names"]:
|
|
938
953
|
origin = entry["origins"][0]
|
|
@@ -943,8 +958,9 @@ def _documenting_lines(skill_dir: Path) -> dict[str, str]:
|
|
|
943
958
|
files = segments.skill_files(skill_dir)
|
|
944
959
|
except OSError:
|
|
945
960
|
files = []
|
|
946
|
-
# floor's fence filter: a fence in an HTML comment or a data block declares nothing, and a Svelte
|
|
947
|
-
# `export let` is a component prop. Unlike floor, an unheaded fence counts
|
|
961
|
+
# floor's fence filter: a fence in an HTML comment or a data block declares and quotes nothing, and a Svelte
|
|
962
|
+
# block's `export let` is a component prop. Unlike floor, an unheaded fence counts; a Svelte, shell or untagged
|
|
963
|
+
# fence's quoted strings count too.
|
|
948
964
|
js_family = segments.JS_FAMILY - {"svelte"}
|
|
949
965
|
for rel, lines in files:
|
|
950
966
|
start = segments._frontmatter_end(lines)
|
|
@@ -954,9 +970,13 @@ def _documenting_lines(skill_dir: Path) -> dict[str, str]:
|
|
|
954
970
|
language = segments._language(info)
|
|
955
971
|
if opening in comments or language in segments.DATA_LANGUAGES:
|
|
956
972
|
continue
|
|
957
|
-
|
|
973
|
+
raw = lines[opening + 1:closing]
|
|
974
|
+
masked = segments.mask_code("\n".join(raw), language).split("\n")
|
|
958
975
|
for offset, declared in coverage._declared_names(masked, language, js_family):
|
|
959
|
-
tiers[
|
|
976
|
+
tiers[3].setdefault(declared, f"{rel}:{opening + offset + 2}")
|
|
977
|
+
for offset, line in enumerate(raw):
|
|
978
|
+
for match in _QUOTED_RE.finditer(line):
|
|
979
|
+
tiers[4].setdefault(match.group(2), f"{rel}:{opening + offset + 2}")
|
|
960
980
|
headings, prose = segments._prose(lines, start, code)
|
|
961
981
|
shaped = [(index, text) for index, _level, text, _above in headings]
|
|
962
982
|
written = list(shaped)
|
|
@@ -968,12 +988,12 @@ def _documenting_lines(skill_dir: Path) -> dict[str, str]:
|
|
|
968
988
|
for index, text in sorted(shaped):
|
|
969
989
|
for key in [text.strip()] + [coverage._floor_name(span) for span in segments._inline_code(text)]:
|
|
970
990
|
if key:
|
|
971
|
-
tiers[
|
|
991
|
+
tiers[1].setdefault(key, f"{rel}:{index + 1}")
|
|
972
992
|
for index, text in sorted(written):
|
|
973
993
|
for span in map(str.strip, segments._inline_code(text)):
|
|
974
994
|
for key in (span, span.split("(", 1)[0] if "(" in span else None):
|
|
975
995
|
if key:
|
|
976
|
-
tiers[
|
|
996
|
+
tiers[2].setdefault(key, f"{rel}:{index + 1}")
|
|
977
997
|
found: dict[str, str] = {}
|
|
978
998
|
for tier in tiers:
|
|
979
999
|
for key, line in tier.items():
|
|
@@ -1578,7 +1598,9 @@ def _alias_fix(name: str, file: str, item: dict) -> str:
|
|
|
1578
1598
|
alias_file, _, alias_line = alias.rpartition(":")
|
|
1579
1599
|
named = isinstance(local, str) and bool(local)
|
|
1580
1600
|
renamed = f"`{local}`" if named else "a name a cited file declares"
|
|
1581
|
-
|
|
1601
|
+
of = f"{renamed}, which a cited file declares" if named else renamed
|
|
1602
|
+
head = (f"`{name}` is an alias of {of}; the source declares the alias at `{alias}`, and no line of `{file}` "
|
|
1603
|
+
f"defines `{name}`. ")
|
|
1582
1604
|
cite = (f"the alias: set `source_file` to `{alias_file}` and `source_line` to `{alias_line}`, and move the "
|
|
1583
1605
|
f"citations of `{name}` there.")
|
|
1584
1606
|
if local == "default":
|
|
@@ -25,9 +25,10 @@ nextWorkflow: ''
|
|
|
25
25
|
Each stage writes its section in place of the heading below that it owns and
|
|
26
26
|
the placeholder comment under that heading, so every heading occurs once.
|
|
27
27
|
Before the result files are written, report.md §4c checks that each heading
|
|
28
|
-
occurs exactly once
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
occurs exactly once, that no placeholder comment is left and that the title
|
|
29
|
+
above names the skill (init.md §6c fills it): a stage that skipped its
|
|
30
|
+
section stops the run there. Do not change the title line, and do not
|
|
31
|
+
reorder or delete the headings or their placeholder comments.
|
|
31
32
|
|
|
32
33
|
Heading / owning step:
|
|
33
34
|
Test Summary → detect-mode §2
|
|
@@ -86,7 +86,7 @@ Headless callers: the inputs, flags, gate map, outputs and the `SKF_UPDATE_RESUL
|
|
|
86
86
|
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-update-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. Record the warning in the run log at once: write `customization_resolver_unavailable: <reason>` to `{run_dir}/resolver-warning.txt` with a file write (a resolver error can hold quotes or `$( )`, so never echo it or type it into an argument), then run `uv run {emitEnvelopeHelper} record --run-dir "{run_dir}" --warning "$(cat "{run_dir}/resolver-warning.txt")"` from `{project-root}`.
|
|
89
|
+
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-update-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, `SKF helper skf-resolve-customization.py 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. Record the warning in the run log at once: write `customization_resolver_unavailable: <reason>` to `{run_dir}/resolver-warning.txt` with a file write (a resolver error can hold quotes or `$( )`, so never echo it or type it into an argument), then run `uv run {emitEnvelopeHelper} record --run-dir "{run_dir}" --warning "$(cat "{run_dir}/resolver-warning.txt")"` from `{project-root}`.
|
|
90
90
|
|
|
91
91
|
Apply the resolved values so no surface is a silent no-op: execute each entry in `workflow.activation_steps_prepend` in order now; treat every entry in `workflow.persistent_facts` as standing context for the whole run (entries prefixed `file:` are paths or globs whose contents load 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); resolve `{onCompleteCommand}` ← `workflow.on_complete` if non-empty, else empty string, and stash it in workflow context (`references/report.md` §5b runs it after a finished run's result files, never in `--detect-only`, `--dry-run` or a halt; empty = no-op). After activation completes, execute each entry in `workflow.activation_steps_append` in order before `init.md` runs.
|
|
92
92
|
|
|
@@ -91,7 +91,7 @@ When `{headless_mode}` is true, step 6 prints one `SKF_VERIFY_STACK_RESULT_JSON:
|
|
|
91
91
|
uv run {resolveCustomizationHelper} --skill {skill-root} --project-root {project-root} --key workflow
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-verify-stack.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 init.md to record once it has created `{run_dir}`.
|
|
94
|
+
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-verify-stack.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, `SKF helper skf-resolve-customization.py 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 init.md to record once it has created `{run_dir}`.
|
|
95
95
|
|
|
96
96
|
Apply the path-scalar fallback now so stage files don't repeat it: `{reportTemplatePath}` ← `workflow.report_template_path` if non-empty, else `assets/feasibility-report-template.md`. An empty or absent value falls through to that default, and init.md §4 loads the variable as it is. Bind `{outputFolderPath}` ← `{forge_data_folder}`, always (no setting moves it): the report and its `-latest` copy go where create-stack-skill and refine-architecture look for them, and step 1 finds the earlier reports there.
|
|
97
97
|
|