bmad-module-skill-forge 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.nvmrc +1 -1
- package/docs/_data/pinned.yaml +1 -1
- package/package.json +2 -2
- package/src/knowledge/skill-lifecycle.md +1 -1
- package/src/module-help.csv +2 -2
- package/src/shared/scripts/skf-comention-pairs.py +350 -0
- package/src/shared/scripts/skf-count-tokens.py +222 -0
- package/src/shared/scripts/skf-detect-docs.py +232 -1
- package/src/shared/scripts/skf-detect-language.py +77 -0
- package/src/shared/scripts/skf-detect-tools.py +105 -2
- package/src/shared/scripts/skf-enumerate-stack-skills.py +111 -2
- package/src/shared/scripts/skf-find-cycles.py +246 -0
- package/src/shared/scripts/skf-hash-content.py +487 -2
- package/src/shared/scripts/skf-manifest-ops.py +67 -1
- package/src/shared/scripts/skf-rebuild-managed-sections.py +180 -0
- package/src/shared/scripts/skf-recommend-scope-type.py +1 -2
- package/src/shared/scripts/skf-render-metadata-stats.py +499 -0
- package/src/shared/scripts/skf-rewrite-skill-name.py +308 -0
- package/src/shared/scripts/skf-shard-body.py +461 -0
- package/src/shared/scripts/skf-skill-inventory.py +139 -3
- package/src/shared/scripts/skf-structural-diff.py +307 -67
- package/src/shared/scripts/skf-validate-brief-schema.py +39 -1
- package/src/shared/scripts/skf-validate-feasibility-report.py +234 -0
- package/src/shared/scripts/skf-validate-frontmatter.py +17 -12
- package/src/shared/scripts/skf-validate-output.py +443 -10
- package/src/shared/scripts/skf-validate-pins.py +1 -1
- package/src/shared/scripts/skf-verify-no-trace.py +214 -0
- package/src/shared/scripts/skf-verify-provenance-completeness.py +439 -0
- package/src/skf-analyze-source/SKILL.md +17 -32
- package/src/skf-analyze-source/customize.toml +2 -0
- package/src/skf-analyze-source/references/auto-docs-only.md +116 -0
- package/src/skf-analyze-source/references/continue.md +8 -13
- package/src/skf-analyze-source/references/generate-briefs.md +28 -12
- package/src/skf-analyze-source/references/headless-contract.md +33 -0
- package/src/skf-analyze-source/references/health-check.md +1 -2
- package/src/skf-analyze-source/references/identify-units.md +25 -36
- package/src/skf-analyze-source/references/init.md +12 -25
- package/src/skf-analyze-source/references/map-and-detect.md +8 -20
- package/src/skf-analyze-source/references/recommend.md +4 -14
- package/src/skf-analyze-source/references/scan-project.md +3 -11
- package/src/skf-analyze-source/references/step-auto-scope.md +42 -180
- package/src/skf-analyze-source/references/unit-detection-heuristics.md +2 -2
- package/src/skf-audit-skill/SKILL.md +9 -6
- package/src/skf-audit-skill/customize.toml +2 -0
- package/src/skf-audit-skill/references/init.md +58 -32
- package/src/skf-audit-skill/references/re-index.md +15 -34
- package/src/skf-audit-skill/references/report.md +11 -58
- package/src/skf-audit-skill/references/semantic-diff.md +4 -22
- package/src/skf-audit-skill/references/severity-classify.md +53 -87
- package/src/skf-audit-skill/references/step-doc-drift.md +49 -19
- package/src/skf-audit-skill/references/structural-diff.md +43 -68
- package/src/skf-brief-skill/SKILL.md +7 -36
- package/src/skf-brief-skill/assets/scope-templates.md +1 -1
- package/src/skf-brief-skill/customize.toml +14 -0
- package/src/skf-brief-skill/references/analyze-target.md +6 -14
- package/src/skf-brief-skill/references/confirm-brief.md +2 -14
- package/src/skf-brief-skill/references/draft-checkpoint.md +8 -8
- package/src/skf-brief-skill/references/gather-intent.md +19 -22
- package/src/skf-brief-skill/references/headless-args.md +1 -1
- package/src/skf-brief-skill/references/health-check.md +1 -22
- package/src/skf-brief-skill/references/invocation-contract.md +37 -0
- package/src/skf-brief-skill/references/scope-definition.md +4 -14
- package/src/skf-brief-skill/references/step-auto-brief.md +1 -5
- package/src/skf-brief-skill/references/step-auto-validate.md +1 -5
- package/src/skf-brief-skill/references/version-resolution.md +1 -1
- package/src/skf-brief-skill/references/write-brief.md +17 -10
- package/src/skf-campaign/SKILL.md +15 -84
- package/src/skf-campaign/customize.toml +2 -0
- package/src/skf-campaign/manifest.yaml +4 -0
- package/src/skf-campaign/references/campaign-contracts.md +67 -0
- package/src/skf-campaign/references/campaign-directive-spec.md +14 -103
- package/src/skf-campaign/references/step-01-setup.md +4 -5
- package/src/skf-campaign/references/step-02-strategy.md +8 -3
- package/src/skf-campaign/references/step-03-pins.md +1 -1
- package/src/skf-campaign/references/step-04-provenance.md +1 -1
- package/src/skf-campaign/references/step-05-skill-loop.md +1 -1
- package/src/skf-campaign/references/step-06-batch.md +7 -6
- package/src/skf-campaign/references/step-11-maintenance.md +2 -2
- package/src/skf-campaign/references/step-resume.md +9 -8
- package/src/skf-campaign/scripts/campaign-deps.py +10 -1
- package/src/skf-campaign/scripts/campaign-render-batch.py +199 -0
- package/src/skf-campaign/scripts/campaign-status.py +162 -0
- package/src/skf-campaign/templates/campaign-brief-template.yaml +1 -1
- package/src/skf-create-skill/SKILL.md +14 -3
- package/src/skf-create-skill/assets/compile-assembly-rules.md +17 -38
- package/src/skf-create-skill/assets/skill-sections.md +9 -100
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +4 -13
- package/src/skf-create-skill/customize.toml +13 -0
- package/src/skf-create-skill/references/authoritative-files-protocol.md +7 -7
- package/src/skf-create-skill/references/compile.md +34 -47
- package/src/skf-create-skill/references/component-extraction.md +8 -35
- package/src/skf-create-skill/references/ecosystem-check.md +9 -25
- package/src/skf-create-skill/references/enrich.md +4 -17
- package/src/skf-create-skill/references/extract.md +14 -29
- package/src/skf-create-skill/references/extraction-patterns-tracing.md +1 -1
- package/src/skf-create-skill/references/extraction-patterns.md +7 -7
- package/src/skf-create-skill/references/generate-artifacts.md +9 -23
- package/src/skf-create-skill/references/health-check.md +1 -1
- package/src/skf-create-skill/references/load-brief.md +16 -18
- package/src/skf-create-skill/references/report.md +12 -15
- package/src/skf-create-skill/references/source-resolution-protocols.md +3 -3
- package/src/skf-create-skill/references/step-auto-shard.md +52 -25
- package/src/skf-create-skill/references/step-doc-rot.md +30 -12
- package/src/skf-create-skill/references/step-doc-sources.md +2 -2
- package/src/skf-create-skill/references/sub/ccc-discover.md +2 -13
- package/src/skf-create-skill/references/sub/fetch-docs.md +8 -50
- package/src/skf-create-skill/references/sub/fetch-temporal.md +5 -20
- package/src/skf-create-skill/references/tier-degradation-rules.md +6 -8
- package/src/skf-create-skill/references/validate.md +59 -28
- package/src/skf-create-skill/scripts/scan-doc-rot.py +245 -0
- package/src/skf-create-stack-skill/SKILL.md +15 -9
- package/src/skf-create-stack-skill/assets/provenance-map-schema.md +1 -1
- package/src/skf-create-stack-skill/customize.toml +10 -0
- package/src/skf-create-stack-skill/references/compile-stack.md +7 -8
- package/src/skf-create-stack-skill/references/compose-mode-rules.md +2 -13
- package/src/skf-create-stack-skill/references/detect-integrations.md +49 -54
- package/src/skf-create-stack-skill/references/detect-manifests.md +37 -30
- package/src/skf-create-stack-skill/references/generate-output.md +24 -75
- package/src/skf-create-stack-skill/references/init.md +9 -81
- package/src/skf-create-stack-skill/references/manifest-patterns.md +5 -35
- package/src/skf-create-stack-skill/references/parallel-extract.md +12 -27
- package/src/skf-create-stack-skill/references/rank-and-confirm.md +6 -6
- package/src/skf-create-stack-skill/references/report.md +27 -62
- package/src/skf-create-stack-skill/references/validate.md +35 -31
- package/src/skf-drop-skill/SKILL.md +20 -23
- package/src/skf-drop-skill/customize.toml +10 -0
- package/src/skf-drop-skill/references/execute.md +51 -31
- package/src/skf-drop-skill/references/headless-contract.md +18 -0
- package/src/skf-drop-skill/references/health-check.md +1 -13
- package/src/skf-drop-skill/references/report.md +42 -10
- package/src/skf-drop-skill/references/select.md +88 -48
- package/src/skf-drop-skill/scripts/dir-sizes.py +116 -0
- package/src/skf-export-skill/SKILL.md +17 -14
- package/src/skf-export-skill/assets/managed-section-format.md +2 -5
- package/src/skf-export-skill/customize.toml +10 -6
- package/src/skf-export-skill/references/generate-snippet.md +21 -35
- package/src/skf-export-skill/references/load-skill.md +44 -42
- package/src/skf-export-skill/references/multi-skill-mode.md +1 -1
- package/src/skf-export-skill/references/orphan-context-detection.md +11 -3
- package/src/skf-export-skill/references/package.md +27 -33
- package/src/skf-export-skill/references/preflight-snippet-root-probe.md +24 -5
- package/src/skf-export-skill/references/result-envelope.md +28 -0
- package/src/skf-export-skill/references/summary.md +11 -5
- package/src/skf-export-skill/references/token-report.md +42 -24
- package/src/skf-export-skill/references/update-context.md +32 -33
- package/src/skf-forger/SKILL.md +27 -39
- package/src/skf-forger/references/pipeline-mode.md +34 -0
- package/src/skf-forger/scripts/parse-pipeline.py +270 -0
- package/src/skf-quick-skill/SKILL.md +10 -60
- package/src/skf-quick-skill/customize.toml +9 -0
- package/src/skf-quick-skill/references/batch-mode.md +6 -4
- package/src/skf-quick-skill/references/compile.md +15 -52
- package/src/skf-quick-skill/references/ecosystem-check.md +5 -20
- package/src/skf-quick-skill/references/finalize.md +12 -6
- package/src/skf-quick-skill/references/halt-contract.md +54 -0
- package/src/skf-quick-skill/references/health-check.md +3 -4
- package/src/skf-quick-skill/references/quick-extract.md +7 -18
- package/src/skf-quick-skill/references/registry-resolution.md +1 -23
- package/src/skf-quick-skill/references/resolve-target.md +27 -34
- package/src/skf-quick-skill/references/write-and-validate.md +10 -25
- package/src/skf-refine-architecture/SKILL.md +14 -7
- package/src/skf-refine-architecture/customize.toml +9 -0
- package/src/skf-refine-architecture/references/compile.md +9 -7
- package/src/skf-refine-architecture/references/gap-analysis.md +21 -50
- package/src/skf-refine-architecture/references/health-check.md +1 -1
- package/src/skf-refine-architecture/references/improvements.md +9 -25
- package/src/skf-refine-architecture/references/init.md +9 -16
- package/src/skf-refine-architecture/references/issue-detection.md +10 -25
- package/src/skf-refine-architecture/references/refinement-rules.md +4 -16
- package/src/skf-refine-architecture/references/report.md +13 -10
- package/src/skf-rename-skill/SKILL.md +11 -21
- package/src/skf-rename-skill/customize.toml +10 -0
- package/src/skf-rename-skill/references/execute.md +107 -78
- package/src/skf-rename-skill/references/exit-codes.md +12 -0
- package/src/skf-rename-skill/references/report.md +16 -8
- package/src/skf-rename-skill/references/select.md +69 -42
- package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -0
- package/src/skf-setup/SKILL.md +20 -12
- package/src/skf-setup/customize.toml +10 -0
- package/src/skf-setup/references/auto-index.md +9 -10
- package/src/skf-setup/references/ccc-index.md +14 -15
- package/src/skf-setup/references/detect-and-tier.md +6 -8
- package/src/skf-setup/references/report.md +22 -31
- package/src/skf-setup/references/tier-rules.md +2 -32
- package/src/skf-setup/references/write-config.md +9 -10
- package/src/skf-test-skill/SKILL.md +10 -8
- package/src/skf-test-skill/customize.toml +2 -0
- package/src/skf-test-skill/references/coherence-check.md +19 -31
- package/src/skf-test-skill/references/coverage-check.md +109 -75
- package/src/skf-test-skill/references/detect-mode.md +10 -41
- package/src/skf-test-skill/references/external-validators.md +11 -19
- package/src/skf-test-skill/references/init.md +38 -20
- package/src/skf-test-skill/references/migration-section-rules.md +4 -4
- package/src/skf-test-skill/references/report.md +60 -40
- package/src/skf-test-skill/references/score.md +32 -64
- package/src/skf-test-skill/references/scoring-rules.md +6 -62
- package/src/skf-test-skill/references/source-access-protocol.md +6 -6
- package/src/skf-test-skill/references/step-hard-gate.md +1 -3
- package/src/skf-test-skill/scripts/aggregate-coherence.py +276 -0
- package/src/skf-test-skill/scripts/check-metadata-coherence.py +359 -0
- package/src/skf-test-skill/scripts/combine-external-scores.py +190 -0
- package/src/skf-test-skill/scripts/compute-score.py +131 -1
- package/src/skf-test-skill/scripts/reconcile-coverage.py +327 -0
- package/src/skf-test-skill/scripts/validate-inventory.py +251 -0
- package/src/skf-test-skill/scripts/verify-declared-numerator.py +192 -0
- package/src/skf-update-skill/SKILL.md +17 -5
- package/src/skf-update-skill/customize.toml +14 -0
- package/src/skf-update-skill/references/detect-changes.md +19 -37
- package/src/skf-update-skill/references/health-check.md +3 -9
- package/src/skf-update-skill/references/init.md +39 -32
- package/src/skf-update-skill/references/manual-section-rules.md +1 -2
- package/src/skf-update-skill/references/merge-conflict-rules.md +1 -52
- package/src/skf-update-skill/references/merge.md +13 -57
- package/src/skf-update-skill/references/re-extract.md +12 -24
- package/src/skf-update-skill/references/report.md +14 -7
- package/src/skf-update-skill/references/validate.md +32 -30
- package/src/skf-update-skill/references/write.md +86 -51
- package/src/skf-update-skill/scripts/skf-new-file-diff.py +167 -0
- package/src/skf-verify-stack/SKILL.md +11 -21
- package/src/skf-verify-stack/customize.toml +12 -3
- package/src/skf-verify-stack/references/coverage-patterns.md +1 -29
- package/src/skf-verify-stack/references/coverage.md +21 -10
- package/src/skf-verify-stack/references/exit-codes.md +14 -0
- package/src/skf-verify-stack/references/health-check.md +1 -1
- package/src/skf-verify-stack/references/init.md +20 -25
- package/src/skf-verify-stack/references/integration-verification-rules.md +10 -19
- package/src/skf-verify-stack/references/integrations.md +44 -49
- package/src/skf-verify-stack/references/report.md +28 -34
- package/src/skf-verify-stack/references/requirements.md +3 -3
- package/src/skf-verify-stack/references/synthesize.md +36 -39
- package/src/skf-verify-stack/scripts/skf-coverage-tally.py +196 -0
- package/src/skf-verify-stack/scripts/skf-report-delta.py +256 -0
- package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +258 -0
- package/src/skf-rename-skill/references/rebuild-context.md +0 -110
|
@@ -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
|
|
|
@@ -33,3 +35,15 @@ activation_steps_append = []
|
|
|
33
35
|
persistent_facts = [
|
|
34
36
|
"file:{project-root}/**/project-context.md",
|
|
35
37
|
]
|
|
38
|
+
|
|
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.
|
|
46
|
+
#
|
|
47
|
+
# Empty string = no-op (default).
|
|
48
|
+
|
|
49
|
+
on_complete = ""
|
|
@@ -36,6 +36,10 @@ provenanceGapDispatchProbeOrder:
|
|
|
36
36
|
detectScriptsAssetsProbeOrder:
|
|
37
37
|
- '{project-root}/_bmad/skf/shared/scripts/skf-detect-scripts-assets.py'
|
|
38
38
|
- '{project-root}/src/shared/scripts/skf-detect-scripts-assets.py'
|
|
39
|
+
# `{newFileDiffHelper}` derives NEW_FILE (inventory source_files not in the
|
|
40
|
+
# provenance map, minus [MANUAL] paths) — the set-difference §Category D would
|
|
41
|
+
# otherwise ask the model to compute by hand. Per-skill helper, always bundled.
|
|
42
|
+
newFileDiffHelper: 'scripts/skf-new-file-diff.py'
|
|
39
43
|
---
|
|
40
44
|
|
|
41
45
|
<!-- Config: communicate in {communication_language}. -->
|
|
@@ -51,7 +55,7 @@ Compare current source code state against the provenance map to produce a comple
|
|
|
51
55
|
- Focus only on detecting and classifying changes — do not extract or merge
|
|
52
56
|
- Use subprocess Pattern 4 (parallel) when available; if unavailable, compare sequentially
|
|
53
57
|
|
|
54
|
-
##
|
|
58
|
+
## Steps
|
|
55
59
|
|
|
56
60
|
### 0. Check for Test Report Input (Gap-Driven Mode)
|
|
57
61
|
|
|
@@ -105,7 +109,7 @@ Read the source directory at `{source_root}` and build a current file inventory:
|
|
|
105
109
|
|
|
106
110
|
### 1b. Discovered Authoritative Files Protocol (Mirror)
|
|
107
111
|
|
|
108
|
-
**Purpose:** mirror `skf-create-skill` §2a into update-skill. `skf-create-skill` §2a catches authoritative AI documentation files (`llms.txt`, `AGENTS.md`, `.cursorrules`, etc.) during **creation
|
|
112
|
+
**Purpose:** mirror `skf-create-skill` §2a into update-skill. `skf-create-skill` §2a catches authoritative AI documentation files (`llms.txt`, `AGENTS.md`, `.cursorrules`, etc.) during **creation**, but a project may add these files *after* the skill was created. Without this mirror, update-skill would either miss the new file entirely (if it doesn't match the provenance map's file patterns) or classify it as a generic ADDED file in §2 Category A with no authoritative-file treatment. The mirror surfaces the discovery with the same P/S/U prompt create-skill uses, honoring any prior amendments.
|
|
109
113
|
|
|
110
114
|
**Skip this section entirely if:**
|
|
111
115
|
|
|
@@ -177,11 +181,11 @@ Read the source directory at `{source_root}` and build a current file inventory:
|
|
|
177
181
|
|
|
178
182
|
**Record for evidence report:** the update-skill evidence report appends `authoritative_files_mirror: {candidates: N, promoted: P, skipped: S, pre_decided: A, already_tracked: T, decisions: [{path, action, heuristic, reason}]}`.
|
|
179
183
|
|
|
180
|
-
**Interaction with §2 change detection:** promoted docs live in `promoted_docs_new[]`,
|
|
184
|
+
**Interaction with §2 change detection:** promoted docs live in `promoted_docs_new[]`, not in the change manifest. §2.0's `change_detection_excludes` set (built below) is what stops §2 Category A from re-classifying them as ADDED — it is the only coordination that survives across the parallel Category A subprocesses.
|
|
181
185
|
|
|
182
186
|
### 1c. Major-Version Scope Reconciliation (Pre-Detection)
|
|
183
187
|
|
|
184
|
-
**Purpose:**
|
|
188
|
+
**Purpose:** §1b handles new authoritative-doc files; §1c handles new **code globs** that fall outside the original scope when upstream restructures (rebrand, package restructure, major-version rewrite) so the brief's `scope.include` no longer reflects the real public API. Without it, update-skill silently misses the new public surface and pays the gap cost on every future update.
|
|
185
189
|
|
|
186
190
|
**Skip this section entirely if:**
|
|
187
191
|
|
|
@@ -233,7 +237,7 @@ Read the source directory at `{source_root}` and build a current file inventory:
|
|
|
233
237
|
- `status: "pre-decided-demoted"` → record as `pre_decided`; do not re-prompt (`prior_action` ∈ `demoted-include`, `demoted-exclude`).
|
|
234
238
|
- `status: "unresolved"` → continue to step 4 (user prompt).
|
|
235
239
|
|
|
236
|
-
**Note:** the Out-of-Scope section is an optional audit-skill output
|
|
240
|
+
**Note:** the Out-of-Scope section is an optional audit-skill output and is often absent or empty — new-file detection is update-skill's job (§1b/§2.2), not audit-skill's — so the `no-report` / `no-candidates` paths above are the common case.
|
|
237
241
|
|
|
238
242
|
3. **Prompt for each unresolved candidate.** Present the same menu shape as §1b:
|
|
239
243
|
|
|
@@ -358,15 +362,14 @@ Translate the helper's output into the change manifest:
|
|
|
358
362
|
- `DELETED_FILE` rows → add to manifest as DELETED_FILE
|
|
359
363
|
- `UNCHANGED` rows → omit from the manifest (no action needed)
|
|
360
364
|
|
|
361
|
-
The helper
|
|
365
|
+
The compare helper reports only tracked files; NEW_FILE detection (a file present in source but absent from the provenance map) is a set-difference, so it runs through a script rather than the prompt. Pipe the same deterministic detector create-skill step 3 §4c uses (resolved via `detectScriptsAssetsProbeOrder`) into `{newFileDiffHelper}`, which subtracts the provenance map's `file_entries[].source_file` and sets aside user-authored `[MANUAL]` paths:
|
|
362
366
|
|
|
363
367
|
```bash
|
|
364
|
-
uv run {detectScriptsAssetsHelper} detect <source-root>
|
|
368
|
+
uv run {detectScriptsAssetsHelper} detect <source-root> \
|
|
369
|
+
| uv run {newFileDiffHelper} {forge_version}/provenance-map.json
|
|
365
370
|
```
|
|
366
371
|
|
|
367
|
-
It emits `
|
|
368
|
-
|
|
369
|
-
Files in `scripts/[MANUAL]/` or `assets/[MANUAL]/` → SKIP (user-authored, preserved).
|
|
372
|
+
It emits `{"new_files":[{source_file, kind}], "skipped_manual":[...], "already_tracked":[...], "stats":{...}}`. Add each `new_files[]` entry to the manifest as NEW_FILE — `kind` (`script`/`asset`) selects the target array. `skipped_manual[]` are user-authored files under `scripts/[MANUAL]/` or `assets/[MANUAL]/`, preserved and not touched; `already_tracked[]` were handled by the compare above.
|
|
370
373
|
|
|
371
374
|
Aggregate all subprocess results into a unified change manifest.
|
|
372
375
|
|
|
@@ -429,8 +432,6 @@ The upstream surface appears to have been substantially replaced. The brief's
|
|
|
429
432
|
- **[B] Brief:** halt with status `halted-for-brief-refinement`. Display: `"Halting update-skill. Re-run skf-brief-skill to refine scope for {skill_name}, then re-run skf-update-skill."` Change manifest discarded — no partial writes.
|
|
430
433
|
- **[A] Audit:** halt with status `halted-for-audit`. Display: `"Halting update-skill. Run skf-audit-skill against {skill_name} to map the new surface — its drift report will feed §1c on the next update-skill run."` Change manifest discarded.
|
|
431
434
|
|
|
432
|
-
**Why both §1c and §2.2:** §1c is precise (per-path P/S/U) but requires upstream signal from audit-skill. §2.2 is coarse (single halt/continue) but self-contained — it fires even when the user runs update-skill directly without audit. Together they cover the major-version case across the two real workflows.
|
|
433
|
-
|
|
434
435
|
### 3. Build Change Manifest
|
|
435
436
|
|
|
436
437
|
Hand the assembled Category A/B/C/D JSON to the helper:
|
|
@@ -490,7 +491,7 @@ The skill `{skill_name}` is current — no update needed.
|
|
|
490
491
|
|
|
491
492
|
→ Skip steps 03-06, immediately load {noChangeReportFile} with "no changes" status.
|
|
492
493
|
|
|
493
|
-
### 5. Display Change Summary and
|
|
494
|
+
### 5. Display Change Summary and Route
|
|
494
495
|
|
|
495
496
|
"**Change Detection Complete:**
|
|
496
497
|
|
|
@@ -500,30 +501,11 @@ The skill `{skill_name}` is current — no update needed.
|
|
|
500
501
|
| Files added | {count} |
|
|
501
502
|
| Files deleted | {count} |
|
|
502
503
|
| Files moved/renamed | {count} |
|
|
503
|
-
| Exports affected | {total_export_changes} |
|
|
504
|
-
|
|
505
|
-
**Proceeding to re-extraction of {affected_file_count if normal mode, or gap_count if gap-driven mode} changes...**"
|
|
506
|
-
|
|
507
|
-
### 6. Present MENU OPTIONS
|
|
508
|
-
|
|
509
|
-
Display: "**Proceeding to re-extraction...**"
|
|
510
|
-
|
|
511
|
-
#### Menu Handling Logic:
|
|
512
|
-
|
|
513
|
-
- **If `detect_only_mode` is true:** display "**Detect-only mode — skipping re-extract/merge/validate/write.** Loading report..." and load `{noChangeReportFile}` (report.md). The report handles the detect-only envelope. Do NOT load `{nextStepFile}`.
|
|
514
|
-
- Else, after change manifest is built, immediately load, read entire file, then execute `{nextStepFile}`.
|
|
515
|
-
- **EXCEPTION:** If no changes detected (section 4), load `{noChangeReportFile}` instead.
|
|
516
|
-
|
|
517
|
-
#### EXECUTION RULES:
|
|
518
|
-
|
|
519
|
-
- This is an auto-proceed step with no user choices
|
|
520
|
-
- Proceed directly to next step after change detection completes (or to report when `detect_only_mode` is true)
|
|
521
|
-
|
|
522
|
-
## CRITICAL STEP COMPLETION NOTE
|
|
504
|
+
| Exports affected | {total_export_changes} |"
|
|
523
505
|
|
|
524
|
-
|
|
506
|
+
This step auto-proceeds — no user choices. Once the change manifest is fully built, load and fully read the next file, then execute it, per the branch that applies:
|
|
525
507
|
|
|
526
|
-
-
|
|
527
|
-
- No changes detected → load `{noChangeReportFile}` (report.md)
|
|
528
|
-
- Otherwise → load `{nextStepFile}` (re-extract.md) to begin re-extraction.
|
|
508
|
+
- **`detect_only_mode == true`** → display "**Detect-only mode — skipping re-extract/merge/validate/write.** Loading report..." and load `{noChangeReportFile}` (report.md), which emits status `detect-only`. Do not load `{nextStepFile}`.
|
|
509
|
+
- **No changes detected** (section 4) → load `{noChangeReportFile}` (report.md), which emits status `no-changes`.
|
|
510
|
+
- **Otherwise** → display "**Proceeding to re-extraction of {affected_file_count if normal mode, or gap_count if gap-driven mode} changes...**" and load `{nextStepFile}` (re-extract.md) to begin re-extraction.
|
|
529
511
|
|