bmad-module-skill-forge 1.6.0 → 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 (83) 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-detect-language.py +34 -0
  10. package/src/shared/scripts/skf-detect-workspaces.py +54 -0
  11. package/src/shared/scripts/skf-enumerate-stack-skills.py +107 -16
  12. package/src/shared/scripts/skf-forge-tier-rw.py +4 -1
  13. package/src/shared/scripts/skf-manifest-ops.py +8 -4
  14. package/src/shared/scripts/skf-merge-ccc-exclusions.py +4 -1
  15. package/src/shared/scripts/skf-qmd-classify-collections.py +29 -3
  16. package/src/shared/scripts/skf-rebuild-managed-sections.py +72 -7
  17. package/src/shared/scripts/skf-scan-skill-md-structure.py +13 -3
  18. package/src/shared/scripts/skf-validate-brief-inputs.py +79 -18
  19. package/src/shared/scripts/skf-validate-brief-schema.py +47 -1
  20. package/src/shared/scripts/skf-validate-frontmatter.py +60 -2
  21. package/src/shared/scripts/skf-write-skill-brief.py +97 -17
  22. package/src/skf-analyze-source/assets/skill-brief-schema.md +10 -2
  23. package/src/skf-analyze-source/references/generate-briefs.md +30 -9
  24. package/src/skf-analyze-source/references/recommend.md +1 -1
  25. package/src/skf-brief-skill/SKILL.md +3 -3
  26. package/src/skf-brief-skill/assets/skill-brief-schema.md +6 -1
  27. package/src/skf-brief-skill/references/analyze-target.md +22 -6
  28. package/src/skf-brief-skill/references/confirm-brief.md +4 -0
  29. package/src/skf-brief-skill/references/gather-intent.md +14 -4
  30. package/src/skf-brief-skill/references/headless-args.md +3 -2
  31. package/src/skf-brief-skill/references/portfolio-similarity-check.md +16 -8
  32. package/src/skf-brief-skill/references/scope-definition.md +20 -2
  33. package/src/skf-brief-skill/references/version-resolution.md +1 -1
  34. package/src/skf-brief-skill/references/write-brief.md +8 -2
  35. package/src/skf-create-skill/assets/compile-assembly-rules.md +19 -0
  36. package/src/skf-create-skill/assets/skill-sections.md +1 -0
  37. package/src/skf-create-skill/assets/tessl-dismissal-rules.md +18 -5
  38. package/src/skf-create-skill/references/compile.md +5 -2
  39. package/src/skf-create-skill/references/extract.md +8 -3
  40. package/src/skf-create-skill/references/extraction-patterns.md +14 -5
  41. package/src/skf-create-skill/references/generate-artifacts.md +5 -1
  42. package/src/skf-create-skill/references/source-resolution-protocols.md +5 -2
  43. package/src/skf-create-skill/references/validate.md +4 -3
  44. package/src/skf-create-stack-skill/assets/provenance-map-schema.md +3 -3
  45. package/src/skf-create-stack-skill/assets/stack-skill-template.md +56 -0
  46. package/src/skf-create-stack-skill/references/compile-stack.md +13 -2
  47. package/src/skf-create-stack-skill/references/compose-mode-rules.md +2 -0
  48. package/src/skf-create-stack-skill/references/detect-integrations.md +5 -2
  49. package/src/skf-create-stack-skill/references/generate-output.md +49 -9
  50. package/src/skf-create-stack-skill/references/validate.md +8 -5
  51. package/src/skf-export-skill/references/load-skill.md +5 -4
  52. package/src/skf-export-skill/references/manifest-rebuild.md +1 -1
  53. package/src/skf-export-skill/references/token-report.md +2 -2
  54. package/src/skf-export-skill/references/update-context.md +25 -11
  55. package/src/skf-quick-skill/assets/skill-template.md +2 -2
  56. package/src/skf-quick-skill/references/compile.md +4 -2
  57. package/src/skf-quick-skill/references/write-and-validate.md +2 -2
  58. package/src/skf-refine-architecture/SKILL.md +1 -1
  59. package/src/skf-refine-architecture/references/gap-analysis.md +30 -4
  60. package/src/skf-refine-architecture/references/init.md +2 -0
  61. package/src/skf-refine-architecture/references/issue-detection.md +8 -3
  62. package/src/skf-test-skill/references/coherence-check.md +4 -4
  63. package/src/skf-test-skill/references/coverage-check.md +58 -16
  64. package/src/skf-test-skill/references/external-validators.md +7 -7
  65. package/src/skf-test-skill/references/init.md +5 -4
  66. package/src/skf-test-skill/references/score.md +6 -3
  67. package/src/skf-test-skill/references/scoring-rules.md +12 -0
  68. package/src/skf-test-skill/references/source-access-protocol.md +19 -3
  69. package/src/skf-test-skill/scripts/compute-score.py +4 -1
  70. package/src/skf-update-skill/references/detect-changes.md +15 -2
  71. package/src/skf-update-skill/references/merge.md +22 -0
  72. package/src/skf-update-skill/references/re-extract.md +19 -7
  73. package/src/skf-update-skill/references/write.md +3 -0
  74. package/src/skf-verify-stack/SKILL.md +2 -2
  75. package/src/skf-verify-stack/assets/feasibility-report-template.md +1 -1
  76. package/src/skf-verify-stack/references/coverage.md +35 -6
  77. package/src/skf-verify-stack/references/init.md +6 -2
  78. package/src/skf-verify-stack/references/integration-verification-rules.md +1 -1
  79. package/src/skf-verify-stack/references/integrations.md +7 -3
  80. package/src/skf-verify-stack/references/report.md +5 -1
  81. package/src/skf-verify-stack/references/requirements.md +3 -1
  82. package/src/skf-verify-stack/references/synthesize.md +11 -3
  83. package/tools/cli/lib/installer.js +1 -0
