bmad-module-skill-forge 7.0.0 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +1 -1
  3. package/docs/_data/pinned.yaml +9 -10
  4. package/docs/campaign.md +2 -2
  5. package/docs/skill-model.md +3 -3
  6. package/docs/verifying-a-skill.md +17 -18
  7. package/docs/workflows.md +7 -7
  8. package/package.json +1 -1
  9. package/src/shared/references/description-guard-protocol.md +12 -10
  10. package/src/shared/scripts/schemas/skf-analyze-result-envelope.v1.json +1 -1
  11. package/src/shared/scripts/schemas/skf-audit-result-envelope.v1.json +1 -1
  12. package/src/shared/scripts/schemas/skf-campaign-result-envelope.v1.json +38 -0
  13. package/src/shared/scripts/schemas/skf-create-skill-result-envelope.v1.json +5 -0
  14. package/src/shared/scripts/schemas/skf-drop-skill-result-envelope.v1.json +1 -1
  15. package/src/shared/scripts/schemas/skf-export-result-envelope.v1.json +1 -1
  16. package/src/shared/scripts/schemas/skf-quick-skill-result-envelope.v1.json +8 -3
  17. package/src/shared/scripts/schemas/skf-refine-architecture-result-envelope.v1.json +1 -1
  18. package/src/shared/scripts/schemas/skf-stack-result-envelope.v1.json +6 -1
  19. package/src/shared/scripts/schemas/skf-test-result-envelope.v1.json +1 -1
  20. package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +7 -2
  21. package/src/shared/scripts/schemas/skf-verify-stack-result-envelope.v1.json +1 -1
  22. package/src/shared/scripts/skf-parse-gaps.py +4 -1
  23. package/src/shared/scripts/skf-quick-batch.py +8 -4
  24. package/src/shared/scripts/skf-verify-provenance-completeness.py +727 -179
  25. package/src/skf-audit-skill/customize.toml +4 -3
  26. package/src/skf-audit-skill/references/report.md +1 -1
  27. package/src/skf-campaign/assets/campaign-state-schema.json +58 -0
  28. package/src/skf-campaign/references/campaign-contracts.md +1 -1
  29. package/src/skf-campaign/references/step-06-batch.md +2 -2
  30. package/src/skf-campaign/references/step-07-capstone.md +1 -1
  31. package/src/skf-campaign/references/step-08-verify.md +1 -1
  32. package/src/skf-campaign/references/step-09-refine.md +1 -1
  33. package/src/skf-campaign/references/step-11-maintenance.md +1 -1
  34. package/src/skf-campaign/scripts/campaign-render-batch.py +43 -10
  35. package/src/skf-campaign/scripts/campaign-report.py +60 -4
  36. package/src/skf-campaign/scripts/campaign-state.py +67 -19
  37. package/src/skf-campaign/templates/campaign-report-template.md +4 -0
  38. package/src/skf-create-skill/assets/skill-sections.md +2 -2
  39. package/src/skf-create-skill/references/report.md +2 -2
  40. package/src/skf-create-skill/references/validate.md +49 -29
  41. package/src/skf-create-stack-skill/references/invocation-contract.md +1 -1
  42. package/src/skf-create-stack-skill/references/report.md +3 -3
  43. package/src/skf-create-stack-skill/references/validate.md +9 -4
  44. package/src/skf-drop-skill/references/invocation-contract.md +1 -1
  45. package/src/skf-export-skill/references/summary.md +1 -1
  46. package/src/skf-quick-skill/references/batch-mode.md +28 -2
  47. package/src/skf-quick-skill/references/compile.md +2 -0
  48. package/src/skf-quick-skill/references/finalize.md +3 -2
  49. package/src/skf-quick-skill/references/halt-contract.md +3 -3
  50. package/src/skf-quick-skill/references/quick-extract.md +2 -1
  51. package/src/skf-quick-skill/references/write-and-validate.md +11 -5
  52. package/src/skf-refine-architecture/customize.toml +1 -1
  53. package/src/skf-refine-architecture/references/report.md +2 -2
  54. package/src/skf-rename-skill/references/report.md +1 -1
  55. package/src/skf-test-skill/references/coverage-check-tiers.md +3 -3
  56. package/src/skf-test-skill/references/coverage-check.md +2 -2
  57. package/src/skf-test-skill/references/invocation-contract.md +1 -1
  58. package/src/skf-test-skill/references/report.md +1 -1
  59. package/src/skf-test-skill/references/scoring-rules.md +1 -1
  60. package/src/skf-test-skill/references/signature-comparison-rules.md +4 -1
  61. package/src/skf-test-skill/scripts/gap-ledger.py +196 -46
  62. package/src/skf-test-skill/scripts/load-coverage-inputs.py +12 -6
  63. package/src/skf-update-skill/references/gap-driven.md +1 -1
  64. package/src/skf-update-skill/references/invocation-contract.md +1 -1
  65. package/src/skf-update-skill/references/merge.md +1 -1
  66. package/src/skf-update-skill/references/report.md +18 -7
  67. package/src/skf-update-skill/references/write.md +24 -12
  68. package/src/skf-verify-stack/references/report.md +1 -1
