bmad-module-skill-forge 1.7.0 → 1.9.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/docs/_data/pinned.yaml +1 -1
- package/docs/bmad-synergy.md +16 -0
- package/docs/deepwiki.md +89 -0
- package/docs/verifying-a-skill.md +8 -2
- package/docs/workflows.md +17 -11
- package/package.json +2 -2
- package/src/shared/_known-workarounds.yaml +155 -0
- package/src/shared/references/pipeline-contracts.md +11 -2
- package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +6 -1
- package/src/shared/scripts/schemas/skill-brief.v1.json +6 -1
- package/src/shared/scripts/skf-detect-docs.py +412 -0
- package/src/shared/scripts/skf-emit-brief-result-envelope.py +12 -1
- package/src/shared/scripts/skf-forge-tier-rw.py +73 -0
- package/src/shared/scripts/skf-preapply.py +192 -0
- package/src/shared/scripts/skf-shape-detect.py +563 -0
- package/src/shared/scripts/skf-validate-frontmatter.py +56 -5
- package/src/shared/scripts/skf-validate-pins.py +368 -0
- package/src/skf-analyze-source/SKILL.md +26 -20
- package/src/skf-analyze-source/assets/skill-brief-schema.md +2 -1
- package/src/skf-analyze-source/references/identify-units.md +41 -4
- package/src/skf-analyze-source/references/init.md +36 -0
- package/src/skf-analyze-source/references/scan-project.md +5 -2
- package/src/skf-analyze-source/references/step-auto-scope.md +525 -0
- package/src/skf-analyze-source/references/step-shape-detect.md +79 -0
- package/src/skf-analyze-source/references/unit-detection-heuristics.md +16 -0
- package/src/skf-audit-skill/SKILL.md +1 -0
- package/src/skf-audit-skill/assets/drift-report-template.md +6 -0
- package/src/skf-audit-skill/references/report.md +4 -0
- package/src/skf-audit-skill/references/severity-classify.md +1 -1
- package/src/skf-audit-skill/references/step-doc-drift.md +147 -0
- package/src/skf-brief-skill/SKILL.md +7 -3
- package/src/skf-brief-skill/references/gather-intent.md +14 -0
- package/src/skf-brief-skill/references/step-auto-brief.md +171 -0
- package/src/skf-brief-skill/references/step-auto-validate.md +209 -0
- package/src/skf-create-skill/SKILL.md +3 -0
- package/src/skf-create-skill/assets/skill-sections.md +2 -0
- package/src/skf-create-skill/references/compile.md +1 -1
- package/src/skf-create-skill/references/generate-artifacts.md +5 -10
- package/src/skf-create-skill/references/step-auto-shard.md +110 -0
- package/src/skf-create-skill/references/step-doc-rot.md +109 -0
- package/src/skf-create-skill/references/step-doc-sources.md +114 -0
- package/src/skf-create-stack-skill/references/generate-output.md +5 -5
- package/src/skf-forger/SKILL.md +8 -2
- package/src/skf-test-skill/SKILL.md +8 -7
- package/src/skf-test-skill/customize.toml +2 -1
- package/src/skf-test-skill/references/external-validators.md +1 -1
- package/src/skf-test-skill/references/init.md +16 -1
- package/src/skf-test-skill/references/report.md +5 -1
- package/src/skf-test-skill/references/score.md +95 -6
- package/src/skf-test-skill/references/source-access-protocol.md +14 -0
- package/src/skf-test-skill/references/step-hard-gate.md +73 -0
- package/src/skf-test-skill/templates/test-report-template.md +1 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
nextStepFile: 'step-doc-rot.md'
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!-- Config: communicate in {communication_language}. -->
|
|
6
|
+
|
|
7
|
+
# Step 5b: Auto-Shard
|
|
8
|
+
|
|
9
|
+
## STEP GOAL:
|
|
10
|
+
|
|
11
|
+
Proactively reduce oversized SKILL.md bodies to under 400 lines by extracting Tier 2 sections (`## Full` headings) to `references/`, providing 100 lines of headroom below the 500-line `body.max_lines` ceiling. Tier 1 sections always remain inline. If the body is already within budget, skip cleanly.
|
|
12
|
+
|
|
13
|
+
## Rules
|
|
14
|
+
|
|
15
|
+
- Auto-proceed step — no user interaction required
|
|
16
|
+
- Graceful skip — if body is under threshold, proceed without modification
|
|
17
|
+
- Only extract Tier 2 sections (identified by `## Full` heading prefix)
|
|
18
|
+
- Tier 1 sections must NEVER be moved to references/
|
|
19
|
+
- Do not modify frontmatter — only body content and references/ directory
|
|
20
|
+
- Do not invoke `npx skill-check split-body` — this step uses direct extraction
|
|
21
|
+
- Do not invoke the Description Guard Protocol — frontmatter is untouched
|
|
22
|
+
|
|
23
|
+
## MANDATORY SEQUENCE
|
|
24
|
+
|
|
25
|
+
### §1. Count Body Lines
|
|
26
|
+
|
|
27
|
+
Count all lines in the staging SKILL.md between the frontmatter closing `---` and EOF. Exclude trailing blank lines from the count. Store as `body_line_count`.
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
body_lines_before = body_line_count
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**IF `body_line_count` <= 400:**
|
|
34
|
+
- Log: `"auto-shard: skipped (body {body_line_count} lines)"`
|
|
35
|
+
- Set context: `auto_shard_triggered: false`, `sections_extracted: []`, `body_lines_before: {body_line_count}`, `body_lines_after: {body_line_count}`
|
|
36
|
+
- Skip to §6 (Auto-Proceed)
|
|
37
|
+
|
|
38
|
+
**ELSE:** Proceed to §2.
|
|
39
|
+
|
|
40
|
+
### §2. Selective Shard — Tier 2 Extraction
|
|
41
|
+
|
|
42
|
+
Identify Tier 2 sections by their `## Full` heading prefix:
|
|
43
|
+
- `## Full API Reference` → `references/full-api-reference.md`
|
|
44
|
+
- `## Full Type Definitions` → `references/full-type-definitions.md`
|
|
45
|
+
- `## Full Integration Patterns` → `references/full-integration-patterns.md`
|
|
46
|
+
|
|
47
|
+
Sort identified sections by line count descending (extract largest first).
|
|
48
|
+
|
|
49
|
+
**FOR EACH Tier 2 section (largest first):**
|
|
50
|
+
|
|
51
|
+
1. Extract the full section content (from `## Full` heading to the next `##` heading or EOF)
|
|
52
|
+
2. Derive the reference filename from the heading: kebab-case (as shown above)
|
|
53
|
+
3. Write the extracted content (preserving the `##` heading) to `<staging-skill-dir>/references/{filename}`
|
|
54
|
+
4. Replace the extracted section in SKILL.md with a cross-reference blockquote:
|
|
55
|
+
```markdown
|
|
56
|
+
> See [Full API Reference](references/full-api-reference.md)
|
|
57
|
+
```
|
|
58
|
+
5. Re-count body lines
|
|
59
|
+
6. **IF `body_line_count` <= 400:** stop extracting, proceed to §3
|
|
60
|
+
|
|
61
|
+
Track: `sections_extracted: [{heading names}]`
|
|
62
|
+
|
|
63
|
+
### §3. Tier 1 Preservation Check
|
|
64
|
+
|
|
65
|
+
Verify ALL Tier 1 sections remain inline in SKILL.md after extraction. Check for these headings:
|
|
66
|
+
|
|
67
|
+
- `## Overview`
|
|
68
|
+
- `## Quick Start`
|
|
69
|
+
- `## Common Workflows`
|
|
70
|
+
- `## Key API Summary` (or `## Component Catalog` for component-library scope)
|
|
71
|
+
- `## Migration & Deprecation Warnings` (conditional — only check if it was present before extraction)
|
|
72
|
+
- `## Key Types`
|
|
73
|
+
- `## Architecture at a Glance`
|
|
74
|
+
- `## CLI` (conditional — only check if present before extraction)
|
|
75
|
+
- `## Scripts & Assets` (conditional — only check if present before extraction)
|
|
76
|
+
- `## Manual Sections` (conditional — only check if present before extraction)
|
|
77
|
+
|
|
78
|
+
**IF any Tier 1 section is missing after extraction:**
|
|
79
|
+
HALT: `"Auto-shard removed Tier 1 section {name}. Aborting."`
|
|
80
|
+
|
|
81
|
+
### §4. Post-Shard Validation
|
|
82
|
+
|
|
83
|
+
Re-count body lines after all Tier 2 sections have been extracted.
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
body_lines_after = body_line_count
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**IF `body_line_count` > 400 after all Tier 2 sections extracted:**
|
|
90
|
+
- Trim oversized Tier 1 sections: reduce `## Key API Summary` and `## Architecture at a Glance` content to fit within the 400-line budget
|
|
91
|
+
- Do NOT move any Tier 1 section to references/
|
|
92
|
+
- Re-count and update `body_lines_after`
|
|
93
|
+
|
|
94
|
+
### §5. Cross-Reference Integrity
|
|
95
|
+
|
|
96
|
+
**FOR EACH extracted reference file:**
|
|
97
|
+
1. Verify the file exists at `<staging-skill-dir>/references/{filename}`
|
|
98
|
+
2. Verify the cross-reference blockquote in SKILL.md contains a link resolving to the file
|
|
99
|
+
|
|
100
|
+
Log: `"auto-shard: {N} sections extracted, body reduced from {body_lines_before} to {body_lines_after} lines"`
|
|
101
|
+
|
|
102
|
+
Set context:
|
|
103
|
+
- `auto_shard_triggered: true`
|
|
104
|
+
- `sections_extracted: [{heading names}]`
|
|
105
|
+
- `body_lines_before: {before}`
|
|
106
|
+
- `body_lines_after: {after}`
|
|
107
|
+
|
|
108
|
+
### §6. Auto-Proceed
|
|
109
|
+
|
|
110
|
+
Load, read the entire file, then execute `{nextStepFile}`.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
nextStepFile: 'validate.md'
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!-- Config: communicate in {communication_language}. -->
|
|
6
|
+
|
|
7
|
+
# Step 5c: Doc-Rot
|
|
8
|
+
|
|
9
|
+
## STEP GOAL:
|
|
10
|
+
|
|
11
|
+
Scan feeder artifacts for doc-rot correction indicators and annotate the compiled SKILL.md with `## CORRECTION` blocks. Matching is grep-based and deterministic — no AI judgment is used for detection.
|
|
12
|
+
|
|
13
|
+
## Rules
|
|
14
|
+
|
|
15
|
+
- Auto-proceed step — no user interaction required
|
|
16
|
+
- Graceful skip — if no corrections are found in any feeder artifact, proceed without modification
|
|
17
|
+
- Only modify the compiled SKILL.md (correction block insertion) and references (if corrections target referenced content)
|
|
18
|
+
- Do not modify feeder artifacts (evidence-report.md, provenance-map.json, metadata.json) — this step READS them only
|
|
19
|
+
- Do not modify frontmatter — correction blocks are body content only
|
|
20
|
+
- Matching is case-insensitive substring grep — no semantic or AI-based assessment
|
|
21
|
+
|
|
22
|
+
## MANDATORY SEQUENCE
|
|
23
|
+
|
|
24
|
+
### §1. Locate Feeder Artifacts
|
|
25
|
+
|
|
26
|
+
Identify the feeder artifacts in the staging directory for the current skill:
|
|
27
|
+
|
|
28
|
+
1. **Evidence report:** `{forge_data_folder}/{skill-name}/{version}/evidence-report.md`
|
|
29
|
+
2. **Provenance map:** `{forge_data_folder}/{skill-name}/{version}/provenance-map.json` — focus on T2/T3 entries with temporal annotations
|
|
30
|
+
3. **Temporal context:** changelogs, migration guides, and issue/PR data fetched by step 3b and enriched by step 4 (available in workflow context)
|
|
31
|
+
4. **Compiled SKILL.md:** the staged SKILL.md itself — check for `[QMD:...]` or `[DOC:...]` annotations referencing corrections
|
|
32
|
+
|
|
33
|
+
For each artifact, attempt to load its content. If an artifact does not exist or is empty, skip it — this is not an error.
|
|
34
|
+
|
|
35
|
+
Store: `feeder_artifacts_scanned: [{list of artifacts that were loaded}]`
|
|
36
|
+
|
|
37
|
+
### §2. Grep for Correction Indicators
|
|
38
|
+
|
|
39
|
+
Scan each loaded feeder artifact for the following correction patterns. All matches are **case-insensitive substring matches** — no regex interpretation, no semantic analysis.
|
|
40
|
+
|
|
41
|
+
| Pattern | Category |
|
|
42
|
+
|---------|----------|
|
|
43
|
+
| `deprecated` | Deprecation |
|
|
44
|
+
| `@deprecated` | Deprecation |
|
|
45
|
+
| `breaking change` | Breaking change |
|
|
46
|
+
| `BREAKING` | Breaking change |
|
|
47
|
+
| `removed in` | Removal |
|
|
48
|
+
| `was removed` | Removal |
|
|
49
|
+
| `renamed to` | Rename |
|
|
50
|
+
| `renamed from` | Rename |
|
|
51
|
+
| `superseded by` | Supersession |
|
|
52
|
+
| `replaced by` | Supersession |
|
|
53
|
+
| `no longer supported` | End of life |
|
|
54
|
+
| `migration required` | Migration |
|
|
55
|
+
| `signature changed` | Signature change |
|
|
56
|
+
|
|
57
|
+
For each match, record:
|
|
58
|
+
- `source`: the feeder artifact path or citation where the match was found
|
|
59
|
+
- `pattern`: the specific pattern string that matched
|
|
60
|
+
- `category`: the category from the table above
|
|
61
|
+
- `context_line`: the line or excerpt containing the match
|
|
62
|
+
- `affected`: the function name, API, or section the correction relates to (extract from surrounding context if identifiable; otherwise set to `"unknown"`)
|
|
63
|
+
|
|
64
|
+
Store all matches as `correction_matches: [{match records}]`
|
|
65
|
+
|
|
66
|
+
**IF `correction_matches` is empty:**
|
|
67
|
+
- Log: `"doc-rot: skipped (no correction indicators found in feeder artifacts)"`
|
|
68
|
+
- Set context: `doc_rot_triggered: false`, `corrections_added: 0`
|
|
69
|
+
- Skip to §5 (Auto-Proceed)
|
|
70
|
+
|
|
71
|
+
**ELSE:** Proceed to §3.
|
|
72
|
+
|
|
73
|
+
### §3. Annotate SKILL.md with Correction Blocks
|
|
74
|
+
|
|
75
|
+
For each entry in `correction_matches`, insert a `## CORRECTION` block into the compiled SKILL.md.
|
|
76
|
+
|
|
77
|
+
**Block format:**
|
|
78
|
+
|
|
79
|
+
```markdown
|
|
80
|
+
## CORRECTION
|
|
81
|
+
|
|
82
|
+
**Source:** {source}
|
|
83
|
+
**Pattern:** {pattern}
|
|
84
|
+
**Affected:** {affected}
|
|
85
|
+
**Detail:** {context_line}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Insertion rules:**
|
|
89
|
+
- **After the relevant API section** in SKILL.md if the `affected` function or section can be identified and located in the document
|
|
90
|
+
- **At the end of SKILL.md body** (before any trailing sections like `## Manual Sections`) if the affected section cannot be determined
|
|
91
|
+
- **Never inside frontmatter** — body content only
|
|
92
|
+
- Each correction block is self-contained with its own source citation
|
|
93
|
+
- Multiple corrections produce multiple `## CORRECTION` blocks
|
|
94
|
+
|
|
95
|
+
Store: `corrections_added: {count of blocks inserted}`
|
|
96
|
+
|
|
97
|
+
### §4. Log Results
|
|
98
|
+
|
|
99
|
+
Log: `"doc-rot: {corrections_added} correction blocks added from {feeder_artifacts_scanned_count} feeder artifacts"`
|
|
100
|
+
|
|
101
|
+
Set context:
|
|
102
|
+
- `doc_rot_triggered: true`
|
|
103
|
+
- `corrections_added: {count}`
|
|
104
|
+
- `feeder_artifacts_scanned: [{list}]`
|
|
105
|
+
- `correction_matches: [{match records}]`
|
|
106
|
+
|
|
107
|
+
### §5. Auto-Proceed
|
|
108
|
+
|
|
109
|
+
Load, read the entire file, then execute `{nextStepFile}`.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
---
|
|
2
|
+
nextStepFile: 'step-auto-shard.md'
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!-- Config: communicate in {communication_language}. -->
|
|
6
|
+
|
|
7
|
+
# Step 5a: Doc Sources
|
|
8
|
+
|
|
9
|
+
## STEP GOAL:
|
|
10
|
+
|
|
11
|
+
Record detected documentation pages and README with content hashes in metadata.json so that downstream audit (analyze-skill) can detect when upstream docs have changed since the skill was compiled.
|
|
12
|
+
|
|
13
|
+
## Rules
|
|
14
|
+
|
|
15
|
+
- Auto-proceed step — no user interaction required
|
|
16
|
+
- Graceful failure — if doc detection fails, skip with a warning and proceed to validate
|
|
17
|
+
- Do not modify any compiled artifact other than `metadata.json`
|
|
18
|
+
- Do not block the pipeline on any doc detection error
|
|
19
|
+
|
|
20
|
+
## MANDATORY SEQUENCE
|
|
21
|
+
|
|
22
|
+
### 1. Check for Upstream Doc Detection Results
|
|
23
|
+
|
|
24
|
+
Check if `doc_detection_results` is already populated in the workflow context (set by BS auto-brief in the deepwiki pipeline).
|
|
25
|
+
|
|
26
|
+
- **If upstream results exist:** use them directly, skip to step 3.
|
|
27
|
+
- **If no upstream results:** continue to step 2.
|
|
28
|
+
|
|
29
|
+
### 2. Run Doc Detection (if needed)
|
|
30
|
+
|
|
31
|
+
Check that `{source_repo}` is available from the skill brief.
|
|
32
|
+
|
|
33
|
+
**If `{source_repo}` is not available:**
|
|
34
|
+
- Set `doc_detection_results` to an empty array `[]`
|
|
35
|
+
- Add evidence note: `"Doc sources: detection skipped — no source_repo in brief"`
|
|
36
|
+
- Skip to step 3.
|
|
37
|
+
|
|
38
|
+
**If `{source_repo}` is available:**
|
|
39
|
+
|
|
40
|
+
Invoke the detect-docs script:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
uv run python src/shared/scripts/skf-detect-docs.py \
|
|
44
|
+
--repo-url {source_repo} \
|
|
45
|
+
[--local-path {source_path}] \
|
|
46
|
+
[--skip-pages-api]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Pass `--local-path {source_path}` when a local clone exists from the extraction step to avoid redundant cloning for `docs/` folder detection.
|
|
50
|
+
|
|
51
|
+
**Handle exit codes:**
|
|
52
|
+
- **Exit 0** (found ≥1 doc): parse JSON stdout as `doc_detection_results`
|
|
53
|
+
- **Exit 1** (none found): set `doc_detection_results` to empty array `[]`
|
|
54
|
+
- **Exit 2** (error): set `doc_detection_results` to empty array `[]`, add evidence note: `"Doc sources: detection partial — skf-detect-docs.py error (exit 2)"`
|
|
55
|
+
|
|
56
|
+
### 3. Ensure README Entry
|
|
57
|
+
|
|
58
|
+
After obtaining detection results, check if any entry has a URL matching `*/README.md` or `*/readme.md`.
|
|
59
|
+
|
|
60
|
+
**If a README entry already exists** from detection (e.g., `detected_via: "docs_folder"` found a README): keep it as-is.
|
|
61
|
+
|
|
62
|
+
**If no README entry exists,** add one:
|
|
63
|
+
|
|
64
|
+
- `url`: Construct from `{source_repo}/blob/main/README.md`
|
|
65
|
+
- `detected_via`: `"readme_always"`
|
|
66
|
+
- `content_hash`: Hash the README content using `sha256:{hexdigest}` convention:
|
|
67
|
+
- If `{source_path}` is available: read `{source_path}/README.md` locally with `encoding="utf-8"`, compute `sha256:{hexdigest}`
|
|
68
|
+
- Else: fetch via `gh api repos/{owner}/{repo}/readme --jq '.content'`, base64-decode, hash
|
|
69
|
+
- If README cannot be found or fetched: set `content_hash` to `null`
|
|
70
|
+
- `recorded_at`: current ISO-8601 timestamp with timezone
|
|
71
|
+
|
|
72
|
+
### 4. Build doc_sources Array
|
|
73
|
+
|
|
74
|
+
Map each detection result to the `doc_sources` schema:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"url": "{url from detection result}",
|
|
79
|
+
"detected_via": "{detected_via from detection result}",
|
|
80
|
+
"content_hash": "{content_hash from detection result — sha256:{hexdigest} or null}",
|
|
81
|
+
"recorded_at": "{current ISO-8601 timestamp with timezone}"
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Field mapping from `skf-detect-docs.py` output:
|
|
86
|
+
- `url` ← `url` (direct copy)
|
|
87
|
+
- `detected_via` ← `detected_via` (direct copy; or `"readme_always"` for the mandatory README entry)
|
|
88
|
+
- `content_hash` ← `content_hash` (direct copy, already in `sha256:{hexdigest}` format from story 1.2)
|
|
89
|
+
- `recorded_at` ← generated at step execution time (ISO-8601 with timezone)
|
|
90
|
+
|
|
91
|
+
Note: `content_type` from detect-docs output is NOT carried into `doc_sources`.
|
|
92
|
+
|
|
93
|
+
### 5. Update metadata.json
|
|
94
|
+
|
|
95
|
+
Read the staging `_bmad-output/{skill-name}/metadata.json` that compile (step 5) wrote.
|
|
96
|
+
|
|
97
|
+
**If the staging metadata.json is unreadable:** HALT — this indicates compile failed (critical, not doc-detection-related).
|
|
98
|
+
|
|
99
|
+
**Replace** the `doc_sources` field entirely (do not merge or append to stale data from prior compiles):
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
metadata["doc_sources"] = new_doc_sources # full replacement
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Write the updated metadata.json back to the staging directory.
|
|
106
|
+
|
|
107
|
+
Add evidence note summarizing the result:
|
|
108
|
+
- Success: `"Doc sources: {N} detected, README tracked"`
|
|
109
|
+
- Skip: `"Doc sources: detection skipped — {reason}"`
|
|
110
|
+
- Partial: `"Doc sources: detection partial — {N} found, {errors}"`
|
|
111
|
+
|
|
112
|
+
### 6. Auto-Proceed
|
|
113
|
+
|
|
114
|
+
Load, read the entire file, then execute `{nextStepFile}`.
|
|
@@ -226,19 +226,19 @@ Use the schema from `{provenanceMapSchemaPath}` — see that asset for the canon
|
|
|
226
226
|
|
|
227
227
|
### 8. Pre-Commit Frontmatter & Body-Size Gate
|
|
228
228
|
|
|
229
|
-
The full schema/frontmatter validation runs in step 8 (`validate.md`) — but that runs *after* commit-dir and flip-link have already published the package. The
|
|
229
|
+
The full schema/frontmatter validation runs in step 8 (`validate.md`) — but that runs *after* commit-dir and flip-link have already published the package. The most common non-auto-fixable `skill-check` hard rejects — a `description` over the 1024-char limit (which `skill-check --fix` cannot trim for you), a SKILL.md body over the `body.max_lines` limit (default **500**), and a body exceeding the `body.max_tokens` limit (default **5000**) — would therefore only surface on an already-committed, symlink-active artifact, forcing edits to the live `SKILL.md`. Additive re-composition of an already-large stack grows the body monotonically, so body overflow (lines or tokens) is the likeliest trigger. Catch all three here, while the package is still in `.skf-tmp`.
|
|
230
230
|
|
|
231
|
-
Resolve `{frontmatterValidator}` from `{frontmatterValidatorProbeOrder}` (first existing path wins). Run it against the **staged** `SKILL.md`, passing the real skill name so the directory-match check is not fooled by the `.skf-tmp` staging suffix,
|
|
231
|
+
Resolve `{frontmatterValidator}` from `{frontmatterValidatorProbeOrder}` (first existing path wins). Run it against the **staged** `SKILL.md`, passing the real skill name so the directory-match check is not fooled by the `.skf-tmp` staging suffix, `--max-body-lines 500` to assert the skill-check body-line limit, and `--max-body-tokens 5000` to assert the skill-check body-token limit pre-commit:
|
|
232
232
|
|
|
233
233
|
```bash
|
|
234
|
-
uv run {frontmatterValidator} {skill_staging}/SKILL.md --skill-dir-name {project_name}-stack --max-body-lines 500
|
|
234
|
+
uv run {frontmatterValidator} {skill_staging}/SKILL.md --skill-dir-name {project_name}-stack --max-body-lines 500 --max-body-tokens 5000
|
|
235
235
|
```
|
|
236
236
|
|
|
237
|
-
The validator emits JSON: `status` (`pass`/`warn`/`fail`), `issues[]` (each with `severity` ∈ `high|medium|low`, `field`, `message`), `body_lines` (the counted body size), and `summary`. Disposition:
|
|
237
|
+
The validator emits JSON: `status` (`pass`/`warn`/`fail`), `issues[]` (each with `severity` ∈ `high|medium|low`, `field`, `message`), `body_lines` (the counted body size), `body_tokens` (the estimated token count), and `summary`. Disposition:
|
|
238
238
|
|
|
239
239
|
- **`status` is `fail`, OR any `issues[]` entry has `severity` `high` or `medium`** — a hard violation that `npx skill-check` (step 8) would reject and `--fix` cannot auto-correct. HALT-to-fix **in staging**, then re-run the validator until it clears. Remediate by `field`:
|
|
240
240
|
- `description` / `name` / `compatibility` — trim/correct `{skill_staging}/SKILL.md` (e.g. shorten `description` to ≤ 1024 chars).
|
|
241
|
-
- `body` (`body lines N exceeds max 500`) — reduce the staged body: prefer a **selective split** of the largest Tier-2 section(s) into `{skill_staging}/references/`, keeping Tier-1 content inline (mirrors `validate.md` §3); or trim redundant content. Re-run the gate until `body_lines ≤ 500`.
|
|
241
|
+
- `body` (`body lines N exceeds max 500` or `body token estimate N exceeds max 5000`) — reduce the staged body: prefer a **selective split** of the largest Tier-2 section(s) into `{skill_staging}/references/`, keeping Tier-1 content inline (mirrors `validate.md` §3); or trim redundant content. Re-run the gate until both `body_lines ≤ 500` and `body_tokens ≤ 5000`.
|
|
242
242
|
|
|
243
243
|
Do NOT proceed to §9 commit-dir with an unresolved high/medium issue. Note: an over-long `description` is rated `medium` and exits `0`, so key the HALT on the issue severities above — not on the exit code.
|
|
244
244
|
- **Only `low`-severity issues (e.g. an unexpected field)** — record each as a WARNING in the evidence report and proceed; these do not block the commit.
|
package/src/skf-forger/SKILL.md
CHANGED
|
@@ -84,13 +84,17 @@ When the user provides multiple workflow codes (e.g., `BS CS TS EX`, `QS TS EX`,
|
|
|
84
84
|
|
|
85
85
|
**Pipeline activation:**
|
|
86
86
|
|
|
87
|
-
1. **Parse the sequence** — split codes, expand aliases (`forge` → `BS CS TS EX`, `forge-quick` → `QS TS EX`, `onboard` → `AN CS TS EX`, `maintain` → `AS US TS EX`), extract any bracket arguments (`CS[cocoindex]`, `TS[min:80]`)
|
|
87
|
+
1. **Parse the sequence** — split codes, expand aliases (`deepwiki` → `AN[auto] BS[auto] CS TS[min:90] EX`, `forge` → `BS CS TS EX`, `forge-quick` → `QS TS EX`, `onboard` → `AN CS TS EX`, `maintain` → `AS US TS EX`), extract any bracket arguments (`CS[cocoindex]`, `TS[min:80]`)
|
|
88
|
+
**Alias lifecycle:** When the parsed alias is `onboard`, emit a deprecation notice **before** expanding the alias and **before** pipeline progress reporting begins. Emit on **every** invocation — this is not a one-time warning. After emitting the notice, expand `onboard` → `AN CS TS EX` as normal (no behavioral change — existing 80% default threshold applies):
|
|
89
|
+
|
|
90
|
+
> ⚠️ **onboard is deprecated — use deepwiki instead.** The onboard pipeline (AN CS TS EX at 80% threshold) still runs, but will be removed in v2.0. To migrate: replace `onboard` with `deepwiki <repo-url>`. deepwiki adds auto-brief generation (BS[auto]) and a 90% quality threshold.
|
|
91
|
+
|
|
88
92
|
2. **Validate the sequence** — check for anti-patterns (EX before TS, CS without BS, duplicates). If found, warn the user and ask to confirm or adjust. In `{headless_mode}`, warn but proceed.
|
|
89
93
|
3. **Set `{headless_mode}` = true** — pipelines auto-activate headless mode for all workflows in the chain. The user committed to the sequence by providing it.
|
|
90
94
|
4. **Execute left to right** — for each workflow in the sequence:
|
|
91
95
|
- a. **Report start**: "Pipeline [{current}/{total}]: Starting {code} ({description})..."
|
|
92
96
|
- b. **Resolve inputs** from the previous workflow's output using the Data Flow table in pipeline-contracts.md. If the previous workflow produced a `skill_name`, `brief_path`, or other handoff data, pass it as the input argument.
|
|
93
|
-
- c. **Invoke the workflow** with `{headless_mode}` = true and any resolved arguments.
|
|
97
|
+
- c. **Invoke the workflow** with `{headless_mode}` = true, `{pipeline_alias}` set to the alias name (`deepwiki`, `forge`, `forge-quick`, `onboard`, `maintain`, or `null` for ad-hoc sequences), and any resolved arguments.
|
|
94
98
|
- d. **Check circuit breaker** after completion. Load the output artifact and validate against the threshold (default or user-specified via `[min:N]`). If the check fails: halt the pipeline, report what completed and what remains.
|
|
95
99
|
- e. **Report completion**: "Pipeline [{current}/{total}]: {code} complete — {brief summary of output}."
|
|
96
100
|
5. **Pipeline summary** — after all workflows complete (or on halt), present a summary:
|
|
@@ -100,6 +104,8 @@ When the user provides multiple workflow codes (e.g., `BS CS TS EX`, `QS TS EX`,
|
|
|
100
104
|
- Next steps recommendation
|
|
101
105
|
6. **Result Contract** — write the pipeline result contract per `shared/references/output-contract-schema.md`: the per-run record at `{sidecar_path}/pipeline-result-{YYYYMMDD-HHmmss}.json` (UTC timestamp, resolution to seconds) and a copy at `{sidecar_path}/pipeline-result-latest.json` (stable path for pipeline consumers — copy, not symlink). Include one entry per completed workflow in `outputs` (referencing each workflow's own `-latest.json` result record); include per-step status and the overall pipeline status in `summary`.
|
|
102
106
|
|
|
107
|
+
**deepwiki pipeline note:** `deepwiki <repo-url> --pin <version>` — the `--pin` argument is passed to AN's pipeline data context alongside the `[auto]` flag. AN's `step-auto-scope.md §0b` consumes it for pin resolution.
|
|
108
|
+
|
|
103
109
|
**Special pipeline behaviors:**
|
|
104
110
|
- `AN` in a pipeline with `CS`: if AN produces multiple recommended briefs, auto-select all and process sequentially in batch mode. If only one unit found, auto-select it.
|
|
105
111
|
- `AS` followed by `US`: if `summary.severity` in `audit-skill-result-latest.json` is CLEAN, skip US and report "No drift detected — skipping update."
|
|
@@ -41,6 +41,7 @@ These rules apply to every step in this workflow:
|
|
|
41
41
|
| 3 | Coverage Check | references/coverage-check.md | Yes |
|
|
42
42
|
| 4 | Coherence Check | references/coherence-check.md | Yes |
|
|
43
43
|
| 4b | External Validators | references/external-validators.md | Yes |
|
|
44
|
+
| 4c | Hard Gate | references/step-hard-gate.md | Yes |
|
|
44
45
|
| 5 | Score | references/score.md | Yes |
|
|
45
46
|
| 6 | Report | references/report.md | No (confirm) |
|
|
46
47
|
| 7 | Workflow Health Check | references/health-check.md | Yes |
|
|
@@ -49,9 +50,9 @@ These rules apply to every step in this workflow:
|
|
|
49
50
|
|
|
50
51
|
| Aspect | Detail |
|
|
51
52
|
|--------|--------|
|
|
52
|
-
| **Inputs** | skill_name [required]; optional flags: `--allow-workspace-drift`, `--no-discovery` (skip §4b Discovery Testing), `--no-health-check` (skip §7 health-check dispatch), `--tier=<Quick\|Forge\|Forge+\|Deep>` (bypass forge-tier.yaml sidecar requirement), `--threshold=<N>` (override pass threshold; CLI wins over `workflow.default_threshold` scalar) |
|
|
53
|
+
| **Inputs** | skill_name [required]; optional flags: `--allow-workspace-drift`, `--no-discovery` (skip §4b Discovery Testing), `--no-health-check` (skip §7 health-check dispatch), `--tier=<Quick\|Forge\|Forge+\|Deep>` (bypass forge-tier.yaml sidecar requirement), `--threshold=<N>` (override pass threshold; CLI wins over per-pipeline defaults and `workflow.default_threshold` scalar) |
|
|
53
54
|
| **Gates** | step 6: Confirm Gate [C] |
|
|
54
|
-
| **Outputs** | per-run `test-report-{skill_name}-{run_id}.md` with completeness score and result (PASS/FAIL); per-run `skf-test-skill-result-{run_id}.json` and `skf-test-skill-result-latest.json` written atomically under `{forge_version}/` — downstream consumers (export-skill, update-skill `--from-test-report`) glob `test-report-{skill_name}-*.md` and pick newest by parsed ISO timestamp |
|
|
55
|
+
| **Outputs** | per-run `test-report-{skill_name}-{run_id}.md` with completeness score and result (PASS/FAIL); per-run `skf-test-skill-result-{run_id}.json` and `skf-test-skill-result-latest.json` written atomically under `{forge_version}/`; `evidence-report-fallback.md` written under `{forge_version}/` when threshold fallback occurs (score between 80% and target threshold) — downstream consumers (export-skill, update-skill `--from-test-report`) glob `test-report-{skill_name}-*.md` and pick newest by parsed ISO timestamp |
|
|
55
56
|
| **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true |
|
|
56
57
|
| **Exit codes** | See "Exit Codes" below |
|
|
57
58
|
|
|
@@ -62,7 +63,7 @@ Every terminal state in this workflow exits with a stable code so headless autom
|
|
|
62
63
|
| Code | Meaning | Raised by |
|
|
63
64
|
| ---- | -------------------- | ------------------------------------------------------------------------------------------ |
|
|
64
65
|
| 0 | success / PASS | step 6 §6b — `testResult: 'pass'` (after the result contract is written in §4c) |
|
|
65
|
-
| 2 | fail / FAIL | step 6 §6b — `testResult: 'fail'` (after the result contract is written in §4c)
|
|
66
|
+
| 2 | fail / FAIL | step 4c §3 — hard gate blocked (`halt_reason: "hard-gate-blocked"`); step 6 §6b — `testResult: 'fail'` (after the result contract is written in §4c) |
|
|
66
67
|
| 3 | inconclusive | step 6 §6b — `testResult: 'inconclusive'` (distinct from fail so orchestrators can route to manual-review queues) |
|
|
67
68
|
| 4 | pass-with-drift | step 6 §6b — `testResult: 'pass-with-drift'` (distinct from clean pass — `--allow-workspace-drift` was in effect; orchestrators MUST route to re-test-against-pinned-commit queues and refuse export; never exit 0 under drift override) |
|
|
68
69
|
|
|
@@ -71,10 +72,10 @@ Every terminal state in this workflow exits with a stable code so headless autom
|
|
|
71
72
|
When `{headless_mode}` is true, step 6 emits a single-line JSON envelope on **stdout** before chaining to step 7, and every HARD HALT emits the same envelope shape on **stderr** with `status: "error"`:
|
|
72
73
|
|
|
73
74
|
```
|
|
74
|
-
SKF_TEST_RESULT_JSON: {"status":"success|error","skill_name":"…","verdict":"PASS|FAIL|INCONCLUSIVE|pass-with-drift","score":N,"threshold":N,"report_path":"…|null","next_workflow":"export-skill|update-skill|null","exit_code":0,"halt_reason":null}
|
|
75
|
+
SKF_TEST_RESULT_JSON: {"status":"success|error","skill_name":"…","verdict":"PASS|FAIL|INCONCLUSIVE|pass-with-drift","score":N,"threshold":N,"report_path":"…|null","next_workflow":"export-skill|update-skill|null","exit_code":0,"halt_reason":null,"threshold_fallback":true,"original_threshold":90}
|
|
75
76
|
```
|
|
76
77
|
|
|
77
|
-
`status` is `"success"` on the terminal happy path (any of PASS / FAIL / INCONCLUSIVE / pass-with-drift — the workflow completed), `"error"` on any HARD HALT before §4c. `verdict` is the canonical result string. `next_workflow` is `"export-skill"` only when `verdict == "PASS"`; `"update-skill"` for `FAIL` or `pass-with-drift`; `null` for `INCONCLUSIVE` (manual review). `halt_reason` is `null` on the terminal path or one of the workflow-defined halt strings (`"forge-tier-missing"`, `"target-inaccessible"`, `"write-failed"`, `"step-completeness-violation"`, `"report-anchor-missing"`, `"health-check-missing"`, `"atomic-writer-missing"`). `exit_code` matches the table above.
|
|
78
|
+
`status` is `"success"` on the terminal happy path (any of PASS / FAIL / INCONCLUSIVE / pass-with-drift — the workflow completed), `"error"` on any HARD HALT before §4c. `verdict` is the canonical result string. `next_workflow` is `"export-skill"` only when `verdict == "PASS"`; `"update-skill"` for `FAIL` or `pass-with-drift`; `null` for `INCONCLUSIVE` (manual review). `halt_reason` is `null` on the terminal path or one of the workflow-defined halt strings (`"forge-tier-missing"`, `"target-inaccessible"`, `"write-failed"`, `"step-completeness-violation"`, `"report-anchor-missing"`, `"health-check-missing"`, `"atomic-writer-missing"`, `"hard-gate-blocked"`). `exit_code` matches the table above. When threshold fallback occurred, the envelope includes `"threshold_fallback":true` and `"original_threshold":N`; these fields are omitted when no fallback occurred.
|
|
78
79
|
|
|
79
80
|
The same payload is persisted to disk by step 6 §4c (atomic write) at two locations under `{forge_version}/`:
|
|
80
81
|
|
|
@@ -83,7 +84,7 @@ The same payload is persisted to disk by step 6 §4c (atomic write) at two locat
|
|
|
83
84
|
| `skf-test-skill-result-{run_id}.json` | Per-run record. `{run_id}` carries UTC timestamp + PID + random suffix. |
|
|
84
85
|
| `skf-test-skill-result-latest.json` | Latest copy — stable path for pipeline consumers (copy, not symlink). |
|
|
85
86
|
|
|
86
|
-
The on-disk payload is the richer form: it adds `outputs[]` (report-path entries), `summary` (`score`, `threshold`, `result`, `testMode`, `activeCategories[]`, `inconclusiveReasons[]` when present), `runId`, and `healthCheckDispatched`. The stdout envelope is the compact subset documented above.
|
|
87
|
+
The on-disk payload is the richer form: it adds `outputs[]` (report-path entries), `summary` (`score`, `threshold`, `result`, `testMode`, `activeCategories[]`, `inconclusiveReasons[]` when present, `threshold_fallback`, `original_threshold`, `evidence_report_path` when threshold fallback occurred), `runId`, and `healthCheckDispatched`. The stdout envelope is the compact subset documented above.
|
|
87
88
|
|
|
88
89
|
## On Activation
|
|
89
90
|
|
|
@@ -113,7 +114,7 @@ The on-disk payload is the richer form: it adds `outputs[]` (report-path entries
|
|
|
113
114
|
- `{testReportTemplatePath}` ← `workflow.test_report_template_path` if non-empty, else `templates/test-report-template.md`
|
|
114
115
|
- `{outputFormatsPath}` ← `workflow.output_formats_path` if non-empty, else `assets/output-section-formats.md`
|
|
115
116
|
- `{scoringRulesPath}` ← `workflow.scoring_rules_path` if non-empty, else `references/scoring-rules.md`
|
|
116
|
-
- `{defaultThreshold}` ← `workflow.default_threshold` if non-empty/non-null, else `80`. CLI `--threshold=<N>` (
|
|
117
|
+
- `{defaultThreshold}` ← `workflow.default_threshold` if non-empty/non-null, else `80`. CLI `--threshold=<N>` wins over per-pipeline defaults (from init.md §1b) which win over this scalar at the usage site in `references/score.md`.
|
|
117
118
|
- `{onCompleteCommand}` ← `workflow.on_complete` if non-empty, else empty string. When empty, the post-finalization hook in `references/report.md` is a no-op.
|
|
118
119
|
|
|
119
120
|
Stash all five as workflow-context variables. Stage files reference `{testReportTemplatePath}` / `{outputFormatsPath}` / `{scoringRulesPath}` / `{defaultThreshold}` / `{onCompleteCommand}` directly — no conditional at the usage site. Empty-string overrides cleanly fall through to the bundled default; non-empty values let orgs swap in house-style copies without forking the skill.
|
|
@@ -43,7 +43,8 @@ test_report_template_path = ""
|
|
|
43
43
|
output_formats_path = ""
|
|
44
44
|
scoring_rules_path = ""
|
|
45
45
|
|
|
46
|
-
# Pass threshold (percent). CLI `--threshold=<N>`
|
|
46
|
+
# Pass threshold (percent). Precedence: CLI `--threshold=<N>` > per-pipeline
|
|
47
|
+
# defaults (from init.md §1b lookup table) > this scalar > bundled fallback 80.
|
|
47
48
|
# Override here to change the org-wide default without forking the skill.
|
|
48
49
|
default_threshold = 80
|
|
49
50
|
|
|
@@ -30,7 +30,7 @@ If skill path was provided as workflow argument, use it directly.
|
|
|
30
30
|
- `--no-discovery` — skip the §4b Discovery Testing block in step 6 (report). Store `no_discovery: true` in workflow context when present.
|
|
31
31
|
- `--no-health-check` — skip the §7 health-check dispatch in step 6 (report). Store `no_health_check: true` in workflow context when present.
|
|
32
32
|
- `--tier=<Quick|Forge|Forge+|Deep>` — bypass the §4 forge-tier.yaml sidecar HALT. Store `tier_flag: '<value>'` in workflow context when present; §4 will set `detected_tier` directly from this value and skip the sidecar probe.
|
|
33
|
-
- `--threshold=<N>` — override the pass threshold for this run. Consumed by `references/score.md` §1; CLI wins over the `workflow.default_threshold` scalar.
|
|
33
|
+
- `--threshold=<N>` — override the pass threshold for this run. Consumed by `references/score.md` §1; CLI wins over per-pipeline defaults (§1b) and the `workflow.default_threshold` scalar.
|
|
34
34
|
|
|
35
35
|
If no path provided, ask:
|
|
36
36
|
|
|
@@ -40,6 +40,21 @@ Provide the skill path or name. I'll search in `{skillsOutputFolder}`.
|
|
|
40
40
|
|
|
41
41
|
**Path or name:**"
|
|
42
42
|
|
|
43
|
+
### 1b. Resolve Per-Pipeline Quality Threshold
|
|
44
|
+
|
|
45
|
+
If `{pipeline_alias}` is set in the workflow data context (forwarded by the forger when TS runs inside a pipeline — see `shared/references/pipeline-contracts.md` Pipeline State), look up the alias in the per-pipeline threshold defaults table:
|
|
46
|
+
|
|
47
|
+
| Pipeline Alias | Default Threshold |
|
|
48
|
+
|----------------|-------------------|
|
|
49
|
+
| `deepwiki` | 90 |
|
|
50
|
+
| `forge` | 80 |
|
|
51
|
+
| `forge-quick` | 80 |
|
|
52
|
+
| `campaign` | 90 |
|
|
53
|
+
|
|
54
|
+
- **If `{pipeline_alias}` is present AND found in the table:** store the corresponding value as `{pipeline_default_threshold}` in workflow context. This variable is consumed by `references/score.md` §1 as a precedence layer between CLI `--threshold` and `{defaultThreshold}`.
|
|
55
|
+
- **If `{pipeline_alias}` is present but NOT in the table:** `{pipeline_default_threshold}` remains unset. Score.md falls through to `{defaultThreshold}`.
|
|
56
|
+
- **If `{pipeline_alias}` is absent** (standalone TS invocation, not running inside a pipeline): `{pipeline_default_threshold}` remains unset. Score.md falls through to `{defaultThreshold}`.
|
|
57
|
+
|
|
43
58
|
### 2. Validate Skill Exists (version-aware)
|
|
44
59
|
|
|
45
60
|
Resolve the skill path using version-aware resolution (see `{versionPathsKnowledge}`):
|
|
@@ -149,7 +149,7 @@ cat payload.json | python3 {atomicWriteHelper} write --target {forge_version}/sk
|
|
|
149
149
|
|
|
150
150
|
Payload contents:
|
|
151
151
|
- `outputs[]` — include the test report path at `{outputFile}` with its `{run_id}` suffix
|
|
152
|
-
- `summary` — `score`, `threshold`, `result` (`"PASS"`, `"PASS_WITH_DRIFT"`, `"FAIL"`, or **`"INCONCLUSIVE"`**), `testMode` (naive/contextual), `activeCategories[]`, `inconclusiveReasons[]` (when present). `PASS_WITH_DRIFT` is set when the workflow observed workspace drift and the user passed `--allow-workspace-drift` — see step 5 §5 drift override. Downstream consumers MUST treat `PASS_WITH_DRIFT` as a non-exportable result: re-run against the pinned commit before export.
|
|
152
|
+
- `summary` — `score`, `threshold`, `result` (`"PASS"`, `"PASS_WITH_DRIFT"`, `"FAIL"`, or **`"INCONCLUSIVE"`**), `testMode` (naive/contextual), `activeCategories[]`, `inconclusiveReasons[]` (when present). `PASS_WITH_DRIFT` is set when the workflow observed workspace drift and the user passed `--allow-workspace-drift` — see step 5 §5 drift override. Downstream consumers MUST treat `PASS_WITH_DRIFT` as a non-exportable result: re-run against the pinned commit before export. When threshold fallback occurred, add `threshold_fallback: true`, `original_threshold: {N}`, and `evidence_report_path: '{path}'` to the summary — these fields are absent (not `false`/`null`) when no fallback occurred.
|
|
153
153
|
- `runId` — the workflow's `{run_id}` for downstream correlation
|
|
154
154
|
- `healthCheckDispatched` — boolean, set by §7 after the dispatch decision
|
|
155
155
|
|
|
@@ -173,6 +173,7 @@ Run it with a bounded timeout (default 60s). On success: log Info note "on_compl
|
|
|
173
173
|
'coverage-check',
|
|
174
174
|
'coherence-check',
|
|
175
175
|
'external-validators',
|
|
176
|
+
'hard-gate',
|
|
176
177
|
'score',
|
|
177
178
|
'report']
|
|
178
179
|
```
|
|
@@ -211,6 +212,9 @@ skipped its append.
|
|
|
211
212
|
|
|
212
213
|
**Result:** **{PASS|PASS_WITH_DRIFT|FAIL|INCONCLUSIVE}** — **{score}%** (threshold: {threshold}%)
|
|
213
214
|
|
|
215
|
+
{If `thresholdFallback` is present in output frontmatter:}
|
|
216
|
+
**Threshold fallback:** scored {score}% against {originalThreshold} target — accepted at 80% floor. Evidence report: {evidenceReportPath}
|
|
217
|
+
|
|
214
218
|
**Gaps Found:** {total_gaps}
|
|
215
219
|
- Critical: {N}
|
|
216
220
|
- High: {N}
|