@@ -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,7 +13,7 @@ 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 (interactive only).** 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 — invoking `/skf-brief-skill` with a path to that brief at the first prompt routes to a fast-path review: 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. This 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.
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
17
 
18
18
  ## Conventions
19
19
 
@@ -83,10 +83,10 @@ These rules apply to every step in this workflow:
83
83
 
84
84
  | Aspect | Detail |
85
85
  |--------|--------|
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] |
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] |
87
87
  | **Gates** | step 1: Input Gate [use args] | step 3: Confirm Gate [C] | step 4: Confirm Gate [C] |
88
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 |
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) |
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 |
90
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. |
91
91
  | **Exit codes** | See "Exit Codes" below |
92
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
 
@@ -114,7 +114,7 @@ Wait for user response. Branch on the response:
114
114
 
115
115
  #### 3.1a Branch — Ratify existing brief
116
116
 
117
- This branch handles the AN→BS handoff: another workflow (typically `skf-analyze-source`) has already produced a `skill-brief.yaml`, and the user wants to review and confirm it without re-running gather-intent / analyze-target / scope-definition. **This branch is interactive-only** — headless mode reaches step 1 via §8's GATE with `target_repo`/`skill_name` args, not via this §3.1a path. (A headless ratify equivalent could be added later via a `from_brief` argument; out of scope here.)
117
+ This branch handles the AN→BS handoff: another workflow (typically `skf-analyze-source`) has already produced a `skill-brief.yaml`, and the user wants to review and confirm it without re-running gather-intent / analyze-target / scope-definition. **This §3.1a path is reached only interactively** — it is entered by typing a brief path at the §3.1 prompt, which headless mode never does. The headless equivalent is the §8 GATE `from_brief` route: it consumes a `from_brief` argument, runs the same schema validation, sets the same `ratify_mode`, hydrates from the same parsed payload, and jumps to step 4 exactly as `[R]` does below see §8. Keep the two paths' hydration in sync.
118
118
 
119
119
  Resolve the path:
120
120
 
@@ -153,10 +153,11 @@ Wait for user response. Branch:
153
153
 
154
154
  - **[R] Ratify** — Confirm overwrite up front: store `ratify_mode: true` and `ratify_source_path: <resolved-brief-path>` in workflow context. Hydrate the brief context variables from the parsed `brief` payload so step 4 has the same field set it normally derives from steps 1-3:
155
155
  - `name` ← `brief.name`; `version` ← `brief.version`; `target_version` ← `brief.target_version`
156
+ - `target_ref` ← `brief.target_ref`; `source_ref` ← `brief.source_ref` (optional git refs; preserve when present)
156
157
  - `source_repo` ← `brief.source_repo`; `source_type` ← `brief.source_type`; `source_authority` ← `brief.source_authority`; `doc_urls` ← `brief.doc_urls`
157
158
  - `language` ← `brief.language`; `description` ← `brief.description`; `forge_tier` ← `brief.forge_tier`
158
159
  - `created` ← `brief.created`; `created_by` ← `brief.created_by`
159
- - `scope.type` / `scope.include` / `scope.exclude` / `scope.notes` / `scope.rationale` ← `brief.scope.*`
160
+ - `scope.type` / `scope.include` / `scope.exclude` / `scope.tier_a_include` / `scope.notes` / `scope.rationale` / `scope.amendments` ← `brief.scope.*` (preserve `tier_a_include` and the `amendments` log verbatim — do not re-derive or drop them)
160
161
  - `scripts_intent` ← `brief.scripts_intent`; `assets_intent` ← `brief.assets_intent`
161
162
 
