bmad-module-skill-forge 1.9.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.nvmrc +1 -1
- package/README.md +10 -5
- package/docs/_data/pinned.yaml +2 -2
- package/docs/agents.md +11 -2
- package/docs/architecture.md +5 -4
- package/docs/bmad-synergy.md +30 -7
- package/docs/campaign.md +172 -0
- package/docs/examples.md +7 -3
- package/docs/forge-auto.md +90 -0
- package/docs/getting-started.md +9 -0
- package/docs/how-it-works.md +6 -4
- package/docs/index.md +4 -3
- package/docs/skill-model.md +1 -1
- package/docs/troubleshooting.md +17 -1
- package/docs/verifying-a-skill.md +2 -2
- package/docs/why-skf.md +1 -1
- package/docs/workflows.md +64 -24
- package/package.json +2 -2
- package/src/knowledge/skill-lifecycle.md +1 -1
- package/src/module-help.csv +4 -3
- package/src/shared/data/language-corpora.json +32 -0
- package/src/shared/references/pipeline-contracts.md +6 -3
- package/src/shared/scripts/schemas/skill-brief.v1.json +6 -1
- package/src/shared/scripts/skf-comention-pairs.py +350 -0
- package/src/shared/scripts/skf-count-tokens.py +222 -0
- package/src/shared/scripts/skf-derive-assembly-shape.py +107 -0
- package/src/shared/scripts/skf-detect-docs.py +240 -4
- package/src/shared/scripts/skf-detect-language.py +82 -3
- package/src/shared/scripts/skf-detect-tools.py +105 -2
- package/src/shared/scripts/skf-emit-result-envelope.py +17 -0
- package/src/shared/scripts/skf-enumerate-stack-skills.py +111 -2
- package/src/shared/scripts/skf-extract-public-api.py +53 -0
- package/src/shared/scripts/skf-find-cycles.py +246 -0
- package/src/shared/scripts/skf-hash-content.py +487 -2
- package/src/shared/scripts/skf-language-corpora.py +100 -0
- package/src/shared/scripts/skf-manifest-ops.py +67 -1
- package/src/shared/scripts/skf-merge-doc-urls.py +244 -0
- package/src/shared/scripts/skf-preapply.py +1 -1
- package/src/shared/scripts/skf-rebuild-managed-sections.py +180 -0
- package/src/shared/scripts/skf-recommend-scope-type.py +1 -2
- package/src/shared/scripts/skf-render-metadata-stats.py +499 -0
- package/src/shared/scripts/skf-rewrite-skill-name.py +308 -0
- package/src/shared/scripts/skf-shape-detect.py +576 -25
- package/src/shared/scripts/skf-shard-body.py +461 -0
- package/src/shared/scripts/skf-skill-inventory.py +139 -3
- package/src/shared/scripts/skf-structural-diff.py +307 -67
- package/src/shared/scripts/skf-validate-brief-schema.py +41 -6
- package/src/shared/scripts/skf-validate-feasibility-report.py +234 -0
- package/src/shared/scripts/skf-validate-frontmatter.py +17 -12
- package/src/shared/scripts/skf-validate-output.py +443 -10
- package/src/shared/scripts/skf-validate-pins.py +3 -3
- package/src/shared/scripts/skf-verify-no-trace.py +214 -0
- package/src/shared/scripts/skf-verify-provenance-completeness.py +439 -0
- package/src/shared/scripts/skf-write-skill-brief.py +21 -5
- package/src/skf-analyze-source/SKILL.md +17 -32
- package/src/skf-analyze-source/assets/skill-brief-schema.md +1 -1
- package/src/skf-analyze-source/customize.toml +2 -0
- package/src/skf-analyze-source/references/auto-docs-only.md +116 -0
- package/src/skf-analyze-source/references/continue.md +8 -13
- package/src/skf-analyze-source/references/generate-briefs.md +28 -12
- package/src/skf-analyze-source/references/headless-contract.md +33 -0
- package/src/skf-analyze-source/references/health-check.md +1 -2
- package/src/skf-analyze-source/references/identify-units.md +25 -36
- package/src/skf-analyze-source/references/init.md +12 -25
- package/src/skf-analyze-source/references/map-and-detect.md +8 -20
- package/src/skf-analyze-source/references/recommend.md +4 -14
- package/src/skf-analyze-source/references/scan-project.md +3 -11
- package/src/skf-analyze-source/references/step-auto-scope.md +211 -197
- package/src/skf-analyze-source/references/step-shape-detect.md +20 -13
- package/src/skf-analyze-source/references/unit-detection-heuristics.md +2 -2
- package/src/skf-audit-skill/SKILL.md +9 -6
- package/src/skf-audit-skill/customize.toml +2 -0
- package/src/skf-audit-skill/references/init.md +58 -32
- package/src/skf-audit-skill/references/re-index.md +15 -34
- package/src/skf-audit-skill/references/report.md +11 -58
- package/src/skf-audit-skill/references/semantic-diff.md +4 -22
- package/src/skf-audit-skill/references/severity-classify.md +53 -87
- package/src/skf-audit-skill/references/step-doc-drift.md +49 -19
- package/src/skf-audit-skill/references/structural-diff.md +43 -68
- package/src/skf-brief-skill/SKILL.md +7 -36
- package/src/skf-brief-skill/assets/scope-templates.md +1 -1
- package/src/skf-brief-skill/assets/skill-brief-schema.md +1 -1
- package/src/skf-brief-skill/customize.toml +14 -0
- package/src/skf-brief-skill/references/analyze-target.md +13 -18
- package/src/skf-brief-skill/references/confirm-brief.md +2 -15
- package/src/skf-brief-skill/references/draft-checkpoint.md +8 -8
- package/src/skf-brief-skill/references/gather-intent.md +27 -24
- package/src/skf-brief-skill/references/headless-args.md +1 -1
- package/src/skf-brief-skill/references/health-check.md +1 -22
- package/src/skf-brief-skill/references/invocation-contract.md +37 -0
- package/src/skf-brief-skill/references/scope-definition.md +6 -15
- package/src/skf-brief-skill/references/step-auto-brief.md +22 -9
- package/src/skf-brief-skill/references/step-auto-validate.md +3 -7
- package/src/skf-brief-skill/references/version-resolution.md +1 -1
- package/src/skf-brief-skill/references/write-brief.md +19 -13
- package/src/skf-campaign/SKILL.md +121 -0
- package/src/skf-campaign/assets/campaign-state-schema.json +191 -0
- package/src/skf-campaign/customize.toml +75 -0
- package/src/skf-campaign/manifest.yaml +15 -0
- package/src/skf-campaign/references/campaign-contracts.md +67 -0
- package/src/skf-campaign/references/campaign-directive-spec.md +32 -0
- package/src/skf-campaign/references/health-check.md +35 -0
- package/src/skf-campaign/references/step-01-setup.md +121 -0
- package/src/skf-campaign/references/step-02-strategy.md +102 -0
- package/src/skf-campaign/references/step-03-pins.md +56 -0
- package/src/skf-campaign/references/step-04-provenance.md +63 -0
- package/src/skf-campaign/references/step-05-skill-loop.md +103 -0
- package/src/skf-campaign/references/step-06-batch.md +88 -0
- package/src/skf-campaign/references/step-07-capstone.md +63 -0
- package/src/skf-campaign/references/step-08-verify.md +75 -0
- package/src/skf-campaign/references/step-09-refine.md +83 -0
- package/src/skf-campaign/references/step-10-export.md +106 -0
- package/src/skf-campaign/references/step-11-maintenance.md +84 -0
- package/src/skf-campaign/references/step-resume.md +115 -0
- package/src/skf-campaign/scripts/.gitkeep +0 -0
- package/src/skf-campaign/scripts/campaign-deps.py +244 -0
- package/src/skf-campaign/scripts/campaign-parse-manifest.py +119 -0
- package/src/skf-campaign/scripts/campaign-provenance.py +247 -0
- package/src/skf-campaign/scripts/campaign-render-batch.py +199 -0
- package/src/skf-campaign/scripts/campaign-render-kickoff.py +158 -0
- package/src/skf-campaign/scripts/campaign-report.py +249 -0
- package/src/skf-campaign/scripts/campaign-status.py +162 -0
- package/src/skf-campaign/scripts/campaign-validate-pins.py +174 -0
- package/src/skf-campaign/scripts/campaign-validate-state.py +207 -0
- package/src/skf-campaign/templates/campaign-brief-template.yaml +34 -0
- package/src/skf-campaign/templates/campaign-report-template.md +54 -0
- package/src/skf-campaign/templates/kickoff-template.md +48 -0
- package/src/skf-create-skill/SKILL.md +15 -4
- package/src/skf-create-skill/assets/compile-assembly-rules.md +35 -34
- package/src/skf-create-skill/assets/skill-sections.md +9 -100
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +4 -13
- package/src/skf-create-skill/customize.toml +13 -0
- package/src/skf-create-skill/references/authoritative-files-protocol.md +7 -7
- package/src/skf-create-skill/references/compile.md +37 -47
- package/src/skf-create-skill/references/component-extraction.md +8 -35
- package/src/skf-create-skill/references/ecosystem-check.md +9 -25
- package/src/skf-create-skill/references/enrich.md +4 -17
- package/src/skf-create-skill/references/extract.md +20 -27
- package/src/skf-create-skill/references/extraction-patterns-tracing.md +1 -1
- package/src/skf-create-skill/references/extraction-patterns.md +10 -8
- package/src/skf-create-skill/references/generate-artifacts.md +20 -25
- package/src/skf-create-skill/references/health-check.md +2 -2
- package/src/skf-create-skill/references/load-brief.md +16 -18
- package/src/skf-create-skill/references/report.md +27 -14
- package/src/skf-create-skill/references/source-resolution-protocols.md +4 -4
- package/src/skf-create-skill/references/step-auto-shard.md +52 -25
- package/src/skf-create-skill/references/step-doc-rot.md +33 -15
- package/src/skf-create-skill/references/step-doc-sources.md +13 -4
- package/src/skf-create-skill/references/sub/ccc-discover.md +2 -13
- package/src/skf-create-skill/references/sub/fetch-docs.md +33 -46
- package/src/skf-create-skill/references/sub/fetch-temporal.md +5 -20
- package/src/skf-create-skill/references/tier-degradation-rules.md +6 -8
- package/src/skf-create-skill/references/validate.md +69 -29
- package/src/skf-create-skill/scripts/scan-doc-rot.py +245 -0
- package/src/skf-create-stack-skill/SKILL.md +15 -9
- package/src/skf-create-stack-skill/assets/provenance-map-schema.md +1 -1
- package/src/skf-create-stack-skill/customize.toml +10 -0
- package/src/skf-create-stack-skill/references/compile-stack.md +7 -8
- package/src/skf-create-stack-skill/references/compose-mode-rules.md +2 -13
- package/src/skf-create-stack-skill/references/detect-integrations.md +49 -54
- package/src/skf-create-stack-skill/references/detect-manifests.md +37 -30
- package/src/skf-create-stack-skill/references/generate-output.md +24 -75
- package/src/skf-create-stack-skill/references/init.md +9 -81
- package/src/skf-create-stack-skill/references/manifest-patterns.md +5 -35
- package/src/skf-create-stack-skill/references/parallel-extract.md +12 -27
- package/src/skf-create-stack-skill/references/rank-and-confirm.md +6 -6
- package/src/skf-create-stack-skill/references/report.md +27 -62
- package/src/skf-create-stack-skill/references/validate.md +35 -31
- package/src/skf-drop-skill/SKILL.md +20 -23
- package/src/skf-drop-skill/customize.toml +10 -0
- package/src/skf-drop-skill/references/execute.md +67 -36
- package/src/skf-drop-skill/references/headless-contract.md +18 -0
- package/src/skf-drop-skill/references/health-check.md +1 -13
- package/src/skf-drop-skill/references/report.md +44 -10
- package/src/skf-drop-skill/references/select.md +93 -48
- package/src/skf-drop-skill/scripts/dir-sizes.py +116 -0
- package/src/skf-export-skill/SKILL.md +17 -14
- package/src/skf-export-skill/assets/managed-section-format.md +2 -5
- package/src/skf-export-skill/customize.toml +10 -6
- package/src/skf-export-skill/references/generate-snippet.md +21 -35
- package/src/skf-export-skill/references/load-skill.md +44 -42
- package/src/skf-export-skill/references/multi-skill-mode.md +1 -1
- package/src/skf-export-skill/references/orphan-context-detection.md +11 -3
- package/src/skf-export-skill/references/package.md +27 -33
- package/src/skf-export-skill/references/preflight-snippet-root-probe.md +24 -5
- package/src/skf-export-skill/references/result-envelope.md +28 -0
- package/src/skf-export-skill/references/summary.md +11 -5
- package/src/skf-export-skill/references/token-report.md +42 -24
- package/src/skf-export-skill/references/update-context.md +32 -33
- package/src/skf-forger/SKILL.md +32 -39
- package/src/skf-forger/references/pipeline-mode.md +34 -0
- package/src/skf-forger/scripts/parse-pipeline.py +270 -0
- package/src/skf-quick-skill/SKILL.md +10 -60
- package/src/skf-quick-skill/customize.toml +9 -0
- package/src/skf-quick-skill/references/batch-mode.md +6 -4
- package/src/skf-quick-skill/references/compile.md +15 -52
- package/src/skf-quick-skill/references/ecosystem-check.md +5 -20
- package/src/skf-quick-skill/references/finalize.md +12 -6
- package/src/skf-quick-skill/references/halt-contract.md +54 -0
- package/src/skf-quick-skill/references/health-check.md +3 -4
- package/src/skf-quick-skill/references/quick-extract.md +7 -18
- package/src/skf-quick-skill/references/registry-resolution.md +1 -23
- package/src/skf-quick-skill/references/resolve-target.md +27 -34
- package/src/skf-quick-skill/references/write-and-validate.md +10 -25
- package/src/skf-refine-architecture/SKILL.md +16 -8
- package/src/skf-refine-architecture/customize.toml +9 -0
- package/src/skf-refine-architecture/references/compile.md +10 -8
- package/src/skf-refine-architecture/references/gap-analysis.md +21 -50
- package/src/skf-refine-architecture/references/health-check.md +1 -1
- package/src/skf-refine-architecture/references/improvements.md +9 -25
- package/src/skf-refine-architecture/references/init.md +9 -16
- package/src/skf-refine-architecture/references/issue-detection.md +10 -25
- package/src/skf-refine-architecture/references/refinement-rules.md +4 -16
- package/src/skf-refine-architecture/references/report.md +14 -11
- package/src/skf-rename-skill/SKILL.md +11 -21
- package/src/skf-rename-skill/customize.toml +10 -0
- package/src/skf-rename-skill/references/execute.md +112 -82
- package/src/skf-rename-skill/references/exit-codes.md +12 -0
- package/src/skf-rename-skill/references/report.md +16 -8
- package/src/skf-rename-skill/references/select.md +69 -42
- package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -0
- package/src/skf-setup/SKILL.md +21 -13
- package/src/skf-setup/customize.toml +10 -0
- package/src/skf-setup/references/auto-index.md +11 -10
- package/src/skf-setup/references/ccc-index.md +14 -15
- package/src/skf-setup/references/detect-and-tier.md +10 -8
- package/src/skf-setup/references/report.md +26 -32
- package/src/skf-setup/references/tier-rules.md +2 -32
- package/src/skf-setup/references/write-config.md +9 -10
- package/src/skf-test-skill/SKILL.md +10 -8
- package/src/skf-test-skill/customize.toml +2 -0
- package/src/skf-test-skill/references/coherence-check.md +19 -31
- package/src/skf-test-skill/references/coverage-check.md +111 -67
- package/src/skf-test-skill/references/detect-mode.md +10 -41
- package/src/skf-test-skill/references/external-validators.md +11 -19
- package/src/skf-test-skill/references/init.md +39 -21
- package/src/skf-test-skill/references/migration-section-rules.md +4 -4
- package/src/skf-test-skill/references/report.md +60 -40
- package/src/skf-test-skill/references/score.md +32 -64
- package/src/skf-test-skill/references/scoring-rules.md +6 -62
- package/src/skf-test-skill/references/source-access-protocol.md +15 -5
- package/src/skf-test-skill/references/step-hard-gate.md +1 -3
- package/src/skf-test-skill/scripts/aggregate-coherence.py +276 -0
- package/src/skf-test-skill/scripts/check-metadata-coherence.py +359 -0
- package/src/skf-test-skill/scripts/combine-external-scores.py +190 -0
- package/src/skf-test-skill/scripts/compute-score.py +135 -4
- package/src/skf-test-skill/scripts/reconcile-coverage.py +327 -0
- package/src/skf-test-skill/scripts/validate-inventory.py +251 -0
- package/src/skf-test-skill/scripts/verify-declared-numerator.py +192 -0
- package/src/skf-update-skill/SKILL.md +17 -5
- package/src/skf-update-skill/customize.toml +11 -6
- package/src/skf-update-skill/references/detect-changes.md +48 -36
- package/src/skf-update-skill/references/health-check.md +5 -11
- package/src/skf-update-skill/references/init.md +40 -32
- package/src/skf-update-skill/references/manual-section-rules.md +1 -2
- package/src/skf-update-skill/references/merge-conflict-rules.md +1 -52
- package/src/skf-update-skill/references/merge.md +13 -57
- package/src/skf-update-skill/references/re-extract.md +27 -25
- package/src/skf-update-skill/references/report.md +14 -7
- package/src/skf-update-skill/references/validate.md +32 -30
- package/src/skf-update-skill/references/write.md +86 -51
- package/src/skf-update-skill/scripts/skf-new-file-diff.py +167 -0
- package/src/skf-verify-stack/SKILL.md +11 -21
- package/src/skf-verify-stack/customize.toml +12 -3
- package/src/skf-verify-stack/references/coverage-patterns.md +1 -29
- package/src/skf-verify-stack/references/coverage.md +21 -10
- package/src/skf-verify-stack/references/exit-codes.md +14 -0
- package/src/skf-verify-stack/references/health-check.md +1 -1
- package/src/skf-verify-stack/references/init.md +20 -25
- package/src/skf-verify-stack/references/integration-verification-rules.md +10 -19
- package/src/skf-verify-stack/references/integrations.md +44 -49
- package/src/skf-verify-stack/references/report.md +28 -34
- package/src/skf-verify-stack/references/requirements.md +3 -3
- package/src/skf-verify-stack/references/synthesize.md +36 -39
- package/src/skf-verify-stack/scripts/skf-coverage-tally.py +196 -0
- package/src/skf-verify-stack/scripts/skf-report-delta.py +256 -0
- package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +258 -0
- package/tools/cli/lib/ui.js +3 -2
- package/docs/deepwiki.md +0 -89
- package/src/skf-rename-skill/references/rebuild-context.md +0 -110
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.10"
|
|
4
|
+
# ///
|
|
5
|
+
"""Deterministic schema validation of the §1 subagent inventory JSON.
|
|
6
|
+
|
|
7
|
+
Structural validator for the SKF test-skill coverage step (coverage-check.md
|
|
8
|
+
§1a "Schema validation" block). test-skill is a quality gate, so it must not
|
|
9
|
+
trust subagent output blindly: before any downstream step consumes the
|
|
10
|
+
documented inventory, the shape is validated. That validation is pure
|
|
11
|
+
structural checking — strip wrapping fences, parse JSON, assert required keys
|
|
12
|
+
and types, assert each export entry is a dict with a non-empty `name` and a
|
|
13
|
+
`kind` drawn from a fixed enum, and assert each cross-check mismatch carries its
|
|
14
|
+
five fields — with exactly one correct pass/fail per input and no
|
|
15
|
+
interpretation of meaning. It is therefore script work, not prompt work; the
|
|
16
|
+
prompt reads this verdict and owns the HALT decision plus the separate
|
|
17
|
+
grep ground-truth spot-check.
|
|
18
|
+
|
|
19
|
+
Distinct from reconcile-coverage.py (coverage numerator arithmetic) and
|
|
20
|
+
compute-score.py (weight tables): this script only validates the *shape* of the
|
|
21
|
+
subagent inventory and, on success, echoes the fence-stripped parsed inventory
|
|
22
|
+
back so the prompt consumes it directly instead of re-parsing the raw response.
|
|
23
|
+
|
|
24
|
+
CLI usage (mirrors reconcile-coverage.py):
|
|
25
|
+
uv run validate-inventory.py '<raw response>' # positional
|
|
26
|
+
uv run validate-inventory.py --json-input '<raw response>' # explicit flag
|
|
27
|
+
echo '<raw response>' | uv run validate-inventory.py --stdin # piped input
|
|
28
|
+
|
|
29
|
+
Input: the subagent's RAW response text (JSON, optionally wrapped in a markdown
|
|
30
|
+
code fence — a leading line of three backticks with an optional language tag and
|
|
31
|
+
a trailing line of three backticks are stripped before parsing).
|
|
32
|
+
|
|
33
|
+
Output (stdout, one object):
|
|
34
|
+
{
|
|
35
|
+
"valid": <bool>, # true only when parse + every schema check pass
|
|
36
|
+
"violations": [<str>, ...], # human-readable failures; empty when valid
|
|
37
|
+
"rejectedCount": <int>, # count of malformed exports[] entries
|
|
38
|
+
"exportsCount": <int>, # len(exports) when parsed; 0 otherwise
|
|
39
|
+
"inventory": {...} | null # fence-stripped parsed inventory when valid; null otherwise
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
Exit codes:
|
|
43
|
+
0 — inventory valid
|
|
44
|
+
1 — no input provided (usage error)
|
|
45
|
+
2 — inventory invalid (parse failure or schema violation; result JSON emitted)
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
from __future__ import annotations
|
|
49
|
+
|
|
50
|
+
import argparse
|
|
51
|
+
import json
|
|
52
|
+
import re
|
|
53
|
+
import sys
|
|
54
|
+
|
|
55
|
+
# Fixed enum for exports[].kind — the constructs SKF documents across languages
|
|
56
|
+
# and skill types: JS/TS (function/class/type/constant/hook/interface/method),
|
|
57
|
+
# Rust public-API items (struct/enum/trait/macro, alongside the shared
|
|
58
|
+
# type/constant/function), and stack-composition scaffolds (adapter). Mirrors the
|
|
59
|
+
# enum documented in coverage-check.md §1a.
|
|
60
|
+
VALID_KINDS = (
|
|
61
|
+
"function",
|
|
62
|
+
"class",
|
|
63
|
+
"type",
|
|
64
|
+
"constant",
|
|
65
|
+
"hook",
|
|
66
|
+
"interface",
|
|
67
|
+
"method",
|
|
68
|
+
"struct",
|
|
69
|
+
"enum",
|
|
70
|
+
"trait",
|
|
71
|
+
"macro",
|
|
72
|
+
"adapter",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
REQUIRED_MISMATCH_FIELDS = (
|
|
76
|
+
"export",
|
|
77
|
+
"skill_md_line",
|
|
78
|
+
"reference_file",
|
|
79
|
+
"reference_line",
|
|
80
|
+
"issue",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
_FENCE_OPEN = re.compile(r"^```[A-Za-z0-9_-]*$")
|
|
84
|
+
_FENCE_CLOSE = re.compile(r"^```$")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def strip_fences(text):
|
|
88
|
+
"""Remove a wrapping markdown code fence, matching coverage-check.md §1a step 1.
|
|
89
|
+
|
|
90
|
+
When the first non-empty line is three backticks (optionally with a language
|
|
91
|
+
tag) and the last non-empty line is three backticks, drop those two lines.
|
|
92
|
+
Otherwise return the text unchanged.
|
|
93
|
+
"""
|
|
94
|
+
lines = text.split("\n")
|
|
95
|
+
non_empty_idx = [i for i, ln in enumerate(lines) if ln.strip()]
|
|
96
|
+
if len(non_empty_idx) < 2:
|
|
97
|
+
return text
|
|
98
|
+
first, last = non_empty_idx[0], non_empty_idx[-1]
|
|
99
|
+
if _FENCE_OPEN.match(lines[first].strip()) and _FENCE_CLOSE.match(lines[last].strip()):
|
|
100
|
+
kept = [ln for i, ln in enumerate(lines) if i != first and i != last]
|
|
101
|
+
return "\n".join(kept)
|
|
102
|
+
return text
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def validate_inventory(raw):
|
|
106
|
+
"""Pure structural validation of the subagent inventory response.
|
|
107
|
+
|
|
108
|
+
`raw` is the raw response text. Returns the result dict documented in the
|
|
109
|
+
module docstring. Deterministic: identical input yields an identical verdict.
|
|
110
|
+
"""
|
|
111
|
+
violations = []
|
|
112
|
+
inner = strip_fences(raw)
|
|
113
|
+
try:
|
|
114
|
+
data = json.loads(inner)
|
|
115
|
+
except json.JSONDecodeError as exc:
|
|
116
|
+
return {
|
|
117
|
+
"valid": False,
|
|
118
|
+
"violations": [f"subagent response not valid JSON: {exc.msg}"],
|
|
119
|
+
"rejectedCount": 0,
|
|
120
|
+
"exportsCount": 0,
|
|
121
|
+
"inventory": None,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if not isinstance(data, dict):
|
|
125
|
+
return {
|
|
126
|
+
"valid": False,
|
|
127
|
+
"violations": ["subagent response is not a JSON object"],
|
|
128
|
+
"rejectedCount": 0,
|
|
129
|
+
"exportsCount": 0,
|
|
130
|
+
"inventory": None,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
exports = data.get("exports")
|
|
134
|
+
if not isinstance(exports, list):
|
|
135
|
+
violations.append(
|
|
136
|
+
"missing/typo: `exports` must be present and a list"
|
|
137
|
+
)
|
|
138
|
+
exports = []
|
|
139
|
+
|
|
140
|
+
cross = data.get("cross_check_mismatches")
|
|
141
|
+
if not isinstance(cross, list):
|
|
142
|
+
violations.append(
|
|
143
|
+
"missing/typo: `cross_check_mismatches` must be present and a list (may be empty)"
|
|
144
|
+
)
|
|
145
|
+
cross = []
|
|
146
|
+
|
|
147
|
+
# Per-entry export validation — count rejections.
|
|
148
|
+
rejected = 0
|
|
149
|
+
for idx, entry in enumerate(exports):
|
|
150
|
+
if not isinstance(entry, dict):
|
|
151
|
+
rejected += 1
|
|
152
|
+
continue
|
|
153
|
+
name = entry.get("name")
|
|
154
|
+
kind = entry.get("kind")
|
|
155
|
+
if not (isinstance(name, str) and name):
|
|
156
|
+
rejected += 1
|
|
157
|
+
continue
|
|
158
|
+
if kind not in VALID_KINDS:
|
|
159
|
+
rejected += 1
|
|
160
|
+
continue
|
|
161
|
+
if rejected > 0:
|
|
162
|
+
subject = "entry does" if rejected == 1 else "entries do"
|
|
163
|
+
violations.append(
|
|
164
|
+
f"{rejected} exports[] {subject} not match schema "
|
|
165
|
+
f"(each needs a non-empty string `name` and a `kind` in "
|
|
166
|
+
f"{{{', '.join(VALID_KINDS)}}})"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Non-empty cross-check mismatch entries must carry all five fields.
|
|
170
|
+
for idx, entry in enumerate(cross):
|
|
171
|
+
if not isinstance(entry, dict):
|
|
172
|
+
violations.append(f"cross_check_mismatches[{idx}] is not an object")
|
|
173
|
+
continue
|
|
174
|
+
missing = [f for f in REQUIRED_MISMATCH_FIELDS if f not in entry]
|
|
175
|
+
if missing:
|
|
176
|
+
violations.append(
|
|
177
|
+
f"cross_check_mismatches[{idx}] missing field(s): {', '.join(missing)}"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
valid = not violations
|
|
181
|
+
return {
|
|
182
|
+
"valid": valid,
|
|
183
|
+
"violations": violations,
|
|
184
|
+
"rejectedCount": rejected,
|
|
185
|
+
"exportsCount": len(exports),
|
|
186
|
+
"inventory": data if valid else None,
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# --- CLI --------------------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _build_parser():
|
|
194
|
+
parser = argparse.ArgumentParser(
|
|
195
|
+
prog="validate-inventory",
|
|
196
|
+
description=(
|
|
197
|
+
"Deterministic schema validation of the §1 subagent inventory JSON "
|
|
198
|
+
"(coverage-check.md §1a). Strips wrapping fences, parses, and asserts "
|
|
199
|
+
"the required-keys/types/enum/mismatch-field contract, returning "
|
|
200
|
+
"{valid, violations, rejectedCount, exportsCount, inventory}."
|
|
201
|
+
),
|
|
202
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
203
|
+
)
|
|
204
|
+
src = parser.add_mutually_exclusive_group()
|
|
205
|
+
src.add_argument(
|
|
206
|
+
"raw_input",
|
|
207
|
+
nargs="?",
|
|
208
|
+
help="Raw subagent response as a positional argument.",
|
|
209
|
+
)
|
|
210
|
+
src.add_argument(
|
|
211
|
+
"--json-input",
|
|
212
|
+
dest="raw_input_flag",
|
|
213
|
+
help="Raw subagent response passed via flag (overrides positional).",
|
|
214
|
+
)
|
|
215
|
+
src.add_argument(
|
|
216
|
+
"--stdin",
|
|
217
|
+
action="store_true",
|
|
218
|
+
help="Read the raw subagent response from stdin.",
|
|
219
|
+
)
|
|
220
|
+
return parser
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _resolve_input(args):
|
|
224
|
+
if args.stdin:
|
|
225
|
+
return sys.stdin.read()
|
|
226
|
+
if args.raw_input_flag is not None:
|
|
227
|
+
return args.raw_input_flag
|
|
228
|
+
if args.raw_input is not None:
|
|
229
|
+
return args.raw_input
|
|
230
|
+
return ""
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def main(argv=None):
|
|
234
|
+
parser = _build_parser()
|
|
235
|
+
args = parser.parse_args(argv)
|
|
236
|
+
raw = _resolve_input(args)
|
|
237
|
+
if not raw.strip():
|
|
238
|
+
parser.print_usage(file=sys.stderr)
|
|
239
|
+
print(
|
|
240
|
+
"error: no input provided (positional arg, --json-input, or --stdin)",
|
|
241
|
+
file=sys.stderr,
|
|
242
|
+
)
|
|
243
|
+
return 1
|
|
244
|
+
|
|
245
|
+
result = validate_inventory(raw)
|
|
246
|
+
print(json.dumps(result, indent=2))
|
|
247
|
+
return 0 if result["valid"] else 2
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
if __name__ == "__main__":
|
|
251
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.10"
|
|
4
|
+
# dependencies = []
|
|
5
|
+
# ///
|
|
6
|
+
"""Deterministic numerator ground-truth verifier.
|
|
7
|
+
|
|
8
|
+
Grep-and-count helper for the SKF test-skill workflow (coverage-check.md §4b
|
|
9
|
+
"Numerator ground-truth"). It fires only on the inflation signature
|
|
10
|
+
(`stats.exports_documented == effective_denominator` exactly): the numerator
|
|
11
|
+
equals the denominator, the tell of a documented count padded to force 100%
|
|
12
|
+
coverage. On that signature the declared count is not trusted — every declared
|
|
13
|
+
export name is greped against the skill's documentation surface and only the
|
|
14
|
+
names that actually appear count toward coverage.
|
|
15
|
+
|
|
16
|
+
Distinct from reconcile-coverage.py (whose scalar/stack branches grep the §1
|
|
17
|
+
inventory names and return only a residual missing *count*): this script greps
|
|
18
|
+
the full declared metadata/provenance set and enumerates the *absent* names so
|
|
19
|
+
§4b can list them in a High-severity gap. Both scripts share the same grep
|
|
20
|
+
semantics (SKILL.md ∪ sorted references/*.md, UTF-8, case-sensitive substring
|
|
21
|
+
containment — mirroring `grep "{name}"`) so their numerators agree.
|
|
22
|
+
|
|
23
|
+
Input schema (one JSON object):
|
|
24
|
+
{
|
|
25
|
+
"declaredNames": ["foo", "Bar::baz", ...], # metadata.exports[] / provenance declared set
|
|
26
|
+
"skillPackagePath": "/path/to/skill" # SKILL.md ∪ references/*.md
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
Output (stdout, one object):
|
|
30
|
+
{
|
|
31
|
+
"declared": <int>, # de-duplicated declared name count
|
|
32
|
+
"verified": <int>, # declared names present in the doc surface
|
|
33
|
+
"present": [names], # sorted
|
|
34
|
+
"absent": [names], # sorted (declared − verified) — the gap list
|
|
35
|
+
"inflated": <bool> # verified < declared (numerator was padded)
|
|
36
|
+
}
|
|
37
|
+
or {"error": ..., "code": "INVALID_INPUT"} on a schema violation.
|
|
38
|
+
|
|
39
|
+
`verified` is the numerator §4b uses for Export Coverage (it overrides the
|
|
40
|
+
declared count when `inflated` is true).
|
|
41
|
+
|
|
42
|
+
CLI usage (mirrors reconcile-coverage.py):
|
|
43
|
+
uv run verify-declared-numerator.py '<JSON>' # positional
|
|
44
|
+
uv run verify-declared-numerator.py --json-input '<JSON>' # explicit flag
|
|
45
|
+
cat input.json | uv run verify-declared-numerator.py --stdin # piped input
|
|
46
|
+
|
|
47
|
+
Exit codes:
|
|
48
|
+
0 — verification emitted successfully
|
|
49
|
+
1 — no input / input could not be parsed as JSON
|
|
50
|
+
2 — input parsed but schema/semantics invalid (error object emitted as JSON)
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
from __future__ import annotations
|
|
54
|
+
|
|
55
|
+
import argparse
|
|
56
|
+
import json
|
|
57
|
+
import sys
|
|
58
|
+
from pathlib import Path
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def make_error(message):
|
|
62
|
+
return {"error": message, "code": "INVALID_INPUT"}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def load_doc_text(skill_package_path):
|
|
66
|
+
"""Concatenate SKILL.md ∪ references/*.md text for name-presence grepping.
|
|
67
|
+
|
|
68
|
+
Deterministic: references are read in sorted order. Reads as UTF-8 so
|
|
69
|
+
non-ASCII exports do not mojibake on Windows (cp1252 default). Mirrors
|
|
70
|
+
reconcile-coverage.py.load_doc_text so the two numerators agree.
|
|
71
|
+
"""
|
|
72
|
+
root = Path(skill_package_path)
|
|
73
|
+
parts = []
|
|
74
|
+
skill_md = root / "SKILL.md"
|
|
75
|
+
if skill_md.is_file():
|
|
76
|
+
parts.append(skill_md.read_text(encoding="utf-8"))
|
|
77
|
+
refs_dir = root / "references"
|
|
78
|
+
if refs_dir.is_dir():
|
|
79
|
+
for ref in sorted(refs_dir.glob("*.md")):
|
|
80
|
+
parts.append(ref.read_text(encoding="utf-8"))
|
|
81
|
+
return "\n".join(parts)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _validate(inp):
|
|
85
|
+
if inp is None or not isinstance(inp, dict):
|
|
86
|
+
return "Input must be a JSON object"
|
|
87
|
+
names = inp.get("declaredNames")
|
|
88
|
+
if not isinstance(names, list) or not names:
|
|
89
|
+
return "declaredNames must be a non-empty list of strings"
|
|
90
|
+
if not inp.get("skillPackagePath"):
|
|
91
|
+
return "skillPackagePath is required to grep SKILL.md ∪ references"
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def verify(inp, doc_text=None):
|
|
96
|
+
"""Pure verification. `doc_text` may be injected (tests); else loaded from disk."""
|
|
97
|
+
err = _validate(inp)
|
|
98
|
+
if err:
|
|
99
|
+
return make_error(err)
|
|
100
|
+
|
|
101
|
+
declared_set = sorted({n for n in inp["declaredNames"] if isinstance(n, str) and n})
|
|
102
|
+
if doc_text is None:
|
|
103
|
+
doc_text = load_doc_text(inp["skillPackagePath"])
|
|
104
|
+
|
|
105
|
+
present = sorted(n for n in declared_set if n in doc_text)
|
|
106
|
+
absent = sorted(n for n in declared_set if n not in doc_text)
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
"declared": len(declared_set),
|
|
110
|
+
"verified": len(present),
|
|
111
|
+
"present": present,
|
|
112
|
+
"absent": absent,
|
|
113
|
+
"inflated": len(present) < len(declared_set),
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
# --- CLI --------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _build_parser():
|
|
121
|
+
parser = argparse.ArgumentParser(
|
|
122
|
+
prog="verify-declared-numerator",
|
|
123
|
+
description=(
|
|
124
|
+
"Deterministic numerator ground-truth verifier (coverage-check.md "
|
|
125
|
+
"§4b). Greps the full declared export set against SKILL.md ∪ "
|
|
126
|
+
"references/*.md and enumerates the present/absent names so a padded "
|
|
127
|
+
"numerator is caught and the verified count replaces it."
|
|
128
|
+
),
|
|
129
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
130
|
+
epilog=(
|
|
131
|
+
"Example:\n"
|
|
132
|
+
" uv run verify-declared-numerator.py "
|
|
133
|
+
"'{\"declaredNames\":[\"foo\",\"bar\"],"
|
|
134
|
+
"\"skillPackagePath\":\"/path/to/skill\"}'"
|
|
135
|
+
),
|
|
136
|
+
)
|
|
137
|
+
src = parser.add_mutually_exclusive_group()
|
|
138
|
+
src.add_argument(
|
|
139
|
+
"json_input",
|
|
140
|
+
nargs="?",
|
|
141
|
+
help="JSON object as a positional argument (single-quote it on the shell).",
|
|
142
|
+
)
|
|
143
|
+
src.add_argument(
|
|
144
|
+
"--json-input",
|
|
145
|
+
dest="json_input_flag",
|
|
146
|
+
help="JSON object passed via flag (overrides positional).",
|
|
147
|
+
)
|
|
148
|
+
src.add_argument(
|
|
149
|
+
"--stdin",
|
|
150
|
+
action="store_true",
|
|
151
|
+
help="Read the JSON object from stdin.",
|
|
152
|
+
)
|
|
153
|
+
return parser
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _resolve_input(args):
|
|
157
|
+
if args.stdin:
|
|
158
|
+
return sys.stdin.read()
|
|
159
|
+
if args.json_input_flag is not None:
|
|
160
|
+
return args.json_input_flag
|
|
161
|
+
if args.json_input is not None:
|
|
162
|
+
return args.json_input
|
|
163
|
+
return ""
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def main(argv=None):
|
|
167
|
+
parser = _build_parser()
|
|
168
|
+
args = parser.parse_args(argv)
|
|
169
|
+
raw = _resolve_input(args)
|
|
170
|
+
if not raw.strip():
|
|
171
|
+
parser.print_usage(file=sys.stderr)
|
|
172
|
+
print(
|
|
173
|
+
"error: no input provided (positional arg, --json-input, or --stdin)",
|
|
174
|
+
file=sys.stderr,
|
|
175
|
+
)
|
|
176
|
+
return 1
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
data = json.loads(raw)
|
|
180
|
+
except json.JSONDecodeError as exc:
|
|
181
|
+
print(json.dumps(make_error(f"Invalid JSON: {exc.msg}"), indent=2))
|
|
182
|
+
return 1
|
|
183
|
+
|
|
184
|
+
result = verify(data)
|
|
185
|
+
print(json.dumps(result, indent=2))
|
|
186
|
+
if isinstance(result, dict) and result.get("code") == "INVALID_INPUT":
|
|
187
|
+
return 2
|
|
188
|
+
return 0
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
if __name__ == "__main__":
|
|
192
|
+
raise SystemExit(main())
|
|
@@ -12,6 +12,7 @@ Surgically updates existing skills when source code changes, preserving all [MAN
|
|
|
12
12
|
## Conventions
|
|
13
13
|
|
|
14
14
|
- Bare paths (e.g. `references/<name>.md`) resolve from the skill root.
|
|
15
|
+
- **Module-level path exception:** bare paths beginning with `knowledge/` or `shared/` resolve from the SKF module root (`{project-root}/_bmad/skf/` installed, `src/` in dev), not the skill root — stage files reference `knowledge/version-paths.md` and `knowledge/tool-resolution.md`, and the terminal step chains to `shared/health-check.md`.
|
|
15
16
|
- `references/` holds prompt content carved out of SKILL.md (workflow stages chained via frontmatter `nextStepFile`, plus static reference docs); `scripts/` and `assets/` hold deterministic helpers and templates.
|
|
16
17
|
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives, if present).
|
|
17
18
|
- `{project-root}`-prefixed paths resolve from the project working directory.
|
|
@@ -50,11 +51,11 @@ These rules apply to every step in this workflow:
|
|
|
50
51
|
| Aspect | Detail |
|
|
51
52
|
|--------|--------|
|
|
52
53
|
| **Inputs** | skill_name [required] |
|
|
53
|
-
| **Flags** | `--headless` / `-H` (auto-resolve all gates); `--from-test-report` (gap-driven mode); `--allow-workspace-drift` (gap-driven only — bypass §0.a pinning guard); `--detect-only` (run detect-changes only, exit before re-extract; envelope `status="detect-only"`); `--dry-run` (run detect-changes + re-extract, exit before merge/write; envelope `status="dry-run"` describes what would change). If both `--detect-only` and `--dry-run` are passed, `--detect-only` wins. |
|
|
54
|
+
| **Flags** | `--headless` / `-H` (auto-resolve all gates); `--from-test-report` (gap-driven mode); `--allow-workspace-drift` (gap-driven only — bypass §0.a pinning guard); `--allow-degraded` (headless only — pre-authorize the lossy degraded full re-extraction when the provenance map is missing, instead of halting `blocked`; init.md §4); `--detect-only` (run detect-changes only, exit before re-extract; envelope `status="detect-only"`); `--dry-run` (run detect-changes + re-extract, exit before merge/write; envelope `status="dry-run"` describes what would change). If both `--detect-only` and `--dry-run` are passed, `--detect-only` wins. |
|
|
54
55
|
| **Gates** | step 1: Confirm Gate [C] | step 4: Confirm Gate [C if clean merge, HALT if conflicts] |
|
|
55
|
-
| **Outputs** | Updated SKILL.md,
|
|
56
|
-
| **Concurrency** | Two simultaneous real-update runs against the same skill would corrupt provenance. init.md §1b acquires a PID-file lock at `{forge_data_folder}/{skill_name}/.skf-update.lock` before any artifact read; live-PID collisions halt with `status: "halted-for-concurrent-run"`. Stale locks (dead PID) are cleared silently with a warning. The lock is released by the terminal health-check step (step 8) and by
|
|
57
|
-
| **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true. Each auto-resolved gate appends a `{gate, default_action, taken_action, reason, evidence?}` entry to `headless_decisions[]`, surfaced in step 7's `SKF_UPDATE_RESULT_JSON` envelope so non-interactive runs can be audited post-hoc. Pipeline branches on the envelope's top-level `status` field (`success`, `no-changes`, `detect-only`, `dry-run`, or one of the documented `halted-for
|
|
56
|
+
| **Outputs** | Updated SKILL.md, metadata.json, provenance-map.json, evidence-report.md (none when `--detect-only` or `--dry-run` is set — those modes are read-only inspection paths) |
|
|
57
|
+
| **Concurrency** | Two simultaneous real-update runs against the same skill would corrupt provenance. init.md §1b acquires a PID-file lock at `{forge_data_folder}/{skill_name}/.skf-update.lock` before any artifact read; live-PID collisions halt with `status: "halted-for-concurrent-run"`. Stale locks (dead PID) are cleared silently with a warning. The lock is released by the terminal health-check step (step 8) on the normal path and by the two init-stage headless halts (§4/§6); mid-workflow halts leave it for the next run's stale-lock self-heal (see init.md §1b Release contract). Read-only modes (`--detect-only`, `--dry-run`) skip the lock entirely — they're safe alongside a concurrent real update. |
|
|
58
|
+
| **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true. Each auto-resolved gate appends a `{gate, default_action, taken_action, reason, evidence?}` entry to `headless_decisions[]`, surfaced in step 7's `SKF_UPDATE_RESULT_JSON` envelope so non-interactive runs can be audited post-hoc. A HALT reached in headless mode emits its own `SKF_UPDATE_RESULT_JSON` at the halt site (the site's `status` code plus an `error: {phase, path?, reason}` object) and exits — halts do not fall through to step 7. Pipeline branches on the envelope's top-level `status` field (`success`, `no-changes`, `detect-only`, `dry-run`, or one of the documented `halted-for-*`/`blocked` codes). The first four are successful exits — pipelines treating non-success as failure must include them in the success set. The status enum is defined once in `src/shared/scripts/schemas/skf-update-result-envelope.v1.json`. |
|
|
58
59
|
|
|
59
60
|
## On Activation
|
|
60
61
|
|
|
@@ -64,4 +65,15 @@ These rules apply to every step in this workflow:
|
|
|
64
65
|
|
|
65
66
|
2. **Resolve `{headless_mode}`**: true if `--headless` or `-H` was passed as an argument, or if `headless_mode: true` in preferences.yaml. Default: false.
|
|
66
67
|
|
|
67
|
-
3.
|
|
68
|
+
3. **Resolve workflow customization.** Run:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
python3 {project-root}/_bmad/scripts/resolve_customization.py \
|
|
72
|
+
--skill {skill-root} --key workflow
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
This merges the three layers per `bmad-customize` rules (scalars override, arrays append): `{skill-root}/customize.toml` (bundled defaults), `_bmad/custom/<skill-name>.toml` under `{project-root}` (team overrides), and `_bmad/custom/<skill-name>.user.toml` under `{project-root}` (personal overrides). If the script is missing or fails, read `{skill-root}/customize.toml` directly.
|
|
76
|
+
|
|
77
|
+
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 any `project-context.md` under `{project-root}`); resolve `{onCompleteCommand}` ← `workflow.on_complete` if non-empty, else empty string, and stash it in workflow context (`references/report.md` §5b invokes it after the result contract is written; empty string = the hook is a no-op). After activation completes, execute each entry in `workflow.activation_steps_append` in order before `init.md` runs.
|
|
78
|
+
|
|
79
|
+
4. Load, read the full file, and then execute `references/init.md` to begin the workflow.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# DO NOT EDIT -- overwritten on every update.
|
|
2
2
|
#
|
|
3
3
|
# Workflow customization surface for skf-update-skill.
|
|
4
|
+
# Team overrides: _bmad/custom/skf-update-skill.toml (under {project-root})
|
|
5
|
+
# Personal overrides: _bmad/custom/skf-update-skill.user.toml (under {project-root})
|
|
4
6
|
|
|
5
7
|
[workflow]
|
|
6
8
|
|
|
@@ -34,11 +36,14 @@ persistent_facts = [
|
|
|
34
36
|
"file:{project-root}/**/project-context.md",
|
|
35
37
|
]
|
|
36
38
|
|
|
37
|
-
#
|
|
39
|
+
# Pipeline-integration hook invoked after report.md §5b writes the result
|
|
40
|
+
# contract (the per-run record plus update-skill-result-latest.json). The
|
|
41
|
+
# command is called as:
|
|
42
|
+
# <on_complete> --result-path=<{forge_version}/update-skill-result-latest.json>
|
|
43
|
+
# Useful for Slack notifications, dashboard ingest, CI hooks, or chaining a
|
|
44
|
+
# downstream skill (audit, export, test). Failures are logged to the run's
|
|
45
|
+
# warnings[] but never fail the workflow.
|
|
38
46
|
#
|
|
39
|
-
#
|
|
40
|
-
# string or commented = use the bundled default (50% deletion ratio,
|
|
41
|
-
# 80% rename similarity).
|
|
47
|
+
# Empty string = no-op (default).
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
rename_similarity_threshold = ""
|
|
49
|
+
on_complete = ""
|