bmad-module-skill-forge 1.5.1 → 1.7.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/workflows.md +1 -1
- package/package.json +2 -2
- package/src/knowledge/version-paths.md +4 -3
- package/src/shared/health-check.md +1 -1
- package/src/shared/scripts/schemas/skill-brief.v1.json +5 -0
- package/src/shared/scripts/skf-atomic-write.py +4 -1
- package/src/shared/scripts/skf-description-guard.py +33 -80
- package/src/shared/scripts/skf-detect-language.py +34 -0
- package/src/shared/scripts/skf-detect-workspaces.py +54 -0
- package/src/shared/scripts/skf-enumerate-stack-skills.py +107 -16
- package/src/shared/scripts/skf-forge-tier-rw.py +4 -1
- package/src/shared/scripts/skf-manifest-ops.py +8 -4
- package/src/shared/scripts/skf-merge-ccc-exclusions.py +4 -1
- package/src/shared/scripts/skf-qmd-classify-collections.py +29 -3
- package/src/shared/scripts/skf-rebuild-managed-sections.py +72 -7
- package/src/shared/scripts/skf-scan-skill-md-structure.py +13 -3
- package/src/shared/scripts/skf-validate-brief-inputs.py +79 -18
- package/src/shared/scripts/skf-validate-brief-schema.py +47 -1
- package/src/shared/scripts/skf-validate-frontmatter.py +60 -2
- package/src/shared/scripts/skf-write-skill-brief.py +97 -17
- package/src/skf-analyze-source/assets/skill-brief-schema.md +10 -2
- package/src/skf-analyze-source/references/continue.md +3 -1
- package/src/skf-analyze-source/references/generate-briefs.md +30 -9
- package/src/skf-analyze-source/references/recommend.md +1 -1
- package/src/skf-brief-skill/SKILL.md +4 -2
- package/src/skf-brief-skill/assets/skill-brief-schema.md +6 -1
- package/src/skf-brief-skill/references/analyze-target.md +22 -6
- package/src/skf-brief-skill/references/confirm-brief.md +4 -0
- package/src/skf-brief-skill/references/gather-intent.md +72 -3
- package/src/skf-brief-skill/references/headless-args.md +3 -2
- package/src/skf-brief-skill/references/portfolio-similarity-check.md +16 -8
- package/src/skf-brief-skill/references/scope-definition.md +20 -2
- package/src/skf-brief-skill/references/version-resolution.md +1 -1
- package/src/skf-brief-skill/references/write-brief.md +12 -2
- package/src/skf-create-skill/assets/compile-assembly-rules.md +20 -1
- package/src/skf-create-skill/assets/skill-sections.md +2 -1
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +18 -5
- package/src/skf-create-skill/references/compile.md +5 -2
- package/src/skf-create-skill/references/extract.md +8 -3
- package/src/skf-create-skill/references/extraction-patterns.md +14 -5
- package/src/skf-create-skill/references/generate-artifacts.md +5 -1
- package/src/skf-create-skill/references/source-resolution-protocols.md +5 -2
- package/src/skf-create-skill/references/validate.md +4 -3
- package/src/skf-create-stack-skill/assets/provenance-map-schema.md +3 -3
- package/src/skf-create-stack-skill/assets/stack-skill-template.md +56 -0
- package/src/skf-create-stack-skill/references/compile-stack.md +13 -2
- package/src/skf-create-stack-skill/references/compose-mode-rules.md +2 -0
- package/src/skf-create-stack-skill/references/detect-integrations.md +5 -2
- package/src/skf-create-stack-skill/references/generate-output.md +49 -9
- package/src/skf-create-stack-skill/references/validate.md +8 -5
- package/src/skf-export-skill/references/load-skill.md +5 -4
- package/src/skf-export-skill/references/manifest-rebuild.md +1 -1
- package/src/skf-export-skill/references/token-report.md +2 -2
- package/src/skf-export-skill/references/update-context.md +25 -11
- package/src/skf-quick-skill/assets/skill-template.md +2 -2
- package/src/skf-quick-skill/references/compile.md +4 -2
- package/src/skf-quick-skill/references/write-and-validate.md +2 -2
- package/src/skf-refine-architecture/SKILL.md +1 -1
- package/src/skf-refine-architecture/references/gap-analysis.md +30 -4
- package/src/skf-refine-architecture/references/init.md +2 -0
- package/src/skf-refine-architecture/references/issue-detection.md +8 -3
- package/src/skf-test-skill/SKILL.md +1 -1
- package/src/skf-test-skill/references/coherence-check.md +4 -4
- package/src/skf-test-skill/references/coverage-check.md +58 -16
- package/src/skf-test-skill/references/external-validators.md +7 -7
- package/src/skf-test-skill/references/init.md +5 -4
- package/src/skf-test-skill/references/score.md +6 -3
- package/src/skf-test-skill/references/scoring-rules.md +12 -0
- package/src/skf-test-skill/references/source-access-protocol.md +19 -3
- package/src/skf-test-skill/scripts/compute-score.py +4 -1
- package/src/skf-update-skill/references/detect-changes.md +15 -2
- package/src/skf-update-skill/references/init.md +8 -1
- package/src/skf-update-skill/references/merge.md +22 -0
- package/src/skf-update-skill/references/re-extract.md +19 -7
- package/src/skf-update-skill/references/write.md +3 -0
- package/src/skf-verify-stack/SKILL.md +2 -2
- package/src/skf-verify-stack/assets/feasibility-report-template.md +1 -1
- package/src/skf-verify-stack/references/coverage.md +35 -6
- package/src/skf-verify-stack/references/init.md +6 -2
- package/src/skf-verify-stack/references/integration-verification-rules.md +1 -1
- package/src/skf-verify-stack/references/integrations.md +7 -3
- package/src/skf-verify-stack/references/report.md +5 -1
- package/src/skf-verify-stack/references/requirements.md +3 -1
- package/src/skf-verify-stack/references/synthesize.md +11 -3
- package/tools/cli/lib/installer.js +1 -0
|
@@ -44,14 +44,19 @@ Context payload shape (consumed by `write`):
|
|
|
44
44
|
"type": "full-library" | ...,
|
|
45
45
|
"include": ["src/**/*.ts"],
|
|
46
46
|
"exclude": ["**/*.test.*"],
|
|
47
|
-
"notes": ""
|
|
47
|
+
"notes": "",
|
|
48
|
+
# Conditionally present (preserved verbatim on a ratify/re-write):
|
|
49
|
+
"tier_a_include": ["code/core/src/**"], # stratified-scope monorepos
|
|
50
|
+
"amendments": [{...}] # post-authoring audit log
|
|
48
51
|
},
|
|
49
52
|
|
|
50
53
|
# Conditionally present:
|
|
51
54
|
"doc_urls": [{"url": "...", "label": "..."}],
|
|
52
55
|
"scripts_intent": "detect" | "none" | free-text,
|
|
53
56
|
"assets_intent": "detect" | "none" | free-text,
|
|
54
|
-
"source_authority": "official" | "community" | "internal"
|
|
57
|
+
"source_authority": "official" | "community" | "internal",
|
|
58
|
+
"target_ref": "livekit/v0.7.42", # monorepo tag escape hatch
|
|
59
|
+
"source_ref": "v0.5.0" # auto-resolved git ref
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
Version precedence (resolved into the rendered YAML's `version` field):
|
|
@@ -84,14 +89,18 @@ Flat input form (`--from-flat`):
|
|
|
84
89
|
"forge_tier": "Quick",
|
|
85
90
|
"created": "2026-05-02",
|
|
86
91
|
"created_by": "armel",
|
|
87
|
-
"scope_type":
|
|
88
|
-
"scope_include":
|
|
89
|
-
"scope_exclude":
|
|
90
|
-
"scope_notes":
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
"
|
|
94
|
-
"
|
|
92
|
+
"scope_type": "full-library",
|
|
93
|
+
"scope_include": ["src/**/*.ts"],
|
|
94
|
+
"scope_exclude": ["**/*.test.*"],
|
|
95
|
+
"scope_notes": "",
|
|
96
|
+
"scope_tier_a_include": null | ["code/core/src/**"],
|
|
97
|
+
"scope_amendments": null | [{...}],
|
|
98
|
+
"doc_urls": null | [...],
|
|
99
|
+
"scripts_intent": null | "detect" | "none" | "...",
|
|
100
|
+
"assets_intent": null | "detect" | "none" | "...",
|
|
101
|
+
"source_authority": null | "official" | "community" | "internal",
|
|
102
|
+
"target_ref": null | "livekit/v0.7.42",
|
|
103
|
+
"source_ref": null | "v0.5.0"
|
|
95
104
|
}
|
|
96
105
|
|
|
97
106
|
Output (success):
|
|
@@ -293,6 +302,42 @@ def validate_context(ctx: dict[str, Any]) -> list[str]:
|
|
|
293
302
|
field=f"scope.rationale.{rk}",
|
|
294
303
|
)
|
|
295
304
|
|
|
305
|
+
# scope.tier_a_include — optional narrower tier-A include list for
|
|
306
|
+
# stratified-scope monorepos (read by skf-test-skill for the coverage
|
|
307
|
+
# denominator). Absent/None → key omitted. When present it must be a list
|
|
308
|
+
# of glob strings; the writer preserves it verbatim so a ratify/re-write
|
|
309
|
+
# does not silently drop it.
|
|
310
|
+
tier_a_include = scope.get("tier_a_include")
|
|
311
|
+
if tier_a_include is not None:
|
|
312
|
+
if not isinstance(tier_a_include, list):
|
|
313
|
+
_die("scope.tier_a_include must be an array of glob strings", field="scope.tier_a_include")
|
|
314
|
+
for i, pat in enumerate(tier_a_include):
|
|
315
|
+
if not isinstance(pat, str) or not pat:
|
|
316
|
+
_die(
|
|
317
|
+
f"scope.tier_a_include[{i}] must be a non-empty glob string",
|
|
318
|
+
field="scope.tier_a_include",
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
# scope.amendments — optional additive audit log written post-authoring by
|
|
322
|
+
# skf-create-skill / skf-update-skill. The writer is not the amendments
|
|
323
|
+
# schema authority (those workflows own the entry shape); it only preserves
|
|
324
|
+
# the log verbatim on re-write. Light check: a list of objects.
|
|
325
|
+
amendments = scope.get("amendments")
|
|
326
|
+
if amendments is not None:
|
|
327
|
+
if not isinstance(amendments, list):
|
|
328
|
+
_die("scope.amendments must be an array of amendment objects", field="scope.amendments")
|
|
329
|
+
for i, entry in enumerate(amendments):
|
|
330
|
+
if not isinstance(entry, dict):
|
|
331
|
+
_die(f"scope.amendments[{i}] must be an object", field="scope.amendments")
|
|
332
|
+
|
|
333
|
+
# target_ref / source_ref — optional git refs (top-level). target_ref is a
|
|
334
|
+
# remote-monorepo tag escape hatch; source_ref is the auto-resolved ref.
|
|
335
|
+
# Both must round-trip on a ratify/re-write rather than being dropped.
|
|
336
|
+
for ref_field in ("target_ref", "source_ref"):
|
|
337
|
+
ref_val = ctx.get(ref_field)
|
|
338
|
+
if ref_val is not None and (not isinstance(ref_val, str) or not ref_val):
|
|
339
|
+
_die(f"{ref_field} must be a non-empty string when present", field=ref_field)
|
|
340
|
+
|
|
296
341
|
# target_version semver shape (when present)
|
|
297
342
|
tv = ctx.get("target_version")
|
|
298
343
|
if tv is not None:
|
|
@@ -320,6 +365,19 @@ def assemble_brief(ctx: dict[str, Any], resolved_version: str) -> dict[str, Any]
|
|
|
320
365
|
if source_type == "docs-only":
|
|
321
366
|
source_authority = "community" # forced
|
|
322
367
|
|
|
368
|
+
# Build the scope sub-object. tier_a_include (when present) sits between
|
|
369
|
+
# exclude and notes, matching the schema doc's illustrative order. Absent →
|
|
370
|
+
# the key is omitted, so briefs without it render byte-identically to before.
|
|
371
|
+
scope_obj: dict[str, Any] = {
|
|
372
|
+
"type": ctx["scope"]["type"],
|
|
373
|
+
"include": list(ctx["scope"]["include"]),
|
|
374
|
+
"exclude": list(ctx["scope"]["exclude"]),
|
|
375
|
+
}
|
|
376
|
+
scope_tier_a = ctx["scope"].get("tier_a_include")
|
|
377
|
+
if scope_tier_a is not None:
|
|
378
|
+
scope_obj["tier_a_include"] = list(scope_tier_a)
|
|
379
|
+
scope_obj["notes"] = ctx["scope"]["notes"]
|
|
380
|
+
|
|
323
381
|
brief: dict[str, Any] = {
|
|
324
382
|
"name": ctx["name"],
|
|
325
383
|
"version": resolved_version,
|
|
@@ -330,12 +388,7 @@ def assemble_brief(ctx: dict[str, Any], resolved_version: str) -> dict[str, Any]
|
|
|
330
388
|
"forge_tier": ctx["forge_tier"],
|
|
331
389
|
"created": ctx["created"],
|
|
332
390
|
"created_by": ctx["created_by"],
|
|
333
|
-
"scope":
|
|
334
|
-
"type": ctx["scope"]["type"],
|
|
335
|
-
"include": list(ctx["scope"]["include"]),
|
|
336
|
-
"exclude": list(ctx["scope"]["exclude"]),
|
|
337
|
-
"notes": ctx["scope"]["notes"],
|
|
338
|
-
},
|
|
391
|
+
"scope": scope_obj,
|
|
339
392
|
}
|
|
340
393
|
|
|
341
394
|
# Conditional: scope.rationale — canonical position is after `notes` and
|
|
@@ -353,6 +406,13 @@ def assemble_brief(ctx: dict[str, Any], resolved_version: str) -> dict[str, Any]
|
|
|
353
406
|
"recorded": scope_rationale["recorded"],
|
|
354
407
|
}
|
|
355
408
|
|
|
409
|
+
# Conditional: scope.amendments — additive audit log, emitted verbatim after
|
|
410
|
+
# rationale (matches the schema doc order). The writer preserves it as-is;
|
|
411
|
+
# the writing authority for entry shape is skf-create-skill / skf-update-skill.
|
|
412
|
+
scope_amendments = ctx["scope"].get("amendments")
|
|
413
|
+
if scope_amendments is not None:
|
|
414
|
+
brief["scope"]["amendments"] = scope_amendments
|
|
415
|
+
|
|
356
416
|
# Conditional: target_version (must equal version)
|
|
357
417
|
tv = ctx.get("target_version")
|
|
358
418
|
if tv is not None:
|
|
@@ -364,6 +424,14 @@ def assemble_brief(ctx: dict[str, Any], resolved_version: str) -> dict[str, Any]
|
|
|
364
424
|
)
|
|
365
425
|
brief["target_version"] = tv
|
|
366
426
|
|
|
427
|
+
# Conditional: target_ref / source_ref git refs — preserved verbatim when
|
|
428
|
+
# present (monorepo tag escape hatch + auto-resolved ref). Emitted after
|
|
429
|
+
# target_version so a ratify/re-write does not strip them.
|
|
430
|
+
for ref_field in ("target_ref", "source_ref"):
|
|
431
|
+
ref_val = ctx.get(ref_field)
|
|
432
|
+
if ref_val is not None:
|
|
433
|
+
brief[ref_field] = ref_val
|
|
434
|
+
|
|
367
435
|
# Conditional: doc_urls (always emitted when present)
|
|
368
436
|
doc_urls = ctx.get("doc_urls")
|
|
369
437
|
if doc_urls:
|
|
@@ -412,8 +480,11 @@ def atomic_write(target: Path, content: str) -> int:
|
|
|
412
480
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
413
481
|
tmp = target.with_name(target.name + ".skf-tmp")
|
|
414
482
|
encoded = content.encode("utf-8")
|
|
483
|
+
# O_BINARY (Windows only; 0 elsewhere) suppresses the text-mode \n -> \r\n
|
|
484
|
+
# translation that would otherwise corrupt verbatim writes on Windows.
|
|
485
|
+
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC | getattr(os, "O_BINARY", 0)
|
|
415
486
|
try:
|
|
416
|
-
fd = os.open(tmp,
|
|
487
|
+
fd = os.open(tmp, flags, 0o644)
|
|
417
488
|
try:
|
|
418
489
|
os.write(fd, encoded)
|
|
419
490
|
os.fsync(fd)
|
|
@@ -439,6 +510,8 @@ _FLAT_SCOPE_KEYS = (
|
|
|
439
510
|
"scope_exclude",
|
|
440
511
|
"scope_notes",
|
|
441
512
|
"scope_rationale",
|
|
513
|
+
"scope_tier_a_include",
|
|
514
|
+
"scope_amendments",
|
|
442
515
|
)
|
|
443
516
|
|
|
444
517
|
|
|
@@ -493,6 +566,13 @@ def flat_to_nested(flat: dict[str, Any]) -> dict[str, Any]:
|
|
|
493
566
|
# six-subkey object validated by validate_context.
|
|
494
567
|
if "scope_rationale" in flat and flat["scope_rationale"] is not None:
|
|
495
568
|
scope["rationale"] = flat["scope_rationale"]
|
|
569
|
+
# Optional stratified-scope tier-A include list and post-authoring
|
|
570
|
+
# amendments log. Same null-drop semantics — preserved on a ratify
|
|
571
|
+
# re-write so the brief's authored surface and audit trail survive.
|
|
572
|
+
if "scope_tier_a_include" in flat and flat["scope_tier_a_include"] is not None:
|
|
573
|
+
scope["tier_a_include"] = flat["scope_tier_a_include"]
|
|
574
|
+
if "scope_amendments" in flat and flat["scope_amendments"] is not None:
|
|
575
|
+
scope["amendments"] = flat["scope_amendments"]
|
|
496
576
|
nested["scope"] = scope
|
|
497
577
|
return nested
|
|
498
578
|
|
|
@@ -27,6 +27,7 @@ Defines the output contract for skill-brief.yaml files generated by analyze-sour
|
|
|
27
27
|
| `scripts_intent` | string | `detect` / `none` / free-text | Describes whether scripts should be extracted. Values: `detect` (auto-detect from source — default when absent), `none` (skip scripts), or a free-text description of expected scripts (e.g., "CLI validation tools in bin/"). |
|
|
28
28
|
| `assets_intent` | string | `detect` / `none` / free-text | Describes whether assets should be extracted. Values: `detect` (auto-detect from source — default when absent), `none` (skip assets), or a free-text description of expected assets (e.g., "JSON schemas in schemas/"). |
|
|
29
29
|
| `target_version` | string | Semantic version (`X.Y.Z` or `X.Y.Z-prerelease`) | User-specified target version. When present, overrides auto-detection and becomes the skill's version. Recommended for docs-only skills where auto-detection is unavailable. |
|
|
30
|
+
| `target_ref` | string | Git ref (tag or branch) | Optional. Explicit git ref used verbatim as the resolved `source_ref`, bypassing version-to-tag matching. Escape hatch for monorepo crate tags whose prefix differs from the skill name (e.g. tag `livekit/v0.7.42` for skill `livekit-rust`). Remote sources only. |
|
|
30
31
|
| `source_authority` | string | `official` / `community` / `internal` | Default `community`. Set to `official` only when the skill creator is the library maintainer. Forced to `community` when `source_type: "docs-only"`. |
|
|
31
32
|
| `source_ref` | string | Git ref (tag/branch/HEAD) | Resolved git ref used for source access. Set automatically during tag resolution — do not set manually. |
|
|
32
33
|
|
|
@@ -59,11 +60,15 @@ The create-skill workflow (extract) also performs version reconciliation at extr
|
|
|
59
60
|
|
|
60
61
|
```yaml
|
|
61
62
|
scope:
|
|
62
|
-
type: full-library | specific-modules | public-api | component-library | docs-only
|
|
63
|
+
type: full-library | specific-modules | public-api | component-library | reference-app | docs-only
|
|
63
64
|
include:
|
|
64
65
|
- 'src/**/*.ts' # At least one required
|
|
65
66
|
exclude:
|
|
66
67
|
- 'src/**/*.test.ts' # Optional
|
|
68
|
+
# Optional: narrower tier-A include list for stratified-scope monorepos
|
|
69
|
+
# and reference-app pattern surfaces (refined later by skf-brief-skill)
|
|
70
|
+
# tier_a_include:
|
|
71
|
+
# - 'code/core/src/manager-api/**'
|
|
67
72
|
notes: 'Optional rationale for scope decision'
|
|
68
73
|
# Additional fields when scope.type is "component-library":
|
|
69
74
|
# registry_path: "path/to/registry.ts" # Optional — auto-detected if omitted
|
|
@@ -83,8 +88,11 @@ scope:
|
|
|
83
88
|
| specific-modules | Selected components or packages |
|
|
84
89
|
| public-api | Only exported interfaces |
|
|
85
90
|
| component-library | UI component libraries with registries, props-based APIs, and design system variants |
|
|
91
|
+
| reference-app | Whole app whose value is wiring patterns, not public exports (embedded sidecars, CLI demos, integration-pattern demonstrators). Also the home for **language / spec references** — engine- or spec-versioned query languages, grammars, or DSLs (e.g. SurrealQL) whose value is construct idioms, not exports. These have no separate scope type; they ride `reference-app` as a sub-shape (see `skf-create-skill` Language / spec-reference sub-shape) |
|
|
86
92
|
| docs-only | When source_type is docs-only — no source code available, all content from doc_urls |
|
|
87
93
|
|
|
94
|
+
> **Documented vs source language.** The `language` field records the language the skill *documents*. For a language / spec reference this may differ from the source language it is extracted from — e.g. a SurrealQL reference extracted from a Rust engine records `language: surrealql`, not `rust`. Use the documented language and a matching code-fence default (e.g. ` ```surql `) throughout the brief.
|
|
95
|
+
|
|
88
96
|
## YAML Template
|
|
89
97
|
|
|
90
98
|
```yaml
|
|
@@ -111,7 +119,7 @@ created_by: '{user_name}'
|
|
|
111
119
|
1. **Name uniqueness**: No duplicate names within forge_data_folder
|
|
112
120
|
2. **Source accessibility**: source_repo path must exist or be reachable
|
|
113
121
|
3. **Language recognized**: Must be a known programming language
|
|
114
|
-
4. **Scope type valid**: Must match one of the
|
|
122
|
+
4. **Scope type valid**: Must match one of the six defined types (full-library, specific-modules, public-api, component-library, reference-app, docs-only)
|
|
115
123
|
5. **Include patterns**: At least one include glob pattern required (exception: `docs-only` scope, where include patterns are optional since no source code is available)
|
|
116
124
|
6. **Forge tier match**: Must match value in forge-tier.yaml
|
|
117
125
|
7. **Docs-only mode**: When `source_type: "docs-only"`, `doc_urls` required (>= 1), `source_repo` optional
|
|
@@ -6,6 +6,7 @@ nextStepOptions:
|
|
|
6
6
|
step 4: 'map-and-detect.md'
|
|
7
7
|
step 5: 'recommend.md'
|
|
8
8
|
step 6: 'generate-briefs.md'
|
|
9
|
+
step 7: 'health-check.md'
|
|
9
10
|
---
|
|
10
11
|
|
|
11
12
|
<!-- Config: communicate in {communication_language}. -->
|
|
@@ -61,8 +62,9 @@ Map the last completed step to the next step file:
|
|
|
61
62
|
| identify-units | map-and-detect |
|
|
62
63
|
| map-and-detect | recommend |
|
|
63
64
|
| recommend | generate-briefs |
|
|
65
|
+
| generate-briefs | health-check |
|
|
64
66
|
|
|
65
|
-
**IF
|
|
67
|
+
**IF `health-check` is in `stepsCompleted`:**
|
|
66
68
|
"**This analysis appears to be complete.** All steps have been finished. Would you like to start a new analysis?"
|
|
67
69
|
|
|
68
70
|
### 5. Update and Route
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
outputFile: '{forge_data_folder}/analyze-source-report-{project_name}.md'
|
|
3
3
|
schemaFile: 'assets/skill-brief-schema.md'
|
|
4
|
+
validateBriefSchemaProbeOrder:
|
|
5
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-validate-brief-schema.py'
|
|
6
|
+
- '{project-root}/src/shared/scripts/skf-validate-brief-schema.py'
|
|
4
7
|
nextStepFile: 'health-check.md'
|
|
5
8
|
---
|
|
6
9
|
|
|
@@ -46,30 +49,47 @@ For EACH unit in `confirmed_units`, construct a skill-brief.yaml using:
|
|
|
46
49
|
| name | Confirmed name from step 05 recommendation card |
|
|
47
50
|
| version | Auto-detect from source (see schema Version Detection), fall back to `1.0.0` |
|
|
48
51
|
| source_repo | `{project_paths[0]}` from frontmatter (or per-unit path if multi-repo) |
|
|
49
|
-
| language |
|
|
52
|
+
| language | Language the skill **documents** (primary language detected in step 03). For a language / spec reference this is the *documented* language, which may differ from the source language it is extracted from — e.g. a SurrealQL reference extracted from a Rust engine records `surrealql`, not `rust` (see {schemaFile} "Documented vs source language") |
|
|
50
53
|
| scope.type | Scope type from step 05 recommendation card |
|
|
51
54
|
| scope.include | Include patterns from step 05 recommendation card |
|
|
52
55
|
| scope.exclude | Inferred from heuristics (test files, generated code) |
|
|
56
|
+
| scope.tier_a_include | Optional — narrower tier-A surface for stratified-scope monorepos and `reference-app` pattern surfaces; usually left to skf-brief-skill to refine |
|
|
53
57
|
| scope.notes | Rationale from step 05 recommendation card |
|
|
54
58
|
| description | Description from step 05 recommendation card |
|
|
55
59
|
| forge_tier | `{forge_tier}` from frontmatter |
|
|
56
|
-
| created | Current date
|
|
60
|
+
| created | Current date as a **quoted** string in ISO format `'YYYY-MM-DD'` — quote it so YAML stores it as text, not a parsed date object (the schema, and the §3a gate, require a string) |
|
|
57
61
|
| created_by | `{user_name}` from frontmatter |
|
|
58
62
|
|
|
59
63
|
### 3. Validate Each Brief
|
|
60
64
|
|
|
61
|
-
|
|
65
|
+
Validation has two parts: a **deterministic schema gate** (authoritative for structure) and **semantic cross-checks** the schema cannot express. Run the schema gate first.
|
|
66
|
+
|
|
67
|
+
**3a. Deterministic schema gate.** Resolve `{validateBriefSchemaHelper}` from `{validateBriefSchemaProbeOrder}` (first existing path wins; HALT with a clear message if no candidate exists). For each generated brief, pipe the *exact* assembled YAML to the helper:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
uv run {validateBriefSchemaHelper} - <<'YAML'
|
|
71
|
+
{assembled-brief-yaml}
|
|
72
|
+
YAML
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The script returns JSON `{valid, errors[], warnings[], halt_reason, brief}` — the same validator and contract `skf-brief-skill` runs at consumption time, so a brief that passes here will not be rejected there for structural reasons. Apply the result:
|
|
76
|
+
|
|
77
|
+
- **`valid: false`** — the `errors[]` name the offending field (e.g. a `description` mis-indented under `scope:`, which leaves the required top-level `description` absent). Repair the assembled YAML and re-run the helper until `valid: true`. **Never write a brief that has not passed this gate.**
|
|
78
|
+
- **`valid: true`** — carry any non-empty `warnings[]` into the §4 preview, then proceed.
|
|
79
|
+
|
|
80
|
+
This catches structural YAML errors where they are created, rather than letting them surface downstream as a HALT in `skf-brief-skill`'s ratify path.
|
|
81
|
+
|
|
82
|
+
**3b. Semantic cross-checks** (not expressible in the JSON schema — apply in addition to 3a):
|
|
62
83
|
|
|
63
84
|
1. **Name uniqueness** — no duplicate names within the batch or existing skills
|
|
64
85
|
2. **Source accessible** — project_path exists
|
|
65
86
|
3. **Language recognized** — valid programming language identifier
|
|
66
|
-
4. **
|
|
67
|
-
5. **
|
|
68
|
-
6. **Forge tier match** — matches forge_tier from config
|
|
87
|
+
4. **Include patterns** — at least one glob pattern present (the schema requires the `scope.include` key but does not enforce a non-empty list)
|
|
88
|
+
5. **Forge tier match** — matches forge_tier from config
|
|
69
89
|
|
|
70
|
-
**If
|
|
90
|
+
**If any check fails:**
|
|
71
91
|
- Document the failure with specific field and reason
|
|
72
|
-
-
|
|
92
|
+
- Repair (3a structural errors) or present to user for correction (3b semantic issues) before writing
|
|
73
93
|
- Do NOT write invalid briefs
|
|
74
94
|
|
|
75
95
|
### 4. Present Generation Preview
|
|
@@ -99,7 +119,7 @@ Wait for explicit user confirmation before writing files.
|
|
|
99
119
|
|
|
100
120
|
For each confirmed brief:
|
|
101
121
|
1. Create directory `{forge_data_folder}/{unit-name}/` if it does not exist
|
|
102
|
-
2. Write `skill-brief.yaml` to `{forge_data_folder}/{unit-name}/skill-brief.yaml`
|
|
122
|
+
2. Write `skill-brief.yaml` to `{forge_data_folder}/{unit-name}/skill-brief.yaml` — write the exact YAML that passed the §3a schema gate verbatim; do not re-serialize, so the bytes on disk are the bytes that validated
|
|
103
123
|
3. Verify file was written successfully
|
|
104
124
|
|
|
105
125
|
**IF user modifies (M):**
|
|
@@ -124,6 +144,7 @@ For each generated brief, recommend the appropriate next workflow:
|
|
|
124
144
|
| Brief has `scope.type: component-library` and registry defines boundaries | create-skill — component boundaries defined by registry |
|
|
125
145
|
| Brief has `scope.type: specific-modules` or scope needs refinement | brief-skill — refine scope before creating skill |
|
|
126
146
|
| Brief has `scope.type: public-api` or complex interface | brief-skill — detailed scoping needed |
|
|
147
|
+
| Brief has `scope.type: reference-app` | brief-skill — refine the pattern surface and capture `tier_a_include` before creating skill |
|
|
127
148
|
| Unit flagged as stack skill candidate | create-stack-skill — after individual skills exist |
|
|
128
149
|
| Unit flagged as already-skilled | update-skill — refresh existing skill |
|
|
129
150
|
|
|
@@ -57,7 +57,7 @@ For EACH qualifying unit, prepare a recommendation card:
|
|
|
57
57
|
|
|
58
58
|
**Proposed Brief Fields:**
|
|
59
59
|
- name: {suggested kebab-case name}
|
|
60
|
-
- scope.type: {full-library / specific-modules / public-api / component-library}
|
|
60
|
+
- scope.type: {full-library / specific-modules / public-api / component-library / reference-app / docs-only}
|
|
61
61
|
- scope.include: {suggested glob patterns}
|
|
62
62
|
- description: {suggested 1-3 sentence description}
|
|
63
63
|
```
|
|
@@ -13,6 +13,8 @@ A good skill brief sets a tight, cohesive boundary: one capability with 3-8 prim
|
|
|
13
13
|
|
|
14
14
|
Brief-skill is split from create-skill so the scoping conversation runs *once*, on cheap signals (manifests, top-level exports, intent), without paying for AST extraction. Compilation is expensive; scoping decisions are cheap to revise. Keeping them in separate workflows lets a user iterate on the brief, share it for review, and re-run create-skill against the same brief whenever the upstream version moves.
|
|
15
15
|
|
|
16
|
+
**Ratify path.** When the user already has a `skill-brief.yaml` produced by another workflow — typically `skf-analyze-source`'s `generate-briefs` step, where one analyze pass emits several recommended briefs — the brief can be *ratified* (reviewed and rewritten without re-deriving its fields) instead of authored from scratch. **Interactively:** invoke `/skf-brief-skill` with a path to that brief at the first prompt — gather-intent §3.1a loads the YAML, hydrates the brief context, and jumps straight to step 4 (confirm-brief) where the standard review/edit cycle still applies before step 5 writes. **Headlessly:** pass `from_brief <path>` — the step 1 §8 GATE runs the same schema validation and hydration and writes through the canonical writer (overwriting in place), enabling a fully headless analyze → brief → create pipeline that preserves the upstream-authored scope. Either path skips re-deriving fields the upstream draft already supplies and saves the 5-10 minutes a full re-run of intent + analyze + scope would cost.
|
|
17
|
+
|
|
16
18
|
## Conventions
|
|
17
19
|
|
|
18
20
|
- Bare paths (e.g. `references/<name>.md`) resolve from the skill root.
|
|
@@ -81,10 +83,10 @@ These rules apply to every step in this workflow:
|
|
|
81
83
|
|
|
82
84
|
| Aspect | Detail |
|
|
83
85
|
|--------|--------|
|
|
84
|
-
| **Inputs** | `target_repo` [required], `skill_name` [required], `scope_hint` [optional], `language_hint` [optional], `target_version` [optional], `source_authority` [optional: official/community/internal, default community], `source_type` [optional: source/docs-only, default source], `doc_urls` [optional: list of `url[,label]` for source_type=docs-only or supplemental], `scope_type` [optional: full-library/specific-modules/public-api/component-library/reference-app/docs-only], `include` [optional: comma-separated globs], `exclude` [optional: comma-separated globs], `scripts_intent` [optional: detect/none/free-text, default detect], `assets_intent` [optional: detect/none/free-text, default detect], `intent` [optional: free-text used to derive description], `force` [optional: overwrite existing brief without prompting] |
|
|
86
|
+
| **Inputs** | `target_repo` [required], `skill_name` [required], `scope_hint` [optional], `language_hint` [optional], `target_version` [optional], `source_authority` [optional: official/community/internal, default community], `source_type` [optional: source/docs-only, default source], `doc_urls` [optional: list of `url[,label]` for source_type=docs-only or supplemental], `scope_type` [optional: full-library/specific-modules/public-api/component-library/reference-app/docs-only], `include` [optional: comma-separated globs], `exclude` [optional: comma-separated globs], `scripts_intent` [optional: detect/none/free-text, default detect], `assets_intent` [optional: detect/none/free-text, default detect], `intent` [optional: free-text used to derive description], `force` [optional: overwrite existing brief without prompting], `from_brief` [optional: path to a pre-authored `skill-brief.yaml` to *ratify* — when supplied it is the source of truth, `target_repo`/`skill_name` become optional/derived-from-brief, and the run mirrors the interactive §3.1a ratify path: schema-validate, skip analyze-target/scope-definition, write through the canonical writer in place] |
|
|
85
87
|
| **Gates** | step 1: Input Gate [use args] | step 3: Confirm Gate [C] | step 4: Confirm Gate [C] |
|
|
86
88
|
| **Outputs** | `skill-brief.yaml` at `{forge_data_folder}/{skill-name}/skill-brief.yaml`; final `SKF_BRIEF_RESULT_JSON` line on stdout when `{headless_mode}` is true |
|
|
87
|
-
| **Headless** | All gates auto-resolve with heuristic-driven or default action when `{headless_mode}` is true; pre-supplied inputs consumed at the gates that would otherwise prompt; absent `source_authority` and `scope_type` are resolved by signal-driven detection (see `references/headless-args.md`); existing briefs are preserved unless `--force` was supplied (HALT with `overwrite-cancelled` otherwise) |
|
|
89
|
+
| **Headless** | All gates auto-resolve with heuristic-driven or default action when `{headless_mode}` is true; pre-supplied inputs consumed at the gates that would otherwise prompt; absent `source_authority` and `scope_type` are resolved by signal-driven detection (see `references/headless-args.md`); existing briefs are preserved unless `--force` was supplied (HALT with `overwrite-cancelled` otherwise); supplying `from_brief <path>` instead routes the step 1 GATE to a ratify path that schema-validates the pre-authored brief, skips analyze-target/scope-definition, and writes through the canonical writer (overwriting in place, no `--force` needed) rather than deriving a new brief |
|
|
88
90
|
| **Transient-failure retry** | This workflow does **not** auto-retry network or subprocess failures. A failed `gh` fetch (analyze-target.md §1, portfolio-similarity-check.md), QMD probe, or extraction script is logged and surfaced in the final result envelope as a warning, but the workflow continues with whatever signal it has. Headless pipelines that want retry semantics should wrap the invocation at their orchestrator level (e.g. CI re-runner on non-zero exit). Rationale: brief-skill is read-mostly with one terminal write (the YAML at step 5); a partial-signal retry has more failure modes than just re-running the whole workflow, which is cheap. |
|
|
89
91
|
| **Exit codes** | See "Exit Codes" below |
|
|
90
92
|
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
| `scripts_intent` | string | `detect` / `none` / free-text | Describes whether scripts should be extracted. Values: `detect` (auto-detect from source — default when absent), `none` (skip scripts), or a free-text description of expected scripts (e.g., "CLI validation tools in bin/"). |
|
|
24
24
|
| `assets_intent` | string | `detect` / `none` / free-text | Describes whether assets should be extracted. Values: `detect` (auto-detect from source — default when absent), `none` (skip assets), or a free-text description of expected assets (e.g., "JSON schemas in schemas/"). |
|
|
25
25
|
| `target_version` | string | Semantic version (`X.Y.Z` or `X.Y.Z-prerelease`) | User-specified target version. When present, overrides auto-detection and becomes the skill's version. Recommended for docs-only skills where auto-detection is unavailable. |
|
|
26
|
+
| `target_ref` | string | Git ref (tag or branch) | Optional. Explicit git ref used verbatim as the resolved `source_ref`, bypassing version-to-tag matching. Escape hatch for monorepo crate tags whose prefix differs from the skill name (e.g. tag `livekit/v0.7.42` for skill `livekit-rust`). Remote sources only. |
|
|
26
27
|
| `source_authority` | string | `official` / `community` / `internal` | Default `community`. Set to `official` only when the skill creator is the library maintainer. Forced to `community` when `source_type: "docs-only"`. |
|
|
27
28
|
| `source_ref` | string | Git ref (tag/branch/HEAD) | Resolved git ref used for source access. Set automatically during tag resolution — do not set manually. |
|
|
28
29
|
| `scope.tier_a_include` | array | Glob patterns | Optional. Narrower tier-A include list for stratified-scope monorepo skills. When present, `skf-test-skill` re-derives the coverage denominator from this list instead of the coarse `scope.include`, so the denominator reflects the authoring surface rather than incidentally-matched internal infrastructure. See `skf-test-skill/references/source-access-protocol.md` stratified-scope resolution. |
|
|
@@ -136,7 +137,7 @@ scope:
|
|
|
136
137
|
| Field | Type | Required | Description |
|
|
137
138
|
|---|---|---|---|
|
|
138
139
|
| `path` | string | yes | Relative path (or glob, for `category: "scope-expansion"`) from source root to the file or tree being amended. For `promoted` actions this matches the literal entry added to `scope.include`. |
|
|
139
|
-
| `action` | string | yes | One of: `promoted` (path added to `scope.include`), `skipped` (user declined promotion; decision recorded to prevent re-prompting), `demoted-include` (path removed from `scope.include` — only valid with `category: "scope-expansion"`), `demoted-exclude` (path removed from `scope.exclude` — only valid with `category: "scope-expansion"`). |
|
|
140
|
+
| `action` | string | yes | One of: `promoted` (path added to `scope.include`), `skipped` (user declined promotion; decision recorded to prevent re-prompting), `excluded` (path added to `scope.exclude` — only valid with `category: "scope-expansion"`; used by gap-driven rescope to remove an internal / `#[doc(hidden)]` / out-of-scope export from the public surface), `demoted-include` (path removed from `scope.include` — only valid with `category: "scope-expansion"`), `demoted-exclude` (path removed from `scope.exclude` — only valid with `category: "scope-expansion"`). |
|
|
140
141
|
| `category` | string | no | One of: `auth-doc` (default for entries without this field — the historical sole use case), `scope-expansion`. Distinguishes which workflow path wrote the entry and which writer-rules apply on re-runs. |
|
|
141
142
|
| `reason` | string | yes | Human-readable sentence explaining the decision. Either user-provided at prompt time or auto-generated. |
|
|
142
143
|
| `heuristic` | string | conditional | Required for `category: "auth-doc"` — the basename that matched (`llms.txt`, `AGENTS.md`, etc.). Omit for `category: "scope-expansion"`. |
|
|
@@ -150,6 +151,8 @@ scope:
|
|
|
150
151
|
|
|
151
152
|
**Demotion (scope-expansion only):** `demoted-include` removes a previously-promoted path from `scope.include` — used when a prior `[P]` decision is reversed. `demoted-exclude` removes a path from `scope.exclude` — used when a previously excluded path needs to be re-evaluated. Both write the structural change and append the amendment so future runs see the rationale. Demotion is not valid for `category: "auth-doc"`: auth-doc skips already prevent re-prompting without scope mutation.
|
|
152
153
|
|
|
154
|
+
**Exclusion (scope-expansion only):** `excluded` adds a path to `scope.exclude` — written by `skf-update-skill` gap-driven rescope (detect-changes §0 rule R1) when a coverage gap's remediation is removal (the export is internal, `#[doc(hidden)]`, or out of scope). The amendment is the audit trail; the `scope.exclude` write is what shrinks the source barrel, so the legitimate scope reduction is expressed in the brief rather than by editing `metadata.stats`. This keeps the reduction visible to `skf-test-skill`'s denominator-deflation check, which re-derives the barrel from `scope.include` filtered by `scope.exclude`. Not valid for `category: "auth-doc"`.
|
|
155
|
+
|
|
153
156
|
**Backward compatibility:** `scope.amendments` is optional. Briefs without this field validate unchanged. Treat missing as an empty list. Existing entries without `category` are equivalent to `category: "auth-doc"` — readers must default the field when absent.
|
|
154
157
|
|
|
155
158
|
**Who reads `amendments[]`:**
|
|
@@ -165,6 +168,7 @@ scope:
|
|
|
165
168
|
- `skf-create-skill` §2a (Discovered Authoritative Files Protocol) — `category: "auth-doc"`
|
|
166
169
|
- `skf-update-skill` §1b (mirror of §2a applied during change detection) — `category: "auth-doc"`
|
|
167
170
|
- `skf-update-skill` §1c (Major-Version Scope Reconciliation) — `category: "scope-expansion"`
|
|
171
|
+
- `skf-update-skill` gap-driven rescope (detect-changes §0 rule R1) — `category: "scope-expansion"`, `action: "excluded"`
|
|
168
172
|
- Manual edits by the brief author are permitted but should include all required fields above (and `category` when the entry is not an auth-doc decision).
|
|
169
173
|
|
|
170
174
|
## YAML Template
|
|
@@ -188,6 +192,7 @@ scope:
|
|
|
188
192
|
- "{pattern}"
|
|
189
193
|
notes: "{optional-scope-notes}"
|
|
190
194
|
# target_version: "X.Y.Z" # Optional: overrides auto-detection when specified
|
|
195
|
+
# target_ref: "livekit/v0.7.42" # Optional: explicit git ref, used verbatim (monorepo crate tags)
|
|
191
196
|
# source_ref: "v0.5.0" # Auto-resolved — do not set manually
|
|
192
197
|
# Optional: documentation URLs for T3 content (required when source_type: "docs-only")
|
|
193
198
|
# doc_urls:
|
|
@@ -32,9 +32,15 @@ To analyze the target repository by resolving its location, reading its structur
|
|
|
32
32
|
### 1. Resolve Target Location
|
|
33
33
|
|
|
34
34
|
**For GitHub URLs:**
|
|
35
|
+
|
|
36
|
+
**Resolve the analysis ref first.** `{analysis_ref}` is the git ref every GitHub-API fetch in this step (tree, manifests, contents) reads from — resolve it before fetching anything so the analyzed structure matches the version being skilled:
|
|
37
|
+
- If neither `target_ref` nor `target_version` was set in step 01: `{analysis_ref}` = `HEAD` (default branch). This is the common case — skip straight to the probes below with no extra call.
|
|
38
|
+
- If `target_ref` is set (an explicit ref the user stated verbatim, highest priority): use it directly as `{analysis_ref}` — no tag lookup.
|
|
39
|
+
- If `target_version` is set: resolve it to a tag via `gh api repos/{owner}/{repo}/git/refs/tags` (paginate if the repo has many tags), matching in priority order — exact `{target_version}`, then `v{target_version}`. This mirrors the clone-path tag matching in `skf-create-skill/references/source-resolution-protocols.md` (see that file for the full monorepo-tag priority if the simple forms miss). On a single match, set `{analysis_ref}` to that tag. On **multiple matches**, present them and ask which to use (headless: take the exact match, else the `v`-prefixed one). On **zero matches**, warn `"No git tag matches version {target_version}; analyzing the default branch (HEAD) instead — structure and exports may not match the pinned version."`, set `{analysis_ref}` = `HEAD`, and record the fallback in the §5 analysis summary.
|
|
40
|
+
|
|
35
41
|
- Issue both probes in **one message with two parallel Bash calls** — they are independent:
|
|
36
42
|
- `gh api repos/{owner}/{repo}` (verify repo exists)
|
|
37
|
-
- `gh api repos/{owner}/{repo}/git/trees/
|
|
43
|
+
- `gh api repos/{owner}/{repo}/git/trees/{analysis_ref}?recursive=1` (fetch file tree at the resolved ref)
|
|
38
44
|
- If the repo-existence probe fails, fall through to the failure-class triage below; the tree response from the parallel call is discarded in that case.
|
|
39
45
|
|
|
40
46
|
**Truncation detection:** After receiving the tree response, check the `truncated` field in the JSON output. If `truncated: true`:
|
|
@@ -73,8 +79,8 @@ echo '{"tree": [<flat list of repo-relative file paths>], "manifests": {"package
|
|
|
73
79
|
uv run {detectWorkspacesHelper}
|
|
74
80
|
```
|
|
75
81
|
|
|
76
|
-
- **`tree`** — pass the flat list of repo-relative file paths already fetched in §1 (for GitHub: the `path` values from the `gh api .../git/trees/
|
|
77
|
-
- **`manifests`** — only the root manifests need contents; child-workspace manifests are looked up from the tree by the script. Include any of `package.json`, `Cargo.toml`, `pnpm-workspace.yaml`, `lerna.json` that appears at the repo root. Fetch them in **one message with N parallel Bash calls** (`gh api .../contents/{path}` for GitHub, file reads for local), then base64-decode together. Per-workspace manifest contents (e.g. `packages/foo/package.json`) are optional — including them populates the workspace `name` field with the manifest's declared package name; omitting them falls back to the directory basename.
|
|
82
|
+
- **`tree`** — pass the flat list of repo-relative file paths already fetched in §1 (for GitHub: the `path` values from the `gh api .../git/trees/{analysis_ref}?recursive=1` response; for local: the equivalent listing).
|
|
83
|
+
- **`manifests`** — only the root manifests need contents; child-workspace manifests are looked up from the tree by the script. Include any of `package.json`, `Cargo.toml`, `pnpm-workspace.yaml`, `lerna.json` that appears at the repo root. Fetch them in **one message with N parallel Bash calls** (`gh api .../contents/{path}?ref={analysis_ref}` for GitHub, file reads for local), then base64-decode together. Per-workspace manifest contents (e.g. `packages/foo/package.json`) are optional — including them populates the workspace `name` field with the manifest's declared package name; omitting them falls back to the directory basename.
|
|
78
84
|
|
|
79
85
|
The script returns a JSON envelope: `{is_monorepo, manifest_kind, workspaces[], warnings[]}`. Apply the result deterministically — see `src/shared/scripts/schemas/workspace-detection.v1.json` for the full contract.
|
|
80
86
|
|
|
@@ -96,6 +102,8 @@ Headless: if the input contract supplied an `include` glob that begins with one
|
|
|
96
102
|
|
|
97
103
|
Surface any non-empty `warnings[]` from the script to the operator log so a malformed root manifest is debuggable; the workflow does not HALT — falling back to repo-root analysis is always safe.
|
|
98
104
|
|
|
105
|
+
**`cross-ecosystem workspace ignored` warning:** when a root workspace manifest from a different language ecosystem co-exists with the surfaced one (e.g. a root `Cargo.toml [workspace]` alongside a pnpm workspace), the script surfaces only the higher-priority kind and emits this warning naming the ignored kind and its member count. The ignored ecosystem's workspaces are **not** in `workspaces[]`, so the numbered menu above will not list them. When this warning is present, tell the operator both ecosystems exist and ask which the skill should cover; if they pick the ignored ecosystem, scope §2-§4b at its root (or the relevant member) rather than the surfaced workspace, and carry the ignored kind into §3 (see the `workspace_signal` note there).
|
|
106
|
+
|
|
99
107
|
### 2. Read Repository Structure
|
|
100
108
|
|
|
101
109
|
List the top-level directory structure:
|
|
@@ -117,10 +125,14 @@ List the top-level directory structure:
|
|
|
117
125
|
Delegate the rule walk to `{detectLanguageHelper}` instead of evaluating manifest presence and extension frequency in prose:
|
|
118
126
|
|
|
119
127
|
```bash
|
|
120
|
-
echo '{"tree": [<flat list of repo-relative file paths from §1>]}' | uv run {detectLanguageHelper}
|
|
128
|
+
echo '{"tree": [<flat list of repo-relative file paths from §1>], "workspace_signal": "<§1b manifest_kind, or omit when null>"}' | uv run {detectLanguageHelper}
|
|
121
129
|
```
|
|
122
130
|
|
|
123
|
-
|
|
131
|
+
Pass the §1b `manifest_kind` as `workspace_signal` (omit the key when it is `null` / not a monorepo). This gives the workspace root precedence: for a `cargo-workspace` or `python-multi-package` root, the script returns the root language (rust/python) instead of being misled into `typescript` by a nested `package.json` + `tsconfig.json` in a non-workspace subdirectory (e.g. a `docs/` or `website/` site). JS-family workspace kinds (`npm-workspaces`/`pnpm-workspaces`/`lerna`) carry no override — their root `package.json` resolves js/ts normally.
|
|
132
|
+
|
|
133
|
+
**When §1b surfaced a `cross-ecosystem workspace ignored` warning and the operator chose the ignored ecosystem:** pass that ignored kind as `workspace_signal` (not the surfaced kind), so a co-located `cargo-workspace`/`python-multi-package` root resolves to rust/python instead of being pinned to the surfaced ecosystem's language by the workspace that won detection priority.
|
|
134
|
+
|
|
135
|
+
The script returns `{language, confidence, detection_source, fallback_to_extension_frequency}` after walking the documented rule table (the `workspace_signal` precedence above first, then manifest presence — package.json with tsconfig.json disambiguation, Cargo.toml, pyproject.toml/setup.py/setup.cfg, go.mod, pom.xml, build.gradle.kts, build.gradle Groovy with Java/Kotlin disambiguation, *.csproj/*.sln, Gemfile — then extension-frequency fallback over recognized source extensions). Use the returned values directly:
|
|
124
136
|
|
|
125
137
|
"**Detected language:** {language}
|
|
126
138
|
**Confidence:** {confidence}
|
|
@@ -140,7 +152,7 @@ This section runs exactly one of §4.1 (script path) or §4.2 (fallback path) ba
|
|
|
140
152
|
|
|
141
153
|
#### 4.1 Procedure — script-supported languages
|
|
142
154
|
|
|
143
|
-
1. Read the relevant files into memory (no parsing yet — just collect content). For GitHub sources, issue **all N `gh api repos/{owner}/{repo}/contents/{file}` calls in a single message with N parallel Bash calls** (one per manifest + each entry point), then base64-decode the responses together — these are 2-4 independent fetches per typical run. For local sources read directly (also parallelisable, but local reads are fast enough that serial Read tool calls are acceptable).
|
|
155
|
+
1. Read the relevant files into memory (no parsing yet — just collect content). For GitHub sources, issue **all N `gh api repos/{owner}/{repo}/contents/{file}?ref={analysis_ref}` calls in a single message with N parallel Bash calls** (one per manifest + each entry point), then base64-decode the responses together — these are 2-4 independent fetches per typical run. Carrying `{analysis_ref}` through here is what keeps the analyzed exports/version aligned with the pinned tag rather than HEAD. For local sources read directly (also parallelisable, but local reads are fast enough that serial Read tool calls are acceptable).
|
|
144
156
|
|
|
145
157
|
| Language | Manifest | Entry points (mode=quick) |
|
|
146
158
|
|----------|----------|--------------------------|
|
|
@@ -252,6 +264,10 @@ Present the complete analysis:
|
|
|
252
264
|
- Docs: {found/not found — location}
|
|
253
265
|
- Config: {list of config files found}
|
|
254
266
|
- Version: {detected version or "Not detected — defaulting to 1.0.0"}
|
|
267
|
+
{If the target was a GitHub URL:}
|
|
268
|
+
- Analysis ref: {analysis_ref} {append " (resolved from target_version {target_version})" when a tag was matched, or " (no tag matched {target_version} — analyzed default branch)" on the zero-match fallback}
|
|
269
|
+
|
|
270
|
+
Store `{analysis_ref}` in workflow context — step 03 (`scope-definition.md`) reuses it for any further `contents/` fetches so scope analysis reads the same ref as this step.
|
|
255
271
|
|
|
256
272
|
---
|
|
257
273
|
|
|
@@ -26,6 +26,8 @@ To present the complete skill brief in human-readable format, highlighting all f
|
|
|
26
26
|
|
|
27
27
|
Use the values already accepted in steps 01-03 directly — do not re-load `{briefSchemaPath}` here. The 18 fields below are all in conversation; the schema is only consulted in §4 if an inline adjustment needs a specific field's validation rule cited.
|
|
28
28
|
|
|
29
|
+
**Ratify run (`ratify_mode: true`):** steps 2-3 were skipped (interactive `[R]` at gather-intent §3.1a, or the headless §8 GATE `from_brief` route), so there is no fresh steps 01-03 output to compile. Use the brief context variables **hydrated from the parsed brief** at step 1 in place of that output — the hydrated variable names match the field references below one-for-one. `detected_version` is absent on this path; rely on the hydrated `version` (step 5 pins it via `version_resolved`).
|
|
30
|
+
|
|
29
31
|
Compile all gathered data from steps 01-03 into the complete brief:
|
|
30
32
|
|
|
31
33
|
- **name:** {skill name from step 01}
|
|
@@ -41,6 +43,7 @@ Compile all gathered data from steps 01-03 into the complete brief:
|
|
|
41
43
|
- **scope.include:** {include patterns from step 03}
|
|
42
44
|
- **scope.exclude:** {exclude patterns from step 03}
|
|
43
45
|
- **scope.notes:** {any scope notes from step 03}
|
|
46
|
+
- **scope.tier_a_include:** {tier-A authoring-surface patterns from step 03 §3c — omit if unset}
|
|
44
47
|
- **scope.rationale:** {recommended -> chosen, reason — from step 03}
|
|
45
48
|
- **source_type:** {source or docs-only, from step 01}
|
|
46
49
|
- **doc_urls:** {collected documentation URLs with labels, from steps 01/03 — include if source_type is "docs-only" or supplemental URLs were collected}
|
|
@@ -69,6 +72,7 @@ Scope: {scope.type}
|
|
|
69
72
|
Include: {scope.include patterns, one per line}
|
|
70
73
|
Exclude: {scope.exclude patterns, one per line}
|
|
71
74
|
Notes: {scope.notes}
|
|
75
|
+
Tier-A: {scope.tier_a_include patterns, one per line — omit this line entirely if scope.tier_a_include unset}
|
|
72
76
|
Rationale: {chosen} chosen over {recommended} — {reason}
|
|
73
77
|
{omit this line entirely if scope.rationale absent}
|
|
74
78
|
|