162
163
  Then load, read entirely, and execute `{ratifyTargetFile}` — bypassing §3.1b/§3.2/§3.3, §3b, §4, §5, §6, §7, §7b, and §8 entirely. Skip step 2 (analyze-target) and step 3 (scope-definition) — both would re-derive fields already on disk. The forward chain resumes at step 4 (confirm-brief) where the user gets the standard review pass and can still adjust fields inline via §4.
@@ -369,7 +370,7 @@ Display: "**Select:** [C] Continue to Target Analysis · [X] Cancel and exit"
369
370
 
370
371
  - **GATE [default: use args]** — If `{headless_mode}`, consume pre-supplied arguments and auto-proceed. The full argument set (required/optional, defaults, halt codes, enum values) is documented in `{headlessArgsFile}` — load it now if you need to look up a specific argument. Validation is delegated to `{validateBriefInputsHelper}`; the table is the canonical operator-facing documentation, the script enforces it.
371
372
 
372
- **Preset merge (before validation).** If the headless args include a `preset` field, load `{sidecar_path}/brief-presets/{preset}.yaml` and merge its contents as defaults — explicit args override preset values, key by key. The preset file is YAML; if it does not exist, log `"warn: preset '{name}' not found at {path} — proceeding without preset"` and continue (do not HALT). If it parses but contains unknown fields, log per-field warnings and pass through unchanged (the validator's KNOWN_FIELDS check will catch any that survive). Drop the `preset` key itself from the merged dict before passing to the validator (it is consumed at this level and is not a brief field).
373
+ **Preset merge (before validation).** Skip this merge entirely when a `from_brief` argument is present — presets seed a *derived* brief and have no meaning on the ratify route below. Otherwise: if the headless args include a `preset` field, load `{sidecar_path}/brief-presets/{preset}.yaml` and merge its contents as defaults — explicit args override preset values, key by key. The preset file is YAML; if it does not exist, log `"warn: preset '{name}' not found at {path} — proceeding without preset"` and continue (do not HALT). If it parses but contains unknown fields, log per-field warnings and pass through unchanged (the validator's KNOWN_FIELDS check will catch any that survive). Drop the `preset` key itself from the merged dict before passing to the validator (it is consumed at this level and is not a brief field).
373
374
 
374
375
  **Delegate validation to `{validateBriefInputsHelper}`** instead of reasoning through the table rules in prose:
375
376
 
@@ -384,7 +385,16 @@ Display: "**Select:** [C] Continue to Target Analysis · [X] Cancel and exit"
384
385
 
385
386
  The script's `KNOWN_FIELDS` set must stay in sync with the table in `{headlessArgsFile}`.
386
387
 
387
- **Headless source-authority detection.** After consuming `normalized`, if `source_authority` is absent AND `source_type=source` AND `target_repo` is a GitHub URL, load `{headlessSourceAuthorityDetectionFile}` and follow the procedure there. Otherwise (precondition unmet, value already supplied, docs-only, or local-path) skip the load `community` is the implicit default for the unmet branches.
388
+ **Ratify route — `from_brief` present.** After a `valid: true` result, branch on `normalized.from_brief`. When it is set, this run ratifies a pre-authored brief instead of deriving one the headless mirror of the interactive §3.1a `[R]` branch. Take this route *before* the source-authority detection and analyze-target routing below (both belong to the derive path and do not apply here):
389
+
390
+ 1. **Resolve the brief path.** If `normalized.from_brief` ends in `skill-brief.yaml`, that is the path; otherwise treat it as a directory and use `<from_brief>/skill-brief.yaml`.
391
+ 2. **Schema-validate.** Resolve `{validateBriefSchemaHelper}` from `{validateBriefSchemaProbeOrder}` (first existing path wins; HALT if no candidate exists), then run `uv run {validateBriefSchemaHelper} <resolved-brief-path>`. The script returns `{valid, errors[], warnings[], halt_reason, brief}`. Apply it — and note that, unlike the interactive §3.1a branch (which re-prompts because the operator might have a corrected path to offer), headless has no second chance, so an unusable brief is terminal:
392
+ - **`valid: false`** with `halt_reason: "brief-missing"` (path absent / unreadable) — emit the error envelope per **step 5 §4b** with `halt_reason: "input-missing"`, surface `errors[]` to the operator log, HALT (exit 2).
393
+ - **`valid: false`** with any other `halt_reason` (`brief-malformed` / `brief-invalid`) — emit the error envelope with `halt_reason: "input-invalid"`, surface `errors[]`, HALT (exit 2).
394
+ - **`valid: true`** — surface any non-empty `warnings[]` to the operator log and proceed with the parsed `brief` payload.
395
+ 3. **Hydrate and route.** Store `ratify_mode: true` and `ratify_source_path: <resolved-brief-path>` in workflow context, then hydrate the brief context variables from the parsed `brief` payload exactly as the §3.1a `[R]` branch does (the identical field-mapping list: `name`/`version`/`target_version`, `target_ref`/`source_ref`, `source_repo`/`source_type`/`source_authority`/`doc_urls`, `language`/`description`/`forge_tier`, `created`/`created_by`, `scope.type`/`scope.include`/`scope.exclude`/`scope.tier_a_include`/`scope.notes`/`scope.rationale`/`scope.amendments`, `scripts_intent`/`assets_intent` — preserving `target_ref`/`source_ref`/`tier_a_include`/`amendments` verbatim). Load, read entirely, and execute `{ratifyTargetFile}` — bypassing step 2 (analyze-target) and step 3 (scope-definition), both of which would re-derive fields already on disk. The forward chain resumes at step 4 (confirm-brief), which auto-confirms `[C]` under headless and proceeds to step 5's write (the step 5 §2b ratify branch auto-overwrites in place). Do **not** run the source-authority detection or the `[C] → {nextStepFile}` routing below — they belong to the derive path.
396
+
397
+ **Headless source-authority detection (derive route only — no `from_brief`).** After consuming `normalized`, if `source_authority` is absent AND `source_type=source` AND `target_repo` is a GitHub URL, load `{headlessSourceAuthorityDetectionFile}` and follow the procedure there. Otherwise (precondition unmet, value already supplied, docs-only, or local-path) skip the load — `community` is the implicit default for the unmet branches.
388
398
 
389
399
  - ONLY proceed to next step when user selects 'C'
390
400
 
@@ -4,8 +4,9 @@ Loaded by step 1 §8 only when `{headless_mode}` is true. Canonical operator-fac
4
4
 
5
5
  | Argument | Required | Default | Notes |
6
6
  |----------|----------|---------|-------|
7
- | `target_repo` | yes | — | HALT (exit 2, `halt_reason: "input-missing"`) if absent |
8
- | `skill_name` | yes | — | HALT (exit 2, `halt_reason: "input-missing"`) if absent; HALT (exit 2, `halt_reason: "input-invalid"`) if non-kebab |
7
+ | `target_repo` | yes¹ | — | HALT (exit 2, `halt_reason: "input-missing"`) if absent. ¹Not required when `from_brief` is supplied — the ratify route derives the target from the brief and ignores `target_repo` (with a warning) if also passed |
8
+ | `skill_name` | yes¹ | — | HALT (exit 2, `halt_reason: "input-missing"`) if absent; HALT (exit 2, `halt_reason: "input-invalid"`) if non-kebab. ¹Not required when `from_brief` is supplied — the ratify route derives the name from the brief and ignores `skill_name` (with a warning) if also passed |
9
+ | `from_brief` | no | — | Path to a pre-authored `skill-brief.yaml` (a file, or a directory containing one) to **ratify** instead of deriving a brief. When present it is the source of truth and routes the step 1 §8 GATE to the headless ratify path — the mirror of the interactive §3.1a `[R]` branch: schema-validate the brief → skip analyze-target / scope-definition (no re-derivation) → write through the canonical writer, overwriting in place (no `force` needed). `target_repo` / `skill_name` become optional and are ignored if also passed. HALT (exit 2, `halt_reason: "input-missing"`) if the value is empty or the resolved path does not exist; HALT (exit 2, `halt_reason: "input-invalid"`) if the brief fails schema validation |
9
10
  | `source_type` | no | `source` | If `docs-only`, `doc_urls` becomes required |
10
11
  | `doc_urls` | conditional | — | Required when `source_type=docs-only` (HALT exit 2, `halt_reason: "input-missing"` if empty). List of `url` or `url,label` |
11
12
  | `source_authority` | no | detected | `official` / `community` / `internal`. When absent and `target_repo` is a GitHub URL, step 1 §8 GATE probes `gh api user` and compares its login to the URL owner — match → `official`, otherwise → `community`. Local-path or `gh api user` failure → `community`. Forced to `community` when `source_type=docs-only` |
@@ -10,17 +10,25 @@ This check catches **semantic near-duplicates** that exact-name collision misses
10
10
 
11
11
  ## Procedure
12
12
 
13
- The brief portfolio is already indexed in QMD collections — one `{skill-name}-brief` collection per existing brief, registered by step 5 §5 of every prior Deep-tier run. The qmd CLI does not support glob-style collection selection, so enumerate first then query per collection in **a single message with N parallel Bash calls**:
13
+ The brief portfolio is already indexed in QMD collections — one `{skill-name}-brief` collection per existing brief, registered by step 5 §5 of every prior Deep-tier run. The qmd CLI does not support glob-style collection selection, so enumerate first (capping the sweep), then query the capped set per collection in **bounded parallel batches (≈4 concurrent Bash calls at a time)** each `qmd query` cold-starts an embedding model, so issuing all of them at once on a large portfolio thrashes memory and stalls:
14
14
 
15
15
  ```bash
16
- # 1. Enumerate brief collections (one per existing brief)
17
- qmd collection list | awk '/-brief$/{print $1}'
18
-
19
- # 2. For each collection, query the proposed name + intent text:
20
- qmd query "{name} {synthesized-or-intent-text}" -c {collection-name} -n 1 --min-score 0.6
16
+ # 1. Enumerate brief collections (one per existing brief), capping the sweep.
17
+ # Match the first whitespace field, not the whole line: `qmd collection list`
18
+ # rows end in a `(qmd://name/)` URI suffix, so a line-anchored `/-brief$/`
19
+ # matches nothing and the check silently no-ops. The cap bounds wall-time and
20
+ # concurrent model loads as the portfolio grows; `qmd collection list` has no
21
+ # guaranteed recency order, so this caps total count, not "newest N".
22
+ qmd collection list | awk '$1 ~ /-brief$/ {print $1}' | head -n 12
23
+
24
+ # 2. For each capped collection, query the proposed name + intent text.
25
+ # `timeout` bounds each cold model start so a slow qmd degrades instead of
26
+ # hanging; a non-zero exit (124 = timed out) falls through to the
27
+ # "times out → warn and continue" failure-mode branch below.
28
+ timeout 20 qmd query "{name} {synthesized-or-intent-text}" -c {collection-name} -n 1 --min-score 0.6
21
29
  ```
22
30
 
23
- Aggregate the top hits across all `-brief` collections; keep the 3 highest-scoring across the union. If any results come back, surface them as a heads-up — *not* a HALT:
31
+ Aggregate the top hits across the swept `-brief` collections; keep the 3 highest-scoring across the union. If any results come back, surface them as a heads-up — *not* a HALT:
24
32
 
25
33
  ```
26
34
  **Heads up — these existing briefs look semantically close to `{name}`:**
@@ -32,4 +40,4 @@ Continue with `{name}`, or pick a different name?
32
40
 
33
41
  ## Failure modes
34
42
 
35
- On any QMD failure (binary missing, collection list empty, any per-collection query times out): log `"warn: portfolio-similarity check skipped — qmd query failed: {error}"` and continue silently — never HALT. Quick / Forge / Forge+ tiers do not run this check (qmd is Deep-tier-only per the canonical tier definition: `Deep = + ast-grep + gh + QMD` in `skf-forge-tier-rw.py`).
43
+ On any QMD failure (binary missing, collection list empty, any per-collection query times out — `timeout` returns exit code 124): log `"warn: portfolio-similarity check skipped — qmd query failed: {error}"` and continue silently — never HALT. A timed-out or failed query drops only that collection from the aggregate; the remaining hits still surface. Quick / Forge / Forge+ tiers do not run this check (qmd is Deep-tier-only per the canonical tier definition: `Deep = + ast-grep + gh + QMD` in `skf-forge-tier-rw.py`).
@@ -22,7 +22,7 @@ To collaboratively define the skill's inclusion and exclusion boundaries using t
22
22
  - Do not make scope decisions unilaterally — user drives all scope choices
23
23
  - Produce: scope type, include patterns, exclude patterns
24
24
  - All user-facing output in `{communication_language}`
25
- - **Re-entry from step 4 [R] revise:** prior selections (`scope.type`, `scope.include`, `scope.exclude`, `scope.notes`, `scope.rationale`, `scripts_intent`, `assets_intent`, supplemental `doc_urls`) are preserved as the current state. Re-present them at each section as the existing answer; the user only re-confirms or overrides. Do not reset to the §2c template menu unless the user explicitly asks to start scope over. When `scope.rationale` is preserved and the user changes `chosen` (the scope type) on this pass, recompute `accepted_recommendation` (`chosen == recommended`) and refresh `reason` and `recorded` per the §2c capture rules — revise in place, do not append.
25
+ - **Re-entry from step 4 [R] revise:** prior selections (`scope.type`, `scope.include`, `scope.exclude`, `scope.notes`, `scope.tier_a_include`, `scope.rationale`, `scripts_intent`, `assets_intent`, supplemental `doc_urls`) are preserved as the current state. Re-present them at each section as the existing answer; the user only re-confirms or overrides. Do not reset to the §2c template menu unless the user explicitly asks to start scope over. When `scope.rationale` is preserved and the user changes `chosen` (the scope type) on this pass, recompute `accepted_recommendation` (`chosen == recommended`) and refresh `reason` and `recorded` per the §2c capture rules — revise in place, do not append.
26
26
 
27
27
  ## MANDATORY SEQUENCE
28
28
 
@@ -88,7 +88,7 @@ Load `{scopeTemplatesPath}` for the scope type options ([F], [M], [P], [C], [R])
88
88
 
89
89
  **Delegate the recommendation to `{recommendScopeTypeHelper}`** instead of walking the heuristic ladder in prose. The script is the single source of truth for the five-rule ladder (component-registry → reference-app keywords → specific-modules naming/count → narrow-public-api → default full-library) plus the docs-only short-circuit. Both the interactive recommendation and the §6 headless GATE invoke the same script — same inputs, same outputs, no drift.
90
90
 
91
- **Fetch registry-file contents before building the payload.** Step-02 §4.1 fetches `package.json` plus the entry-point files but does not fetch `registry.ts` / `components.ts` — the deep-match branch of the component-registry rule needs those contents. Scan the tree for any of `registry.ts` / `registry.tsx` / `components.ts` / `components.tsx` (any depth). For each match, fetch its contents in **one message with N parallel Bash calls** (`gh api repos/{owner}/{repo}/contents/{path}` for GitHub, file reads for local), then base64-decode the responses together. Skip the fetch if the tree contains no registry files.
91
+ **Fetch registry-file contents before building the payload.** Step-02 §4.1 fetches `package.json` plus the entry-point files but does not fetch `registry.ts` / `components.ts` — the deep-match branch of the component-registry rule needs those contents. Scan the tree for any of `registry.ts` / `registry.tsx` / `components.ts` / `components.tsx` (any depth). For each match, fetch its contents in **one message with N parallel Bash calls** (`gh api repos/{owner}/{repo}/contents/{path}?ref={analysis_ref}` for GitHub — `{analysis_ref}` is the ref resolved in step 02 §1, defaulting to `HEAD`; file reads for local), then base64-decode the responses together. Skip the fetch if the tree contains no registry files.
92
92
 
93
93
  Build the payload and invoke:
94
94
 
@@ -126,6 +126,24 @@ Wait for user selection. Empty input or just Enter accepts the recommendation; a
126
126
 
127
127
  Using the boundary definitions from `{scopeTemplatesPath}`, present the appropriate flow for the user's selected scope type ([F], [M], [P], [C], or [R]). Follow each type's prompts and wait for user input at each phase before proceeding.
128
128
 
129
+ ### 3b. Monorepo Subpackage Convention
130
+
131
+ **Applies only when step 02 §1b selected a workspace (`monorepo_workspace` is set).** A subpackage skill documents one package inside a larger repository, so the source and scope fields follow a fixed convention. Express them wrong and `skf-create-skill` cannot resolve the scope globs against the cloned source:
132
+
133
+ - **`source_repo` stays the repository URL**, never the subpackage. `skf-create-skill` clones the whole repo at the pinned ref and then roots extraction at the subpackage, so the repo URL is what it clones.
134
+ - **`scope.include` / `scope.exclude` globs are repo-root-relative and subpackage-prefixed.** Step 02 rebased its analysis against `monorepo_workspace`, but the emitted globs must still carry the workspace prefix (e.g. `packages/sdk/src/**`, not `src/**`) because they resolve against the repo root, not the subpackage root.
135
+ - **Record the subpackage layout in `scope.notes`:** the subpackage root (the `monorepo_workspace` path), the published package name and version, the resolved git ref, and the local-clone directory. This is the only field that maps the repo-URL `source_repo` to the actual skilled subpackage — downstream workflows and re-forges read it to reconstruct the source layout.
136
+
137
+ Carry the `monorepo_workspace` path forward from step 02 §1b into the `scope.notes` you draft here rather than recomputing it.
138
+
139
+ ### 3c. Tier-A Authoring Surface (coarse-glob monorepo subsets)
140
+
141
+ **Applies when a monorepo subpackage's `scope.include` uses coarse directory globs (`packages/foo/src/**`, `bin/**`) rather than an explicit file list.** Coarse globs also sweep in internal-only files (build scripts, state-store impls, generated config) that the package's public entry barrel never re-exports. `skf-create-skill` scores coverage against the *authoring* surface, and `skf-test-skill` re-derives that surface from the brief to guard against a deflated coverage denominator — so when the coarse-glob union is much larger than the documented export count and the brief names no narrower surface, the test gate inflates the denominator and an otherwise-complete skill scores as if it had large coverage gaps.
142
+
143
+ Head this off by capturing the authoring surface as **`scope.tier_a_include`**: the concrete source files whose named exports the package's public entry barrel (`index.ts`, `lib.rs`, `__init__.py`) actually re-exports. Derive the candidate list by tracing the entry barrel's re-export targets (the entry-point file was fetched in step 02), present it for the user to confirm or adjust, and store the confirmed list as `scope.tier_a_include`. List the definition files, not the umbrella barrel itself — a barrel re-exports the whole package, so including it widens the surface instead of narrowing it.
144
+
145
+ `scope.tier_a_include` does not change what gets extracted (that still follows `scope.include` / `scope.exclude`); it only pins the coverage denominator so the create-side and test-side counts agree without a mid-test hand-edit. Leave it unset when `scope.include` is already an explicit file list, or when the target is not a monorepo subset — there the coarse-glob union and the authoring surface coincide and no narrowing is needed.
146
+
129
147
  ### 4. Handle Language Override
130
148
 
131
149
  {If language detection confidence was low from step 02:}
@@ -13,7 +13,7 @@ For the detected source language, attempt the lookups in order. Stop at the firs
13
13
  - **Rust:** `Cargo.toml` `[package] version` (static). If `version = { workspace = true }`, resolve from workspace root `Cargo.toml` → `git describe --tags --abbrev=0`.
14
14
  - **Go:** version tag from `go.mod`, or `git describe --tags --abbrev=0`.
15
15
 
16
- For remote GitHub sources, fetch version-bearing files via `gh api repos/{owner}/{repo}/contents/{file}` (decode base64). For local sources, read the file directly.
16
+ For remote GitHub sources, fetch version-bearing files via `gh api repos/{owner}/{repo}/contents/{file}?ref={analysis_ref}` (decode base64) — `{analysis_ref}` is the ref resolved in step 02 §1, defaulting to `HEAD` when no `target_ref`/`target_version` was pinned; reading at the pinned ref keeps the "Detected version" consistent with the version being skilled. For local sources, read the file directly.
17
17
 
18
18
  If every step fails or returns a non-semver value, the detected version is `null` — the resolver below falls back to `"1.0.0"`.
19
19
 
@@ -54,7 +54,7 @@ Before writing, check whether the resolved target path already exists.
54
54
 
55
55
  **Ratify path (`ratify_mode: true` in workflow context):**
56
56
 
57
- The user already confirmed overwrite up-front at step 1 §3.1a when they chose `[R] Ratify` against the same file. Skip the interactive prompt below; log a single-line `brief-skill: ratify-mode auto-overwriting existing brief at {path}` and proceed to §3. Resuming the prompt would be friction the user has by now also reviewed and approved the brief at step 4. This auto-accept does not apply to headless mode (handled below) nor to the standard interactive path (also below).
57
+ The overwrite was already authorized when ratify mode was entered — interactively at step 1 §3.1a (`[R] Ratify` against the same file, then reviewed and approved at step 4), or headlessly at the step 1 §8 GATE `from_brief` route (the operator pointed the run at a brief to ratify). Either way, skip the interactive prompt below; log a single-line `brief-skill: ratify-mode auto-overwriting existing brief at {path}` and proceed to §3. **This ratify branch takes precedence over both the interactive and headless branches below** when `ratify_mode` is set, neither of those runs. In particular, a headless ratify (`from_brief`) auto-overwrites the brief in place without requiring `force`; `force` governs only the derive route, where overwriting a pre-existing brief is a genuine clobber the operator must opt into.
58
58
 
59
59
  **Interactive (`{headless_mode}` is false, `ratify_mode` not set):**
60
60
 
@@ -96,13 +96,19 @@ Assemble the brief context as a **flat** JSON object — every approved value is
96
96
  "scope_exclude": ["{approved exclude patterns}"],
97
97
  "scope_notes": "{approved scope notes or empty string}",
98
98
  "scope_rationale": null | {"recommended":"...","chosen":"...","accepted_recommendation":true|false,"heuristic":"...","reason":"...","recorded":"YYYY-MM-DD"},
99
+ "scope_tier_a_include": null | ["{tier-A authoring-surface patterns — from step 03 §3c capture, or hydrated on a ratify run}"],
100
+ "scope_amendments": null | [{"path":"...","action":"...","reason":"...","date":"YYYY-MM-DD","workflow":"..."}],
99
101
  "doc_urls": null | [{"url": "...", "label": "..."}],
100
102
  "scripts_intent": null | "{detect|none|free-text}",
101
103
  "assets_intent": null | "{detect|none|free-text}",
102
- "source_authority": null | "{official|community|internal}"
104
+ "source_authority": null | "{official|community|internal}",
105
+ "target_ref": null | "{explicit git ref — ratify only}",
106
+ "source_ref": null | "{resolved git ref — ratify only}"
103
107
  }
104
108
  ```
105
109
 
110
+ **Ratify mode (`ratify_mode: true`):** this path never ran step 2, so the version was not re-derived — it was hydrated from the upstream brief at step 1 §3.1a (interactive) or the §8 GATE `from_brief` route (headless). Add a `version_resolved` key set to that hydrated `version`; the writer's precedence checks `version_resolved` first, so this pins the output to the brief's authored version. **Without it**, `target_version` and `detected_version` are both null on a ratify run and the writer falls through to the `1.0.0` default, silently discarding the upstream version. Keep `target_version` set to the brief's `target_version` (null if it had none) so the writer's `target_version == version` invariant still holds. Likewise carry `target_ref`/`source_ref` and `scope_tier_a_include`/`scope_amendments` from the hydrated brief (all null on a derive run) so the writer round-trips the monorepo git ref, the stratified tier-A surface, and the amendment audit log instead of dropping them.
111
+
106
112
  Pipe it into the writer script with the `--from-flat` flag:
107
113
 
108
114
  ```bash
@@ -268,6 +268,25 @@ Replace per-function subsections with `references/pattern-*.md` groupings: one r
268
268
  - `stats.exports_documented` MAY be set to the Pattern Surface row count as a proxy, but its semantics change: it counts authored pattern surfaces, not public exports. Emit an adjacent `stats.pattern_surfaces_documented` with the same integer so downstream consumers (test-skill, feasibility, discovery) can discriminate.
269
269
  - `exports_public_api` / `exports_internal` / `exports_total` are not meaningful for a reference app — omit them, or set all three to the Pattern Surface count with a note in `stats.notes`: `"reference-app: export counts are pattern-surface proxies, not library exports"`.
270
270
  - Do NOT fabricate signature / type-coverage data from pattern surfaces. skf-test-skill will skip those categories when metadata flags a reference-app (same skip path as `stackSkill` once that flag is wired — see scoring-rules.md).
271
+ - Do NOT emit `effective_denominator` for reference-app scope, even when the source is a monorepo. `effective_denominator` counts public exports from the authoring-surface barrel — a library concept that `pattern_surfaces_documented` already replaces here. A reference-app-in-monorepo literally satisfies `compile.md` §4's emit-conditions (monorepo + non-`full-library` + stratified `scope.notes`), so this carve-out takes precedence: pattern-surface coverage is the reference-app basis, and skf-test-skill skips library-export coverage for it.
272
+
273
+ **Language / spec-reference sub-shape:** A reference app that documents an engine- or spec-versioned **language** — a query language, grammar, or DSL (e.g. SurrealQL @ SurrealDB) whose value is construct idioms rather than wiring or exports — is a recognized reference-app sub-shape. Keep `scope.type: "reference-app"` (there is no separate enum value), so every carve-out above applies unchanged (`pattern_surfaces_documented` proxy, no `effective_denominator`, test-skill signature/type skip). Adapt the three overrides to the language's surface instead of app wiring:
274
+
275
+ - **Section 4 (Pattern Surface)** becomes a **construct-area map**, not a wiring list. Each row is a language construct area, where it lives in the source, and what the user writes there:
276
+
277
+ ```markdown
278
+ ## Pattern Surface
279
+
280
+ | # | Construct Area | Where in Source | Purpose |
281
+ |---|----------------|-----------------|---------|
282
+ | 1 | {statements / DDL} | {sql::statements} | {what the user writes} |
283
+ | 2 | {operators} | {sql::operator} | {…} |
284
+ | … | … | … | … |
285
+ ```
286
+
287
+ - **Section 3 (Adoption Steps)** becomes **production-task workflows** — ordered tasks a user performs *in the language* (define a schema, write a query, call a built-in function), each with a minimal snippet — not a copy-this-wiring narrative.
288
+ - **Tier 2** groups `references/*.md` by **language concern** (e.g. `statements.md`, `functions.md`, `types.md`) — one file per construct family — instead of `pattern-*.md` wiring concerns.
289
+ - **metadata.json:** `pattern_surfaces_documented` counts the documented construct areas; the no-`effective_denominator` carve-out applies (a language has no export barrel). The brief's `language` field records the **documented** language (e.g. `surrealql`), which may differ from the source language it was extracted from (e.g. `rust`) — see `skf-analyze-source/assets/skill-brief-schema.md`.
271
290
 
272
291
  **When this clause does NOT apply:** `full-library`, `specific-modules`, `public-api`, `component-library`, or `docs-only`. Those scope types have their own assembly semantics and export-count conventions — do not mix.
273
292
 
@@ -192,6 +192,7 @@ Indexed pipe-delimited format for CLAUDE.md managed section (~80-120 tokens per
192
192
  "name": "{skill-name}",
193
193
  "version": "{source-version}",
194
194
  "skill_type": "single",
195
+ "scope_type": "{full-library|specific-modules|public-api|component-library|reference-app|docs-only}",
195
196
  "source_authority": "{official|community|internal}",
196
197
  "source_repo": "{github-url}",
197
198
  "source_root": "{resolved-source-path}",