@@ -130,7 +130,7 @@
130
130
  "result_path": {
131
131
  "type": ["string", "null"],
132
132
  "minLength": 1,
133
- "description": "The per-run result file the emitter wrote in the version folder, skf-test-skill-result-<YYYYMMDD-HHmmss>.json (UTC; -2, -3 appended when a run took that second's name), beside its skf-test-skill-result-latest.json copy. Stamped by the emitter, never typed. Null on a halt, which writes no result file, and when the write failed (warnings say why)."
133
+ "description": "The per-run result file the emitter wrote in the version folder, skf-test-skill-result-<YYYYMMDD-HHmmss>.json (UTC; -2, -3 appended when a run took that second's name), beside its skf-test-skill-result-latest.json copy. Stamped by the emitter, never typed. Null on a halt, which writes no result file, when --result-dir names no folder (nothing is written, and no warning is added), and when the per-run record could not be written (a result_file_write_failed warning says why); a -latest copy that could not be written leaves it set, and a result_file_write_failed warning names the copy."
134
134
  },
135
135
  "threshold_fallback": {
136
136
  "type": "boolean",
@@ -50,7 +50,7 @@
50
50
  "halted-for-concurrent-run",
51
51
  "blocked"
52
52
  ],
53
- "description": "Single-field outcome for pipeline branching. 'success' when the run completed and wrote the updated artifacts. 'no-changes' when detect-changes found nothing to update. 'detect-only' when the workflow was invoked with --detect-only and exited after step 2 without modifying any artifact. 'dry-run' when the workflow was invoked with --dry-run and exited after step 3 (re-extract) without merging or writing: the envelope describes what merge+write WOULD have done. Each 'halted-for-*' value corresponds to a documented halt in the stage prose (workspace-drift from gap-driven.md §3, brief-refinement from detect-changes.md §1c [U] / §2.2 [B], audit from §2.2 [A], remediation-path from gap-driven.md §4a, manual-mismatch from write.md §1, after write.md §6's provenance fixes and after write.md §7's skill-check edits (all error.phase 'write:verify-manual-integrity'), from merge.md §6b's check of the merged SKILL.md before it is published (error.phase 'merge:verify-manual-integrity') and from a headless merge that meets a [MANUAL] conflict (error.phase 'merge:conflict-resolution'), write-failure for a merge.md §6b version-folder or SKILL.md write failure (among them error.phase 'merge:new-version-folder' when the version folder already exists or the next version cannot be computed), a failed write in write.md §2 to §5 (error.phase 'write:artifact-write') and a write.md §8/§8a active-link failure, concurrent-run when init.md §1b's acquire finds another run's fresh run lock (error.phase 'init:concurrency-guard'), or when this run no longer holds its lock at the renewal before merge.md §6b or write.md §2 writes (error.phase 'merge:run-lock' or 'write:run-lock'; error.reason 'run-lock-lost: ...' when the lock lapsed while the run waited), or when write.md §8 finds another flip of the active link holding its lock for 5 s (error.phase 'write:active-symlink', error.reason 'busy: ...'). error.reason names the run that holds a run lock, never a PID). 'blocked' is the catch-all for halts that don't map to a documented status code, among them a headless run given no skill name (error.phase 'init:skill-name', error.reason starting 'input-missing'), a stack skill (error.phase 'init:stack-skill-guard'), init.md §6b (error.phase 'init:source-tree') when no commit to read could be obtained, a helper that failed (each its own '<step>:<section>' phase), write.md §7's body split (error.phase 'write:body-split': skf-shard-body.py exited 1 or 2 or printed no JSON, or reported a Tier 1 heading pulled or a cross-reference that does not resolve), and a private source tree that disappeared mid-run (error.phase '<step>:source-tree-missing'). A halt after merge.md §6b's first write, until write.md §10 closes the rollback window, first undoes it: a gap-driven repair's package is restored from the snapshot it took, and a new version's folders are removed, unless the active link already names the new version, which the halt keeps. write.md §8 points the active link at the new version only after §6 and §7, the last checks that can halt, so a [MANUAL] halt never leaves a live version behind. NOTE: detect-only and dry-run are successful exits: pipelines treating non-success as failure must include them in the success set."
53
+ "description": "Single-field outcome for pipeline branching. 'success' when the run completed and wrote the updated artifacts. 'no-changes' when detect-changes found nothing to update. 'detect-only' when the workflow was invoked with --detect-only and exited after step 2 without modifying any artifact. 'dry-run' when the workflow was invoked with --dry-run and exited after step 3 (re-extract) without merging or writing: the envelope describes what merge+write WOULD have done. Each 'halted-for-*' value corresponds to a documented halt in the stage prose (workspace-drift from gap-driven.md §3, brief-refinement from detect-changes.md §1c [U] / §2.2 [B], audit from §2.2 [A], remediation-path from gap-driven.md §4a, manual-mismatch from write.md §1, after write.md §6's provenance fixes and after write.md §7's skill-check edits (all error.phase 'write:verify-manual-integrity'), from merge.md §6b's check of the merged SKILL.md before it is published (error.phase 'merge:verify-manual-integrity') and from a headless merge that meets a [MANUAL] conflict (error.phase 'merge:conflict-resolution'), write-failure for a merge.md §6b version-folder or SKILL.md write failure (among them error.phase 'merge:new-version-folder' when the version folder already exists or the next version cannot be computed), a failed write in write.md §2 to §5 (error.phase 'write:artifact-write') and a write.md §8/§8a active-link failure, and a headless result contract report.md §5b could not write (error.phase 'report:result-contract', printed after the run's last write, so nothing is undone), concurrent-run when init.md §1b's acquire finds another run's fresh run lock (error.phase 'init:concurrency-guard'), or when this run no longer holds its lock at the renewal before merge.md §6b or write.md §2 writes (error.phase 'merge:run-lock' or 'write:run-lock'; error.reason 'run-lock-lost: ...' when the lock lapsed while the run waited), or when write.md §8 finds another flip of the active link holding its lock for 5 s (error.phase 'write:active-symlink', error.reason 'busy: ...'). error.reason names the run that holds a run lock, never a PID). 'blocked' is the catch-all for halts that don't map to a documented status code, among them a headless run given no skill name (error.phase 'init:skill-name', error.reason starting 'input-missing'), a stack skill (error.phase 'init:stack-skill-guard'), init.md §6b (error.phase 'init:source-tree') when no commit to read could be obtained, a helper that failed (each its own '<step>:<section>' phase), write.md §7's body split (error.phase 'write:body-split': skf-shard-body.py exited 1 or 2 or printed no JSON, or reported a Tier 1 heading pulled or a cross-reference that does not resolve), and a private source tree that disappeared mid-run (error.phase '<step>:source-tree-missing'). A halt after merge.md §6b's first write, until write.md §10 closes the rollback window, first undoes it: a gap-driven repair's package is restored from the snapshot it took, and a new version's folders are removed, unless the active link already names the new version, which the halt keeps. write.md §8 points the active link at the new version only after §6 and §7, the last checks that can halt, so a [MANUAL] halt never leaves a live version behind. NOTE: detect-only and dry-run are successful exits: pipelines treating non-success as failure must include them in the success set."
54
54
  },
