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.
Files changed (87) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/docs/_data/pinned.yaml +1 -1
  3. package/docs/workflows.md +1 -1
  4. package/package.json +2 -2
  5. package/src/knowledge/version-paths.md +4 -3
  6. package/src/shared/health-check.md +1 -1
  7. package/src/shared/scripts/schemas/skill-brief.v1.json +5 -0
  8. package/src/shared/scripts/skf-atomic-write.py +4 -1
  9. package/src/shared/scripts/skf-description-guard.py +33 -80
  10. package/src/shared/scripts/skf-detect-language.py +34 -0
  11. package/src/shared/scripts/skf-detect-workspaces.py +54 -0
  12. package/src/shared/scripts/skf-enumerate-stack-skills.py +107 -16
  13. package/src/shared/scripts/skf-forge-tier-rw.py +4 -1
  14. package/src/shared/scripts/skf-manifest-ops.py +8 -4
  15. package/src/shared/scripts/skf-merge-ccc-exclusions.py +4 -1
  16. package/src/shared/scripts/skf-qmd-classify-collections.py +29 -3
  17. package/src/shared/scripts/skf-rebuild-managed-sections.py +72 -7
  18. package/src/shared/scripts/skf-scan-skill-md-structure.py +13 -3
  19. package/src/shared/scripts/skf-validate-brief-inputs.py +79 -18
  20. package/src/shared/scripts/skf-validate-brief-schema.py +47 -1
  21. package/src/shared/scripts/skf-validate-frontmatter.py +60 -2
  22. package/src/shared/scripts/skf-write-skill-brief.py +97 -17
  23. package/src/skf-analyze-source/assets/skill-brief-schema.md +10 -2
  24. package/src/skf-analyze-source/references/continue.md +3 -1
  25. package/src/skf-analyze-source/references/generate-briefs.md +30 -9
  26. package/src/skf-analyze-source/references/recommend.md +1 -1
  27. package/src/skf-brief-skill/SKILL.md +4 -2
  28. package/src/skf-brief-skill/assets/skill-brief-schema.md +6 -1
  29. package/src/skf-brief-skill/references/analyze-target.md +22 -6
  30. package/src/skf-brief-skill/references/confirm-brief.md +4 -0
  31. package/src/skf-brief-skill/references/gather-intent.md +72 -3
  32. package/src/skf-brief-skill/references/headless-args.md +3 -2
  33. package/src/skf-brief-skill/references/portfolio-similarity-check.md +16 -8
  34. package/src/skf-brief-skill/references/scope-definition.md +20 -2
  35. package/src/skf-brief-skill/references/version-resolution.md +1 -1
  36. package/src/skf-brief-skill/references/write-brief.md +12 -2
  37. package/src/skf-create-skill/assets/compile-assembly-rules.md +20 -1
  38. package/src/skf-create-skill/assets/skill-sections.md +2 -1
  39. package/src/skf-create-skill/assets/tessl-dismissal-rules.md +18 -5
  40. package/src/skf-create-skill/references/compile.md +5 -2
  41. package/src/skf-create-skill/references/extract.md +8 -3
  42. package/src/skf-create-skill/references/extraction-patterns.md +14 -5
  43. package/src/skf-create-skill/references/generate-artifacts.md +5 -1
  44. package/src/skf-create-skill/references/source-resolution-protocols.md +5 -2
  45. package/src/skf-create-skill/references/validate.md +4 -3
  46. package/src/skf-create-stack-skill/assets/provenance-map-schema.md +3 -3
  47. package/src/skf-create-stack-skill/assets/stack-skill-template.md +56 -0
  48. package/src/skf-create-stack-skill/references/compile-stack.md +13 -2
  49. package/src/skf-create-stack-skill/references/compose-mode-rules.md +2 -0
  50. package/src/skf-create-stack-skill/references/detect-integrations.md +5 -2
  51. package/src/skf-create-stack-skill/references/generate-output.md +49 -9
  52. package/src/skf-create-stack-skill/references/validate.md +8 -5
  53. package/src/skf-export-skill/references/load-skill.md +5 -4
  54. package/src/skf-export-skill/references/manifest-rebuild.md +1 -1
  55. package/src/skf-export-skill/references/token-report.md +2 -2
  56. package/src/skf-export-skill/references/update-context.md +25 -11
  57. package/src/skf-quick-skill/assets/skill-template.md +2 -2
  58. package/src/skf-quick-skill/references/compile.md +4 -2
  59. package/src/skf-quick-skill/references/write-and-validate.md +2 -2
  60. package/src/skf-refine-architecture/SKILL.md +1 -1
  61. package/src/skf-refine-architecture/references/gap-analysis.md +30 -4
  62. package/src/skf-refine-architecture/references/init.md +2 -0
  63. package/src/skf-refine-architecture/references/issue-detection.md +8 -3
  64. package/src/skf-test-skill/SKILL.md +1 -1
  65. package/src/skf-test-skill/references/coherence-check.md +4 -4
  66. package/src/skf-test-skill/references/coverage-check.md +58 -16
  67. package/src/skf-test-skill/references/external-validators.md +7 -7
  68. package/src/skf-test-skill/references/init.md +5 -4
  69. package/src/skf-test-skill/references/score.md +6 -3
  70. package/src/skf-test-skill/references/scoring-rules.md +12 -0
  71. package/src/skf-test-skill/references/source-access-protocol.md +19 -3
  72. package/src/skf-test-skill/scripts/compute-score.py +4 -1
  73. package/src/skf-update-skill/references/detect-changes.md +15 -2
  74. package/src/skf-update-skill/references/init.md +8 -1
  75. package/src/skf-update-skill/references/merge.md +22 -0
  76. package/src/skf-update-skill/references/re-extract.md +19 -7
  77. package/src/skf-update-skill/references/write.md +3 -0
  78. package/src/skf-verify-stack/SKILL.md +2 -2
  79. package/src/skf-verify-stack/assets/feasibility-report-template.md +1 -1
  80. package/src/skf-verify-stack/references/coverage.md +35 -6
  81. package/src/skf-verify-stack/references/init.md +6 -2
  82. package/src/skf-verify-stack/references/integration-verification-rules.md +1 -1
  83. package/src/skf-verify-stack/references/integrations.md +7 -3
  84. package/src/skf-verify-stack/references/report.md +5 -1
  85. package/src/skf-verify-stack/references/requirements.md +3 -1
  86. package/src/skf-verify-stack/references/synthesize.md +11 -3
  87. 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": "full-library",
88
- "scope_include": ["src/**/*.ts"],
89
- "scope_exclude": ["**/*.test.*"],
90
- "scope_notes": "",
91
- "doc_urls": null | [...],
92
- "scripts_intent": null | "detect" | "none" | "...",
93
- "assets_intent": null | "detect" | "none" | "...",
94
- "source_authority": null | "official" | "community" | "internal"
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, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o644)
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 five defined types (full-library, specific-modules, public-api, component-library, docs-only)
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 all steps completed:**
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 | Primary language detected in step 03 |
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 (ISO format YYYY-MM-DD) |
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
- For each generated brief, check against {schemaFile} validation rules:
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. **Scope type valid** — matches `full-library`, `specific-modules`, `public-api`, or `component-library`
67
- 5. **Include patterns** — at least one glob pattern present
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 validation fails for any brief:**
90
+ **If any check fails:**
71
91
  - Document the failure with specific field and reason
72
- - Present to user for correction before writing
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/HEAD?recursive=1` (fetch file tree)
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/HEAD?recursive=1` response; for local: the equivalent listing).
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
- The script returns `{language, confidence, detection_source, fallback_to_extension_frequency}` after walking the documented rule table (manifest presence first 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:
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