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,525 @@
|
|
|
1
|
+
---
|
|
2
|
+
nextStepFile: 'health-check.md'
|
|
3
|
+
outputFile: '{forge_data_folder}/analyze-source-report-{project_name}.md'
|
|
4
|
+
shapeDetectScript: 'src/shared/scripts/skf-shape-detect.py'
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<!-- Config: communicate in {communication_language}. -->
|
|
8
|
+
|
|
9
|
+
# Step 1a: Auto-Scope Analysis
|
|
10
|
+
|
|
11
|
+
## STEP GOAL:
|
|
12
|
+
|
|
13
|
+
To automatically scope a repo using shape detection and export surface analysis, producing a scope and skill-brief.yaml without requiring manual input. This step replaces the interactive scan-project → identify-units → map-and-detect → recommend → generate-briefs chain when `{auto_mode}` is true.
|
|
14
|
+
|
|
15
|
+
## Rules
|
|
16
|
+
|
|
17
|
+
- Auto-proceed step — no user interaction required
|
|
18
|
+
- This step is conditional — only loaded when `[auto]` flag is present in the pipeline context
|
|
19
|
+
- Must produce the same output artifacts as the interactive chain: analysis report + skill-brief.yaml
|
|
20
|
+
- On unknown shape (exit code 1), fall back to `scan-project.md` (the normal interactive entry point)
|
|
21
|
+
- On error (exit code 2), HARD HALT with exit code 3 (`resolution-failure`)
|
|
22
|
+
|
|
23
|
+
## MANDATORY SEQUENCE
|
|
24
|
+
|
|
25
|
+
### 0. URL Type Detection
|
|
26
|
+
|
|
27
|
+
Read the target URL or path from the pipeline context (`{project_path}` or the first entry in `project_paths[]`).
|
|
28
|
+
|
|
29
|
+
Apply the following heuristic to classify the input:
|
|
30
|
+
|
|
31
|
+
| Input Pattern | Classification | Route |
|
|
32
|
+
|---------------|---------------|-------|
|
|
33
|
+
| `github.com/{owner}/{repo}` (with or without `.git` suffix, with or without scheme prefix) | GitHub repo | §1 (standard auto-scope) |
|
|
34
|
+
| `gitlab.com/...`, `bitbucket.org/...` | Git hosting | §1 (standard auto-scope) |
|
|
35
|
+
| Starts with `/`, `./`, `~/`, or `~` | Local filesystem path | §1 (standard auto-scope) |
|
|
36
|
+
| Any other `https://` or `http://` URL | Documentation URL | §0a (docs-only) |
|
|
37
|
+
| Anything else (SSH URLs, `git://`, bare hostnames, etc.) | Unclassified | §1 (standard auto-scope) |
|
|
38
|
+
|
|
39
|
+
Store the classification result (documentation URL vs. repo/local/other). For all input types, continue to §0b (Pin Resolution).
|
|
40
|
+
|
|
41
|
+
### 0b. Pin Resolution
|
|
42
|
+
|
|
43
|
+
This section validates and resolves version pins. It runs for repo URLs and local paths only — skip for documentation URLs (doc URLs have no git repo to pin against). Initialize `{pinned_ref}`, `{pinned_ref_type}`, and `{pinned_version}` as null.
|
|
44
|
+
|
|
45
|
+
**For documentation URLs:** Skip this section entirely. Continue to §0c.
|
|
46
|
+
|
|
47
|
+
**For local paths when `--pin` is provided:** Emit a warning: "**Local source may not match pinned version {pin_value}.** Ensure you've checked out the correct version locally, or use a remote GitHub URL so SKF can clone from the git tag automatically." Store `{pinned_ref}` = `{pin_value}`, `{pinned_ref_type}` = `"local"`, `{pinned_version}` = `{pin_value}`. Continue to §0c without running `skf-validate-pins.py`.
|
|
48
|
+
|
|
49
|
+
**For repo URLs when `--pin` is provided:**
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
uv run python src/shared/scripts/skf-validate-pins.py --repo-url {project_path} --pin {pin_value}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Handle exit codes:
|
|
56
|
+
|
|
57
|
+
- **Exit 0** (`status: "valid"`): Store `{pinned_ref}` = `resolved_ref`, `{pinned_ref_type}` = `ref_type`, `{pinned_version}` = `version`. Continue to §0c.
|
|
58
|
+
- **Exit 1** (`status: "invalid"`): HARD HALT with exit code 3 (`resolution-failure`). Emit error: `"Version pin '{pin_value}' not found in {project_path}. Available matches: {suggestions}. Use a valid tag, branch, or omit --pin for latest."` Emit error envelope:
|
|
59
|
+
```
|
|
60
|
+
SKF_ANALYZE_RESULT_JSON: {"status":"error","report_path":null,"brief_paths":[],"unit_counts":{"confirmed":0,"skipped":0,"maybe":0},"exit_code":3,"halt_reason":"pin-invalid","mode":"auto"}
|
|
61
|
+
```
|
|
62
|
+
- **Exit 2** (error): HARD HALT with exit code 3 (`resolution-failure`). Emit error envelope with `halt_reason: "resolution-failure"`.
|
|
63
|
+
|
|
64
|
+
**For repo URLs when `--pin` is NOT provided (default):**
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
uv run python src/shared/scripts/skf-validate-pins.py --repo-url {project_path}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Handle exit codes:
|
|
71
|
+
|
|
72
|
+
- **Exit 0** (`status: "resolved"`): Store `{pinned_ref}` = `resolved_ref`, `{pinned_ref_type}` = `ref_type`, `{pinned_version}` = `version`. Log: "Default pin resolved: {resolved_ref}". Continue to §0c.
|
|
73
|
+
- **Exit 1** (no releases found): Set `{pinned_ref}` = null, `{pinned_ref_type}` = null, `{pinned_version}` = null. Log: "No release tags found — using HEAD." Continue to §0c without pinning.
|
|
74
|
+
- **Exit 2**: Log warning, continue without pinning (same as exit 1 behavior).
|
|
75
|
+
|
|
76
|
+
### 0c. Coexistence Detection
|
|
77
|
+
|
|
78
|
+
This section checks for existing skills matching the target before proceeding. It runs for all input types (repo URLs, doc URLs, and local paths). Initialize `{coexistence_suffix}` as empty.
|
|
79
|
+
|
|
80
|
+
**1. Load skill inventory:**
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
uv run python src/shared/scripts/skf-skill-inventory.py {skills_output_folder}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Parse the JSON output. If the exit code is non-zero or the `skills` array is empty, skip coexistence detection silently (no existing skills to conflict with) and continue to the next section: §0a for documentation URLs, §1 for all other input types.
|
|
87
|
+
|
|
88
|
+
**2. Match target against existing skills:**
|
|
89
|
+
|
|
90
|
+
For each skill in the inventory, check two match conditions (either triggers a hit):
|
|
91
|
+
|
|
92
|
+
- **URL match:** Normalize both the target URL/path and the skill's `metadata.source_repo` — strip scheme (`http://`, `https://`), strip trailing `.git`, strip trailing `/`, compare case-insensitively. A match on the normalized values is a hit.
|
|
93
|
+
- **Name match:** Derive the expected skill name from the target (same logic as §6 for repo URLs, §0a for doc URLs — kebab-case from the project/domain name), then compare against each skill's `name`.
|
|
94
|
+
|
|
95
|
+
**3. If zero matches:**
|
|
96
|
+
|
|
97
|
+
Complete silently. Continue to §0a for documentation URLs, §1 for all other input types. No user output.
|
|
98
|
+
|
|
99
|
+
**4. If one or more matches — coexistence gate:**
|
|
100
|
+
|
|
101
|
+
Present the user with the coexistence decision:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
⚠️ Existing skill(s) found for {target_name}:
|
|
105
|
+
|
|
106
|
+
• {skill_name} (v{version}) — source: {source_repo}
|
|
107
|
+
[repeat for each match]
|
|
108
|
+
|
|
109
|
+
Actions:
|
|
110
|
+
[A]longside — Create a new wiki skill with "-wiki" suffix (existing skill untouched)
|
|
111
|
+
[M]erge — Update the existing skill via US workflow (wiki data enriches it)
|
|
112
|
+
[S]kip — Do not create or modify any skill for this library
|
|
113
|
+
|
|
114
|
+
Choose [A/M/S]:
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
In headless mode (`{headless_mode}` is true): auto-select `[A]longside` and log: "Headless: coexistence detected for {target_name}, auto-selecting [A]longside"
|
|
118
|
+
|
|
119
|
+
**5. Handle user selection:**
|
|
120
|
+
|
|
121
|
+
- **[A]longside:** Set `{coexistence_suffix}` to `-wiki`. Continue to §0a for documentation URLs, §1 for all other input types. The existing skill is untouched.
|
|
122
|
+
|
|
123
|
+
- **[M]erge:** If multiple skills match, prompt the user to select which one to merge into before proceeding. Emit a redirect envelope signaling the forger to route to the US workflow for the selected skill:
|
|
124
|
+
```
|
|
125
|
+
SKF_ANALYZE_RESULT_JSON: {"status":"redirect","redirect_to":"US","skill_name":"{matched_skill_name}","skill_path":"{matched_active_path}","exit_code":0,"halt_reason":null,"mode":"auto","coexistence":"merge"}
|
|
126
|
+
```
|
|
127
|
+
Write the result contract per `shared/references/output-contract-schema.md` with `status: "redirect"`.
|
|
128
|
+
Chain to {nextStepFile} (health-check.md). **STOP HERE — do not proceed to §0a or §1.**
|
|
129
|
+
|
|
130
|
+
- **[S]kip:** Emit a skip envelope:
|
|
131
|
+
```
|
|
132
|
+
SKF_ANALYZE_RESULT_JSON: {"status":"skipped","report_path":null,"brief_paths":[],"unit_counts":{"confirmed":0,"skipped":1,"maybe":0},"exit_code":0,"halt_reason":null,"mode":"auto","coexistence":"skip","skipped_reason":"Existing skill for {matched_skill_name}"}
|
|
133
|
+
```
|
|
134
|
+
Write the result contract with `status: "skipped"`.
|
|
135
|
+
Chain to {nextStepFile} (health-check.md). **STOP HERE — do not proceed to §0a or §1.**
|
|
136
|
+
|
|
137
|
+
### 0a. Docs-Only Short-Circuit
|
|
138
|
+
|
|
139
|
+
This section handles documentation URLs that are not GitHub repos or local paths. It validates the URL, writes a minimal brief and analysis report, emits the envelope, and chains directly to health-check — skipping §1 through §11 entirely.
|
|
140
|
+
|
|
141
|
+
**1. Validate URL reachability:**
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
curl -sI --max-time 5 {url}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
- On **2xx/3xx** response: URL is reachable. Continue.
|
|
148
|
+
- On **4xx/5xx**, DNS failure, or timeout: HARD HALT with exit code 3 (`resolution-failure`). Emit error message: `"Documentation URL unreachable: {url} — {status or error}"`. Emit error envelope:
|
|
149
|
+
```
|
|
150
|
+
SKF_ANALYZE_RESULT_JSON: {"status":"error","report_path":null,"brief_paths":[],"unit_counts":{"confirmed":0,"skipped":0,"maybe":0},"exit_code":3,"halt_reason":"path-invalid","mode":"auto","source_type":"docs-only"}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**2. Derive skill name from URL domain:**
|
|
154
|
+
|
|
155
|
+
Extract the hostname from the URL (e.g., `docs.example.com` from `https://docs.example.com/guide/intro`), convert to kebab-case (replace `.` with `-`), yielding e.g. `docs-example-com`. If `{coexistence_suffix}` is non-empty, append it to the skill name (e.g., `docs-example-com-wiki`).
|
|
156
|
+
|
|
157
|
+
**3. Write analysis report:**
|
|
158
|
+
|
|
159
|
+
Update {outputFile} with docs-only results.
|
|
160
|
+
|
|
161
|
+
**Update frontmatter:**
|
|
162
|
+
```yaml
|
|
163
|
+
stepsCompleted: ['init', 'auto-scope']
|
|
164
|
+
lastStep: 'auto-scope'
|
|
165
|
+
source_type: docs-only
|
|
166
|
+
confirmed_units:
|
|
167
|
+
- name: '{skill_name}'
|
|
168
|
+
shape: 'docs-only'
|
|
169
|
+
confidence: 1.0
|
|
170
|
+
export_count: 0
|
|
171
|
+
package_count: 0
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**Append body section:**
|
|
175
|
+
```markdown
|
|
176
|
+
## Auto-Scope Analysis
|
|
177
|
+
|
|
178
|
+
**Mode:** auto (docs-only short-circuit)
|
|
179
|
+
**Source Type:** docs-only
|
|
180
|
+
**Documentation URL:** {url}
|
|
181
|
+
**Skill Name:** {skill_name}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**4. Write skill brief via canonical writer:**
|
|
185
|
+
|
|
186
|
+
Create directory `{forge_data_folder}/{skill_name}/` if it does not exist.
|
|
187
|
+
|
|
188
|
+
Write `{forge_data_folder}/{skill_name}/skill-brief.yaml` through the canonical writer (`skf-write-skill-brief.py`) with the following context:
|
|
189
|
+
|
|
190
|
+
```json
|
|
191
|
+
{
|
|
192
|
+
"name": "{skill_name}",
|
|
193
|
+
"target_version": null,
|
|
194
|
+
"detected_version": null,
|
|
195
|
+
"source_type": "docs-only",
|
|
196
|
+
"source_repo": "{url}",
|
|
197
|
+
"language": "",
|
|
198
|
+
"description": "Skill created from documentation at {url}",
|
|
199
|
+
"forge_tier": "{forge_tier}",
|
|
200
|
+
"created": "{current_date}",
|
|
201
|
+
"created_by": "{user_name}",
|
|
202
|
+
"scope_type": "docs-only",
|
|
203
|
+
"scope_include": [],
|
|
204
|
+
"scope_exclude": [],
|
|
205
|
+
"scope_notes": "Docs-only skill created from documentation URL",
|
|
206
|
+
"scope_rationale": null,
|
|
207
|
+
"scope_tier_a_include": null,
|
|
208
|
+
"scope_amendments": null,
|
|
209
|
+
"doc_urls": [{"url": "{url}", "label": "Primary Documentation"}],
|
|
210
|
+
"scripts_intent": null,
|
|
211
|
+
"assets_intent": null,
|
|
212
|
+
"source_authority": "community",
|
|
213
|
+
"target_ref": null,
|
|
214
|
+
"source_ref": null,
|
|
215
|
+
"version_resolved": "1.0.0"
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**5. Emit success envelope:**
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
SKF_ANALYZE_RESULT_JSON: {"status":"success","report_path":"{outputFile_path}","brief_paths":["{brief_path}"],"unit_counts":{"confirmed":1,"skipped":0,"maybe":0},"exit_code":0,"halt_reason":null,"mode":"auto","source_type":"docs-only"}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
If `{coexistence_suffix}` is non-empty (i.e., [A]longside was selected in §0c), include `"coexistence":"alongside"` in the envelope.
|
|
226
|
+
|
|
227
|
+
The `source_type` field signals downstream consumers (BS) to skip repo-based enrichment.
|
|
228
|
+
|
|
229
|
+
**6. Write result contract** per `shared/references/output-contract-schema.md`: the per-run record and latest copy, same as §10.
|
|
230
|
+
|
|
231
|
+
If `{onCompleteCommand}` is non-empty, invoke it now with `--result-path={result_json_path}`.
|
|
232
|
+
|
|
233
|
+
**7. Chain to health check:**
|
|
234
|
+
|
|
235
|
+
Load, read fully, then execute {nextStepFile} to run the shared workflow health check. **Skip §1 through §11 entirely.**
|
|
236
|
+
|
|
237
|
+
### 1. Load Context
|
|
238
|
+
|
|
239
|
+
Read {outputFile} frontmatter to obtain:
|
|
240
|
+
- `project_paths[]` — the root(s) to analyze
|
|
241
|
+
- `forge_tier` — for brief generation
|
|
242
|
+
- `project_name`, `user_name`, `date`
|
|
243
|
+
|
|
244
|
+
Load `references/step-shape-detect.md` as reference for shape detection invocation contract and shape→scope mapping.
|
|
245
|
+
|
|
246
|
+
### 2. Manifest Scan
|
|
247
|
+
|
|
248
|
+
Perform a lightweight manifest scan — find standard package manifests in the project root and workspace paths. Do NOT crawl the full directory tree.
|
|
249
|
+
|
|
250
|
+
**For each path in `project_paths[]`:**
|
|
251
|
+
|
|
252
|
+
1. Check the project root for: `package.json`, `pyproject.toml`, `Cargo.toml`
|
|
253
|
+
2. If a workspace configuration exists (e.g., `pnpm-workspace.yaml`, Cargo.toml `[workspace].members`), scan workspace member paths for additional manifests
|
|
254
|
+
3. Record each discovered manifest as `{path, type}` pairs
|
|
255
|
+
|
|
256
|
+
**IF no manifests found:**
|
|
257
|
+
- Emit fallback message: "**Auto-scope could not find any package manifests — switching to interactive mode.**"
|
|
258
|
+
- Load, read fully, then execute `references/scan-project.md`. **STOP HERE.**
|
|
259
|
+
|
|
260
|
+
### 3. Invoke Shape Detection
|
|
261
|
+
|
|
262
|
+
Invoke the shape detection script with discovered manifests:
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
uv run python {shapeDetectScript} --repo-url <project_path_or_url> --manifests <comma_separated_manifest_paths>
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Parse the JSON output: `{shape, signals, confidence, export_count, package_count}`
|
|
269
|
+
|
|
270
|
+
**Handle exit codes:**
|
|
271
|
+
|
|
272
|
+
- **Exit 0 (shape classified):** Continue to §3a.
|
|
273
|
+
- **Exit 1 (unknown shape):** Emit fallback message: "**Auto-scope could not classify this repo — switching to interactive mode.**" Load, read fully, then execute `references/scan-project.md`. **STOP HERE.**
|
|
274
|
+
- **Exit 2 (error):** HARD HALT with exit code 3 (`resolution-failure`). Emit the error envelope:
|
|
275
|
+
```
|
|
276
|
+
SKF_ANALYZE_RESULT_JSON: {"status":"error","report_path":null,"brief_paths":[],"unit_counts":{"confirmed":0,"skipped":0,"maybe":0},"exit_code":3,"halt_reason":"resolution-failure","mode":"auto"}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### 3a. Check Decomposition Thresholds
|
|
280
|
+
|
|
281
|
+
Evaluate the shape detection output to determine whether this repo should be decomposed into multiple skills.
|
|
282
|
+
|
|
283
|
+
**Threshold conditions** (both constants are `[PENDING VALIDATION]` — subject to empirical tuning):
|
|
284
|
+
|
|
285
|
+
| Threshold | Condition | Signal |
|
|
286
|
+
|-----------|-----------|--------|
|
|
287
|
+
| Large export surface | `export_count > 500` `[PENDING VALIDATION]` | Single skill covering 500+ exports produces unwieldy output |
|
|
288
|
+
| Monorepo / multi-package | `package_count > 3` `[PENDING VALIDATION]` | 4+ packages are almost always multi-concern |
|
|
289
|
+
|
|
290
|
+
**Decision:**
|
|
291
|
+
|
|
292
|
+
- **Neither threshold met** → Continue to §4 (single-scope flow, entirely unchanged).
|
|
293
|
+
- **Either or both thresholds met** → Route to §4a (multi-scope decomposition flow). Log: "Auto-decomposition triggered: {reason} ({value} exceeds threshold {threshold} `[PENDING VALIDATION]`)" where reason is `export_threshold`, `package_threshold`, or `both`.
|
|
294
|
+
|
|
295
|
+
### 4. Map Shape to Scope
|
|
296
|
+
|
|
297
|
+
Apply the shape→scope.type mapping:
|
|
298
|
+
|
|
299
|
+
| Shape (from skf-shape-detect.py) | scope.type | Condition |
|
|
300
|
+
|----------------------------------|------------|-----------|
|
|
301
|
+
| `library-API` | `full-library` | export_count ≤ 200 |
|
|
302
|
+
| `library-API` | `public-api` | export_count > 200 |
|
|
303
|
+
| `reference-app` | `reference-app` | — |
|
|
304
|
+
| `language-reference` | `full-library` | — |
|
|
305
|
+
| `stack-compose` | `full-library` | Multi-scope via §3a when `package_count > 3` `[PENDING VALIDATION]` |
|
|
306
|
+
|
|
307
|
+
### 5. Generate Include/Exclude Patterns
|
|
308
|
+
|
|
309
|
+
Generate `scope.include` and `scope.exclude` arrays from the detected language and project structure.
|
|
310
|
+
|
|
311
|
+
**Detect primary language** from manifest type:
|
|
312
|
+
- `package.json` → TypeScript/JavaScript
|
|
313
|
+
- `pyproject.toml` → Python
|
|
314
|
+
- `Cargo.toml` → Rust
|
|
315
|
+
- `go.mod` → Go
|
|
316
|
+
|
|
317
|
+
**Default patterns (adjust based on actual project structure):**
|
|
318
|
+
|
|
319
|
+
| Language | Default include | Default exclude |
|
|
320
|
+
|----------|-----------------|-----------------|
|
|
321
|
+
| TypeScript/JavaScript | `['src/**/*.ts', 'src/**/*.tsx']` | `['**/*.test.ts', '**/*.spec.ts', '**/node_modules/**']` |
|
|
322
|
+
| Python | `['src/**/*.py']` or `['{package_name}/**/*.py']` | `['**/*_test.py', '**/test_*.py', '**/tests/**']` |
|
|
323
|
+
| Rust | `['src/**/*.rs']` | `['**/tests/**', '**/benches/**']` |
|
|
324
|
+
|
|
325
|
+
**Adjust for actual layout:** If the project uses a non-standard layout (e.g., `lib/` instead of `src/`, or a named package directory for Python), detect and use the actual paths. Check for the existence of common source directories (`src/`, `lib/`, `pkg/`, the package name directory) and prefer the one that exists.
|
|
326
|
+
|
|
327
|
+
### 6. Build Scope and Determine Skill Name
|
|
328
|
+
|
|
329
|
+
Build the scope object:
|
|
330
|
+
```yaml
|
|
331
|
+
scope:
|
|
332
|
+
type: '{mapped_scope_type}'
|
|
333
|
+
include: ['{generated_include_patterns}']
|
|
334
|
+
exclude: ['{generated_exclude_patterns}']
|
|
335
|
+
notes: 'Auto-scoped from shape detection (shape: {shape}, confidence: {confidence})'
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Determine the skill name from the project name or package name (kebab-case, lowercase). Use the manifest `name` field if available, otherwise derive from the project directory name. If `{coexistence_suffix}` is non-empty, append it to the skill name.
|
|
339
|
+
|
|
340
|
+
Detect the primary language from the manifest ecosystem:
|
|
341
|
+
- `npm` → `typescript` (or `javascript` if no `.ts` files in includes)
|
|
342
|
+
- `python` → `python`
|
|
343
|
+
- `rust` → `rust`
|
|
344
|
+
|
|
345
|
+
### 4a. Multi-Scope Decomposition
|
|
346
|
+
|
|
347
|
+
This section is reached only from §3a when one or both decomposition thresholds are met. It replaces §4→§5→§6 for repos that will produce N > 1 skills.
|
|
348
|
+
|
|
349
|
+
**Determine decomposition path:**
|
|
350
|
+
|
|
351
|
+
- **Monorepo path** (`package_count > 3`): Use workspace package discovery from §2 manifest scan results. Each workspace package with its own manifest becomes a separate skill boundary. Name each skill as `{project_name}-{package_name}` (kebab-case); if `{coexistence_suffix}` is non-empty, append it. Trivial workspace members (no source files, no exports) are excluded.
|
|
352
|
+
- **Large-export path** (`export_count > 500`, single package): Group by top-level source directory modules (e.g., `src/auth/`, `src/core/`, `src/api/`). Each directory subtree with a meaningful export surface becomes a separate skill boundary. Candidate boundaries with fewer than ~50 exports `[PENDING VALIDATION]` should be merged into an "other" catch-all skill rather than becoming standalone skills. Name each skill as `{project_name}-{module_name}` (kebab-case); if `{coexistence_suffix}` is non-empty, append it. If no clear module structure exists (flat `src/` with all files at root level), **do not force decomposition** — fall back to single-scope flow at §4.
|
|
353
|
+
- **Combined path** (both thresholds met): Use the monorepo path. Package boundaries are explicit and take priority over export-count grouping (which is heuristic).
|
|
354
|
+
|
|
355
|
+
**Per-boundary shape→scope mapping:**
|
|
356
|
+
|
|
357
|
+
For each decomposed boundary, apply the shape→scope mapping from §4 independently — the boundary's local characteristics determine its scope.type:
|
|
358
|
+
|
|
359
|
+
| Decomposition Type | Per-Boundary Shape Mapping |
|
|
360
|
+
|--------------------|---------------------------|
|
|
361
|
+
| Monorepo (`package_count > 3`) | Re-run the shape→scope heuristic ladder from `step-shape-detect.md` per package using each package's own manifest data. Packages may have different shapes (e.g., a `library-API` core + a `reference-app` CLI). |
|
|
362
|
+
| Large-export (`export_count > 500`) | All boundaries inherit the parent shape. scope.type varies by per-boundary export count (e.g., ≤200 → `full-library`, >200 → `public-api`). |
|
|
363
|
+
|
|
364
|
+
### 5a. Generate Multi-Scope Patterns
|
|
365
|
+
|
|
366
|
+
For each decomposed boundary, generate include/exclude patterns using the same language-aware rules as §5, but scoped to the boundary's source paths.
|
|
367
|
+
|
|
368
|
+
- Monorepo boundaries: patterns are rooted at the package path (e.g., `packages/auth/src/**/*.ts` instead of `src/**/*.ts`)
|
|
369
|
+
- Large-export boundaries: patterns are rooted at the module directory (e.g., `src/auth/**/*.ts`)
|
|
370
|
+
|
|
371
|
+
### 6a. Build Multi-Scope
|
|
372
|
+
|
|
373
|
+
For each boundary, build a scope object following the same structure as §6.
|
|
374
|
+
|
|
375
|
+
Include decomposition metadata in `scope.notes`: "Decomposed from {project_name} — boundary {i}/{N} ({reason})"
|
|
376
|
+
|
|
377
|
+
Determine each boundary's skill name from the boundary-derived name (kebab-case, lowercase). If `{coexistence_suffix}` is non-empty, append it to each skill name. Detect the primary language from each boundary's manifest ecosystem (same rules as §6).
|
|
378
|
+
|
|
379
|
+
**Pin data (from §0b):** All N decomposed briefs share the same pin — the pin targets a repo-level ref, not a package-level version. Apply the same `target_version`/`target_ref` values from §0b to all N boundaries at brief write time (§8).
|
|
380
|
+
|
|
381
|
+
After building all N scopes, continue to §7 with the full set of boundaries.
|
|
382
|
+
|
|
383
|
+
### 7. Write Analysis Report
|
|
384
|
+
|
|
385
|
+
Update {outputFile} with auto-scope results.
|
|
386
|
+
|
|
387
|
+
**Update frontmatter:**
|
|
388
|
+
```yaml
|
|
389
|
+
stepsCompleted: ['init', 'auto-scope']
|
|
390
|
+
lastStep: 'auto-scope'
|
|
391
|
+
confirmed_units:
|
|
392
|
+
- name: '{skill_name}'
|
|
393
|
+
shape: '{shape}'
|
|
394
|
+
confidence: {confidence}
|
|
395
|
+
export_count: {export_count}
|
|
396
|
+
package_count: {package_count}
|
|
397
|
+
boundary_path: '{boundary_path}' # present only for decomposed units
|
|
398
|
+
# ... N entries when decomposition is active
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
**When decomposition was triggered (N > 1 units):**
|
|
402
|
+
|
|
403
|
+
Add `decomposition` to frontmatter:
|
|
404
|
+
```yaml
|
|
405
|
+
decomposition:
|
|
406
|
+
triggered: true
|
|
407
|
+
reason: 'export_threshold' | 'package_threshold' | 'both'
|
|
408
|
+
boundary_count: N
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Each `confirmed_units` entry includes `boundary_path` — the relative path to the boundary's root (e.g., `packages/core` for monorepo, `src/auth` for large-export). Omit the `decomposition` key entirely when single-scope (N = 1).
|
|
412
|
+
|
|
413
|
+
**When single-scope (N = 1):** No `decomposition` key. `confirmed_units` contains a single entry (existing behavior).
|
|
414
|
+
|
|
415
|
+
**Append body section:**
|
|
416
|
+
|
|
417
|
+
For single-scope (unchanged):
|
|
418
|
+
```markdown
|
|
419
|
+
## Auto-Scope Analysis
|
|
420
|
+
|
|
421
|
+
**Mode:** auto
|
|
422
|
+
**Shape:** {shape} (confidence: {confidence})
|
|
423
|
+
**Signals:** {signals list}
|
|
424
|
+
**Export Count:** {export_count}
|
|
425
|
+
**Package Count:** {package_count}
|
|
426
|
+
**Resolved Scope Type:** {scope_type}
|
|
427
|
+
**Include Patterns:** {include patterns}
|
|
428
|
+
**Exclude Patterns:** {exclude patterns}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
For multi-scope (N > 1):
|
|
432
|
+
```markdown
|
|
433
|
+
## Auto-Scope Analysis — Decomposition ({N} skills)
|
|
434
|
+
|
|
435
|
+
**Mode:** auto
|
|
436
|
+
**Decomposition:** {reason} ({N} boundaries)
|
|
437
|
+
**Parent Shape:** {shape} (confidence: {confidence})
|
|
438
|
+
**Export Count:** {export_count}
|
|
439
|
+
**Package Count:** {package_count}
|
|
440
|
+
|
|
441
|
+
### Boundary 1: {boundary_name}
|
|
442
|
+
**Scope Type:** {scope_type}
|
|
443
|
+
**Boundary Path:** {boundary_path}
|
|
444
|
+
**Include Patterns:** {include patterns}
|
|
445
|
+
**Exclude Patterns:** {exclude patterns}
|
|
446
|
+
**Rationale:** {boundary_rationale}
|
|
447
|
+
|
|
448
|
+
### Boundary 2: {boundary_name}
|
|
449
|
+
...
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### 8. Write Skill Brief
|
|
453
|
+
|
|
454
|
+
**For each confirmed unit** (1 for single-scope, N for decomposition):
|
|
455
|
+
|
|
456
|
+
Create directory `{forge_data_folder}/{skill_name}/` if it does not exist.
|
|
457
|
+
|
|
458
|
+
Write `{forge_data_folder}/{skill_name}/skill-brief.yaml` conforming to the skill-brief schema (`assets/skill-brief-schema.md`):
|
|
459
|
+
|
|
460
|
+
```yaml
|
|
461
|
+
name: '{skill_name}'
|
|
462
|
+
version: '{detected_version or 1.0.0}'
|
|
463
|
+
source_repo: '{project_path}'
|
|
464
|
+
language: '{detected_language}'
|
|
465
|
+
scope:
|
|
466
|
+
type: '{scope_type}'
|
|
467
|
+
include:
|
|
468
|
+
- '{include_patterns}'
|
|
469
|
+
exclude:
|
|
470
|
+
- '{exclude_patterns}'
|
|
471
|
+
notes: 'Auto-scoped from shape detection (shape: {shape}, confidence: {confidence})'
|
|
472
|
+
description: '{1-3 sentence description based on shape, language, and manifest name}'
|
|
473
|
+
forge_tier: '{forge_tier}'
|
|
474
|
+
created: '{current_date}'
|
|
475
|
+
created_by: '{user_name}'
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
**When decomposition is active (N > 1 units):**
|
|
479
|
+
|
|
480
|
+
Loop over all N boundaries. For each boundary:
|
|
481
|
+
- `name` is the boundary-derived skill name (e.g., `my-monorepo-core`)
|
|
482
|
+
- `include`/`exclude` patterns are boundary-scoped (from §5a)
|
|
483
|
+
- `scope.notes` includes decomposition context: "Decomposed from {project_name} ({N} skills) — boundary {i}/{N}: {boundary_description}"
|
|
484
|
+
- `description` references the parent project and boundary role (e.g., "Core library package of the my-monorepo project, providing...")
|
|
485
|
+
- All N briefs share the same `version`, `source_repo`, `language`, `forge_tier`, `created`, `created_by` values as the parent project
|
|
486
|
+
|
|
487
|
+
**Version detection:** Attempt to auto-detect the source version per the version detection rules in `assets/skill-brief-schema.md`. Fall back to `1.0.0` if detection fails.
|
|
488
|
+
|
|
489
|
+
**Pin data (from §0b):** When `{pinned_ref}` is non-null, enrich the brief with pin data:
|
|
490
|
+
|
|
491
|
+
- If `{pinned_ref_type}` is `"tag"`: set `target_version` = `{pinned_version}`, `target_ref` = `{pinned_ref}`, `version` = `{pinned_version}`.
|
|
492
|
+
- If `{pinned_ref_type}` is `"branch"`: set `target_ref` = `{pinned_ref}`, leave `target_version` = null, `version` = auto-detected or `1.0.0`.
|
|
493
|
+
- If `{pinned_ref_type}` is `"local"`: set `target_version` = `{pinned_version}`, `target_ref` = null, `version` = `{pinned_version}`.
|
|
494
|
+
|
|
495
|
+
When `{pinned_ref}` is null (no pin, no releases): leave `target_version` = null, `target_ref` = null — existing version detection applies unchanged.
|
|
496
|
+
|
|
497
|
+
In the docs-only path (§0a), `--pin` is ignored (already skipped at §0b). No changes to §0a.
|
|
498
|
+
|
|
499
|
+
### 9. Emit Result Envelope
|
|
500
|
+
|
|
501
|
+
Emit the `SKF_ANALYZE_RESULT_JSON` envelope on stdout:
|
|
502
|
+
|
|
503
|
+
```
|
|
504
|
+
SKF_ANALYZE_RESULT_JSON: {"status":"success","report_path":"{outputFile_path}","brief_paths":["{brief_path_1}","{brief_path_2}",...,"{brief_path_N}"],"unit_counts":{"confirmed":N,"skipped":0,"maybe":0},"exit_code":0,"halt_reason":null,"mode":"auto"}
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
`brief_paths` contains N paths (one per confirmed unit). `unit_counts.confirmed` is N. The envelope JSON format is structurally unchanged — `brief_paths` was already an array and `unit_counts.confirmed` was already a number. No breaking change for downstream consumers.
|
|
508
|
+
|
|
509
|
+
If `{coexistence_suffix}` is non-empty (i.e., [A]longside was selected in §0c), include `"coexistence":"alongside"` in the envelope.
|
|
510
|
+
|
|
511
|
+
When `{pinned_ref}` is non-null, include `"pinned_ref":"{pinned_ref}"` and `"pinned_version":"{pinned_version}"` in the envelope. These flow downstream to BS/CS for provenance recording. When `{pinned_ref}` is null, omit these fields (backward-compatible — existing consumers don't expect them).
|
|
512
|
+
|
|
513
|
+
### 10. Write Result Contract
|
|
514
|
+
|
|
515
|
+
Write the result contract per `shared/references/output-contract-schema.md`: the per-run record at `{forge_data_folder}/analyze-source-result-{YYYYMMDD-HHmmss}.json` (UTC timestamp, resolution to seconds) and a copy at `{forge_data_folder}/analyze-source-result-latest.json`. `outputs` lists all N brief paths and `summary` includes the brief count N.
|
|
516
|
+
|
|
517
|
+
If `{onCompleteCommand}` is non-empty, invoke it now with `--result-path={result_json_path}`.
|
|
518
|
+
|
|
519
|
+
### 11. Chain to Health Check
|
|
520
|
+
|
|
521
|
+
Load, read fully, then execute {nextStepFile} to run the shared workflow health check.
|
|
522
|
+
|
|
523
|
+
## CRITICAL STEP COMPLETION NOTE
|
|
524
|
+
|
|
525
|
+
ONLY WHEN the analysis report has been updated, the skill-brief.yaml written and validated, the result envelope emitted, and the result contract saved will you load and read fully {nextStepFile} to begin the health check.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Shape Detection Reference
|
|
2
|
+
|
|
3
|
+
Reference document for invoking `skf-shape-detect.py` — the shared shape classification module. Loaded by `step-auto-scope.md` for auto-scope analysis.
|
|
4
|
+
|
|
5
|
+
## Invocation Contract
|
|
6
|
+
|
|
7
|
+
**Script:** `src/shared/scripts/skf-shape-detect.py`
|
|
8
|
+
|
|
9
|
+
**Command:**
|
|
10
|
+
```
|
|
11
|
+
uv run python src/shared/scripts/skf-shape-detect.py --repo-url <url> --manifests <path1,path2,...>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
**Arguments:**
|
|
15
|
+
|
|
16
|
+
| Arg | Required | Description |
|
|
17
|
+
|-----|----------|-------------|
|
|
18
|
+
| `--repo-url` | Yes | Repository URL (context only — no cloning performed) |
|
|
19
|
+
| `--manifests` | Yes | Comma-separated local file paths to manifest files |
|
|
20
|
+
|
|
21
|
+
**Supported manifests:** `package.json`, `pyproject.toml`, `Cargo.toml`
|
|
22
|
+
|
|
23
|
+
## Output Schema
|
|
24
|
+
|
|
25
|
+
JSON object on stdout:
|
|
26
|
+
|
|
27
|
+
| Field | Type | Description |
|
|
28
|
+
|-------|------|-------------|
|
|
29
|
+
| `shape` | string | `library-API` \| `reference-app` \| `language-reference` \| `stack-compose` \| `unknown` |
|
|
30
|
+
| `signals` | array[string] | Human-readable evidence strings |
|
|
31
|
+
| `confidence` | float | 0.0–1.0 |
|
|
32
|
+
| `export_count` | integer | Total public-facing exports detected |
|
|
33
|
+
| `package_count` | integer | Distinct packages detected |
|
|
34
|
+
|
|
35
|
+
## Exit Codes
|
|
36
|
+
|
|
37
|
+
| Code | Meaning | Consumer Action |
|
|
38
|
+
|------|---------|-----------------|
|
|
39
|
+
| 0 | Shape classified (not unknown) | Use shape result for scope mapping |
|
|
40
|
+
| 1 | Unknown shape (no heuristic matched) | Fall back to interactive mode |
|
|
41
|
+
| 2 | Error (invalid args, missing/unreadable files, parse failure) | HARD HALT with `resolution-failure` |
|
|
42
|
+
|
|
43
|
+
On exit code 2, error details are written to stderr as JSON: `{"error": "message", "code": "ERROR_CODE"}`
|
|
44
|
+
|
|
45
|
+
## Shape → Scope Type Mapping
|
|
46
|
+
|
|
47
|
+
| Shape | scope.type | Condition |
|
|
48
|
+
|-------|------------|-----------|
|
|
49
|
+
| `library-API` | `full-library` | export_count ≤ 200 |
|
|
50
|
+
| `library-API` | `public-api` | export_count > 200 (surface too large for full coverage) |
|
|
51
|
+
| `reference-app` | `reference-app` | Direct mapping — apps, CLIs, demos |
|
|
52
|
+
| `language-reference` | `full-library` | Language tools/parsers are library-shaped from a skill perspective |
|
|
53
|
+
| `stack-compose` | `full-library` | Multi-skill decomposition when `package_count > 3` `[PENDING VALIDATION]` — see step-auto-scope.md §3a |
|
|
54
|
+
| `unknown` | N/A | Triggers fallback to interactive mode |
|
|
55
|
+
|
|
56
|
+
## Decomposition Thresholds
|
|
57
|
+
|
|
58
|
+
When auto-scope detects a repo exceeding complexity thresholds, it recommends multi-skill decomposition instead of producing a single unwieldy skill. The thresholds are evaluated in step-auto-scope.md §3a.
|
|
59
|
+
|
|
60
|
+
| Threshold | Value | Signal | Decomposition Path |
|
|
61
|
+
|-----------|-------|--------|-------------------|
|
|
62
|
+
| Large export surface | `export_count > 500` `[PENDING VALIDATION]` | Shape detection `export_count` | Group by top-level source directory modules |
|
|
63
|
+
| Multi-package / monorepo | `package_count > 3` `[PENDING VALIDATION]` | Shape detection `package_count` | One skill per workspace package |
|
|
64
|
+
|
|
65
|
+
Both thresholds are marked `[PENDING VALIDATION]` — no empirical data exists yet for real-world repos. Expected tuning: after running deepwiki against 5–10 real repos, adjust thresholds based on whether decomposition produces useful skill boundaries or noise.
|
|
66
|
+
|
|
67
|
+
When both thresholds are met simultaneously, the monorepo path takes priority (package boundaries are explicit; export-count grouping is heuristic).
|
|
68
|
+
|
|
69
|
+
When neither threshold is met, the single-scope flow proceeds unchanged.
|
|
70
|
+
|
|
71
|
+
## Heuristic Ladder
|
|
72
|
+
|
|
73
|
+
The five-shape heuristic ladder applies in order (first match wins):
|
|
74
|
+
|
|
75
|
+
1. **language-reference** — parser/grammar/language-toolchain project. Signals: parser-related deps (pest, antlr4, tree-sitter, lark, etc.)
|
|
76
|
+
2. **stack-compose** — multi-ecosystem composite project. Signals: manifests from 2+ distinct ecosystems
|
|
77
|
+
3. **reference-app** — application, CLI, or demo project. Signals: npm `bin` field, Rust `[[bin]]`, framework deps (next, fastapi, axum, etc.)
|
|
78
|
+
4. **library-API** — library exposing a programmatic API. Signals: `main`/`module`/`exports` fields, `[lib]` target, export count
|
|
79
|
+
5. **unknown** — no heuristic matched
|
|
@@ -70,6 +70,22 @@ Rules for identifying discrete skillable units within a project. A "skillable un
|
|
|
70
70
|
- Props interfaces outnumber function signatures as primary API surface
|
|
71
71
|
- Scope type: `component-library`
|
|
72
72
|
|
|
73
|
+
### Composite Boundary
|
|
74
|
+
- Two or more Package or Module boundaries that only deliver value together (no constituent is independently useful to the skill consumer)
|
|
75
|
+
- Hard cross-boundary dependency: constituents share types, traits, or interfaces that are not re-exported through a single barrel — consumers must import from multiple constituents to use the integration
|
|
76
|
+
- Common pattern: a set of crates/packages in the same repo that implement a protocol together (e.g., plugin crates for a framework, verification + encoding halves of a cryptographic library)
|
|
77
|
+
- Scope type: inherits from the dominant constituent (typically `full-library` or `specific-modules`)
|
|
78
|
+
|
|
79
|
+
**Detection heuristic (apply after initial classification, before user confirmation):**
|
|
80
|
+
1. Among the qualifying units, find groups of ≥2 boundaries where EITHER:
|
|
81
|
+
- **Mutual hard dependency:** Every constituent imports from at least one other constituent in the group, AND no constituent's public API is self-contained (removing any one breaks the others)
|
|
82
|
+
- **Shared integration surface:** Constituents share types/traits defined in one constituent but consumed by all others, AND the consuming constituents have no independent barrel (their value depends on the shared definitions)
|
|
83
|
+
2. For each detected group, propose merging into a single composite unit:
|
|
84
|
+
- Name: `{common-prefix}` or `{integration-name}` (derive from shared namespace or repo name)
|
|
85
|
+
- Constituents: list of merged boundary names and paths
|
|
86
|
+
- Rationale: which heuristic triggered (mutual hard dependency or shared integration surface)
|
|
87
|
+
3. The merge is a **recommendation** — present to user for confirmation in identify-units §3b
|
|
88
|
+
|
|
73
89
|
## Disqualification Rules
|
|
74
90
|
|
|
75
91
|
Do NOT recommend as a skillable unit if:
|
|
@@ -40,6 +40,7 @@ These rules apply to every step in this workflow:
|
|
|
40
40
|
| 3 | Structural Diff | references/structural-diff.md | Yes |
|
|
41
41
|
| 4 | Semantic Diff | references/semantic-diff.md | Yes (skip at non-Deep) |
|
|
42
42
|
| 5 | Severity Classification | references/severity-classify.md | Yes |
|
|
43
|
+
| 5a | Doc Drift | references/step-doc-drift.md | Yes |
|
|
43
44
|
| 6 | Report | references/report.md | Yes |
|
|
44
45
|
| 7 | Workflow Health Check | references/health-check.md | Yes |
|
|
45
46
|
|
|
@@ -27,6 +27,10 @@ Update the ## Audit Summary section at the top of {outputFile} with final calcul
|
|
|
27
27
|
- Fill in severity count table from Step 05 classification summary
|
|
28
28
|
- Set overall drift score
|
|
29
29
|
- Add total findings count
|
|
30
|
+
- Include doc drift summary from `doc_drift_summary` context (set by step 5a):
|
|
31
|
+
- If `changed > 0`: "**Doc Drift:** {changed} of {total_tracked} tracked doc(s) have changed since compile. Consider re-running CS to update doc_sources."
|
|
32
|
+
- If `fetch_failed > 0`: "{fetch_failed} doc URL(s) could not be reached during audit."
|
|
33
|
+
- If `skipped_entirely`: no mention in summary (already noted in the doc drift section)
|
|
30
34
|
|
|
31
35
|
### 2. Generate Remediation Suggestions
|
|
32
36
|
|