55
55
  "skill_name": {
56
56
  "type": "string",
@@ -89,6 +89,11 @@
89
89
  "uniqueItems": true,
90
90
  "description": "Artifacts actually written or updated this run. 'references' stands for one or more references/*.md files of the skill package written, added or removed (merge.md §6b's merge, at any priority, write.md §6's provenance fixes or write.md §7's body split), one value however many. Empty array when a halt fired before any write completed."
91
91
  },
92
+ "security_result": {
93
+ "type": "string",
94
+ "enum": ["pass", "warn", "skipped"],
95
+ "description": "The outcome of write.md §7's security scan, from the Security row of this update's Validation Summary in the evidence report (PASS as 'pass', WARN as 'warn', SKIP as 'skipped'), and the value follows that row. 'pass': the scan exited 0 and skill-check reported no finding, which does not show that the scan found nothing. 'warn': it reported findings. 'skipped': it did not run (skill-check unavailable, or the scan could not run, such as without SNYK_TOKEN). Absent on 'no-changes', 'detect-only' and 'dry-run', which run no scan, at every halt, and when that row is missing or starts with another word."
96
+ },
92
97
  "headless_decisions": {
93
98
  "type": "array",
94
99
  "items": {
@@ -136,7 +141,7 @@
136
141
  "warnings": {
137
142
  "type": "array",
138
143
  "items": { "type": "string" },
139
- "description": "Non-fatal anomalies surfaced during the run. May include workspace_drift_overridden, scope_reconciliation_post entries, qmd indexing failures, source-tree:, source-not-fetched, source-version-lower, file-diff-unavailable, no-baseline-time, moved-check-skipped and unknown-language (detect-changes.md §2.1 Category A), new-files-not-checked (Category D: a skill with no brief, so no new script or asset was looked for), workspace-clone-not-updated, target-ref-not-recorded, source-tree-not-removed, run-lock-replaced (init.md §1b took over a stale run lock) and run-lock-not-released entries, unconsumed-test-report (init.md §4b: a failing test report newer than the skill that this normal run did not apply), interrupted-run-cleaned and interrupted-run-not-cleaned (init.md §1b undid what an update whose stale lock it replaced had written), proposed-amendment: entries (a --detect-only or --dry-run run's skill brief decisions, not written), doc-fetch-failed, doc-not-hashed and doc-drift-not-checked (a docs-only skill's documents), rollback-incomplete (a halt could not undo all of this run's writes), run-state-not-finished, public-api-counted-by-hand: entries (write.md §2: the recipe runner exited 2, so the public names were found without it; each quotes the runner's error line), customization_resolver_unavailable, test-report: entries (what the test report helpers could not read, and test-report: not routed: for each gap a gap-driven run does not repair), etc. Empty when none."
144
+ "description": "Non-fatal anomalies surfaced during the run. May include workspace_drift_overridden, scope_reconciliation_post entries, qmd indexing failures, source-tree:, source-not-fetched, source-version-lower, file-diff-unavailable, no-baseline-time, moved-check-skipped and unknown-language (detect-changes.md §2.1 Category A), new-files-not-checked (Category D: a skill with no brief, so no new script or asset was looked for), workspace-clone-not-updated, target-ref-not-recorded, source-tree-not-removed, run-lock-replaced (init.md §1b took over a stale run lock) and run-lock-not-released entries, unconsumed-test-report (init.md §4b: a failing test report newer than the skill that this normal run did not apply), interrupted-run-cleaned and interrupted-run-not-cleaned (init.md §1b undid what an update whose stale lock it replaced had written), proposed-amendment: entries (a --detect-only or --dry-run run's skill brief decisions, not written), doc-fetch-failed, doc-not-hashed and doc-drift-not-checked (a docs-only skill's documents), rollback-incomplete (a halt could not undo all of this run's writes), run-state-not-finished, public-api-counted-by-hand: entries (write.md §2: the recipe runner exited 2, so the public names were found without it; each quotes the runner's error line), customization_resolver_unavailable, test-report: entries (what the test report helpers could not read, and test-report: not routed: for each gap a gap-driven run does not repair), result_file_write_failed (a result file not written, or a version forge folder not created), etc. Empty when none."
140
145
  },
141
146
  "error": {
142
147
  "oneOf": [
@@ -111,7 +111,7 @@
111
111
  "result_path": {
112
112
  "type": ["string", "null"],
113
113
  "minLength": 1,
114
- "description": "The per-run result file the emitter wrote in the forge data folder, verify-stack-result-<YYYYMMDD-HHmmss>.json, named after the run's own stamp (its run_id, the stamp the timestamped report's file name carries; -2, -3, ... appended when a run already took the name), beside its verify-stack-result-latest.json copy. Stamped by the emitter, never typed. Null on every halt, which writes no result file, and when the write failed (warnings say why)."
114
+ "description": "The per-run result file the emitter wrote in the forge data folder, verify-stack-result-<YYYYMMDD-HHmmss>.json, named after the run's own stamp (its run_id, the stamp the timestamped report's file name carries; -2, -3, ... appended when a run already took the name), beside its verify-stack-result-latest.json copy. Stamped by the emitter, never typed. Null on every halt, which writes no result file, when --result-dir names no folder (nothing is written, and no warning is added), and when the per-run record could not be written (a result_file_write_failed warning says why); a -latest copy that could not be written leaves it set, and a result_file_write_failed warning names the copy."
115
115
  },
116
116
  "headless_decisions": {
117
117
  "type": "array",
@@ -1023,7 +1023,10 @@ NOT_ROUTED_REASONS = {
1023
1023
  "provenance-unverified": (
1024
1024
  "the line-check rules could not verify the line, or the recorded line is not a number, so a person checks "
1025
1025
  "by hand which line defines the export and, when it is not the recorded one, sets its provenance "
1026
- "`source_line` to that line"),
1026
+ "`source_line` to that line; when the gap names an alias, no line of the cited file defines the export, so "
1027
+ "a person records the rename in its provenance entry (`export_name` the name the alias renames, "
1028
+ "`reexported_as` the export) only when no other entry has that `export_name` and the alias does not "
1029
+ "rename a default export, and otherwise cites the alias line, as the remediation says"),
1027
1030
  "denominator-inflation": (
1028
1031
  "it concerns the brief's scope and the count design, which a person decides in the skill brief, as the "
1029
1032
  "gap's remediation says"),
@@ -123,7 +123,9 @@ summarize
123
123
  stderr, on stdout otherwise): {"batch_summary": true, "targets_total",
124
124
  "succeeded", "failed", "status", "fail_fast_triggered", "exit_code",
125
125
  "summary_path"}, with "warnings" when a summary file could not be
126
- written. A batch already summarized prints its event again.
126
+ written, and "batch_file", the path of the run folder's batch.jsonl,
127
+ when summary_path is null. A batch already summarized prints its event
128
+ again.
127
129
  The batch's result is that event and the file its summary_path names
128
130
  (with summary_path null, when the per-batch file could not be written,
129
131
  the event and batch.jsonl; a summary_write_failed warning beside a set
@@ -625,13 +627,15 @@ def _write_json(path: Path, value: dict) -> None:
625
627
  raise
626
628
 
627
629
 
628
- def summary_event(summary: dict) -> dict:
630
+ def summary_event(summary: dict, run_dir: Path) -> dict:
629
631
  event = {"batch_summary": True}
630
632
  for key in ("targets_total", "succeeded", "failed", "status", "fail_fast_triggered", "exit_code",
631
633
  "summary_path"):
632
634
  event[key] = summary.get(key)
633
635
  if summary.get("warnings"):
634
636
  event["warnings"] = summary["warnings"]
637
+ if summary.get("summary_path") is None:
638
+ event["batch_file"] = (run_dir / BATCH_FILE).as_posix() # no summary file: batch.jsonl holds the results
635
639
  return event
636
640
 
637
641
 
@@ -648,7 +652,7 @@ def cmd_summarize(run_dir: Path, output_dir: Path) -> dict:
648
652
  state = load(run_dir)
649
653
  name = workflow(state)
650
654
  if state["summary"] is not None:
651
- return summary_event(state["summary"])
655
+ return summary_event(state["summary"], run_dir)
652
656
  left, stopped = pending(state), _stop_early(state)
653
657
  if left and not stopped:
654
658
  raise BatchError(f"{len(left)} target(s) of the batch are still pending: run next and finish "
@@ -692,7 +696,7 @@ def cmd_summarize(run_dir: Path, output_dir: Path) -> dict:
692
696
  inside = output_dir.resolve().is_relative_to(run_dir.resolve())
693
697
  if not failed and summary_path is not None and not inside and run_dir.name.startswith(f"{name}-"):
694
698
  _remove_tree(run_dir) # kept when a target halted, or when batch.jsonl is the only record
695
- return summary_event(final)
699
+ return summary_event(final, run_dir)
696
700
 
697
701
 
698
702
  # ─── CLI ─────────────────────────────────────────────────────────────────────