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
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
nextStepFile: 'write.md'
|
|
3
|
+
# Resolve `{hashContentHelper}` to the first existing path; HALT if neither
|
|
4
|
+
# candidate exists. Check B uses its `manual-verify` subcommand to verify the
|
|
5
|
+
# merged (on-disk) SKILL.md against the byte-exact [MANUAL] inventory captured
|
|
6
|
+
# in step 1 §5 — the deterministic replacement for the LLM byte-identity
|
|
7
|
+
# eyeball, which could silently pass a subtly truncated block.
|
|
8
|
+
hashContentProbeOrder:
|
|
9
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-hash-content.py'
|
|
10
|
+
- '{project-root}/src/shared/scripts/skf-hash-content.py'
|
|
3
11
|
---
|
|
4
12
|
|
|
5
13
|
<!-- Config: communicate in {communication_language}. -->
|
|
@@ -14,9 +22,9 @@ Validate the merged skill content against the agentskills.io specification, veri
|
|
|
14
22
|
|
|
15
23
|
- Focus only on validation — do not fix issues (that's the user's choice)
|
|
16
24
|
- Validation is read-only — do not modify merged content
|
|
17
|
-
-
|
|
25
|
+
- Run the two live structural checks (B, then C) in order; Checks A, D, E, F are deferred to step 6
|
|
18
26
|
|
|
19
|
-
##
|
|
27
|
+
## Steps
|
|
20
28
|
|
|
21
29
|
### 1. Check Tool Availability and Validation Timing
|
|
22
30
|
|
|
@@ -27,30 +35,40 @@ Run: `npx skill-check -h`
|
|
|
27
35
|
|
|
28
36
|
**Important:** Do not assume availability — empirical check required.
|
|
29
37
|
|
|
30
|
-
**Validation timing note:** Step-04 section 6b has already written SKILL.md
|
|
38
|
+
**Validation timing note:** Step-04 section 6b has already written SKILL.md to disk. External-tool checks against written files (skill-check Checks A, E, F) still run in **step 6 section 7** to co-locate external-tool validation with post-write verification. Check D (Provenance Completeness) is deterministic but needs both `metadata.json` and `provenance-map.json` on disk — neither exists yet at this step — so it is deferred to **step 6 section 6a**, which runs it via the completeness helper against the freshly-written artifacts. Structural checks (B, C) run here against the merged content — content on disk is byte-identical to the in-context copy.
|
|
31
39
|
|
|
32
|
-
### 2.
|
|
40
|
+
### 2. Run Validation Checks
|
|
33
41
|
|
|
34
|
-
|
|
42
|
+
Per the §1 timing note, only Check B (a single deterministic `manual-verify` script) and Check C (an in-prompt tier-label consistency check) run live at this step; Checks A, D, E, F are deferred to step 6. Run B then C in order — one script plus one in-prompt comparison have nothing to fan out.
|
|
35
43
|
|
|
36
44
|
**Check A — Spec Compliance (deferred to post-write):**
|
|
37
45
|
|
|
38
46
|
Skill-check requires written files on disk. This check is deferred to step 6 section 7. Perform manual structural check only: verify merged SKILL.md has required sections (exports, usage patterns, conventions), verify export entries have name/type/signature/file:line reference, flag missing sections.
|
|
39
47
|
|
|
40
48
|
**Check B — [MANUAL] Section Integrity:**
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
|
|
50
|
+
Run the deterministic [MANUAL]-integrity verifier against the step-1 §5 inventory — do not eyeball byte-identity (a subtly truncated block reads as intact to the eye):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
uv run {hashContentHelper} manual-verify {skill_package}/SKILL.md \
|
|
54
|
+
--inventory {manual_inventory}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Read the verdict JSON `{"preserved":[...], "modified":[...], "missing":[...], "moved":[...], "ok":bool}` (the on-disk SKILL.md is byte-identical to the merged in-context copy per the timing note above):
|
|
58
|
+
- `ok == true` → status PASS. `preserved` blocks are byte-identical in place; any `moved` blocks are byte-identical but relocated with their logical parent section (report as informational, not a finding).
|
|
59
|
+
- `modified` non-empty → FAIL: these blocks' byte-exact interiors changed (the truncation a marker-count/eyeball check would miss). List each by name.
|
|
60
|
+
- `missing` non-empty → FAIL: these blocks lost their markers entirely. List each by name.
|
|
61
|
+
|
|
62
|
+
Populate the §3 `manual_integrity` record from this verdict (`sections_verified` = inventory count, `sections_intact` = `len(preserved) + len(moved)`). This check is advisory here (findings inform, do not block); write.md §1 enforces the same `ok` verdict as a HALT gate before any derived artifact is written.
|
|
44
63
|
|
|
45
64
|
**Check C — Confidence Tier Consistency:**
|
|
46
65
|
- Verify all re-extracted exports have confidence labels (T1/T1-low/T2)
|
|
47
66
|
- Verify tier labels match forge tier: Quick=T1-low only, Forge=T1 (T1-low for degraded), Forge+=T1 (same as Forge, CCC improves coverage not confidence), Deep=T1+T2
|
|
48
67
|
- Flag mismatched or missing tier labels
|
|
49
68
|
|
|
50
|
-
**Check D — Provenance Completeness:**
|
|
51
|
-
|
|
52
|
-
-
|
|
53
|
-
- Flag orphaned provenance entries (export removed but provenance remains)
|
|
69
|
+
**Check D — Provenance Completeness (deferred to post-write):**
|
|
70
|
+
|
|
71
|
+
The provenance map does not exist on disk until step 6 §3 writes it, and completeness is a deterministic set-diff (metadata `exports[]` vs provenance `entries[].export_name`) plus file:line citation resolution — not something to eyeball. This check is deferred to **step 6 section 6a**, which runs `skf-verify-provenance-completeness.py` against the just-written `metadata.json` and `provenance-map.json` and reads back `missing[]` / `orphaned[]` / `stale[]` findings. Do not attempt the set comparison here — there is no provenance map to compare against yet. The `Provenance` row in §5's summary is populated from §6a's result.
|
|
54
72
|
|
|
55
73
|
**Check E — Diff Comparison (via skill-check):**
|
|
56
74
|
|
|
@@ -83,16 +101,6 @@ Validation Results:
|
|
|
83
101
|
quality_score: [0-100] # from skill-check, if available
|
|
84
102
|
```
|
|
85
103
|
|
|
86
|
-
### 4. For Stack Skills — Validate Reference Files
|
|
87
|
-
|
|
88
|
-
**ONLY if skill_type == "stack":**
|
|
89
|
-
|
|
90
|
-
Repeat checks A-D for each reference file:
|
|
91
|
-
- `references/{library}.md` — spec compliance and [MANUAL] integrity
|
|
92
|
-
- `references/integrations/{pair}.md` — spec compliance and [MANUAL] integrity
|
|
93
|
-
|
|
94
|
-
**If skill_type != "stack":** Skip with notice.
|
|
95
|
-
|
|
96
104
|
### 5. Display Validation Summary
|
|
97
105
|
|
|
98
106
|
"**Validation Results:**
|
|
@@ -110,13 +118,7 @@ Repeat checks A-D for each reference file:
|
|
|
110
118
|
|
|
111
119
|
**If findings exist:** List each with severity, description, and location. Add: "Validation is advisory. Findings do not block the update."
|
|
112
120
|
|
|
113
|
-
### 6.
|
|
114
|
-
|
|
115
|
-
Display: "**Proceeding to write updated files...**"
|
|
116
|
-
|
|
117
|
-
After validation summary is displayed, immediately load, read entire file, then execute {nextStepFile}. This is an auto-proceed step — validation is advisory, not blocking.
|
|
118
|
-
|
|
119
|
-
## CRITICAL STEP COMPLETION NOTE
|
|
121
|
+
### 6. Route to Next Step
|
|
120
122
|
|
|
121
|
-
|
|
123
|
+
This step auto-proceeds — no user choices; validation is advisory and does not block, it only informs. Once all validation checks have completed and findings are displayed, display "**Proceeding to write updated files...**", then load, fully read, and execute `{nextStepFile}` to write the updated files.
|
|
122
124
|
|
|
@@ -17,6 +17,31 @@ descriptionGuardProbeOrder:
|
|
|
17
17
|
updateActiveSymlinkProbeOrder:
|
|
18
18
|
- '{project-root}/_bmad/skf/shared/scripts/skf-update-active-symlink.py'
|
|
19
19
|
- '{project-root}/src/shared/scripts/skf-update-active-symlink.py'
|
|
20
|
+
# Resolve `{verifyProvenanceCompletenessHelper}` to the first existing path.
|
|
21
|
+
# §6a runs Check D (Provenance Completeness), deferred from step 5 because it
|
|
22
|
+
# needs both metadata.json + provenance-map.json on disk: the export/provenance
|
|
23
|
+
# set-diff (missing + orphaned entries) and file:line citation resolution.
|
|
24
|
+
# Advisory — do not HALT if neither path resolves; fall back to the in-prose
|
|
25
|
+
# set comparison §6a documents (graceful degradation).
|
|
26
|
+
verifyProvenanceCompletenessProbeOrder:
|
|
27
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-verify-provenance-completeness.py'
|
|
28
|
+
- '{project-root}/src/shared/scripts/skf-verify-provenance-completeness.py'
|
|
29
|
+
# Resolve `{hashContentHelper}` to the first existing path; HALT if neither
|
|
30
|
+
# candidate exists. §1 uses its `manual-verify` subcommand to verify the
|
|
31
|
+
# post-merge file against the byte-exact [MANUAL] inventory captured in step 1
|
|
32
|
+
# §5; a marker-count comparison would pass a block whose interior was truncated
|
|
33
|
+
# without changing the marker count.
|
|
34
|
+
hashContentProbeOrder:
|
|
35
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-hash-content.py'
|
|
36
|
+
- '{project-root}/src/shared/scripts/skf-hash-content.py'
|
|
37
|
+
# Resolve `{renderMetadataStatsHelper}` by probing `{renderMetadataStatsProbeOrder}`
|
|
38
|
+
# in order (installed SKF module path first, src/ dev-checkout fallback); first
|
|
39
|
+
# existing path wins. HALT if neither resolves — §2's `stats` block and
|
|
40
|
+
# `confidence_distribution` are computed values that must not be hand-binned,
|
|
41
|
+
# and this is the same helper sibling create-skill compiles them with.
|
|
42
|
+
renderMetadataStatsProbeOrder:
|
|
43
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-render-metadata-stats.py'
|
|
44
|
+
- '{project-root}/src/shared/scripts/skf-render-metadata-stats.py'
|
|
20
45
|
---
|
|
21
46
|
|
|
22
47
|
<!-- Config: communicate in {communication_language}. -->
|
|
@@ -25,7 +50,7 @@ updateActiveSymlinkProbeOrder:
|
|
|
25
50
|
|
|
26
51
|
## STEP GOAL:
|
|
27
52
|
|
|
28
|
-
Verify the merged SKILL.md
|
|
53
|
+
Verify the merged SKILL.md that step 4 section 6b wrote to disk, then write the derived artifacts (metadata.json, provenance-map.json, evidence-report.md, context-snippet.md, and the active symlink).
|
|
29
54
|
|
|
30
55
|
## Rules
|
|
31
56
|
|
|
@@ -34,7 +59,7 @@ Verify the merged SKILL.md and stack reference files that step 4 section 6b wrot
|
|
|
34
59
|
- Do not skip provenance map update — critical for future audits
|
|
35
60
|
- HALT immediately on verification failure before writing any derived artifact — a partial-write skill package is worse than an unchanged one
|
|
36
61
|
|
|
37
|
-
##
|
|
62
|
+
## Steps
|
|
38
63
|
|
|
39
64
|
### 0. Description Guard Protocol
|
|
40
65
|
|
|
@@ -48,13 +73,19 @@ Update-skill does not run the optional post-restore frontmatter re-validation to
|
|
|
48
73
|
|
|
49
74
|
SKILL.md was written in step 4 section 6b. Verify the write landed intact before proceeding to any derived-artifact writes.
|
|
50
75
|
|
|
51
|
-
- Read `{skill_package}/SKILL.md` from disk
|
|
52
|
-
- Count `<!-- [MANUAL:*] -->` opening markers and compare against the [MANUAL] inventory captured in step 1
|
|
53
76
|
- Verify the resolved `{skill_package}` path matches the version directory step 4 wrote to (if the version changed, step 4 updated `{skill_package}` in context to point at the new path)
|
|
54
|
-
-
|
|
55
|
-
- **If [MANUAL] count does not match: HALT immediately.** Do not write `metadata.json`, `provenance-map.json`, or any other artifact — further writes would compound the inconsistency. Alert the user:
|
|
77
|
+
- Run the deterministic [MANUAL]-integrity verifier against the byte-exact inventory captured in step 1 §5:
|
|
56
78
|
|
|
57
|
-
|
|
79
|
+
```bash
|
|
80
|
+
uv run {hashContentHelper} manual-verify {skill_package}/SKILL.md \
|
|
81
|
+
--inventory {manual_inventory}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The verdict JSON is `{"preserved":[...], "modified":[...], "missing":[...], "moved":[...], "ok":bool}`. A `modified` block is one whose byte-exact interior changed (an interior truncation); a `missing` block lost its markers entirely; a `moved` block is byte-identical but relocated with its logical parent section (clean — does not fail the gate). `ok == (modified empty AND missing empty)`.
|
|
85
|
+
- If `ok == true` and the path resolves: proceed to section 2
|
|
86
|
+
- **If `ok == false`: HALT immediately** with status `halted-for-manual-mismatch`. Do not write `metadata.json`, `provenance-map.json`, or any other artifact — further writes would compound the inconsistency. In `{headless_mode}`, emit the halt envelope per SKILL.md §Headless (`error: {phase: "write:verify-manual-integrity", path: "{skill_package}/SKILL.md", reason: "..."}`). Alert the user:
|
|
87
|
+
|
|
88
|
+
"**[MANUAL] section integrity failure after write.** Blocks modified (interior changed): {modified}. Blocks missing (markers lost): {missing}. Relocated-but-intact (advisory only): {moved}. Verified against the step-1 inventory `{manual_inventory}`, on disk at `{skill_package}/SKILL.md`. The skill package is in an inconsistent state. Manual recovery required — restore the previous version from `{skill_group}/{previous_version}/` or fix the file in place, then re-run update-skill."
|
|
58
89
|
|
|
59
90
|
### 2. Write Updated metadata.json
|
|
60
91
|
|
|
@@ -64,22 +95,28 @@ Update `{skill_package}/metadata.json`:
|
|
|
64
95
|
- Update `version`: **if `update_mode == "gap-driven"`, do not bump — the skill is being repaired against the same source commit, so leave `version` unchanged and update only `generation_date` / `last_update` below.** This keeps metadata `version` consistent with the on-disk `{skill_package}` path, which step 4 §6b also leaves unchanged in gap-driven mode (see step 4 §6b's "If the source version detected during step 3 differs..." carve-out — in gap-driven mode no source version is detected, so step 4 writes into the existing version directory). Otherwise, if a source version was detected during re-extraction and differs from the current metadata version, use the source version; otherwise increment patch version
|
|
65
96
|
- Update `generation_date` timestamp to current ISO-8601 date
|
|
66
97
|
- Update `exports` array to reflect current export list
|
|
67
|
-
-
|
|
68
|
-
|
|
98
|
+
- **Compute the `stats` block and `confidence_distribution` deterministically** with `{renderMetadataStatsHelper}` (resolve from `{renderMetadataStatsProbeOrder}`; first existing path wins) — the same helper sibling create-skill compiles them with, so create and update emit byte-identical stats for identical inputs. The helper owns all the arithmetic: it bins each provenance `entries[]` row once by its `signature_source` tier into `confidence_distribution.{t1,t1_low,t2,t3}`, sets `exports_documented` = the entry count, and derives `exports_total` = `exports_public_api` + `exports_internal`, `public_api_coverage` = documented / public_api (`null` if public_api is 0), `total_coverage` = documented / total (`null` if total is 0), plus `scripts_count` / `assets_count` from the inventory arrays. Run `uv run {renderMetadataStatsHelper} --help` for the full contract. Do not hand-bin the distribution — binning T2 annotations + T3 doc items on top of the per-export tiers double-counts, which per-entry binning by `signature_source` makes structurally impossible. You supply only the judgment payload:
|
|
99
|
+
|
|
100
|
+
**Judgment payload (what you decide — passed as JSON on stdin):**
|
|
69
101
|
- `exports_public_api`: count of exports from public entry points (`__init__.py`, `index.ts`, `lib.rs`, or equivalent)
|
|
70
102
|
- `exports_internal`: count of all other non-underscore-prefixed exports
|
|
71
|
-
- `
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
103
|
+
- `scripts` / `assets`: the scripts / assets inventory arrays (or `[]` when empty) — the helper sets `scripts_count` / `assets_count` from their lengths
|
|
104
|
+
|
|
105
|
+
**Invoke** — since the helper reads `entries[]`, stage §3's `provenance-map.json` write first (§3 does not depend on these stats):
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
echo '{"exports_public_api": {N}, "exports_internal": {M}, "scripts": {scripts-inventory-or-[]}, "assets": {assets-inventory-or-[]}}' \
|
|
109
|
+
| uv run {renderMetadataStatsHelper} {forge_version}/provenance-map.json
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Write the returned `stats` and `confidence_distribution` objects into `metadata.json` **verbatim**. If the helper reports `coherence.ok: false`, some provenance entries carry a missing/unrecognized `signature_source` (§3 must write it on every entry) — fix the provenance map, do not hand-edit the stats.
|
|
78
113
|
|
|
79
114
|
### 3. Write Updated provenance-map.json
|
|
80
115
|
|
|
81
116
|
Write to `{forge_version}/provenance-map.json`:
|
|
82
117
|
|
|
118
|
+
**Every entry this step writes or rewrites carries a `signature_source` (`T1` / `T1-low` / `T2` / `T3`)** — the tier that contributed the structural signature, matching create-skill's entry contract. §2's stats helper bins each entry on this field, so a missing value trips its `coherence.ok: false` check. Preserve it byte-identical on untouched entries; set it from the contributing extraction tier on every re-extracted or new entry.
|
|
119
|
+
|
|
83
120
|
**If `no_reextraction == true` (gap-driven mode from step 3 section 0):**
|
|
84
121
|
Dispatch per-entry on the verification outcome recorded by step 3 — gap-driven runs produce a mix of `verified`, `moved`, `re-extracted`, and `unknown` outcomes, and each requires a different provenance-map write strategy:
|
|
85
122
|
|
|
@@ -122,6 +159,8 @@ Dispatch per-entry on the verification outcome recorded by step 3 — gap-driven
|
|
|
122
159
|
}
|
|
123
160
|
```
|
|
124
161
|
|
|
162
|
+
`manual_sections_preserved` = `len(preserved) + len(moved)` from the §1 `manual-verify` verdict (blocks that survived byte-identical, whether in place or relocated). Do not re-count markers by hand — the §1 verdict is the deterministic source.
|
|
163
|
+
|
|
125
164
|
### 4. Write Updated evidence-report.md
|
|
126
165
|
|
|
127
166
|
Append update operation section to `{forge_version}/evidence-report.md` (create the file with a standard header if it does not yet exist):
|
|
@@ -168,18 +207,9 @@ Append update operation section to `{forge_version}/evidence-report.md` (create
|
|
|
168
207
|
|
|
169
208
|
**Context Snippet population** (used by §5 after the staleness check runs): §4 writes the sub-block with placeholders; §5 updates the on-disk evidence report in place after deciding whether to regenerate. Set `Regenerated: true` and populate `Triggers fired` with any combination of `headline-exports`, `version`, `gotchas` when at least one trigger fired. Set `Regenerated: false` and `Triggers fired: —` when none fired (the gap-driven / internals-only outcome). Always fill `Notes` with a one-sentence reason (e.g., `"Gap-driven repair — no snippet surface changed"`, `"Version bumped 0.1.0 → 0.2.0; headline exports re-ranked"`).
|
|
170
209
|
|
|
171
|
-
### 5.
|
|
210
|
+
### 5. Regenerate context-snippet.md
|
|
172
211
|
|
|
173
|
-
**
|
|
174
|
-
|
|
175
|
-
Stack reference files were written in step 4 section 6b. Verify each affected reference file that the merge produced:
|
|
176
|
-
|
|
177
|
-
- Read each `references/{library}.md` back from disk
|
|
178
|
-
- Read each `references/integrations/{pair}.md` back from disk
|
|
179
|
-
- Verify per-file [MANUAL] section counts match the per-file inventory captured in step 1
|
|
180
|
-
- **If any verification fails: HALT** using the same recovery protocol as section 1 — do not regenerate `context-snippet.md` or write any further derived artifact
|
|
181
|
-
|
|
182
|
-
**For all skills (both single and stack) — regenerate `context-snippet.md` if stale:**
|
|
212
|
+
**Regenerate `context-snippet.md` if stale:**
|
|
183
213
|
|
|
184
214
|
`context-snippet.md` is a `{skill_package}` deliverable that goes stale whenever **headline exports**, **version**, or **gotchas** change in this run. Regenerate it only when at least one of these triggers fired; otherwise skip — a skip is the correct outcome for gap-driven repairs and other runs that touch internals below the snippet's surface, where regenerating would produce byte-identical content.
|
|
185
215
|
|
|
@@ -193,10 +223,7 @@ Stack reference files were written in step 4 section 6b. Verify each affected re
|
|
|
193
223
|
|
|
194
224
|
**If no trigger fired:** skip regeneration — do not touch `context-snippet.md` on disk. The snippet remains valid against the prior run's surface. Continue to §5b.
|
|
195
225
|
|
|
196
|
-
**If at least one trigger fired:** regenerate the snippet using the format from
|
|
197
|
-
|
|
198
|
-
- For single skills: `skf-create-skill/assets/skill-sections.md` (pipe-delimited indexed format)
|
|
199
|
-
- For stack skills: `skf-create-stack-skill/assets/stack-skill-template.md`
|
|
226
|
+
**If at least one trigger fired:** regenerate the snippet using the format from `skf-create-skill/assets/skill-sections.md` (pipe-delimited indexed format).
|
|
200
227
|
|
|
201
228
|
Use the **flat draft form** for the `root:` path in the draft snippet: `root: skills/{skill-name}/`. The per-IDE skill root (e.g., `.claude/skills/`, `.windsurf/skills/`, `.github/skills/` — see `skf-export-skill/assets/managed-section-format.md`) is applied later by `export-skill` step 3 when the skill is exported. Do not choose an IDE-specific prefix in update-skill — that is an export-time decision that depends on config.yaml.
|
|
202
229
|
|
|
@@ -204,8 +231,6 @@ Pull values for the regenerated snippet from the updated metadata.json (version,
|
|
|
204
231
|
|
|
205
232
|
Write the regenerated snippet to `{skill_package}/context-snippet.md`, preserving file permissions.
|
|
206
233
|
|
|
207
|
-
**If skill_type == "stack"**, also verify that the reference file updates from the first half of this section have been applied before writing the snippet so the stack snippet reflects the newest integration list.
|
|
208
|
-
|
|
209
234
|
### 5b. Update Active Symlink
|
|
210
235
|
|
|
211
236
|
Flip `{skill_group}/active` to point at the current `{version}` via the helper. The call is **always** run — atomic, idempotent, and verified in one shot. The helper's no-op path handles the "version did not change" case (gap-driven mode, or no source drift) without writing to disk:
|
|
@@ -225,9 +250,11 @@ The helper emits a result envelope with `status` ∈ `{ok, flipped, mismatch, mi
|
|
|
225
250
|
- **`missing-target`** (exit 2): `{skill_group}/{version}/` directory does not exist on disk. HALT — display `halt_message` verbatim. This indicates §4 §6b did not write the version directory before §5b ran (a workflow bug, not a user error).
|
|
226
251
|
- **`mismatch`** (exit 2): re-read after flip showed the symlink still points elsewhere. HALT — display `halt_message`. Should be impossible because the helper uses `os.replace` (atomic rename); a mismatch here indicates filesystem-level interference (concurrent writer, broken FUSE mount).
|
|
227
252
|
|
|
253
|
+
Both exit-2 halts carry status `halted-for-write-failure`; in `{headless_mode}`, emit the halt envelope per SKILL.md §Headless (`error: {phase: "write:active-symlink", reason: "..."}`).
|
|
254
|
+
|
|
228
255
|
### 6. Verify Derived Artifact Writes
|
|
229
256
|
|
|
230
|
-
SKILL.md was verified in section 1
|
|
257
|
+
SKILL.md was verified in section 1 (written by step 4 section 6b). This section verifies the artifacts this step wrote: `metadata.json`, `provenance-map.json`, `evidence-report.md`, `context-snippet.md`, and the `active` symlink from §5b.
|
|
231
258
|
|
|
232
259
|
For each derived artifact:
|
|
233
260
|
- Read back the file
|
|
@@ -246,12 +273,33 @@ For each derived artifact:
|
|
|
246
273
|
| evidence-report.md | {VERIFIED/FAILED} |
|
|
247
274
|
| context-snippet.md | {VERIFIED/FAILED} |
|
|
248
275
|
| {skill_group}/active symlink | {VERIFIED/FAILED} (readlink → {resolved_version}, expected {version}) |
|
|
249
|
-
| {stack reference files...} | {VERIFIED in section 5} |
|
|
250
276
|
|
|
251
|
-
**On symlink `mismatch` (helper exit 2):** HALT
|
|
277
|
+
**On symlink `mismatch` (helper exit 2):** HALT with status `halted-for-write-failure`. Do not proceed to §7 post-write validation or §8 menu. Display the helper's `halt_message` verbatim — it already includes the diverged target, the expected version, and the recovery command; in `{headless_mode}`, emit the halt envelope per SKILL.md §Headless (`error: {phase: "write:verify-active-symlink", reason: "..."}`). This matches the severity of the other four artifact checks — silent divergence here mis-routes any downstream consumer that uses the symlink fallback.
|
|
252
278
|
|
|
253
279
|
**All files written and verified.**"
|
|
254
280
|
|
|
281
|
+
### 6a. Provenance Completeness (Check D — Deferred from Step 05)
|
|
282
|
+
|
|
283
|
+
`validate.md` Check D (Provenance Completeness) is a deterministic set-diff plus citation resolution, and it needs both `metadata.json` (written in §2) and `provenance-map.json` (written in §3) on disk — neither exists at validate time, so the check is deferred here. Run it against the just-written artifacts via `{verifyProvenanceCompletenessHelper}`:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
uv run {verifyProvenanceCompletenessHelper} verify \
|
|
287
|
+
--metadata {skill_package}/metadata.json \
|
|
288
|
+
--provenance {forge_version}/provenance-map.json \
|
|
289
|
+
--source-root {source_root}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
The helper reads its `--source-root` (falling back to the provenance map's own `source_root` field when the flag is omitted); pass the resolved `{source_root}` so citation resolution runs against the same tree re-extraction read. **Read the emitted JSON — do NOT recompute the set operations by eye; an LLM set-diff can silently pass a dropped or orphaned entry:**
|
|
293
|
+
|
|
294
|
+
- `missing[]` — documented exports (metadata `exports[]`) with no provenance entry: a coverage gap.
|
|
295
|
+
- `orphaned[]` — provenance `entries[].export_name` whose export was removed but the entry remains.
|
|
296
|
+
- `stale[]` — entries whose `source_file:source_line` no longer resolves; each carries a `reason` of `file-missing`, `line-out-of-bounds`, or `line-invalid`. Internal names are canonicalized through `reexport_map` before the diff, so a barrel-renamed export does not read as missing or orphaned.
|
|
297
|
+
- `summary.stale_check` — `"checked"` when citations were resolved against the source tree, or `"skipped-no-source-root"` when no source root resolved on disk (the completeness + orphan diffs still ran; `stale` is empty by construction, not clean-by-verification).
|
|
298
|
+
|
|
299
|
+
Map `status` to the `Provenance:` line of the §4 evidence report's Validation Summary: `PASS` when `status == "pass"`, `WARN` when `status == "findings"` (this is the persistent record — step 5 §5's table was rendered before the provenance map existed, so it necessarily showed this row as deferred). Provenance findings are **advisory** — they do not block the update. Surface each `missing` / `orphaned` / `stale` entry in the evidence report's Validation Summary so the user can decide, and note when `stale_check` was `skipped-no-source-root`.
|
|
300
|
+
|
|
301
|
+
**Graceful degradation:** if neither probe path resolves (no `uv` / script available), fall back to the manual set comparison the script encapsulates — enumerate metadata `exports[]` and provenance `entries[].export_name` (canonicalizing internal names through `reexport_map`), diff the two sets for missing/orphaned entries, and spot-check that each `source_file:source_line` still points at a real line in the source tree. Prefer the script — it does this deterministically.
|
|
302
|
+
|
|
255
303
|
### 7. Run Post-Write Validation (Deferred from Step 05)
|
|
256
304
|
|
|
257
305
|
External tool checks deferred from step 5 now run against the written files.
|
|
@@ -284,20 +332,7 @@ Record findings in the evidence report (section 4), including any `description_g
|
|
|
284
332
|
|
|
285
333
|
**If skill-check unavailable:** Skip with note — structural checks from step 5 are sufficient.
|
|
286
334
|
|
|
287
|
-
### 8.
|
|
288
|
-
|
|
289
|
-
Display: "**Proceeding to report...**"
|
|
290
|
-
|
|
291
|
-
#### Menu Handling Logic:
|
|
292
|
-
|
|
293
|
-
- After all writes verified and post-write validation complete, immediately load, read entire file, then execute {nextStepFile}
|
|
294
|
-
|
|
295
|
-
#### EXECUTION RULES:
|
|
296
|
-
|
|
297
|
-
- This is an auto-proceed step with no user choices
|
|
298
|
-
- Proceed directly to report after verification
|
|
299
|
-
|
|
300
|
-
## CRITICAL STEP COMPLETION NOTE
|
|
335
|
+
### 8. Route to Next Step
|
|
301
336
|
|
|
302
|
-
|
|
337
|
+
This step auto-proceeds — no user choices. Once all files have been written and verified and post-write validation is complete, display "**Proceeding to report...**", then load, fully read, and execute `{nextStepFile}` to display the change report.
|
|
303
338
|
|
|
@@ -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 = ""
|