bmad-module-skill-forge 6.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +1 -1
- package/docs/_data/pinned.yaml +9 -10
- package/docs/campaign.md +2 -2
- package/docs/skill-model.md +3 -3
- package/docs/verifying-a-skill.md +17 -18
- package/docs/workflows.md +10 -10
- package/package.json +1 -1
- package/src/shared/references/description-guard-protocol.md +12 -10
- package/src/shared/references/output-contract-schema.md +1 -1
- package/src/shared/scripts/schemas/skf-analyze-result-envelope.v1.json +1 -1
- package/src/shared/scripts/schemas/skf-audit-result-envelope.v1.json +1 -1
- package/src/shared/scripts/schemas/skf-campaign-result-envelope.v1.json +38 -0
- package/src/shared/scripts/schemas/skf-create-skill-result-envelope.v1.json +5 -0
- package/src/shared/scripts/schemas/skf-drop-skill-result-envelope.v1.json +1 -1
- package/src/shared/scripts/schemas/skf-export-result-envelope.v1.json +1 -1
- package/src/shared/scripts/schemas/skf-quick-skill-result-envelope.v1.json +8 -3
- package/src/shared/scripts/schemas/skf-refine-architecture-result-envelope.v1.json +1 -1
- package/src/shared/scripts/schemas/skf-stack-result-envelope.v1.json +6 -1
- package/src/shared/scripts/schemas/skf-test-result-envelope.v1.json +1 -1
- package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +7 -2
- package/src/shared/scripts/schemas/skf-verify-stack-result-envelope.v1.json +1 -1
- package/src/shared/scripts/skf-parse-gaps.py +4 -1
- package/src/shared/scripts/skf-quick-batch.py +24 -7
- package/src/shared/scripts/skf-verify-provenance-completeness.py +809 -85
- package/src/skf-analyze-source/customize.toml +3 -2
- package/src/skf-analyze-source/references/auto-docs-only.md +2 -2
- package/src/skf-analyze-source/references/generate-briefs.md +5 -3
- package/src/skf-analyze-source/references/headless-contract.md +2 -2
- package/src/skf-analyze-source/references/step-auto-scope.md +2 -2
- package/src/skf-audit-skill/customize.toml +4 -3
- package/src/skf-audit-skill/references/report.md +1 -1
- package/src/skf-campaign/assets/campaign-state-schema.json +58 -0
- package/src/skf-campaign/references/campaign-contracts.md +1 -1
- package/src/skf-campaign/references/step-06-batch.md +2 -2
- package/src/skf-campaign/references/step-07-capstone.md +3 -3
- package/src/skf-campaign/references/step-08-verify.md +3 -3
- package/src/skf-campaign/references/step-09-refine.md +3 -3
- package/src/skf-campaign/references/step-11-maintenance.md +1 -1
- package/src/skf-campaign/scripts/campaign-render-batch.py +43 -10
- package/src/skf-campaign/scripts/campaign-report.py +60 -4
- package/src/skf-campaign/scripts/campaign-state.py +82 -17
- package/src/skf-campaign/templates/campaign-report-template.md +4 -0
- package/src/skf-create-skill/SKILL.md +2 -2
- package/src/skf-create-skill/assets/skill-sections.md +2 -2
- package/src/skf-create-skill/customize.toml +4 -4
- package/src/skf-create-skill/references/batch-mode.md +3 -1
- package/src/skf-create-skill/references/report.md +9 -4
- package/src/skf-create-skill/references/validate.md +49 -29
- package/src/skf-create-stack-skill/references/invocation-contract.md +1 -1
- package/src/skf-create-stack-skill/references/report.md +3 -3
- package/src/skf-create-stack-skill/references/validate.md +21 -8
- package/src/skf-drop-skill/references/invocation-contract.md +1 -1
- package/src/skf-export-skill/references/summary.md +1 -1
- package/src/skf-quick-skill/SKILL.md +1 -1
- package/src/skf-quick-skill/references/batch-mode.md +30 -2
- package/src/skf-quick-skill/references/compile.md +2 -0
- package/src/skf-quick-skill/references/finalize.md +3 -2
- package/src/skf-quick-skill/references/halt-contract.md +3 -3
- package/src/skf-quick-skill/references/quick-extract.md +2 -1
- package/src/skf-quick-skill/references/write-and-validate.md +27 -11
- package/src/skf-refine-architecture/customize.toml +1 -1
- package/src/skf-refine-architecture/references/report.md +2 -2
- package/src/skf-rename-skill/references/report.md +1 -1
- package/src/skf-test-skill/references/coverage-check-tiers.md +3 -3
- package/src/skf-test-skill/references/coverage-check.md +4 -4
- package/src/skf-test-skill/references/inventory-contract.md +1 -1
- package/src/skf-test-skill/references/invocation-contract.md +1 -1
- package/src/skf-test-skill/references/report.md +1 -1
- package/src/skf-test-skill/references/scoring-rules.md +2 -2
- package/src/skf-test-skill/references/signature-comparison-rules.md +7 -2
- package/src/skf-test-skill/scripts/gap-ledger.py +196 -46
- package/src/skf-test-skill/scripts/load-coverage-inputs.py +253 -17
- package/src/skf-test-skill/scripts/score-signatures.py +9 -13
- package/src/skf-test-skill/scripts/validate-inventory.py +91 -12
- package/src/skf-update-skill/customize.toml +1 -1
- package/src/skf-update-skill/references/gap-driven.md +1 -1
- package/src/skf-update-skill/references/invocation-contract.md +1 -1
- package/src/skf-update-skill/references/merge.md +1 -1
- package/src/skf-update-skill/references/report.md +27 -9
- package/src/skf-update-skill/references/write.md +24 -12
- package/src/skf-verify-stack/references/report.md +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "https://github.com/armelhbobdad/bmad-module-skill-forge/src/shared/scripts/schemas/skf-quick-skill-result-envelope.v1.json",
|
|
4
4
|
"title": "SKF_QUICK_SKILL_RESULT_JSON envelope (v1)",
|
|
5
|
-
"description": "Single-line JSON document skf-quick-skill prints, prefixed with the literal 'SKF_QUICK_SKILL_RESULT_JSON: ': on stdout when a run finishes (step 6, finalize), and on stderr at every HARD HALT. The shared emitter (skf-emit-result-envelope.py) builds both from a staged payload, so pipelines branch on status, exit_code and halt_reason without parsing the human-readable output. A --batch run prints one per target.",
|
|
5
|
+
"description": "Single-line JSON document skf-quick-skill prints, prefixed with the literal 'SKF_QUICK_SKILL_RESULT_JSON: ': on stdout when a run finishes (step 6, finalize), and on stderr at every HARD HALT. The shared emitter (skf-emit-result-envelope.py) builds both from a staged payload, so pipelines branch on status, exit_code and halt_reason without parsing the human-readable output. A --batch run prints one per target, and a batch-level halt line of its own when the batch stops outside a target: phase on-activation when a single-target flag came with --batch, phase batch-mode when the batch cannot start or stops because next, a second record or summarize failed.",
|
|
6
6
|
"$defs": {
|
|
7
7
|
"skf-envelope": {
|
|
8
8
|
"description": "The shared emitter's settings for this workflow, read by skf-emit-result-envelope.py: the workflow, the line's prefix, the wrapper, the status a halt carries, the exit code of each halt_reason and the result file stem. Nothing in the schema references this entry, so it constrains no envelope.",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"phase": {
|
|
59
59
|
"type": ["string", "null"],
|
|
60
60
|
"minLength": 1,
|
|
61
|
-
"description": "The step slug where the HARD HALT fired (resolve-target, ecosystem-check, quick-extract, compile, write-and-validate or finalize), on-activation for a halt before the run starts (SKILL.md On Activation, or batch mode's refusal of --description and --exports), or batch-mode for a batch that cannot start (batch mode
|
|
61
|
+
"description": "The step slug where the HARD HALT fired (resolve-target, ecosystem-check, quick-extract, compile, write-and-validate or finalize), on-activation for a halt before the run starts (SKILL.md On Activation, or batch mode's refusal of --description and --exports), or batch-mode for a batch that cannot start or go on (batch mode sections 1 to 4). Null on success."
|
|
62
62
|
},
|
|
63
63
|
"halt_reason": {
|
|
64
64
|
"type": ["string", "null"],
|
|
@@ -187,6 +187,11 @@
|
|
|
187
187
|
"security": { "type": "integer", "minimum": 0 }
|
|
188
188
|
},
|
|
189
189
|
"description": "The advisory issues step 5's validators returned for each heading of its validation report: skill_md (skill-check's diagnostics, or the frontmatter validator's issues without it, plus the body checks), context_snippet, metadata and security (skill-check's security findings). Auto-fixed issues count nothing, and a check that did not run counts 0."
|
|
190
|
+
},
|
|
191
|
+
"security_result": {
|
|
192
|
+
"type": "string",
|
|
193
|
+
"enum": ["pass", "warn", "skipped"],
|
|
194
|
+
"description": "The outcome of step 5's security scan. 'pass': the scan exited 0 and skill-check reported no finding, which does not show that the scan found nothing (without --ci, agent-scan also exits 0 when its analysis fails). 'warn': it reported findings. 'skipped': it did not run (skill-check unavailable or stopped before it validated, or the scan could not run, such as without SNYK_TOKEN). validation_issues.security reads 0 for a skipped scan as for a passed one, so read this field to tell them apart."
|
|
190
195
|
}
|
|
191
196
|
},
|
|
192
197
|
"description": "Empty at a HARD HALT. On success, the run's facts a pipeline branches on."
|
|
@@ -202,7 +207,7 @@
|
|
|
202
207
|
},
|
|
203
208
|
"run_id": {
|
|
204
209
|
"type": ["string", "null"],
|
|
205
|
-
"description": "The run folder's id, stamped by the emitter; a --batch target's is the batch run's id and the target's number."
|
|
210
|
+
"description": "The run folder's id, stamped by the emitter; a --batch target's is the batch run's id and the target's number. Null on a line emitted without --run-dir (On Activation's run-folder halt, batch mode's write-failure halts)."
|
|
206
211
|
},
|
|
207
212
|
"result_path": {
|
|
208
213
|
"type": ["string", "null"],
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
"result_path": {
|
|
124
124
|
"type": ["string", "null"],
|
|
125
125
|
"minLength": 1,
|
|
126
|
-
"description": "The per-run result file the emitter wrote in the output folder, refine-architecture-result-<YYYYMMDD-HHmmss>.json (UTC), beside its refine-architecture-result-latest.json copy. Stamped by the emitter, never typed; step 6 passes it to the on_complete hook. Null on every halt, which writes no result file, and when the
|
|
126
|
+
"description": "The per-run result file the emitter wrote in the output folder, refine-architecture-result-<YYYYMMDD-HHmmss>.json (UTC), beside its refine-architecture-result-latest.json copy. Stamped by the emitter, never typed; step 6 passes it to the on_complete hook. 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."
|
|
127
127
|
},
|
|
128
128
|
"headless_decisions": {
|
|
129
129
|
"type": "array",
|
|
@@ -76,6 +76,11 @@
|
|
|
76
76
|
"minimum": 0,
|
|
77
77
|
"description": "The skill-check score step 8 recorded for the committed package; null when skill-check did not run and at every halt. It is not a test-skill score: [TS] grades the stack."
|
|
78
78
|
},
|
|
79
|
+
"security_result": {
|
|
80
|
+
"type": "string",
|
|
81
|
+
"enum": ["pass", "warn", "skipped"],
|
|
82
|
+
"description": "The outcome of step 8's security scan. 'pass': the scan exited 0 and skill-check reported no finding, which does not show that the scan found nothing (without --ci, agent-scan also exits 0 when its analysis fails). 'warn': it reported findings. 'skipped': it did not run (skill-check unavailable or stopped before it validated, or the scan could not run, such as without SNYK_TOKEN). Absent at every halt."
|
|
83
|
+
},
|
|
79
84
|
"exit_code": {
|
|
80
85
|
"type": "integer",
|
|
81
86
|
"enum": [0, 2, 3, 4, 5, 6],
|
|
@@ -130,7 +135,7 @@
|
|
|
130
135
|
},
|
|
131
136
|
"result_path": {
|
|
132
137
|
"type": ["string", "null"],
|
|
133
|
-
"description": "The per-run create-stack-skill-result-<YYYYMMDD-HHmmss>.json the emitter wrote in {forge_version} on success, beside its -latest copy; null at a halt and when the
|
|
138
|
+
"description": "The per-run create-stack-skill-result-<YYYYMMDD-HHmmss>.json the emitter wrote in {forge_version} on success, beside its -latest copy; null at a halt, 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
139
|
},
|
|
135
140
|
"headless_decisions": {
|
|
136
141
|
"type": "array",
|
|
@@ -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
|
|
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
|
|
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,18 @@ 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
|
|
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.
|
|
129
|
+
The batch's result is that event and the file its summary_path names
|
|
130
|
+
(with summary_path null, when the per-batch file could not be written,
|
|
131
|
+
the event and batch.jsonl; a summary_write_failed warning beside a set
|
|
132
|
+
summary_path means only the copy failed): <stem>-latest.json holds the
|
|
133
|
+
last batch summary written, which may be another batch's, so it never
|
|
134
|
+
stands for this batch, and a batch cut off before summarize leaves it in
|
|
135
|
+
place. A summarize on a run folder that is gone (a batch whose targets
|
|
136
|
+
all succeeded, summarized and removed) exits 1, naming the per-batch
|
|
137
|
+
file whose input_file (or input_briefs) is this batch's.
|
|
127
138
|
|
|
128
139
|
The batch file is one JSON object per line, so a write cut short spoils at
|
|
129
140
|
most its own line, which is skipped when read: the next write ends that
|
|
@@ -616,26 +627,32 @@ def _write_json(path: Path, value: dict) -> None:
|
|
|
616
627
|
raise
|
|
617
628
|
|
|
618
629
|
|
|
619
|
-
def summary_event(summary: dict) -> dict:
|
|
630
|
+
def summary_event(summary: dict, run_dir: Path) -> dict:
|
|
620
631
|
event = {"batch_summary": True}
|
|
621
632
|
for key in ("targets_total", "succeeded", "failed", "status", "fail_fast_triggered", "exit_code",
|
|
622
633
|
"summary_path"):
|
|
623
634
|
event[key] = summary.get(key)
|
|
624
635
|
if summary.get("warnings"):
|
|
625
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
|
|
626
639
|
return event
|
|
627
640
|
|
|
628
641
|
|
|
629
642
|
def cmd_summarize(run_dir: Path, output_dir: Path) -> dict:
|
|
630
643
|
if not (run_dir / BATCH_FILE).exists() and not run_dir.exists():
|
|
631
|
-
|
|
644
|
+
create = run_dir.name.startswith(f"{CREATE_WORKFLOW}-")
|
|
645
|
+
stem = SUMMARY_STEMS[CREATE_WORKFLOW if create else WORKFLOW]
|
|
646
|
+
names = "input_briefs name its briefs" if create else "input_file names its batch file"
|
|
632
647
|
raise BatchError(f"no batch in {run_dir.as_posix()}: a batch whose targets all succeeded removes its "
|
|
633
|
-
f"run folder once summarized; its summary is "
|
|
634
|
-
f"{
|
|
648
|
+
f"run folder once summarized; its summary is the {stem}-<YYYYMMDD-HHmmss>.json in "
|
|
649
|
+
f"{output_dir.as_posix()} whose {names}, and "
|
|
650
|
+
f"{(output_dir / f'{stem}-latest.json').as_posix()} holds the last batch summary "
|
|
651
|
+
f"written, which may be another batch's")
|
|
635
652
|
state = load(run_dir)
|
|
636
653
|
name = workflow(state)
|
|
637
654
|
if state["summary"] is not None:
|
|
638
|
-
return summary_event(state["summary"])
|
|
655
|
+
return summary_event(state["summary"], run_dir)
|
|
639
656
|
left, stopped = pending(state), _stop_early(state)
|
|
640
657
|
if left and not stopped:
|
|
641
658
|
raise BatchError(f"{len(left)} target(s) of the batch are still pending: run next and finish "
|
|
@@ -679,7 +696,7 @@ def cmd_summarize(run_dir: Path, output_dir: Path) -> dict:
|
|
|
679
696
|
inside = output_dir.resolve().is_relative_to(run_dir.resolve())
|
|
680
697
|
if not failed and summary_path is not None and not inside and run_dir.name.startswith(f"{name}-"):
|
|
681
698
|
_remove_tree(run_dir) # kept when a target halted, or when batch.jsonl is the only record
|
|
682
|
-
return summary_event(final)
|
|
699
|
+
return summary_event(final, run_dir)
|
|
683
700
|
|
|
684
701
|
|
|
685
702
|
# ─── CLI ─────────────────────────────────────────────────────────────────────
|