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,167 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.10"
|
|
4
|
+
# ///
|
|
5
|
+
"""Deterministic NEW_FILE detection for skf-update-skill (detect-changes Category D).
|
|
6
|
+
|
|
7
|
+
`skf-hash-content compare` classifies files already tracked in the provenance
|
|
8
|
+
map (UNCHANGED / MODIFIED_FILE / DELETED_FILE) but by design cannot report a
|
|
9
|
+
file that is present in source yet absent from the provenance map — a NEW_FILE.
|
|
10
|
+
Deriving that set is a set-difference plus a filter with exactly one correct
|
|
11
|
+
answer per input: take every `source_file` in the scripts/assets inventory
|
|
12
|
+
(emitted by `skf-detect-scripts-assets detect`), subtract the paths already in
|
|
13
|
+
`file_entries[].source_file`, and set aside any user-authored `[MANUAL]` path.
|
|
14
|
+
Doing that subtraction in the prompt drifts across runs; doing it here is
|
|
15
|
+
byte-stable.
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
skf-detect-scripts-assets.py detect <source-root> \
|
|
19
|
+
| skf-new-file-diff.py <provenance-map-path>
|
|
20
|
+
|
|
21
|
+
Reads the detect JSON on stdin (needs `scripts_inventory[]` and
|
|
22
|
+
`assets_inventory[]`, each row carrying `source_file`). Reads the provenance
|
|
23
|
+
map at the path argument (canonical `{file_entries: [...]}` object, or a bare
|
|
24
|
+
array of entries).
|
|
25
|
+
|
|
26
|
+
Output JSON (stdout):
|
|
27
|
+
{
|
|
28
|
+
"new_files": [ {"source_file": "...", "kind": "script|asset"}, ... ],
|
|
29
|
+
"skipped_manual": [ "...", ... ], # under scripts/[MANUAL]/ or assets/[MANUAL]/
|
|
30
|
+
"already_tracked": [ "...", ... ], # present in file_entries
|
|
31
|
+
"stats": {"inventory_total": N, "new": N,
|
|
32
|
+
"skipped_manual": N, "already_tracked": N}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
All three arrays are sorted by source_file; new_files carries the kind of the
|
|
36
|
+
inventory it came from (scripts -> "script", assets -> "asset"). A path found
|
|
37
|
+
in both inventories is counted once, resolved as "script".
|
|
38
|
+
|
|
39
|
+
Exit codes:
|
|
40
|
+
0 success
|
|
41
|
+
2 bad input (unreadable/invalid stdin JSON, missing/invalid provenance map)
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
import json
|
|
45
|
+
import re
|
|
46
|
+
import sys
|
|
47
|
+
from pathlib import Path
|
|
48
|
+
|
|
49
|
+
# scripts/[MANUAL]/... or assets/[MANUAL]/... anywhere in the (posix) path
|
|
50
|
+
_MANUAL_RE = re.compile(r"(?:^|/)(?:scripts|assets)/\[MANUAL\]/")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _fail(msg: str) -> "NoReturn": # type: ignore[valid-type]
|
|
54
|
+
print(json.dumps({"error": msg}), file=sys.stderr)
|
|
55
|
+
sys.exit(2)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _posix(p: str) -> str:
|
|
59
|
+
return p.replace("\\", "/")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def load_tracked_source_files(provenance_path: Path) -> set[str]:
|
|
63
|
+
"""Return the set of file_entries[].source_file already in the provenance map."""
|
|
64
|
+
try:
|
|
65
|
+
data = json.loads(provenance_path.read_text(encoding="utf-8"))
|
|
66
|
+
except (json.JSONDecodeError, OSError) as exc:
|
|
67
|
+
_fail(f"failed to read provenance map {provenance_path}: {exc}")
|
|
68
|
+
|
|
69
|
+
if isinstance(data, list):
|
|
70
|
+
entries = data
|
|
71
|
+
elif isinstance(data, dict):
|
|
72
|
+
entries = data.get("file_entries")
|
|
73
|
+
if entries is None:
|
|
74
|
+
_fail(f"provenance map {provenance_path} has no `file_entries` field")
|
|
75
|
+
if not isinstance(entries, list):
|
|
76
|
+
_fail(f"`file_entries` in {provenance_path} is not an array")
|
|
77
|
+
else:
|
|
78
|
+
_fail(
|
|
79
|
+
f"provenance map {provenance_path} must be an object or array; "
|
|
80
|
+
f"got {type(data).__name__}"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
tracked: set[str] = set()
|
|
84
|
+
for entry in entries:
|
|
85
|
+
if isinstance(entry, dict):
|
|
86
|
+
sf = entry.get("source_file")
|
|
87
|
+
if isinstance(sf, str) and sf:
|
|
88
|
+
tracked.add(_posix(sf))
|
|
89
|
+
return tracked
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def collect_inventory(detect: dict) -> list[tuple[str, str]]:
|
|
93
|
+
"""Return [(source_file, kind), ...] from the detect JSON, de-duplicated.
|
|
94
|
+
|
|
95
|
+
Scripts win over assets when a path appears in both, so kind is stable.
|
|
96
|
+
"""
|
|
97
|
+
seen: dict[str, str] = {}
|
|
98
|
+
for key, kind in (("scripts_inventory", "script"), ("assets_inventory", "asset")):
|
|
99
|
+
rows = detect.get(key)
|
|
100
|
+
if rows is None:
|
|
101
|
+
continue
|
|
102
|
+
if not isinstance(rows, list):
|
|
103
|
+
_fail(f"`{key}` in detect JSON is not an array")
|
|
104
|
+
for row in rows:
|
|
105
|
+
if not isinstance(row, dict):
|
|
106
|
+
_fail(f"`{key}` entry is not an object: {row!r}")
|
|
107
|
+
sf = row.get("source_file")
|
|
108
|
+
if not isinstance(sf, str) or not sf:
|
|
109
|
+
_fail(f"`{key}` entry missing string `source_file`: {row!r}")
|
|
110
|
+
sf = _posix(sf)
|
|
111
|
+
seen.setdefault(sf, kind) # first inventory wins the kind
|
|
112
|
+
return sorted(seen.items())
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def diff(detect: dict, tracked: set[str]) -> dict:
|
|
116
|
+
new_files: list[dict] = []
|
|
117
|
+
skipped_manual: list[str] = []
|
|
118
|
+
already_tracked: list[str] = []
|
|
119
|
+
|
|
120
|
+
inventory = collect_inventory(detect)
|
|
121
|
+
for sf, kind in inventory:
|
|
122
|
+
if sf in tracked:
|
|
123
|
+
already_tracked.append(sf)
|
|
124
|
+
elif _MANUAL_RE.search(sf):
|
|
125
|
+
skipped_manual.append(sf)
|
|
126
|
+
else:
|
|
127
|
+
new_files.append({"source_file": sf, "kind": kind})
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
"new_files": new_files,
|
|
131
|
+
"skipped_manual": sorted(skipped_manual),
|
|
132
|
+
"already_tracked": sorted(already_tracked),
|
|
133
|
+
"stats": {
|
|
134
|
+
"inventory_total": len(inventory),
|
|
135
|
+
"new": len(new_files),
|
|
136
|
+
"skipped_manual": len(skipped_manual),
|
|
137
|
+
"already_tracked": len(already_tracked),
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def main(argv: list[str]) -> int:
|
|
143
|
+
if len(argv) != 2:
|
|
144
|
+
_fail("usage: skf-new-file-diff.py <provenance-map-path> (detect JSON on stdin)")
|
|
145
|
+
|
|
146
|
+
provenance_path = Path(argv[1])
|
|
147
|
+
if not provenance_path.exists():
|
|
148
|
+
_fail(f"provenance map not found: {provenance_path}")
|
|
149
|
+
|
|
150
|
+
raw = sys.stdin.read()
|
|
151
|
+
if not raw.strip():
|
|
152
|
+
_fail("no detect JSON on stdin")
|
|
153
|
+
try:
|
|
154
|
+
detect = json.loads(raw)
|
|
155
|
+
except json.JSONDecodeError as exc:
|
|
156
|
+
_fail(f"invalid detect JSON on stdin: {exc}")
|
|
157
|
+
if not isinstance(detect, dict):
|
|
158
|
+
_fail(f"detect JSON must be an object; got {type(detect).__name__}")
|
|
159
|
+
|
|
160
|
+
tracked = load_tracked_source_files(provenance_path)
|
|
161
|
+
result = diff(detect, tracked)
|
|
162
|
+
print(json.dumps(result, indent=2))
|
|
163
|
+
return 0
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
if __name__ == "__main__":
|
|
167
|
+
sys.exit(main(sys.argv))
|
|
@@ -7,9 +7,9 @@ description: Pre-code stack feasibility verification against architecture and PR
|
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
-
Cross-references generated skills against architecture and PRD documents to produce a feasibility report with evidence-backed integration verdicts, coverage analysis, and requirements mapping.
|
|
10
|
+
Cross-references generated skills against architecture and PRD documents to produce a feasibility report with evidence-backed integration verdicts, coverage analysis, and requirements mapping. Read-only: it reads skills and input documents and writes only the feasibility report (see Workflow Rules).
|
|
11
11
|
|
|
12
|
-
**Schema contract:** This skill is the
|
|
12
|
+
**Schema contract:** This skill is the producer of the SKF shared feasibility report schema — every report conforms to it.
|
|
13
13
|
|
|
14
14
|
## Conventions
|
|
15
15
|
|
|
@@ -53,34 +53,20 @@ These rules apply to every step in this workflow:
|
|
|
53
53
|
|--------|--------|
|
|
54
54
|
| **Inputs** | architecture_doc_path [required], prd_path [optional], previous_report_path [optional] |
|
|
55
55
|
| **Flags** | `--headless` / `-H` (auto-resolve all gates); `--architecture-doc <path>` (skip step 1 prompt for the required input); `--prd <path>` (skip step 1 prompt for the optional PRD); `--previous-report <path>` (skip step 1 prompt for delta comparison) |
|
|
56
|
-
| **Gates** | step 1
|
|
56
|
+
| **Gates** | step 1 Input Gate (use args); step 6 Report Menu ([R] review / [X] exit, headless default X). Steps 2-3 also hold elective vacuous-analysis guards (0% coverage, all-Blocked) that only fire in degenerate cases; every guard auto-resolves to Continue in headless. |
|
|
57
57
|
| **Outputs** | `feasibility-report-{projectSlug}-{timestamp}.md` and `feasibility-report-{projectSlug}-latest.md` (copy, not symlink) per the SKF shared feasibility report schema (`_bmad/skf/shared/references/feasibility-report-schema.md`; `src/shared/references/…` in a dev checkout) — with integration verdicts, coverage analysis, recommendations, and evidence sources; plus `verify-stack-result-{timestamp}.json` and `verify-stack-result-latest.json` |
|
|
58
58
|
| **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true. Per-flag args (`--architecture-doc`, `--prd`, `--previous-report`) consumed at the gates that would otherwise prompt. |
|
|
59
|
-
| **Exit codes** | See
|
|
60
|
-
|
|
61
|
-
## Exit Codes
|
|
62
|
-
|
|
63
|
-
Every HARD HALT in this workflow exits with a stable code so headless automators can branch on the failure class without grepping message text:
|
|
64
|
-
|
|
65
|
-
| Code | Meaning | Raised by |
|
|
66
|
-
| ---- | -------------------- | -------------------------------------------------------------------------------------------- |
|
|
67
|
-
| 0 | success | step 7 (terminal) |
|
|
68
|
-
| 2 | input-missing / input-invalid | step 1 §1 (headless missing `architecture-doc` arg, or invalid path) → `input-missing`; non-existent file → `input-invalid` |
|
|
69
|
-
| 3 | resolution-failure | step 1 §2 (`{skills_output_folder}` does not exist or is empty); step 1 §3 (forge_data_folder unconfigured) |
|
|
70
|
-
| 4 | write-failure | On-Activation §3 pre-flight write probe; step 1 §4 (atomic write of report skeleton failed); step 6 §4b (result-contract write failed) |
|
|
71
|
-
| 5 | state-conflict | step 1 §3 (fewer than 2 valid skills found — stack requires ≥2); step 1 §1 (`previousReport` resolves to same inode as `{outputFile}`); step 6 §1 (report section order or schemaVersion mismatch — schema-violation) |
|
|
72
|
-
| 6 | user-cancelled | step 1 §1 prompt cancelled; any prompt that accepted `cancel`/`exit`/`:q`; step 6 menu cancelled |
|
|
73
|
-
| 7 | inventory-unreliable | step 1 §2 (>20% subagent failures or enumerate-stack-skills warnings exceed budget) |
|
|
59
|
+
| **Exit codes** | See `references/exit-codes.md` |
|
|
74
60
|
|
|
75
61
|
## Result Contract (Headless)
|
|
76
62
|
|
|
77
|
-
When `{headless_mode}` is true, step 6 emits a single-line JSON envelope on **stdout** before chaining to step 7, and every
|
|
63
|
+
When `{headless_mode}` is true, step 6 emits a single-line JSON envelope on **stdout** before chaining to step 7, and every headless hard halt emits the same envelope shape on **stderr** with `status: "error"`:
|
|
78
64
|
|
|
79
65
|
```
|
|
80
66
|
SKF_VERIFY_STACK_RESULT_JSON: {"status":"success|error","report_path":"…|null","report_latest_path":"…|null","overall_verdict":"…|null","coverage_percentage":0,"recommendation_count":0,"exit_code":0,"halt_reason":null}
|
|
81
67
|
```
|
|
82
68
|
|
|
83
|
-
`status` is `"success"` on the terminal happy path, `"error"` on any
|
|
69
|
+
`status` is `"success"` on the terminal happy path, `"error"` on any halt. `halt_reason` is one of: `null` (success), `"input-missing"`, `"input-invalid"`, `"skills-folder-missing"`, `"insufficient-skills"`, `"forge-folder-unconfigured"`, `"resolution-failure"`, `"previous-report-collision"`, `"inventory-unreliable"`, `"schema-violation"`, `"write-failed"`, `"user-cancelled"`. `exit_code` matches `references/exit-codes.md` (the `analysis-halted` / exit-8 gates are interactive-only, so they never reach this envelope). `overall_verdict` uses the schema tokens (`FEASIBLE`/`CONDITIONALLY_FEASIBLE`/`NOT_FEASIBLE`).
|
|
84
70
|
|
|
85
71
|
## On Activation
|
|
86
72
|
|
|
@@ -91,7 +77,7 @@ SKF_VERIFY_STACK_RESULT_JSON: {"status":"success|error","report_path":"…|null"
|
|
|
91
77
|
2. **Compute run-scoped variables** (same place as config so every stage can reference them without re-derivation):
|
|
92
78
|
- `project_slug` ← slugify `project_name` (lowercase, hyphens only, no unicode, no whitespace)
|
|
93
79
|
- `timestamp` ← UTC `YYYYMMDD-HHmmss` captured at activation time
|
|
94
|
-
- These two combine in init.md §4 into `{outputFile}` per the stage frontmatter template, but the values themselves are fixed for the entire workflow run — every later reference to `{outputFile}` resolves consistently.
|
|
80
|
+
- These two combine in init.md §4 into `{outputFile}` per the stage frontmatter template, but the values themselves are fixed for the entire workflow run — every later reference to `{outputFile}` resolves consistently.
|
|
95
81
|
|
|
96
82
|
3. **Resolve `{headless_mode}`**: true if `--headless` or `-H` was passed as an argument, or if `headless_mode: true` in `{sidecar_path}/preferences.yaml`. Default: false.
|
|
97
83
|
|
|
@@ -119,6 +105,10 @@ SKF_VERIFY_STACK_RESULT_JSON: {"status":"success|error","report_path":"…|null"
|
|
|
119
105
|
|
|
120
106
|
Stash all four as workflow-context variables. Stage files reference them directly — no conditional at the usage site. Empty-string overrides cleanly fall through to the bundled default.
|
|
121
107
|
|
|
108
|
+
The same merge resolves `workflow.on_complete` (default empty = no-op); report.md §5 executes it, if non-empty, at the terminal stage.
|
|
109
|
+
|
|
110
|
+
Also apply the array surfaces so they are not silent no-ops: 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 (`file:`-prefixed entries load their file/glob contents as facts — the bundled default glob is `{project-root}/**/project-context.md`); then execute each entry in `workflow.activation_steps_append` after activation completes.
|
|
111
|
+
|
|
122
112
|
5. **Pre-flight write probe.** Verify `{outputFolderPath}` is writable. A read-only mount, full disk, or permissions-denied path otherwise only surfaces at init.md §4 atomic write — by then the user has already gone through the input prompts:
|
|
123
113
|
|
|
124
114
|
```bash
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# DO NOT EDIT -- overwritten on every update.
|
|
2
2
|
#
|
|
3
3
|
# Workflow customization surface for skf-verify-stack.
|
|
4
|
+
# Team overrides: _bmad/custom/skf-verify-stack.toml under {project-root}
|
|
5
|
+
# Personal overrides: _bmad/custom/skf-verify-stack.user.toml under {project-root}
|
|
4
6
|
|
|
5
7
|
[workflow]
|
|
6
8
|
|
|
@@ -34,6 +36,12 @@ persistent_facts = [
|
|
|
34
36
|
"file:{project-root}/**/project-context.md",
|
|
35
37
|
]
|
|
36
38
|
|
|
39
|
+
# Instruction executed when the workflow reaches its terminal stage (after the
|
|
40
|
+
# feasibility report + result JSON are written). Empty = no-op. Use to route
|
|
41
|
+
# the verdict onward or trigger a downstream step without forking the skill.
|
|
42
|
+
|
|
43
|
+
on_complete = ""
|
|
44
|
+
|
|
37
45
|
# --- Optional asset overrides ---
|
|
38
46
|
#
|
|
39
47
|
# Lift the canonical asset paths so orgs can substitute house-style copies
|
|
@@ -43,8 +51,9 @@ report_template_path = ""
|
|
|
43
51
|
integration_rules_path = ""
|
|
44
52
|
coverage_patterns_path = ""
|
|
45
53
|
|
|
46
|
-
# Override the destination directory for the feasibility report. Empty =
|
|
47
|
-
# use {forge_data_folder} from config.yaml.
|
|
48
|
-
#
|
|
54
|
+
# Override the destination directory for the feasibility report only. Empty =
|
|
55
|
+
# use {forge_data_folder} from config.yaml. The result-contract JSON
|
|
56
|
+
# (verify-stack-result-*.json) always stays in {forge_data_folder} as a stable
|
|
57
|
+
# path for pipeline consumers, so redirecting this does not relocate it.
|
|
49
58
|
|
|
50
59
|
output_folder_path = ""
|
|
@@ -22,32 +22,4 @@ Parse document section headers for technology groupings:
|
|
|
22
22
|
- `## Backend Core` → technologies in backend layer
|
|
23
23
|
- `## AI Layer` → AI-related technologies
|
|
24
24
|
|
|
25
|
-
**Mermaid Diagram Handling:** Do
|
|
26
|
-
|
|
27
|
-
### Coverage Verdict
|
|
28
|
-
|
|
29
|
-
The coverage matrix renders two verdict tokens per referenced technology: **Covered** and **Missing**. Extra skills (skills present in the inventory but NOT referenced by the architecture document) are tracked in a separate informational subdivision — they are not coverage verdicts and do not appear in the primary `Technology → Skill Match → Verdict` column. See step 2 §4 for the Extra-skill subdivision output.
|
|
30
|
-
|
|
31
|
-
| Verdict | Meaning |
|
|
32
|
-
|-------------|------------------------------------------------------------------|
|
|
33
|
-
| **Covered** | A generated skill exists in `skills/` for this technology |
|
|
34
|
-
| **Missing** | Technology is referenced in architecture doc but no skill exists |
|
|
35
|
-
|
|
36
|
-
**Informational — Extra skills subdivision (not a verdict; rendered in a separate section per step 2 §4):**
|
|
37
|
-
|
|
38
|
-
| Label | Meaning |
|
|
39
|
-
|----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------|
|
|
40
|
-
| **Extra (unreferenced)** | Skill exists with a resolvable `source_repo` / `source_root`, but no architecture tech token matches it |
|
|
41
|
-
| **Orphan (source_repo unresolvable)** | Skill's `source_repo` is empty, malformed, or its basename cannot be extracted — cross-reference against architecture impossible |
|
|
42
|
-
|
|
43
|
-
## Output Format
|
|
44
|
-
|
|
45
|
-
When presenting coverage results, use the following structure:
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
| Technology | Source Section | Skill Match | Verdict |
|
|
49
|
-
|------------|---------------|-------------|---------|
|
|
50
|
-
| {tech_name} | {section_heading} | {skill_name or '—'} | Covered / Missing |
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Include a summary line: `Coverage: {covered_count}/{total_count} ({percentage}%)`
|
|
25
|
+
**Mermaid Diagram Handling:** Do not parse Mermaid diagram syntax (`graph`, `flowchart`, `sequenceDiagram`, etc.) for technology detection — use only prose text (headings, paragraphs, lists, tables). If the architecture document appears to list technologies exclusively inside Mermaid diagrams, note this in the coverage results as a detection limitation and recommend the user add prose-based technology listings.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
nextStepFile: 'integrations.md'
|
|
3
3
|
coveragePatternsData: '{coveragePatternsPath}'
|
|
4
|
+
coverageTallyScript: 'scripts/skf-coverage-tally.py'
|
|
4
5
|
feasibilitySchemaProbeOrder:
|
|
5
6
|
- '{project-root}/_bmad/skf/shared/references/feasibility-report-schema.md'
|
|
6
7
|
- '{project-root}/src/shared/references/feasibility-report-schema.md'
|
|
@@ -66,7 +67,7 @@ For each referenced technology in the list:
|
|
|
66
67
|
4. Compare the resulting basenames/segments against the tech tokens via case-insensitive equality (no substring/fuzzy matching)
|
|
67
68
|
5. A match on either `source_repo` basename or `source_root` last segment counts as a hit
|
|
68
69
|
|
|
69
|
-
**Detect a deliberate-removal signal (from the architecture document):** Before assigning Covered/Missing, check whether the referenced technology is explicitly marked for removal or replacement in the architecture document itself. Be conservative — recognize a removal signal
|
|
70
|
+
**Detect a deliberate-removal signal (from the architecture document):** Before assigning Covered/Missing, check whether the referenced technology is explicitly marked for removal or replacement in the architecture document itself. Be conservative — recognize a removal signal only when one of these is present, and when in doubt leave it as a normal reference (a false removal signal silently drops a real coverage gap):
|
|
70
71
|
- The technology is listed under a section whose heading matches (case-insensitive) one of: "deprecated", "removed", "legacy", "migrating away", "being replaced", "to be removed", "sunset", "retiring".
|
|
71
72
|
- The technology's own mention carries an inline removal annotation, e.g. "(deprecated)", "(being replaced by …)", "(removing)", "(to be removed)", "(legacy)".
|
|
72
73
|
|
|
@@ -77,13 +78,19 @@ Record the cited section heading or annotation text as evidence for every techno
|
|
|
77
78
|
- **Replaced** — no matching skill exists AND a deliberate-removal signal (above) was found; the technology is intentionally being removed/replaced, so no skill should exist for it
|
|
78
79
|
- **Missing** — no matching skill found and no removal signal
|
|
79
80
|
|
|
80
|
-
**Replaced** technologies are EXCLUDED from the coverage denominator — they are not a gap to close. The denominator (`live_count`) is the count of referenced technologies that are **Covered** or **Missing**; **Replaced** technologies do not count toward it.
|
|
81
|
-
|
|
82
81
|
Build the coverage matrix as a structured table.
|
|
83
82
|
|
|
83
|
+
**Tally the matrix deterministically.** Assigning each verdict is judgment; counting the classes and computing the percentage has one correct answer, so delegate it. Serialize the matrix as `{"rows": [{"technology": "…", "verdict": "Covered|Missing|Replaced"}, …]}` and run:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
echo '<rows JSON>' | uv run {coverageTallyScript} --stdin
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The script (run `uv run {coverageTallyScript} --help` for the contract) returns `covered_count`, `missing_count`, `replaced_count`, `live_count` (the denominator — Covered + Missing, with Replaced excluded because a technology being removed is not a gap to close), `total_referenced`, and `coverage_percentage` (Replaced excluded from the denominator, half-up rounding pinned so the same matrix always yields the same integer). Consume these values in §5 and §6 rather than recomputing them. If `uv` is unavailable (e.g. claude.ai web), compute the same values inline per the `--help` contract: `live_count = covered + missing`; `coverage_percentage = round-half-up(covered / live_count * 100)`, or `0` when `live_count` is `0`.
|
|
90
|
+
|
|
84
91
|
### 4. Detect Extra Skills
|
|
85
92
|
|
|
86
|
-
Check if any skills in the inventory are
|
|
93
|
+
Check if any skills in the inventory are not referenced in the architecture document.
|
|
87
94
|
|
|
88
95
|
**Subdivide into two categories (both informational — not errors):**
|
|
89
96
|
- **Extra (unreferenced)** — The skill's `source_repo` / `source_root` resolves cleanly (both non-empty and well-formed), but no architecture document tech token matches it.
|
|
@@ -103,7 +110,7 @@ Extra and Orphan skills are informational only. They do not affect the coverage
|
|
|
103
110
|
|------------|---------------|-------------|---------|
|
|
104
111
|
| {tech_name} | {section_heading} | {skill_name or '—'} | {Covered / Missing / Replaced} |
|
|
105
112
|
|
|
106
|
-
**Coverage: {covered_count}/{live_count} ({
|
|
113
|
+
**Coverage: {covered_count}/{live_count} ({coverage_percentage}%)** (from the §3 tally; `live_count` excludes **Replaced** technologies)
|
|
107
114
|
|
|
108
115
|
{IF 100% coverage AND no Extra skills:}
|
|
109
116
|
**All referenced technologies have a matching skill. No extra skills detected.**
|
|
@@ -125,7 +132,7 @@ Extra and Orphan skills are informational only. They do not affect the coverage
|
|
|
125
132
|
|
|
126
133
|
### 6. Append to Report
|
|
127
134
|
|
|
128
|
-
**Resolve `{atomicWriteHelper}`** from `{atomicWriteProbeOrder}`; first existing path wins.
|
|
135
|
+
**Resolve `{atomicWriteHelper}`** from `{atomicWriteProbeOrder}`; first existing path wins. If no candidate exists: HALT (exit code 3, `halt_reason: "resolution-failure"`); in headless, emit the error envelope.
|
|
129
136
|
|
|
130
137
|
**Resolve `{feasibilitySchemaRef}`** from `{feasibilitySchemaProbeOrder}`; first existing path wins (installed SKF module path first, dev-checkout `src/` fallback).
|
|
131
138
|
|
|
@@ -133,9 +140,9 @@ Write the **Coverage Analysis** section to `{outputFile}` (see `{feasibilitySche
|
|
|
133
140
|
- Include the full coverage table
|
|
134
141
|
- Include coverage percentage
|
|
135
142
|
- Include missing skill recommendations
|
|
136
|
-
- Include the Replaced (being removed/replaced) subdivision from section 3, with the cited removal evidence — these are
|
|
143
|
+
- Include the Replaced (being removed/replaced) subdivision from section 3, with the cited removal evidence — these are not gaps and carry no [CS]/[QS] recommendation
|
|
137
144
|
- Include the Extra (unreferenced) and Orphan (source_repo unresolvable) subdivisions from section 4
|
|
138
|
-
- Update frontmatter: append `'coverage'` to `stepsCompleted`; set `coveragePercentage`
|
|
145
|
+
- Update frontmatter: append `'coverage'` to `stepsCompleted`; set `coveragePercentage` to the `coverage_percentage` value from the §3 tally (integer 0..100)
|
|
139
146
|
- Pipe the updated full content through `python3 {atomicWriteHelper} write --target {outputFile}` and again with `--target {outputFileLatest}`
|
|
140
147
|
|
|
141
148
|
### 7. Auto-Proceed to Next Step
|
|
@@ -147,7 +154,9 @@ Write the **Coverage Analysis** section to `{outputFile}` (see `{feasibilitySche
|
|
|
147
154
|
|
|
148
155
|
**Select:** [X] Halt workflow (recommended) | [C] Continue anyway"
|
|
149
156
|
|
|
150
|
-
-
|
|
157
|
+
**GATE [default: C]** — Interactive-only guard. If `{headless_mode}`: auto-proceed with [C] Continue, log: "headless: continuing past all-Replaced coverage gate (nothing live to verify)". Headless never takes [X], so step 6 still emits the result contract — the run resolves to `NOT_FEASIBLE` via the synthesize zero-coverage short-circuit.
|
|
158
|
+
|
|
159
|
+
- IF X: "**Workflow halted.** Coverage Analysis saved to `{outputFile}`. Update the architecture document and re-run [VS] when ready." HALT (exit code 8, `halt_reason: "analysis-halted"`).
|
|
151
160
|
- IF C: "**Continuing — the analysis covers only technologies marked for removal and will be limited.**" Load, read the full file and then execute `{nextStepFile}`.
|
|
152
161
|
|
|
153
162
|
{IF coveragePercentage is 0% AND live_count > 0:}
|
|
@@ -157,7 +166,9 @@ Write the **Coverage Analysis** section to `{outputFile}` (see `{feasibilitySche
|
|
|
157
166
|
|
|
158
167
|
**Select:** [X] Halt workflow (recommended) | [C] Continue anyway"
|
|
159
168
|
|
|
160
|
-
|
|
169
|
+
**GATE [default: C]** — Interactive-only guard. If `{headless_mode}`: auto-proceed with [C] Continue, log: "headless: continuing past 0%-coverage gate". Headless never takes [X], so step 6 still emits the result contract — the run resolves to `NOT_FEASIBLE` via the synthesize zero-coverage short-circuit.
|
|
170
|
+
|
|
171
|
+
- IF X: "**Workflow halted.** Coverage Analysis saved to `{outputFile}`. Generate skills and re-run [VS] when ready." HALT (exit code 8, `halt_reason: "analysis-halted"`).
|
|
161
172
|
- IF C: "**Continuing with 0% coverage — results will be limited.**"
|
|
162
173
|
|
|
163
174
|
Load, read the full file and then execute `{nextStepFile}`.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Exit Codes
|
|
2
|
+
|
|
3
|
+
Every hard halt in this workflow exits with a stable code so headless automators can branch on the failure class without grepping message text. Each code pairs with a `halt_reason` string carried in the headless result envelope.
|
|
4
|
+
|
|
5
|
+
| Code | Meaning | Raised by |
|
|
6
|
+
| ---- | -------------------- | -------------------------------------------------------------------------------------------- |
|
|
7
|
+
| 0 | success | step 7 (terminal) |
|
|
8
|
+
| 2 | input-missing / input-invalid | step 1 §1 (headless missing `architecture-doc` arg, or invalid path) → `input-missing`; non-existent file → `input-invalid` |
|
|
9
|
+
| 3 | resolution-failure | step 1 §2 (`{skills_output_folder}` does not exist or is empty → `skills-folder-missing`); step 1 §3 (forge_data_folder unconfigured → `forge-folder-unconfigured`); any stage that cannot resolve a required shared helper (atomic-write, schema ref, validate-feasibility-report) from its probe order → `resolution-failure` |
|
|
10
|
+
| 4 | write-failure | On-Activation §5 pre-flight write probe; step 1 §4 (atomic write of report skeleton failed); step 6 §4b (result-contract write failed) |
|
|
11
|
+
| 5 | state-conflict | step 1 §3 (fewer than 2 valid skills found — stack requires ≥2 → `insufficient-skills`); step 1 §1 (`previousReport` resolves to same inode as `{outputFile}` → `previous-report-collision`); step 6 §1 (report section order or schemaVersion mismatch → `schema-violation`) |
|
|
12
|
+
| 6 | user-cancelled | step 1 §1 prompt cancelled; any prompt that accepted `cancel`/`exit`/`:q`; step 6 menu cancelled |
|
|
13
|
+
| 7 | inventory-unreliable | step 1 §2 (>20% subagent failures or enumerate-stack-skills warnings exceed budget); step 3 §3 (>20% API-surface subagents return malformed JSON) |
|
|
14
|
+
| 8 | analysis-halted | coverage.md §7 & integrations.md §7 — user picks [X] at an elective vacuous-analysis gate. These gates are interactive-only; in headless they auto-continue, so exit 8 never fires headlessly and `analysis-halted` never appears in the result envelope. |
|
|
@@ -24,4 +24,4 @@ Chain to the shared workflow self-improvement health check at `{nextStepFile}`.
|
|
|
24
24
|
Attempt to load `{nextStepFile}`.
|
|
25
25
|
|
|
26
26
|
- **If `{nextStepFile}` loads successfully:** Read it fully, then execute it.
|
|
27
|
-
- **If `{nextStepFile}` cannot be resolved or loaded** (e.g., running against a partial installation, module root not resolvable, or the file has been removed): log exactly `health-check unavailable at {path}` (substitute the attempted resolved path) to the user-visible output and exit the workflow cleanly.
|
|
27
|
+
- **If `{nextStepFile}` cannot be resolved or loaded** (e.g., running against a partial installation, module root not resolvable, or the file has been removed): log exactly `health-check unavailable at {path}` (substitute the attempted resolved path) to the user-visible output and exit the workflow cleanly. This is not an error halt — the health check is an optional self-improvement hook, and the feasibility report (written in step 6) is the authoritative workflow output. Exiting cleanly keeps CI and headless runs from failing on a missing optional hook.
|
|
@@ -6,20 +6,15 @@ feasibilitySchemaProbeOrder:
|
|
|
6
6
|
atomicWriteProbeOrder:
|
|
7
7
|
- '{project-root}/_bmad/skf/shared/scripts/skf-atomic-write.py'
|
|
8
8
|
- '{project-root}/src/shared/scripts/skf-atomic-write.py'
|
|
9
|
-
# {outputFile} and {outputFileLatest} resolve from the activation-stored
|
|
10
|
-
# {project_slug}, {timestamp}, and {outputFolderPath} variables (set in
|
|
11
|
-
# SKILL.md On Activation §2 + §4). The activation-stored values are
|
|
12
|
-
# fixed for the entire run, so every later reference sees the same
|
|
13
|
-
# filename — no order-of-operations bug.
|
|
14
9
|
outputFile: '{outputFolderPath}/feasibility-report-{project_slug}-{timestamp}.md'
|
|
15
10
|
outputFileLatest: '{outputFolderPath}/feasibility-report-{project_slug}-latest.md'
|
|
16
11
|
# Resolve `{enumerateStackSkillsHelper}` by probing
|
|
17
12
|
# `{enumerateStackSkillsProbeOrder}` in order (installed SKF module path
|
|
18
13
|
# first, src/ dev-checkout fallback); first existing path wins. §2 calls
|
|
19
14
|
# it for the deterministic skills inventory (cascade-resolved exports,
|
|
20
|
-
# metadata-hash for change-detection, confidence-tier mapping).
|
|
21
|
-
#
|
|
22
|
-
#
|
|
15
|
+
# metadata-hash for change-detection, confidence-tier mapping). If neither
|
|
16
|
+
# candidate exists, §2 does NOT halt — it falls through to the LLM-driven
|
|
17
|
+
# subagent fan-out as graceful degradation (see §2).
|
|
23
18
|
enumerateStackSkillsProbeOrder:
|
|
24
19
|
- '{project-root}/_bmad/skf/shared/scripts/skf-enumerate-stack-skills.py'
|
|
25
20
|
- '{project-root}/src/shared/scripts/skf-enumerate-stack-skills.py'
|
|
@@ -67,10 +62,16 @@ Wait for user input. **GATE [default: use args]** — If `{headless_mode}` and `
|
|
|
67
62
|
|
|
68
63
|
**Validate previous report (if provided):**
|
|
69
64
|
- Confirm the file exists and is readable
|
|
70
|
-
- **Collision check:** Resolve `{outputFile}` from the activation-stored `{outputFolderPath}`, `{project_slug}`, and `{timestamp}
|
|
65
|
+
- **Collision check:** Resolve `{outputFile}` from the activation-stored `{outputFolderPath}`, `{project_slug}`, and `{timestamp}`. Then compare both the provided path and `{outputFile}` via `(st_dev, st_ino)` tuples obtained from `stat(2)` on each path (do not rely on absolute-path string equality — symlinks, bind mounts, and case-insensitive filesystems can defeat string comparison). If `{outputFile}` does not yet exist, resolve its parent via `realpath`, stat that directory, and combine `(st_dev, parent_ino, basename)` for comparison. If the two paths resolve to the same inode, warn: "The previous report path points to the same inode as the new report. This file will be overwritten during this run. Provide a path to a backup copy, or leave empty to skip delta comparison." HALT (exit code 5, `halt_reason: "previous-report-collision"`) until resolved. In headless, emit the error envelope.
|
|
71
66
|
- If missing → "Previous report not found at `{path}`. Proceeding without delta comparison."
|
|
72
67
|
- Store as `previousReport: {path}` (or empty string if not provided)
|
|
73
68
|
|
|
69
|
+
**Auto-discover a prior report (only when none was provided above):** If neither `--previous-report` nor an interactive previous-report path was given, glob `{outputFolderPath}` for `feasibility-report-{project_slug}-*.md`, excluding this run's `{timestamp}` file and `feasibility-report-{project_slug}-latest.md`. Every run writes a new timestamped report, so a usable prior usually already persists on disk with no manual backup. If one or more matches remain, pick the most recent by the embedded `YYYYMMDD-HHmmss` timestamp:
|
|
70
|
+
- Interactive: offer "Found a prior report from `{date}` — compare against it? **[Y]** use it / paste a different path / **[skip]**". On [Y], set `previousReport` to that path; on a pasted path, use it; on `skip`, leave `previousReport` empty.
|
|
71
|
+
- **GATE [default: Y]** — If `{headless_mode}`: auto-select the most recent match, set `previousReport` to it, log: "headless: delta comparison against `{path}`".
|
|
72
|
+
- Excluding this run's `{timestamp}` and `-latest` files guarantees the auto-selected path is a distinct inode from `{outputFile}`, so it can never trip the collision check above.
|
|
73
|
+
- If no match remains, leave `previousReport` empty — first run, no delta.
|
|
74
|
+
|
|
74
75
|
### 2. Scan Skills Folder
|
|
75
76
|
|
|
76
77
|
**Pre-flight — skills folder existence:**
|
|
@@ -82,18 +83,20 @@ Wait for user input. **GATE [default: use args]** — If `{headless_mode}` and `
|
|
|
82
83
|
**Primary path — deterministic enumeration via shared helper:**
|
|
83
84
|
|
|
84
85
|
```bash
|
|
85
|
-
python3 {enumerateStackSkillsHelper} enumerate {skills_output_folder}
|
|
86
|
+
python3 {enumerateStackSkillsHelper} enumerate {skills_output_folder} --reliability
|
|
86
87
|
```
|
|
87
88
|
|
|
88
89
|
The helper walks `{skills_output_folder}`, reads each `metadata.json`, applies the exports cascade (metadata → references/ → SKILL.md prose), maps `confidence_tier` (T1/T2/T1-low), captures stack-skill cycles via `composes:`, and emits structured JSON with one entry per skill plus a top-level `warnings[]` array. Cache the result as `skill_inventory` (used by §3, §4, §5, and the integrations + coverage stages).
|
|
89
90
|
|
|
90
91
|
Each helper-emitted entry includes: `skill_name`, `version`, `language`, `confidence_tier`, `exports` (cascade-resolved), `source_repo`, `source_root`, plus a `metadata_hash` for change-detection across runs. The helper's `warnings[]` carries per-skill skip reasons (missing manifest, malformed JSON, non-symlink `active`, orphan-versions, schema-version violations).
|
|
91
92
|
|
|
92
|
-
|
|
93
|
+
`--reliability` additionally attaches the inventory reliability verdict: `inventory_reliable` (bool), `unreliable_ratio` (float), `skill_count`, and `warning_count`.
|
|
94
|
+
|
|
95
|
+
**Failure-budget guard:** Read `inventory_reliable` from the helper JSON. If it is `false`, HALT (exit code 7, `halt_reason: "inventory-unreliable"`) with: "Inventory scan unreliable — {warning_count}/{skill_count + warning_count} skills returned malformed metadata or skip warnings. Re-run [VS] after skills stabilize." (substitute `warning_count` and `skill_count + warning_count` from the helper JSON). In headless, emit the error envelope. The helper's threshold is chosen so a single malformed skill in a small 3-5 skill inventory does not trip the halt.
|
|
93
96
|
|
|
94
97
|
**Capture mtime:** For each accepted skill in `skill_inventory`, also record `metadata.json`'s mtime via `stat` into the entry as `metadata_mtime`. Step-03 will re-verify this to detect mid-run modifications.
|
|
95
98
|
|
|
96
|
-
**Fallback path — graceful degradation when the helper is unavailable:** If `{enumerateStackSkillsHelper}` has no existing candidate (e.g. partial installation), fall through to the LLM-driven subagent fan-out: launch up to **8 subagents concurrently**, each reading one resolved skill package's `metadata.json` and returning the same JSON shape the helper would emit.
|
|
99
|
+
**Fallback path — graceful degradation when the helper is unavailable:** If `{enumerateStackSkillsHelper}` has no existing candidate (e.g. partial installation), fall through to the LLM-driven subagent fan-out: launch up to **8 subagents concurrently**, each reading one resolved skill package's `metadata.json` and returning the same JSON shape the helper would emit. In this branch `inventory_reliable` is unavailable, so compute the failure-budget guard inline: when `warning_count / (skill_count + warning_count) > 0.20`, HALT (exit code 7, `halt_reason: "inventory-unreliable"`) with the same message as the primary path. In headless, emit the error envelope.
|
|
97
100
|
|
|
98
101
|
### 3. Validate Minimum Requirements
|
|
99
102
|
|
|
@@ -107,23 +110,15 @@ Each helper-emitted entry includes: `skill_name`, `version`, `language`, `confid
|
|
|
107
110
|
- If undefined or empty → "**Cannot proceed.** `forge_data_folder` is not configured in config.yaml. Re-run [SF] Setup Forge to initialize."
|
|
108
111
|
- HALT (exit code 3, `halt_reason: "forge-folder-unconfigured"`). In headless, emit the error envelope.
|
|
109
112
|
|
|
110
|
-
**Check architecture document:**
|
|
111
|
-
- Confirm it was loaded successfully in section 1
|
|
112
|
-
- If not → HALT with error (should not reach here if section 1 validation passed)
|
|
113
|
-
|
|
114
113
|
### 4. Create Feasibility Report
|
|
115
114
|
|
|
116
|
-
**Resolve `{atomicWriteHelper}`** from `{atomicWriteProbeOrder}`; first existing path wins.
|
|
115
|
+
**Resolve `{atomicWriteHelper}`** from `{atomicWriteProbeOrder}`; first existing path wins. If no candidate exists: HALT (exit code 3, `halt_reason: "resolution-failure"`); in headless, emit the error envelope.
|
|
117
116
|
|
|
118
|
-
**Resolve `{feasibilitySchemaRef}`** from `{feasibilitySchemaProbeOrder}`; first existing path wins (installed SKF module path first, dev-checkout `src/` fallback).
|
|
117
|
+
**Resolve `{feasibilitySchemaRef}`** from `{feasibilitySchemaProbeOrder}`; first existing path wins (installed SKF module path first, dev-checkout `src/` fallback). If no candidate exists: HALT (exit code 3, `halt_reason: "resolution-failure"`); in headless, emit the error envelope.
|
|
119
118
|
|
|
120
|
-
This skill
|
|
119
|
+
This skill produces the feasibility report schema defined in `{feasibilitySchemaRef}`; every output conforms to that schema — `schemaVersion: "1.0"`, the verdict token set (`Verified|Plausible|Risky|Blocked`; overall `FEASIBLE|CONDITIONALLY_FEASIBLE|NOT_FEASIBLE`), the filename pattern, and the section-heading order.
|
|
121
120
|
|
|
122
|
-
**Filename variables
|
|
123
|
-
- `project_slug`: set in SKILL.md On Activation §2 from `project_name`
|
|
124
|
-
- `timestamp`: set in SKILL.md On Activation §2 (UTC `YYYYMMDD-HHmmss`, fixed for the run)
|
|
125
|
-
- `outputFile` resolves to `{outputFolderPath}/feasibility-report-{project_slug}-{timestamp}.md` per the stage frontmatter template
|
|
126
|
-
- `outputFileLatest` resolves to `{outputFolderPath}/feasibility-report-{project_slug}-latest.md` (a copy, not a symlink — per schema)
|
|
121
|
+
**Filename variables:** `project_slug` and `timestamp` were fixed at activation per SKILL.md On Activation §2; reuse them, do not re-derive. `{outputFile}` and `{outputFileLatest}` resolve from them per the stage frontmatter template — the latter a copy, not a symlink (per schema).
|
|
127
122
|
|
|
128
123
|
**Load** `{reportTemplatePath}` (the customize-aware template path resolved in SKILL.md On Activation §4) and stage the initial content. Substitute the template's `Schema contract:` line `{feasibilitySchemaRef}` placeholder with the resolved schema path so every emitted report cites a path that exists on the running machine.
|
|
129
124
|
|
|
@@ -145,7 +140,7 @@ This skill is the PRODUCER of the feasibility report schema defined in `{feasibi
|
|
|
145
140
|
- `skillsAnalyzed: {count}`
|
|
146
141
|
- `stepsCompleted: ['init']`
|
|
147
142
|
|
|
148
|
-
**Atomic write:** Pipe the staged content through `python3 {atomicWriteHelper} write --target {outputFile}` and then again with `--target {outputFileLatest}`. Both writes use the same staged content
|
|
143
|
+
**Atomic write:** Pipe the staged content through `python3 {atomicWriteHelper} write --target {outputFile}` and then again with `--target {outputFileLatest}`. Both writes use the same staged content through the atomic helper — a plain `rm`+rewrite risks a partial write corrupting the report, and the `-latest` file is a copy, not a symlink (per the shared schema).
|
|
149
144
|
|
|
150
145
|
On any non-zero exit from either write: HALT (exit code 4, `halt_reason: "write-failed"`) and emit the error envelope per SKILL.md "Result Contract (Headless)" with `report_path: null`, `report_latest_path: null`, `overall_verdict: null`.
|
|
151
146
|
|
|
@@ -12,9 +12,9 @@ Token set is defined canonically in the SKF shared feasibility report schema (`_
|
|
|
12
12
|
|
|
13
13
|
| Verdict | Meaning | Required Evidence |
|
|
14
14
|
|---------------|------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
15
|
-
| **Verified** | APIs demonstrably connect
|
|
15
|
+
| **Verified** | APIs demonstrably connect and docs cross-reference each other | Check 1 (language) passes with declared evidence; Check 3 (types) passes from cited `exports` signatures; **Check 4 (docs cross-reference) passes with a literal substring/name citation** — without Check 4 evidence, cap at `Plausible`. Check 2 is best-effort only and cannot by itself promote to `Verified`. |
|
|
16
16
|
| **Plausible** | Checks pass but rely on inferred or indirect evidence | Language + type checks pass; Check 2 uses inferred `protocols_inferred`/`data_formats_inferred` (prose scan); Check 4 is weak or missing (no literal cross-reference). This is the mandatory cap whenever Check 4 does not surface a literal citation. |
|
|
17
|
-
| **Risky** | Type mismatch, protocol gap, or language boundary requiring a bridge | A clear gap exists (e.g., TypeScript↔Rust FFI needed) but a workaround is architecturally feasible — a named workaround
|
|
17
|
+
| **Risky** | Type mismatch, protocol gap, or language boundary requiring a bridge | A clear gap exists (e.g., TypeScript↔Rust FFI needed) but a workaround is architecturally feasible — cite a named workaround in the recommendation |
|
|
18
18
|
| **Blocked** | Fundamental incompatibility — no feasible integration path even with a bridge or adapter layer | The two libraries cannot exchange data in any documented way; requires replacing one of the libraries |
|
|
19
19
|
|
|
20
20
|
**Promotion rule:** `Verified` requires Check 4 evidence. If Checks 1 and 3 pass but Check 4 fails (no literal substring/name citation from either skill's SKILL.md), the verdict is capped at `Plausible`. This rule is enforced by step 3 §4 and is the producer obligation declared in the shared schema.
|
|
@@ -27,22 +27,13 @@ For each integration pair (Library A ↔ Library B):
|
|
|
27
27
|
|
|
28
28
|
### 1. Language Boundary Check
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
| Same language | Same language | No boundary — direct API calls possible |
|
|
33
|
-
| TypeScript ↔ Rust | Requires FFI, IPC, or WebSocket bridge | Check if a bridge library exists in the stack (e.g., Tauri provides JS↔Rust IPC) |
|
|
34
|
-
| TypeScript ↔ Python | Requires REST/gRPC/WebSocket bridge | Typically not direct |
|
|
35
|
-
| Any ↔ C/C++ | FFI available in most languages | Check for bindings |
|
|
30
|
+
- Same language on both sides → no boundary, direct API calls. Different languages → the integration needs a bridge (FFI, IPC, or a network protocol; the mechanism follows from the pair — e.g. C/C++ exposes FFI most languages can bind).
|
|
31
|
+
- The load-bearing nudge: **check whether a bridge library already exists in the stack** before assuming one must be built (e.g., Tauri provides JS↔Rust IPC).
|
|
36
32
|
|
|
37
33
|
### 2. Protocol Compatibility Check
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
| In-process (same runtime) | In-process | Direct — function calls |
|
|
42
|
-
| HTTP/REST | HTTP/REST | Network bridge — compatible if API endpoints match |
|
|
43
|
-
| WebSocket | WebSocket | Real-time bridge — check message format compatibility |
|
|
44
|
-
| Shared filesystem | Shared filesystem | Async — check format compatibility |
|
|
45
|
-
| Embedded database | Embedded database | May conflict on lock files — check for multi-writer support |
|
|
35
|
+
- Matching transports are compatible modulo format alignment: both in-process → direct calls; both HTTP/REST → compatible if endpoints match; both WebSocket → check message-format compatibility; both shared-filesystem → async, check format.
|
|
36
|
+
- The load-bearing nudge: **two embedded databases may conflict on lock files — check for multi-writer support** before treating them as compatible.
|
|
46
37
|
|
|
47
38
|
### 3. Type Compatibility Check
|
|
48
39
|
|
|
@@ -54,15 +45,15 @@ For each integration pair (Library A ↔ Library B):
|
|
|
54
45
|
|
|
55
46
|
- Search Skill A's SKILL.md for a literal substring/name citation of Library B
|
|
56
47
|
- Search Skill B's SKILL.md for the reciprocal citation
|
|
57
|
-
- Accept literal names or aliases declared in that skill's metadata;
|
|
58
|
-
- A pass requires at least one literal citation in at least one direction; the exact substring and location
|
|
59
|
-
- If neither skill literally cites the other, Check 4
|
|
48
|
+
- Accept literal names or aliases declared in that skill's metadata; a paraphrase or fuzzy match does not satisfy Check 4
|
|
49
|
+
- A pass requires at least one literal citation in at least one direction; record the exact substring and location in the evidence block
|
|
50
|
+
- If neither skill literally cites the other, Check 4 fails and the per-pair verdict caps at `Plausible` (not `Verified`)
|
|
60
51
|
|
|
61
52
|
---
|
|
62
53
|
|
|
63
54
|
## Verdict Evidence Format
|
|
64
55
|
|
|
65
|
-
Each verdict
|
|
56
|
+
Each verdict includes:
|
|
66
57
|
|
|
67
58
|
```
|
|
68
59
|
**{Library A} ↔ {Library B}: {VERDICT}**
|