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.
- package/.claude-plugin/marketplace.json +1 -1
- package/docs/_data/pinned.yaml +1 -1
- package/docs/workflows.md +1 -1
- package/package.json +2 -2
- package/src/knowledge/version-paths.md +4 -3
- package/src/shared/health-check.md +1 -1
- package/src/shared/scripts/schemas/skill-brief.v1.json +5 -0
- package/src/shared/scripts/skf-atomic-write.py +4 -1
- package/src/shared/scripts/skf-detect-language.py +34 -0
- package/src/shared/scripts/skf-detect-workspaces.py +54 -0
- package/src/shared/scripts/skf-enumerate-stack-skills.py +107 -16
- package/src/shared/scripts/skf-forge-tier-rw.py +4 -1
- package/src/shared/scripts/skf-manifest-ops.py +8 -4
- package/src/shared/scripts/skf-merge-ccc-exclusions.py +4 -1
- package/src/shared/scripts/skf-qmd-classify-collections.py +29 -3
- package/src/shared/scripts/skf-rebuild-managed-sections.py +72 -7
- package/src/shared/scripts/skf-scan-skill-md-structure.py +13 -3
- package/src/shared/scripts/skf-validate-brief-inputs.py +79 -18
- package/src/shared/scripts/skf-validate-brief-schema.py +47 -1
- package/src/shared/scripts/skf-validate-frontmatter.py +60 -2
- package/src/shared/scripts/skf-write-skill-brief.py +97 -17
- package/src/skf-analyze-source/assets/skill-brief-schema.md +10 -2
- package/src/skf-analyze-source/references/generate-briefs.md +30 -9
- package/src/skf-analyze-source/references/recommend.md +1 -1
- package/src/skf-brief-skill/SKILL.md +3 -3
- package/src/skf-brief-skill/assets/skill-brief-schema.md +6 -1
- package/src/skf-brief-skill/references/analyze-target.md +22 -6
- package/src/skf-brief-skill/references/confirm-brief.md +4 -0
- package/src/skf-brief-skill/references/gather-intent.md +14 -4
- package/src/skf-brief-skill/references/headless-args.md +3 -2
- package/src/skf-brief-skill/references/portfolio-similarity-check.md +16 -8
- package/src/skf-brief-skill/references/scope-definition.md +20 -2
- package/src/skf-brief-skill/references/version-resolution.md +1 -1
- package/src/skf-brief-skill/references/write-brief.md +8 -2
- package/src/skf-create-skill/assets/compile-assembly-rules.md +19 -0
- package/src/skf-create-skill/assets/skill-sections.md +1 -0
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +18 -5
- package/src/skf-create-skill/references/compile.md +5 -2
- package/src/skf-create-skill/references/extract.md +8 -3
- package/src/skf-create-skill/references/extraction-patterns.md +14 -5
- package/src/skf-create-skill/references/generate-artifacts.md +5 -1
- package/src/skf-create-skill/references/source-resolution-protocols.md +5 -2
- package/src/skf-create-skill/references/validate.md +4 -3
- package/src/skf-create-stack-skill/assets/provenance-map-schema.md +3 -3
- package/src/skf-create-stack-skill/assets/stack-skill-template.md +56 -0
- package/src/skf-create-stack-skill/references/compile-stack.md +13 -2
- package/src/skf-create-stack-skill/references/compose-mode-rules.md +2 -0
- package/src/skf-create-stack-skill/references/detect-integrations.md +5 -2
- package/src/skf-create-stack-skill/references/generate-output.md +49 -9
- package/src/skf-create-stack-skill/references/validate.md +8 -5
- package/src/skf-export-skill/references/load-skill.md +5 -4
- package/src/skf-export-skill/references/manifest-rebuild.md +1 -1
- package/src/skf-export-skill/references/token-report.md +2 -2
- package/src/skf-export-skill/references/update-context.md +25 -11
- package/src/skf-quick-skill/assets/skill-template.md +2 -2
- package/src/skf-quick-skill/references/compile.md +4 -2
- package/src/skf-quick-skill/references/write-and-validate.md +2 -2
- package/src/skf-refine-architecture/SKILL.md +1 -1
- package/src/skf-refine-architecture/references/gap-analysis.md +30 -4
- package/src/skf-refine-architecture/references/init.md +2 -0
- package/src/skf-refine-architecture/references/issue-detection.md +8 -3
- package/src/skf-test-skill/references/coherence-check.md +4 -4
- package/src/skf-test-skill/references/coverage-check.md +58 -16
- package/src/skf-test-skill/references/external-validators.md +7 -7
- package/src/skf-test-skill/references/init.md +5 -4
- package/src/skf-test-skill/references/score.md +6 -3
- package/src/skf-test-skill/references/scoring-rules.md +12 -0
- package/src/skf-test-skill/references/source-access-protocol.md +19 -3
- package/src/skf-test-skill/scripts/compute-score.py +4 -1
- package/src/skf-update-skill/references/detect-changes.md +15 -2
- package/src/skf-update-skill/references/merge.md +22 -0
- package/src/skf-update-skill/references/re-extract.md +19 -7
- package/src/skf-update-skill/references/write.md +3 -0
- package/src/skf-verify-stack/SKILL.md +2 -2
- package/src/skf-verify-stack/assets/feasibility-report-template.md +1 -1
- package/src/skf-verify-stack/references/coverage.md +35 -6
- package/src/skf-verify-stack/references/init.md +6 -2
- package/src/skf-verify-stack/references/integration-verification-rules.md +1 -1
- package/src/skf-verify-stack/references/integrations.md +7 -3
- package/src/skf-verify-stack/references/report.md +5 -1
- package/src/skf-verify-stack/references/requirements.md +3 -1
- package/src/skf-verify-stack/references/synthesize.md +11 -3
- package/tools/cli/lib/installer.js +1 -0
|
@@ -13,10 +13,13 @@ When parsing tessl output, check these fields against the thresholds below. Viol
|
|
|
13
13
|
| Field | Threshold | Severity on violation | Rationale |
|
|
14
14
|
|---|---|---|---|
|
|
15
15
|
| `review_score` (overall) | `>= 60` | warn | Two-tier SKF skills trade some conciseness score for progressive disclosure. 60 is the floor below which something is genuinely wrong. |
|
|
16
|
-
| `
|
|
16
|
+
| `description_field` validator finding | none expected | **error — recover, then halt** | The **deterministic** `description_field` validator surfaces as a `findings[]` entry (not as the judge percentage). It parses the frontmatter `description` as a raw string and rejects angle-bracket / XML-tag content. step 5 §2a unconditionally replaces `<` with `{` and `>` with `}` before validation, so a `description_field` finding means the sanitizer was bypassed — a downstream tool rewrote the description. Attempt recovery per rule `description-xml-tags-guarded-upstream` (re-apply the §2a substitution in place on the staging SKILL.md frontmatter and re-run tessl). Halt only if recovery fails. |
|
|
17
|
+
| `description_score` (LLM judge %) | `>= 60` | warn | The judge's 0–100 discoverability score, composed of `trigger_term_quality`, `specificity`, `completeness`, and `distinctiveness` sub-scores. A value below 100 while the deterministic `description_field` validator PASSES is a soft discoverability hint (e.g. jargon density), **not** a sanitizer bypass — record a warning and continue. Never halt on the judge percentage alone; the authored brief is the source of truth for the description. |
|
|
17
18
|
| `content_score` | `>= 60` | warn | Two-tier design deliberately duplicates key API data across Tier 1 and Tier 2. Conciseness scorer penalizes this. 60 is the acceptable floor. |
|
|
18
19
|
|
|
19
|
-
If `
|
|
20
|
+
**Recovery is gated on the deterministic validator, not the judge percentage.** If tessl emits a `findings[]` entry with rule ID `description_field` (angle-bracket / XML-tag rejection), **attempt recovery first** per rule `description-xml-tags-guarded-upstream` below: re-apply step 5 §2a's `<` → `{` / `>` → `}` substitution in place on the staging SKILL.md frontmatter `description`, then re-run `npx -y tessl skill review <staging-skill-dir>` once. If the re-run clears the `description_field` finding, log `description-recovery: applied ({count} substitutions)` in the evidence report and proceed to normal suggestion handling. If recovery fails, halt with: "Description sanitization recovery failed — step 5 §2a's `<`/`>` → `{`/`}` replacement did not resolve the tessl finding. Investigate the staging SKILL.md frontmatter for non-angle-bracket content that tessl is still rejecting, and patch §2a accordingly. Do not edit the description manually." Do NOT proceed to §6b user prompt on an unrecovered failure.
|
|
21
|
+
|
|
22
|
+
A `description_score` (LLM judge %) below 100 **without** an accompanying `description_field` finding is **not** a recovery trigger and **not** a halt condition: record `description_judge_score: {n}% (deterministic description_field validator PASSED)` as a warning in the evidence report and continue to suggestion handling. The recover-then-halt path is reserved for the deterministic validator.
|
|
20
23
|
|
|
21
24
|
---
|
|
22
25
|
|
|
@@ -61,6 +64,16 @@ Each rule below describes a tessl suggestion that Skill Forge expects and dismis
|
|
|
61
64
|
- **Rationale:** Progressive disclosure is the intentional SKF design. The conciseness scorer has no concept of Tier 1 / Tier 2 and flags the structure as redundancy. Collapsing the two tiers would eliminate standalone discoverability and break split-body behavior.
|
|
62
65
|
- **Action:** dismiss. Do not attempt to raise the score above 2/3.
|
|
63
66
|
|
|
67
|
+
### Rule: `provenance-citations-required`
|
|
68
|
+
|
|
69
|
+
- **Scorer:** `conciseness`, `actionability`, or any
|
|
70
|
+
- **Match criteria (any of):**
|
|
71
|
+
- Suggestion text references `[SRC:`, `[AST:`, `[QMD:`, or `[EXT:` annotations AND one of: `remove`, `strip`, `omit`, `drop`
|
|
72
|
+
- Suggestion mentions `provenance annotations`, `source citations`, or `source markers` AND one of: `remove`, `unnecessary`, `token bloat`, `token cost`
|
|
73
|
+
- **Expected score:** `conciseness` 1/3 is normal while inline provenance annotations are present. Raising it would require stripping the annotations.
|
|
74
|
+
- **Rationale:** SKF's zero-hallucination policy requires every claim in SKILL.md to carry a provenance citation — see the "Signature fidelity" rule in `assets/compile-assembly-rules.md` and the "Provenance Citation Format" table in `assets/skill-sections.md`. Removing them would break the provenance audit trail and the trust contract that lets `skf-update-skill` detect source drift and `skf-audit-skill` verify claims against source.
|
|
75
|
+
- **Action:** dismiss.
|
|
76
|
+
|
|
64
77
|
### Rule: `description-xml-tags-guarded-upstream`
|
|
65
78
|
|
|
66
79
|
- **Scorer:** `description_field` (deterministic validator)
|
|
@@ -70,8 +83,8 @@ Each rule below describes a tessl suggestion that Skill Forge expects and dismis
|
|
|
70
83
|
- **Action:** **Attempt recovery, then halt if recovery fails.** This is not a dismissal — the finding represents a real sanitizer bypass that step 6 must resolve before proceeding.
|
|
71
84
|
1. **Re-apply §2a in place.** Read the current `description` from the on-disk staging SKILL.md frontmatter, replace every `<` with `{` and every `>` with `}`, and write the result back to the frontmatter. Re-sync the in-context copy to match. Count the substitutions; if zero, the description is already clean and the tessl finding points at something other than angle brackets — skip to the halt branch below.
|
|
72
85
|
2. **Re-run tessl once.** Execute `npx -y tessl skill review <staging-skill-dir>` a second time and re-parse the JSON output.
|
|
73
|
-
3. **On success** (`
|
|
74
|
-
4. **On failure** (`
|
|
86
|
+
3. **On success** (the `description_field` finding is absent from the re-run): log `description-recovery: applied ({count} substitutions)` in the evidence report under "Dismissed tessl suggestions", then continue §6 with the recovered review result (proceed to normal suggestion handling against the rules below). The rerun's `judge_suggestions[]` replaces the original. A residual judge `description_score < 100` on the re-run is a warning, not a failure — recovery success is keyed on the deterministic finding clearing, not on the judge percentage reaching 100.
|
|
87
|
+
4. **On failure** (the `description_field` finding persists after re-sanitization): halt with: "Description sanitization recovery failed — step 5 §2a's `<`/`>` → `{`/`}` replacement did not resolve the tessl finding. Investigate the staging SKILL.md frontmatter for non-angle-bracket content that tessl is still rejecting, and patch §2a accordingly. Do not edit the description manually."
|
|
75
88
|
|
|
76
89
|
The recovery path makes the skill shippable when a downstream tool (`skill-check --fix`, `split-body`, or a future validator) re-introduces angle brackets into the description after §2a has run. The Description Guard Protocol in step 6 §0 is the first line of defense against such rewrites; this rule is the second line, active when the guard also missed.
|
|
77
90
|
|
|
@@ -81,7 +94,7 @@ The recovery path makes the skill shippable when a downstream tool (`skill-check
|
|
|
81
94
|
|
|
82
95
|
1. Load this file completely at the start of §6.
|
|
83
96
|
2. Run `npx -y tessl skill review <staging-skill-dir>` and parse JSON output.
|
|
84
|
-
3. Check score thresholds. For `
|
|
97
|
+
3. Check score thresholds. For a deterministic `description_field` finding (angle-bracket / XML-tag rejection): follow the recovery-then-halt path described in the threshold table above and the `description-xml-tags-guarded-upstream` rule below — re-apply §2a in place, re-run tessl once, and continue on recovery success (finding cleared) or halt on recovery failure. A judge `description_score` below 100 while the `description_field` validator passes is a warning, not a halt. For other warns: continue, log warnings to evidence report.
|
|
85
98
|
4. For each `judge_suggestions[]` entry in the output:
|
|
86
99
|
a. Iterate the rules above in order.
|
|
87
100
|
b. If a rule's match criteria are satisfied, record `{rule_id, rationale, suggestion_text}` in the evidence report under "Dismissed tessl suggestions" and move to the next suggestion.
|
|
@@ -100,6 +100,7 @@ Following the structure from the skill-sections data file:
|
|
|
100
100
|
- Set `generation_date` to current ISO-8601 timestamp
|
|
101
101
|
- Set `source_commit` from resolved source (if available)
|
|
102
102
|
- Set `source_ref` from resolved source ref (tag name, branch, or `HEAD`; null if unavailable)
|
|
103
|
+
- Set `scope_type` from the brief's `scope.type` value verbatim (`full-library`, `specific-modules`, `public-api`, `component-library`, `reference-app`, or `docs-only`). **Always emit this field.** `skf-test-skill` keys reference-app handling on `metadata.json.scope_type == "reference-app"` — both the scoring redistribution (Signature Accuracy / Type Coverage marked N/A) and the coverage-check §4b count-coherence skip. Omitting it silently mis-scores a reference-app skill as a library (zero-export barrel HALT or false metadata-drift findings), which is why it previously had to be hand-added during test-skill. The value is informational for the other scope types (consumers only branch on `reference-app`), but emit it for all so the producer/consumer contract holds.
|
|
103
104
|
- Set `stats` from extraction aggregate counts:
|
|
104
105
|
- `exports_documented`: count of exports with documentation in the assembled SKILL.md
|
|
105
106
|
- `exports_public_api`: count of exports from public entry points (`__init__.py`, `index.ts`, `lib.rs`, or equivalent) — derive this from step 3's entry-point validation (section 4b), NOT from the provenance-map entry count (which may be incomplete if extraction patterns missed some export types)
|
|
@@ -107,12 +108,14 @@ Following the structure from the skill-sections data file:
|
|
|
107
108
|
- `exports_total`: `exports_public_api` + `exports_internal`
|
|
108
109
|
- `public_api_coverage`: `exports_documented / exports_public_api` (1.0 when all public API exports are documented; `null` if `exports_public_api` is 0)
|
|
109
110
|
- `total_coverage`: `exports_documented / exports_total` (may be low for large codebases — this is expected; `null` if `exports_total` is 0)
|
|
110
|
-
- `effective_denominator` (**optional** — emit only for stratified-scope monorepo packages): the count of public exports from files matched by the brief's authoring-surface globs, filtered by `scope.exclude`, resolved against `source_path`. **Prefer `scope.tier_a_include` when the brief supplies it** — that narrow list represents the authoring surface the brief intends to document; resolve its globs across `source_path` and count the union of named exports. **Otherwise use `scope.include`** — the coarse list. This is the coverage denominator `skf-test-skill` uses when the package is a curated subset of a multi-package repository, so it must match the brief's authoring intent. Compute when ALL of the following hold:
|
|
111
|
+
- `effective_denominator` (**optional** — emit only for stratified-scope monorepo packages): the count of public exports from files matched by the brief's authoring-surface globs, filtered by `scope.exclude`, resolved against `source_path`. **Prefer `scope.tier_a_include` when the brief supplies it** — that narrow list represents the authoring surface the brief intends to document; resolve its globs across `source_path` and count the union of **named exports** — items reachable from the language's public entry-point barrel (`lib.rs` `pub use`, `index.ts` / `index.js` re-exports, `__init__.py` exports), where a type counts **once** with its methods and impl-block members rolling up under it (count a `pub fn` only when it is a free function reachable from the barrel, never a method on an already-counted type). **Otherwise use `scope.include`** — the coarse list. This counting unit matches what `skf-test-skill` re-derives on the consumer side (`coverage-check.md` §2c excludes `kind: "method"` from `documented_set`), so the producer- and test-side denominators stay aligned; without it, a type-heavy API (a few handle types carrying hundreds of methods) inflates the denominator by an order of magnitude and auto-fails the coverage gate. This is the coverage denominator `skf-test-skill` uses when the package is a curated subset of a multi-package repository, so it must match the brief's authoring intent. Compute when ALL of the following hold:
|
|
111
112
|
1. The source is a monorepo (detected via `packages/` layout, `workspaces` field in root `package.json`, `lerna.json`, `rush.json`, `nx.json`, or Cargo `[workspace]`).
|
|
112
|
-
2. `scope.type` is not `full-library
|
|
113
|
+
2. `scope.type` is not `full-library` (and not `reference-app` — see carve-out below), AND the resolved include list (`tier_a_include` if present, else `scope.include`) lists a curated file/directory subset rather than the full workspace.
|
|
113
114
|
3. `scope.notes` is present and documents the stratification strategy (e.g., a tiered A/B/C plan) — this serves as the intent marker confirming the subset is by design.
|
|
114
115
|
|
|
115
116
|
Otherwise omit the field entirely — when absent, `skf-test-skill` falls back to `exports_public_api`. See `skf-test-skill` `references/source-access-protocol.md` §Source API Surface Definition ("Stratified-scope monorepo packages") for the test-side consumption rules.
|
|
117
|
+
|
|
118
|
+
**Reference-app carve-out:** never emit `effective_denominator` for `scope.type: "reference-app"`, even when the three conditions above are literally satisfied (a reference-app-in-monorepo matches all of them). A reference app's coverage basis is `pattern_surfaces_documented`, not library exports — see the Reference-App stats semantics in `{compileAssemblyRules}`.
|
|
116
119
|
- Set `description` from the SKILL.md frontmatter `description` field (already assembled in section 2)
|
|
117
120
|
- Set `language` from source analysis (e.g., `"typescript"`, `"python"`) — use the primary language of the entry point file
|
|
118
121
|
- Set `ast_node_count` from extraction stats if ast-grep was used (Forge/Deep tier), otherwise omit
|
|
@@ -91,9 +91,9 @@ If ALL of these conditions are true:
|
|
|
91
91
|
|
|
92
92
|
Then run CCC indexing and discovery on the resolved clone (workspace or ephemeral):
|
|
93
93
|
|
|
94
|
-
1. **Check existing index:** If `{remote_clone_path}/.cocoindex_code/` already exists (workspace repo with a persisted CCC index), skip steps 2-3 and proceed directly to step 4 using `ccc search --refresh` instead of plain `ccc search`. The `--refresh` flag tells CCC to re-index if files have changed since the last index, then search. This is the fast path for workspace repos that have been indexed before. **Note:** If `--refresh` is not supported by the installed ccc version, omit the flag — ccc will use the existing index.
|
|
94
|
+
1. **Check existing index:** If `{remote_clone_path}/.cocoindex_code/` already exists (workspace repo with a persisted CCC index), skip steps 2-3 and proceed directly to step 4 using `ccc search --refresh` instead of plain `ccc search`. The `--refresh` flag tells CCC to re-index if files have changed since the last index, then search. This is the fast path for workspace repos that have been indexed before. **Note:** If `--refresh` is not supported by the installed ccc version, omit the flag — ccc will use the existing index. Before trusting a reused index, run the same `ccc status` `Languages:`-breakdown integrity check as step 3: a persisted index can be degraded (source language absent), and `--refresh` re-indexes changed files but does not repair degraded settings — if the source language is missing, fall through to a clean rebuild (`ccc init -f` + `ccc index`) rather than searching it.
|
|
95
95
|
|
|
96
|
-
2. **Initialize index (first time only):** Run `cd {remote_clone_path} && ccc init`. If init fails, set `{ccc_discovery: []}` and continue — this is not an error.
|
|
96
|
+
2. **Initialize index (first time only):** Run `cd {remote_clone_path} && ccc init`. If init exits non-zero with `A parent directory has a project marker` — the common case when the clone is nested under a ccc-indexed project (e.g. a `.forge-sources/` checkout inside this repo) — re-run as `cd {remote_clone_path} && ccc init -f` to initialize at the subtree anyway (same handling as step 7 §6b). If init fails for any other reason, or the `-f` retry also fails, set `{ccc_discovery: []}` and continue — this is not an error.
|
|
97
97
|
|
|
98
98
|
**Apply standard exclusions:** After `ccc init`, apply generic build/dependency exclusions to `{remote_clone_path}/.cocoindex_code/settings.yml`. These are standard artifact patterns, NOT SKF-specific paths (the workspace checkout is a source repo, not an SKF project):
|
|
99
99
|
|
|
@@ -105,7 +105,7 @@ Then run CCC indexing and discovery on the resolved clone (workspace or ephemera
|
|
|
105
105
|
|
|
106
106
|
**Note:** Brief-specific `include_patterns` and `exclude_patterns` are NOT written to `settings.yml`. The CCC index is general-purpose — it indexes everything (minus standard artifacts). Brief-specific filtering happens at search result time, not index time. This allows a single workspace CCC index to serve multiple briefs with different scope filters.
|
|
107
107
|
|
|
108
|
-
3. **Index the clone:** Run `cd {remote_clone_path} && ccc index` with an extended timeout or in background mode. Indexing can take several minutes on large codebases (1000+ files).
|
|
108
|
+
3. **Index the clone:** Run `cd {remote_clone_path} && ccc index` with an extended timeout or in background mode. Indexing can take several minutes on large codebases (1000+ files). Verify completion with `ccc status`, then **verify integrity**: a non-zero `Chunks`/`Files` total is not sufficient — read the `Languages:` breakdown and confirm the source's primary language (`{brief.language}`) reports a non-trivial chunk count. An index dominated by `markdown`/config chunks with the source language absent or near-zero means `ccc index` ran against degraded settings (a stale `.cocoindex_code/` inherited from a parent, or a no-op init over a prior partial index) and the source code was never indexed — searches over it return nothing useful. When the source language is absent, rebuild from a clean init: `cd {remote_clone_path} && ccc init -f` then `ccc index`, and re-check the `Languages:` breakdown. If indexing fails, or the source language is still missing after a forced re-init, set `{ccc_discovery: []}` and continue — this is not an error.
|
|
109
109
|
|
|
110
110
|
4. **Construct semantic query:** Build from brief data: `"{brief.name} {brief.scope}"`. Truncate to 80 characters — keep the full skill name and trim `brief.scope` from the end. If `brief.scope` is very short (< 10 chars), append terms from `brief.description` to fill the remaining space.
|
|
111
111
|
|
|
@@ -205,6 +205,11 @@ After extraction, validate the collected exports against the package's actual pu
|
|
|
205
205
|
- **TypeScript/JS:** Read `index.ts`/`index.js` — same comparison logic.
|
|
206
206
|
- **Rust:** Read `lib.rs` — extract `pub use` items. Same logic. **Go:** Scan for exported (capitalized) identifiers.
|
|
207
207
|
|
|
208
|
+
**Multi-entry packages (`exports` map / declaration-file entry points).** A single per-language entry-point read misses public surface that a package ships through its `package.json` `exports` map — especially committed `.d.ts` / `.d.mts` declaration files that resolve **outside** the conventional source dir (e.g. a monorepo package whose `./macro` subpath maps to `macro/index.d.mts`, listed in `files[]` but not under `src/`). When the in-scope package declares an `exports` map:
|
|
209
|
+
|
|
210
|
+
- Resolve each `exports` subpath to its target file and treat that file — and any committed `.d.ts` / `.d.mts` declaration it resolves to — as an authoritative public entry point, reading it the same way as the primary barrel above even when it lives outside `src/`.
|
|
211
|
+
- If a resolved `exports` subpath target falls **outside** the brief's `scope.include` globs, surface a note: `"warn: public entry point {path} (exports subpath '{subpath}') resolves outside scope.include — widen scope.include before extraction, or this surface stays undocumented and excluded from the coverage denominator."` Widening `scope.include` here keeps the documented surface aligned with the `effective_denominator` that compile.md §4 derives from those same globs, without mid-run scope surgery.
|
|
212
|
+
|
|
208
213
|
Use the entry point as the authoritative source for `metadata.json`'s `exports[]` array.
|
|
209
214
|
|
|
210
215
|
**If entry point is missing or unreadable:** Skip validation with a warning.
|
|
@@ -249,7 +249,7 @@ constraints:
|
|
|
249
249
|
|
|
250
250
|
**JavaScript/TypeScript — exported functions:**
|
|
251
251
|
|
|
252
|
-
> **Language selection:** Use `language: typescript` for `.ts` files and `language: tsx` for `.tsx` files. Patterns that work with `typescript` may return zero results with `tsx` and vice versa — they use different tree-sitter parsers. For mixed codebases, run each pattern twice (once per language) and merge results. Note: `export function`
|
|
252
|
+
> **Language selection:** Use `language: typescript` for `.ts` files and `language: tsx` for `.tsx` files. Patterns that work with `typescript` may return zero results with `tsx` and vice versa — they use different tree-sitter parsers. For mixed codebases, run each pattern twice (once per language) and merge results. Note: the `export function $NAME($$$PARAMS)` pattern returns **zero** with `tsx` on ast-grep 0.41.x (see Known Limitation #5) **and** with plain `typescript` on 0.42.x (see Known Limitation #9) — use source reading as the fallback for `export function` on both.
|
|
253
253
|
|
|
254
254
|
```yaml
|
|
255
255
|
id: js-exported-functions
|
|
@@ -278,16 +278,16 @@ rule:
|
|
|
278
278
|
|
|
279
279
|
> **JS/TS Pattern Merging:** Modern TypeScript codebases often use `export const` exclusively for all exports (arrow functions, objects, constants). Run ALL four JS/TS patterns (functions, arrow functions, constants, classes) and merge results by `$NAME`. Priority when deduplicating: arrow function match > function declaration match > constant match. Arrow function matches capture parameters directly; constant matches require inspecting `$VALUE` to extract signatures.
|
|
280
280
|
|
|
281
|
-
**JavaScript/TypeScript — exported classes
|
|
281
|
+
**JavaScript/TypeScript — exported classes:**
|
|
282
282
|
|
|
283
283
|
```yaml
|
|
284
284
|
id: js-exported-classes
|
|
285
285
|
language: typescript
|
|
286
286
|
rule:
|
|
287
|
-
pattern: 'export class $NAME'
|
|
287
|
+
pattern: 'export class $NAME { $$$ }'
|
|
288
288
|
```
|
|
289
289
|
|
|
290
|
-
> **Important:**
|
|
290
|
+
> **Important:** The body (`{ $$$ }`) is required on ast-grep 0.42.x. The bare `export class $NAME` pattern returns zero matches — and emits a `Pattern contains an ERROR node` warning — through **both** `find_code()` and the CLI, because an incomplete class declaration does not parse as a complete statement (see Known Limitation #9). With the body present, the simple `find_code()` pattern detects class exports reliably; `find_code_by_rule` would additionally require an explicit AST `kind` rule.
|
|
291
291
|
|
|
292
292
|
**JavaScript/TypeScript — re-export detection (use `find_code`):**
|
|
293
293
|
|
|
@@ -383,7 +383,7 @@ Unlinked Props interfaces are included as standalone type exports. Unlinked comp
|
|
|
383
383
|
|
|
384
384
|
When using ast-grep for extraction, be aware of these documented limitations:
|
|
385
385
|
|
|
386
|
-
1. **`find_code_by_rule`
|
|
386
|
+
1. **`export class $NAME` needs a body on 0.42.x; `find_code_by_rule` needs explicit `kind`:** The bare `export class $NAME` pattern returns zero through **both** `find_code()` and the CLI on ast-grep 0.42.x — add the body, `export class $NAME { $$$ }` (see #9). With `find_code_by_rule`, a class export additionally needs a `kind` rule for the tree-sitter node type; the simpler `find_code()` with the body-form pattern is the lighter path.
|
|
387
387
|
|
|
388
388
|
2. **Re-export patterns produce multiple AST nodes:** `export { A, B, C } from './module'` decomposes into multiple metavariable bindings for `$$$NAMES`. Results require post-processing to split comma-separated names.
|
|
389
389
|
|
|
@@ -402,6 +402,15 @@ When using ast-grep for extraction, be aware of these documented limitations:
|
|
|
402
402
|
|
|
403
403
|
8. **Rust `pub fn` any-pattern returns zero; bare `pub fn $NAME` over-captures (ast-grep 0.42.x):** The `rust-public-functions` recipe's `any:` of `pub fn $NAME($$$PARAMS) -> $RET` / `pub fn $NAME($$$PARAMS)` returns "No matches found" on real Rust sources with ast-grep 0.42.2, even on crates containing 200+ public functions. Dropping to the bare `pub fn $NAME` pattern matches, but over-captures restricted-visibility functions such as `pub(crate) fn` / `pub(super) fn`, which are **not** public API. **Workaround:** Prefer a visibility-constrained source grep — `rg '^\s*pub fn ' <src>` filtered to exclude lines beginning `pub(` — cross-checked against the AN-verified public surface, at T1-low confidence. Never silently accept zero results for Rust public functions, and never treat a bare `pub fn $NAME` match set as the public API without stripping `pub(...)`-restricted items. See the Rust — public functions recipe above.
|
|
404
404
|
|
|
405
|
+
9. **Plain `language: typescript` declaration patterns without a body return zero (ast-grep 0.42.x):** For `language: typescript` on ast-grep 0.42.2, the incomplete-statement patterns `export class $NAME`, `export function $NAME($$$PARAMS)`, `export type $NAME`, and `export enum $NAME` all return **zero** matches against real `.ts` sources — `export class` / `export type` / `export enum` additionally print `Pattern contains an ERROR node`. This affects the CLI (`ast-grep run -p ... -l typescript`) and the MCP `find_code()` API **identically** — `find_code()` is not a workaround. Only `export const $NAME = $VALUE` matches as documented. The cause is that a declaration pattern missing its body/initializer does not parse as a complete statement. **Workarounds (verified on 0.42.2 via both CLI and `find_code`):**
|
|
406
|
+
- **class:** add the body — `export class $NAME { $$$ }` matches.
|
|
407
|
+
- **enum:** add the body — `export enum $NAME { $$$ }` matches.
|
|
408
|
+
- **type alias:** drop `export` and include the initializer — `type $NAME = $T` matches (and captures both `export type` and bare `type` declarations).
|
|
409
|
+
- **interface:** `export interface $NAME { $$$ }` already carries a body and **works** for plain interfaces — but it misses generic (`interface $NAME<T>`) and `extends` forms; source-read those at T1-low.
|
|
410
|
+
- **function:** the body form `export function $NAME($$$PARAMS) { $$$ }` matches only functions with no return-type annotation and no `async` modifier, so it is unreliable. Prefer a source-read fallback (or a barrel cross-check) at T1-low for `export function`, mirroring the tsx guidance in #5.
|
|
411
|
+
|
|
412
|
+
Never silently accept zero results for a declaration form the source language commonly uses.
|
|
413
|
+
|
|
405
414
|
### Component Library Demo/Example Auto-Exclusion
|
|
406
415
|
|
|
407
416
|
When `scope.type: "component-library"`, auto-detect and propose demo/example exclusions before extraction begins. **User confirmation is required before applying** — some `examples/` directories contain API-level code.
|
|
@@ -187,7 +187,11 @@ Ensure the source path used for extraction is indexed by ccc and registered in t
|
|
|
187
187
|
|
|
188
188
|
**Index verification:**
|
|
189
189
|
|
|
190
|
-
|
|
190
|
+
`ccc index` requires the directory to be initialized first. Run `ccc init {source_root}` (idempotent — a no-op once initialized) before `ccc index {source_root}`, or use the ccc MCP tool. `ccc_bridge.ensure_index` is a conceptual interface, not a callable function. Indexing is a no-op if the source was already indexed during setup or step 2b.
|
|
191
|
+
|
|
192
|
+
**Nested project marker:** when `{source_root}` is a subtree of a repo that already carries a project marker (e.g. a cloned source tree under `.forge-sources/`, or a `.cocoindex_code` / VCS marker in a parent), `ccc init {source_root}` exits non-zero with `A parent directory has a project marker`. This is expected — re-run as `ccc init -f {source_root}` to initialize at the subtree anyway, then `ccc index`. Do not treat the parent-marker warning as fatal.
|
|
193
|
+
|
|
194
|
+
**Verify the index is not degraded:** after `ccc index`, run `ccc status {source_root}` and read the `Languages:` breakdown — a non-zero `Chunks`/`Files` total is not sufficient. Confirm the source's primary language (`{brief.language}`) reports a non-trivial chunk count. An index dominated by `markdown`/config chunks with the source language absent or near-zero means `ccc index` ran against degraded settings (a stale `.cocoindex_code/` inherited from a parent project, or a no-op `ccc init` over a prior partial index that returned `Project already initialized.`) — the source code was never indexed, which silently cripples later `skf-audit-skill` / `skf-update-skill` searches with no error surfaced. When the source language is absent, rebuild from a clean init (`ccc init -f {source_root}` then `ccc index {source_root}`) and re-check the breakdown. If the source language is still missing after a forced re-init, log it and continue per the error handling below — a degraded index is not workflow-fatal, but it must be recorded so the gap is visible.
|
|
191
195
|
|
|
192
196
|
**Registry update:**
|
|
193
197
|
|
|
@@ -6,9 +6,11 @@ Every shell snippet in this document uses `{...}` placeholders for paths. **Alwa
|
|
|
6
6
|
|
|
7
7
|
## Tag Resolution
|
|
8
8
|
|
|
9
|
-
Tag resolution maps a declared version in the brief onto a concrete git ref before cloning, so the skill is built from code matching its declared version.
|
|
9
|
+
Tag resolution maps a declared version in the brief onto a concrete git ref before cloning, so the skill is built from code matching its declared version. Three signals can drive it, in priority order: an explicit `brief.target_ref` (a ref the user states verbatim — highest priority), an **explicit** `brief.target_version` (deliberate user intent), or an **implicit** `brief.version` (auto-populated hint from `brief-skill`). All apply only when `source_repo` is a remote URL.
|
|
10
10
|
|
|
11
|
-
**
|
|
11
|
+
**Explicit ref override (when `target_ref` is set):** When `brief.target_ref` is present AND `source_repo` is a remote URL, use its value verbatim as `source_ref` and skip all version-to-tag matching below. This is the escape hatch for ref conventions the matching heuristics don't cover — notably monorepo crate tags whose prefix differs from the skill name (e.g. skill `livekit-rust` built from tag `livekit/v0.7.42`). Confirm it resolves first: `git ls-remote "{source_repo}" "{target_ref}"` (matches a tag or branch) — if it returns nothing, ⚠️ warn "`target_ref` ({target_ref}) does not resolve in {source_repo}; falling back to version matching" and continue with the version-based matching below.
|
|
12
|
+
|
|
13
|
+
**When none of `brief.target_ref`, `brief.target_version`, or `brief.version` is set:** skip tag resolution entirely. Set `source_ref` to `HEAD` (default branch).
|
|
12
14
|
|
|
13
15
|
### Explicit Tag Resolution (when target_version is set)
|
|
14
16
|
|
|
@@ -22,6 +24,7 @@ When `brief.target_version` is present AND `source_repo` is a remote URL, resolv
|
|
|
22
24
|
- **Exact match:** `{target_version}` (e.g., `0.5.0`)
|
|
23
25
|
- **With `v` prefix:** `v{target_version}` (e.g., `v0.5.0`)
|
|
24
26
|
- **With package scope (monorepos):** `{brief.name}@{target_version}` or `@{scope}/{brief.name}@{target_version}`
|
|
27
|
+
- **With crate/package-directory prefix (monorepos):** `{brief.name}/v{target_version}`, `{brief.name}/{target_version}`, or `{brief.name}-v{target_version}` (e.g. `tokio/v1.0.0`). Covers monorepos whose tags are prefixed by the crate/package directory **when that directory equals the skill name**. When the directory differs from the skill name (e.g. crate `livekit` for skill `livekit-rust`, tag `livekit/v0.7.42`), this heuristic can't infer it — set `target_ref` explicitly instead.
|
|
25
28
|
|
|
26
29
|
3. **Resolution outcomes:**
|
|
27
30
|
- **Single match:** Store the matched tag as `source_ref`. Use it as `{branch}` in all subsequent clone/API commands.
|
|
@@ -76,7 +76,7 @@ npx skill-check check <staging-skill-dir> --fix --format json {security_scan_fla
|
|
|
76
76
|
|
|
77
77
|
This performs frontmatter validation, description quality checks, body limit enforcement, local link resolution, file formatting, auto-fix of deterministic issues, and quality scoring (0-100) across five weighted categories.
|
|
78
78
|
|
|
79
|
-
**Parse the JSON output** for: `
|
|
79
|
+
**Parse the JSON output** for: `scores[].score` (0-100 — match the entry by `relativePath`/`skillId`; falls back to a top-level `qualityScore` on older skill-check builds), `diagnostics[]` (remaining issues), `fixed[]` (auto-corrected issues).
|
|
80
80
|
|
|
81
81
|
**Description Guard Protocol:** This invocation may modify SKILL.md (especially when `fixed[]` is non-empty). Wrap the `skill-check check --fix` call in the four-phase guard defined in §0 by invoking `{descriptionGuardHelper}` at the capture and verify-restore points:
|
|
82
82
|
|
|
@@ -94,7 +94,7 @@ uv run {descriptionGuardHelper} verify-restore <staging-skill-dir>/SKILL.md \
|
|
|
94
94
|
|
|
95
95
|
If `restored: true` in the verify-restore output, apply §0's post-restore re-validation hook. If `fixed[]` was non-empty in the skill-check output, also re-read the modified SKILL.md to sync the in-context copy before proceeding — this prevents silent divergence between the in-context and on-disk versions that step 7 will use for artifact generation.
|
|
96
96
|
|
|
97
|
-
**Note:** `skill-check` may return non-zero exit code even when `errorCount` is 0. Always rely on parsed JSON, not the shell exit code.
|
|
97
|
+
**Note:** `skill-check` may return non-zero exit code even when `summary.errorCount` is 0. Always rely on parsed JSON, not the shell exit code.
|
|
98
98
|
|
|
99
99
|
- **Score ≥ 70:** Record "Schema: PASS (score: {score}/100)" in evidence-report
|
|
100
100
|
- **Score < 70:** Log remaining diagnostics as warnings, record "Schema: WARN — score {score}/100, {count} remaining issues", proceed
|
|
@@ -182,7 +182,8 @@ Parse output for: `description_score`, `content_score`, `review_score`, `validat
|
|
|
182
182
|
**Apply dismissal rules** in this order:
|
|
183
183
|
|
|
184
184
|
1. **Check score thresholds** against the "Score Thresholds" table in `{tesslDismissalData}`. Most importantly:
|
|
185
|
-
- If `
|
|
185
|
+
- If tessl's output contains a `findings[]` entry with rule ID `description_field` (the deterministic angle-bracket / XML-tag validator): follow the **recover-then-halt** path defined by the `description-xml-tags-guarded-upstream` rule in `{tesslDismissalData}`. Re-apply step 5 §2a's `<`/`>` → `{`/`}` substitution in place on the staging SKILL.md frontmatter `description`, re-sync the in-context copy, and re-run `npx -y tessl skill review <staging-skill-dir>` once. **Re-run gate:** treat the `description_field` finding being **absent** from the re-run as the only successful recovery outcome. If the finding persists on the re-run — whether the re-substitution improved the description or not — that counts as recovery failure: halt with the original `description-xml-tags-guarded-upstream` failure message from `{tesslDismissalData}`, do NOT proceed to §6b, and do NOT downgrade the recovery to a warning. On successful recovery (finding cleared), log `description-recovery: applied ({count} substitutions)` in the evidence report under "Dismissed tessl suggestions" and continue suggestion iteration against the rerun's `judge_suggestions[]`.
|
|
186
|
+
- If the LLM-judge `description_score` is below 100 **but no `description_field` finding is present** (the deterministic validator PASSED): this is a soft discoverability signal (jargon density, trigger-term phrasing) from the judge's sub-scores, not a sanitizer bypass. Record `description_judge_score: {n}% (deterministic description_field validator PASSED)` as a warning in the evidence report and continue — do NOT trigger the recover-then-halt path and do NOT halt.
|
|
186
187
|
- If `review_score < 60` or `content_score < 60`: record warnings in the evidence report, continue.
|
|
187
188
|
2. **Iterate `judge_suggestions[]`.** For each suggestion:
|
|
188
189
|
- Cross-reference against the rules in `{tesslDismissalData}` in order.
|
|
@@ -53,7 +53,7 @@ Used when the workflow ran in code-mode against an actual codebase. `source_repo
|
|
|
53
53
|
|
|
54
54
|
## Compose-mode variant
|
|
55
55
|
|
|
56
|
-
Used when the workflow ran in compose-mode against pre-generated constituent skills. Source-anchor fields (`source_repo`, `source_commit`, `source_ref`) are `null` because there is no codebase to anchor against — provenance traces back to the constituent skills instead, captured in the `constituents[]` array. Each entry's `extraction_method` is `"compose-from-skill"`; integrations have `detection_method` of `"architecture_co_mention"` (named in the architecture doc) or `"inferred_from_shared_domain"` (synthesized inference,
|
|
56
|
+
Used when the workflow ran in compose-mode against pre-generated constituent skills. Source-anchor fields (`source_repo`, `source_commit`, `source_ref`) are `null` because there is no codebase to anchor against — provenance traces back to the constituent skills instead, captured in the `constituents[]` array. Each entry's `extraction_method` is `"compose-from-skill"`; integrations have `detection_method` of `"architecture_co_mention"` (named in the architecture doc), `"constituent_documented_contract"` (a cross-library contract documented in a constituent skill's integration docs but not co-mentioned in the architecture document — e.g. a grep-verified upstream seam cited from a source skill), or `"inferred_from_shared_domain"` (synthesized inference from shared language/domain, no cited contract). `detection_method` records *how* an edge was discovered; it is orthogonal to `confidence`, which is inherited from the constituent skills per the Confidence Tier Inheritance matrix in `references/compose-mode-rules.md` (the integration tier is the weaker of the pair — never forced to a fixed band by detection method).
|
|
57
57
|
|
|
58
58
|
```json
|
|
59
59
|
{
|
|
@@ -82,9 +82,9 @@ Used when the workflow ran in compose-mode against pre-generated constituent ski
|
|
|
82
82
|
{
|
|
83
83
|
"libraries": ["{libA}", "{libB}"],
|
|
84
84
|
"pattern_type": "{type}",
|
|
85
|
-
"detection_method": "architecture_co_mention|inferred_from_shared_domain",
|
|
85
|
+
"detection_method": "architecture_co_mention|constituent_documented_contract|inferred_from_shared_domain",
|
|
86
86
|
"co_import_files": [],
|
|
87
|
-
"confidence": "T2|T3"
|
|
87
|
+
"confidence": "T1|T1-low|T2|T3"
|
|
88
88
|
}
|
|
89
89
|
],
|
|
90
90
|
"constituents": [
|
|
@@ -49,6 +49,36 @@ description: >
|
|
|
49
49
|
- {convention_2}
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
## Sizing Guidance for Large Stacks
|
|
53
|
+
|
|
54
|
+
A stack capstone grows monotonically as patterns and libraries are added, so a
|
|
55
|
+
mature stack eventually pushes SKILL.md past skill-check's `body.max_lines`
|
|
56
|
+
budget (default **500**) and/or the `description` past 1024 chars. Pre-empt both
|
|
57
|
+
ceilings at compile time:
|
|
58
|
+
|
|
59
|
+
- **Catalog placement.** The `Library Reference Index` table and `Per-Library
|
|
60
|
+
Summaries` are the largest, most reference-like sections. For a **large stack**
|
|
61
|
+
(heuristic: **> 6 libraries OR > 6 integration patterns**, the point at which
|
|
62
|
+
the inline catalog crowds the 500-line budget), author both into
|
|
63
|
+
`references/stack-catalog.md` and leave only an inline pointer in SKILL.md (see
|
|
64
|
+
below). For a **small stack**, keep them inline — inline passive context yields
|
|
65
|
+
higher task accuracy than on-demand retrieval, and small stacks fit comfortably.
|
|
66
|
+
Integration Patterns and Conventions stay inline regardless (Tier 1, load-bearing).
|
|
67
|
+
- **Inline pointer form** (replaces the two sections above when extracted):
|
|
68
|
+
|
|
69
|
+
```markdown
|
|
70
|
+
## Library Catalog
|
|
71
|
+
|
|
72
|
+
{lib_count} libraries indexed in [references/stack-catalog.md](references/stack-catalog.md) —
|
|
73
|
+
reference-index table + per-library summaries. Load it for a specific library's exports or role.
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- **Description cap.** Keep `description` ≤ 1024 chars. Do NOT enumerate every
|
|
77
|
+
library by name; the generic "{lib_count} libraries with {integration_count}
|
|
78
|
+
integration patterns" form already scales. If a per-library parenthetical is
|
|
79
|
+
used, cap it to the top libraries by import/export count with a `+{N} more`
|
|
80
|
+
suffix — the full list lives in `metadata.json` `libraries[]` and the catalog.
|
|
81
|
+
|
|
52
82
|
## context-snippet.md Format (Vercel-Aligned)
|
|
53
83
|
|
|
54
84
|
Indexed format targeting ~80-120 tokens per stack:
|
|
@@ -107,6 +137,32 @@ Indexed format targeting ~80-120 tokens per stack:
|
|
|
107
137
|
}
|
|
108
138
|
```
|
|
109
139
|
|
|
140
|
+
## references/stack-catalog.md Structure
|
|
141
|
+
|
|
142
|
+
Written **only for large stacks** (see Sizing Guidance) when the catalog is
|
|
143
|
+
extracted out of SKILL.md. Holds the two sections verbatim from the inline form:
|
|
144
|
+
|
|
145
|
+
```markdown
|
|
146
|
+
# {project_name} Stack — Library Catalog
|
|
147
|
+
|
|
148
|
+
> Reference index + per-library summaries extracted from SKILL.md to keep the
|
|
149
|
+
> capstone under the body-size budget. See SKILL.md for integration patterns.
|
|
150
|
+
|
|
151
|
+
## Library Reference Index
|
|
152
|
+
|
|
153
|
+
| Library | Imports | Key Exports | Confidence | Reference |
|
|
154
|
+
|---------|---------|-------------|------------|-----------|
|
|
155
|
+
| {name} | {count} | {top_exports} | {tier} | [ref](references/{name}.md) |
|
|
156
|
+
|
|
157
|
+
## Per-Library Summaries
|
|
158
|
+
|
|
159
|
+
### {library_name}
|
|
160
|
+
**Role in stack:** {one-line description of what this library does in this project}
|
|
161
|
+
**Key exports used:** {comma-separated list}
|
|
162
|
+
**Usage pattern:** {brief pattern description}
|
|
163
|
+
**Confidence:** {T1/T1-low/T2}
|
|
164
|
+
```
|
|
165
|
+
|
|
110
166
|
## references/{library}.md Structure
|
|
111
167
|
|
|
112
168
|
```markdown
|
|
@@ -41,7 +41,7 @@ description: >
|
|
|
41
41
|
**Frontmatter rules:**
|
|
42
42
|
|
|
43
43
|
- `name`: lowercase alphanumeric + hyphens only, must match skill output directory name. **Stack skills MUST end in `-stack`** (e.g., `{project_name}-stack`) — this is how consumers (skf-verify-stack, skf-test-skill) detect stack vs individual skills.
|
|
44
|
-
- `description`: non-empty, max 1024 chars, trigger-optimized for agent discovery. MUST use third-person voice ("Processes..." not "I can..." or "You can...").
|
|
44
|
+
- `description`: non-empty, max 1024 chars, trigger-optimized for agent discovery. MUST use third-person voice ("Processes..." not "I can..." or "You can..."). **Do NOT enumerate every library by name** — a 12+ library stack overruns 1024 chars. Keep the generic "{lib_count} libraries with {integration_count} integration patterns" form; if a per-library parenthetical is used, cap it to the top libraries by import/export count with a `+{N} more` suffix (full list lives in `metadata.json` `libraries[]`). See "Sizing Guidance for Large Stacks" in `{stackSkillTemplate}`.
|
|
45
45
|
- No other frontmatter fields — only `name`, `description`, `license`, `compatibility`, `metadata`, `allowed-tools` are permitted by spec
|
|
46
46
|
|
|
47
47
|
### 3. Compile Integration Layer
|
|
@@ -68,6 +68,16 @@ description: >
|
|
|
68
68
|
|
|
69
69
|
### 4. Compile Per-Library Sections
|
|
70
70
|
|
|
71
|
+
**Catalog placement (decide once, applies to this section and §6).** The
|
|
72
|
+
`Per-Library Summaries` and `Library Reference Index` are the largest sections
|
|
73
|
+
and grow with the stack. For a **large stack** (heuristic: **> 6 libraries OR
|
|
74
|
+
> 6 integration patterns**), author both into `references/stack-catalog.md`
|
|
75
|
+
(structure in `{stackSkillTemplate}`) and place only the inline pointer from the
|
|
76
|
+
template's "Sizing Guidance" in SKILL.md — this keeps the body under the 500-line
|
|
77
|
+
`body.max_lines` budget that step 08 enforces. For a **small stack**, keep both
|
|
78
|
+
inline (inline passive context yields higher task accuracy). Either way, step 08's
|
|
79
|
+
body-size gate is the backstop; step 08 (`validate.md` §4) accepts both forms.
|
|
80
|
+
|
|
71
81
|
For each confirmed library (ordered by integration connectivity, then import count — **in compose-mode**, order by integration connectivity, then skill confidence tier since import counts are not available):
|
|
72
82
|
|
|
73
83
|
- Role in stack (one-line description)
|
|
@@ -86,7 +96,8 @@ Extract project-specific conventions from the extractions:
|
|
|
86
96
|
|
|
87
97
|
### 6. Compile Library Reference Index
|
|
88
98
|
|
|
89
|
-
|
|
99
|
+
Place per the §4 catalog-placement decision (inline for small stacks; in
|
|
100
|
+
`references/stack-catalog.md` for large stacks). Create the reference index table:
|
|
90
101
|
|
|
91
102
|
| Library | Imports | Key Exports | Confidence | Reference |
|
|
92
103
|
|---------|---------|-------------|------------|-----------|
|
|
@@ -85,3 +85,5 @@ When no architecture document is available:
|
|
|
85
85
|
- Infer from skills sharing domain keywords in their `SKILL.md` descriptions
|
|
86
86
|
- Mark all inferred integrations: `[inferred from shared domain]`
|
|
87
87
|
- Inferred integrations default to lowest confidence of the pair with `[inferred from shared domain]` suffix (use this instead of `[composed]` for inferred integrations)
|
|
88
|
+
|
|
89
|
+
**Constituent-documented contracts (distinct from shared-domain inference):** When a constituent skill's own integration docs cite a verifiable cross-library contract (e.g. a grep-verified upstream seam) that the architecture document does not co-mention, record it with `detection_method: constituent_documented_contract` (see `assets/provenance-map-schema.md`) — NOT `inferred_from_shared_domain`. It is a cited contract, not a synthesized guess. Its confidence still inherits the weaker tier of the pair per the matrix above — detection method is orthogonal to tier and never forces a fixed band.
|
|
@@ -142,10 +142,13 @@ For each detected integration:
|
|
|
142
142
|
- **Assign confidence (M1/M3) — derive from per-library tiers + detection-method qualifier (NOT from AST):** integration detection here is grep + co-import (optionally CCC-augmented), never AST. The integration's confidence is the **weaker** of the two libraries' tiers from `per_library_extractions[]` (tie-break: T1-low > T1, T2 > T1-low, T3 > T2 — never overstate). Then append a detection-method qualifier:
|
|
143
143
|
- `grep-co-import` — the pair qualified via direct co-import grep (the default).
|
|
144
144
|
- `ccc-augmented` — the pair qualified only after CCC semantic search elevated it (per §2 CCC augmentation), and the post-hoc import verification (H3) confirmed both imports.
|
|
145
|
-
- `architecture-co-mention` — compose-mode pair qualified via word-boundary co-mention in the architecture document (per §2 H2 guards).
|
|
146
|
-
- `
|
|
145
|
+
- `architecture-co-mention` — compose-mode pair qualified via word-boundary co-mention in the architecture document (per §2 H2 guards). Maps to `detection_method: architecture_co_mention` in `provenance-map.json`.
|
|
146
|
+
- `constituent-documented-contract` — compose-mode pair whose cross-library contract is documented in a constituent skill's integration docs (cited, e.g. a grep-verified upstream seam) but not co-mentioned in the architecture document. Maps to `detection_method: constituent_documented_contract` in `provenance-map.json`.
|
|
147
|
+
- `inferred-shared-domain` — compose-mode pair without an architecture document, inferred from shared `language` or domain keywords (no cited contract). Maps to `detection_method: inferred_from_shared_domain` in `provenance-map.json`.
|
|
147
148
|
- Render as `{tier} ({qualifier})` — e.g., `T1-low (grep-co-import)`, `T1 (ccc-augmented)`, `T1-low (architecture-co-mention) [composed]`. The `[composed]`/`[inferred from shared domain]` suffix from `compose-mode-rules.md` is appended after the qualifier in compose-mode.
|
|
148
149
|
|
|
150
|
+
**Provenance ↔ SKILL.md tier parity:** The tier derived above is the single value for this edge — write the *same* tier to both the SKILL.md integration label and `provenance-map.json` `integrations[].confidence`. The detection-method qualifier (and the `provenance-map.json` `detection_method` it maps to) records *how* the edge was found and is orthogonal to confidence; it never forces the tier into a fixed band.
|
|
151
|
+
|
|
149
152
|
### 4. Build Integration Graph
|
|
150
153
|
|
|
151
154
|
Assemble the integration graph:
|
|
@@ -9,6 +9,14 @@ provenanceMapSchemaPath: 'assets/provenance-map-schema.md'
|
|
|
9
9
|
atomicWriteProbeOrder:
|
|
10
10
|
- '{project-root}/_bmad/skf/shared/scripts/skf-atomic-write.py'
|
|
11
11
|
- '{project-root}/src/shared/scripts/skf-atomic-write.py'
|
|
12
|
+
# Resolve `{frontmatterValidator}` by probing `{frontmatterValidatorProbeOrder}`
|
|
13
|
+
# in order (installed SKF module path first, src/ dev-checkout fallback); first
|
|
14
|
+
# existing path wins. Used by the §8 pre-commit frontmatter + body-size gate
|
|
15
|
+
# (`--max-body-lines`). If neither resolves, the gate degrades to a WARNING —
|
|
16
|
+
# step 8 (validate.md) still runs the full post-commit check.
|
|
17
|
+
frontmatterValidatorProbeOrder:
|
|
18
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-validate-frontmatter.py'
|
|
19
|
+
- '{project-root}/src/shared/scripts/skf-validate-frontmatter.py'
|
|
12
20
|
---
|
|
13
21
|
|
|
14
22
|
<!-- Config: communicate in {communication_language}. Artifact text in {document_output_language}. -->
|
|
@@ -29,7 +37,7 @@ Write all deliverable and workspace artifact files to their target directories.
|
|
|
29
37
|
|
|
30
38
|
### 1. Resolve Paths and Stage Target Directory
|
|
31
39
|
|
|
32
|
-
Resolve `{version}`
|
|
40
|
+
Resolve `{version}` per S11 below — the primary library version in code-mode, or the stack-local scheme in compose-mode (`1.0.0` for a new stack, or a bump of `{prior_stack_version}` on re-composition). The final artifact paths are:
|
|
33
41
|
|
|
34
42
|
```
|
|
35
43
|
{skill_group} # {skills_output_folder}/{project_name}-stack/
|
|
@@ -41,7 +49,14 @@ Resolve `{version}` from the primary library version or default to `1.0.0` (see
|
|
|
41
49
|
|
|
42
50
|
Where the skill name is `{project_name}-stack` and `{version}` is the semver version (with build metadata stripped per `knowledge/version-paths.md`).
|
|
43
51
|
|
|
44
|
-
**Primary library definition (S11):** In code-mode, the primary library is the dependency with the highest import count from step 3; its `version` (from the manifest) becomes `{primary_library_version}
|
|
52
|
+
**Primary library definition (S11):** In code-mode, the primary library is the dependency with the highest import count from step 3; its `version` (from the manifest) becomes `{primary_library_version}`, falling back to `1.0.0` if unavailable. In compose-mode, the stack carries its own release identity: default `{version}` to `1.0.0` (a stack-local scheme) — do NOT borrow the highest constituent semver. Constituent versions are preserved in `dependencies[]`, so no information is lost, and the package no longer reads as tracking whichever constituent happened to have the highest version. The `1.0.0` default applies only to a **genuinely new** stack — see the re-composition rule below.
|
|
53
|
+
|
|
54
|
+
**Re-composition versioning (S11, compose-mode):** When the S3 pre-flight resolves a `{prior_stack_version}` (re-composing a stack that already has a release line), continue that line instead of resetting — defaulting to `1.0.0` would publish a version *below* the existing release (e.g. `1.0.0` shadowing a prior `3.0.5`, a backward jump). Bump `{prior_stack_version}`:
|
|
55
|
+
|
|
56
|
+
- **Major** if any library in `{prior_libraries}` is removed or replaced — dropping a documented library is breaking for consumers of the stack.
|
|
57
|
+
- **Minor** otherwise — libraries only added, and/or integration content changed (backward-compatible).
|
|
58
|
+
|
|
59
|
+
Never emit a `{version}` ≤ `{prior_stack_version}`. Narrate the resolved version and the bump rationale.
|
|
45
60
|
|
|
46
61
|
**Pre-flight: group-dir type check (S3):** If `{skills_output_folder}/{project_name}-stack/` already exists, probe `{skills_output_folder}/{project_name}-stack/active/{project_name}-stack/metadata.json`. If that metadata exists and `skill_type != "stack"`, HALT with:
|
|
47
62
|
|
|
@@ -49,6 +64,8 @@ Where the skill name is `{project_name}-stack` and `{version}` is the semver ver
|
|
|
49
64
|
|
|
50
65
|
Do NOT proceed to staging or commit.
|
|
51
66
|
|
|
67
|
+
If that metadata exists and `skill_type == "stack"`, this run is a **re-composition**: capture its `version` as `{prior_stack_version}` and its `libraries` array as `{prior_libraries}` for the S11 re-composition rule above. If the group dir is absent — or exists but has no resolvable `active` stack metadata — treat this as a new stack and leave `{prior_stack_version}` unset.
|
|
68
|
+
|
|
52
69
|
**Atomic write strategy (C2 / B5):** All artifact writes for `{skill_package}` MUST stage into a temp directory first, then commit atomically via `skf-atomic-write.py commit-dir`. The active symlink flip only happens AFTER the commit succeeds.
|
|
53
70
|
|
|
54
71
|
Create the staging directory:
|
|
@@ -92,6 +109,8 @@ Load structure from `{stackSkillTemplate}` references section:
|
|
|
92
109
|
- Usage patterns with file:line citations (**in compose-mode**: usage patterns from source skill SKILL.md)
|
|
93
110
|
- Confidence tier label
|
|
94
111
|
|
|
112
|
+
**If the catalog was extracted** (large stack — step 06 §4 placed the `Library Reference Index` + `Per-Library Summaries` out of SKILL.md), also write `{skill_staging}/references/stack-catalog.md` using the structure in `{stackSkillTemplate}`, and confirm SKILL.md carries the inline pointer instead of the two sections. Small stacks keep the catalog inline and write no `stack-catalog.md`.
|
|
113
|
+
|
|
95
114
|
### 4. Stage Integration Pair Reference Files
|
|
96
115
|
|
|
97
116
|
For each detected integration pair, write `{skill_staging}/references/integrations/{libraryA}-{libraryB}.md`:
|
|
@@ -112,7 +131,7 @@ Write `{skill_staging}/context-snippet.md`:
|
|
|
112
131
|
Use the Vercel-aligned indexed format targeting **~80-120 tokens** (M2). Token estimation is heuristic — use `ceil(char_count / 4)` as the working approximation (the standard rule-of-thumb for English text in BPE-style tokenizers; precise counts differ per model). Compute against the rendered snippet body (excluding trailing newline).
|
|
113
132
|
|
|
114
133
|
```
|
|
115
|
-
[{project_name}-stack v{version —
|
|
134
|
+
[{project_name}-stack v{version — per S11: code-mode primary_library_version or 1.0.0; compose-mode stack-local scheme — 1.0.0 for a new stack, or the bumped {prior_stack_version} on re-composition}]|root: skills/{project_name}-stack/
|
|
116
135
|
|IMPORTANT: {project_name}-stack — read SKILL.md before writing integration code. Do NOT rely on training data.
|
|
117
136
|
|stack: {dep-1}@{v1}, {dep-2}@{v2}, {dep-3}@{v3}
|
|
118
137
|
|integrations: {pattern-1}, {pattern-2}
|
|
@@ -144,7 +163,7 @@ Populate all fields from the metadata.json schema defined in `{stackSkillTemplat
|
|
|
144
163
|
{
|
|
145
164
|
"skill_type": "stack",
|
|
146
165
|
"name": "{project_name}-stack",
|
|
147
|
-
"version": "{primary_library_version or 1.0.0}",
|
|
166
|
+
"version": "{version — resolved per S11: code-mode primary_library_version or 1.0.0; compose-mode 1.0.0 (new stack) or bumped {prior_stack_version} (re-composition)}",
|
|
148
167
|
"generation_date": "{current_date}",
|
|
149
168
|
"forge_tier": "{Quick|Forge|Forge+|Deep — the tier under which this run executed}",
|
|
150
169
|
"confidence_tier": "{T1|T1-low|T2|T3 — dominant T-code from confidence_distribution below}",
|
|
@@ -195,7 +214,7 @@ If any workspace write fails, invoke the rollback contract from §1.
|
|
|
195
214
|
Use the schema from `{provenanceMapSchemaPath}` — see that asset for the canonical templates and field definitions of both variants:
|
|
196
215
|
|
|
197
216
|
- **In code-mode:** use the code-mode variant (`source_repo` / `source_commit` populated; `extraction_method` ∈ `ast_bridge|source_reading|qmd_bridge`; `detection_method = "co-import grep"`).
|
|
198
|
-
- **In compose-mode:** use the compose-mode variant (source-anchor fields `null`; `extraction_method = "compose-from-skill"`; `detection_method ∈ "architecture_co_mention|inferred_from_shared_domain"`; includes the additional `constituents[]` array for drift detection).
|
|
217
|
+
- **In compose-mode:** use the compose-mode variant (source-anchor fields `null`; `extraction_method = "compose-from-skill"`; `detection_method ∈ "architecture_co_mention|constituent_documented_contract|inferred_from_shared_domain"`; includes the additional `constituents[]` array for drift detection).
|
|
199
218
|
|
|
200
219
|
**Use the `metadata_hash` value already stored in workflow state during step 2 (S13) — do NOT re-read and re-hash at step 7 time. The stored hash captures the state as it was at manifest-detection time, which is the correct provenance anchor.**
|
|
201
220
|
|
|
@@ -205,7 +224,28 @@ Use the schema from `{provenanceMapSchemaPath}` — see that asset for the canon
|
|
|
205
224
|
- Warnings and failures encountered
|
|
206
225
|
- Confidence tier distribution
|
|
207
226
|
|
|
208
|
-
### 8. Commit
|
|
227
|
+
### 8. Pre-Commit Frontmatter & Body-Size Gate
|
|
228
|
+
|
|
229
|
+
The full schema/frontmatter validation runs in step 8 (`validate.md`) — but that runs *after* commit-dir and flip-link have already published the package. The two most common non-auto-fixable `skill-check` hard rejects — a `description` over the 1024-char limit (which `skill-check --fix` cannot trim for you) and a SKILL.md body over the `body.max_lines` limit (default **500**) — would therefore only surface on an already-committed, symlink-active artifact, forcing edits to the live `SKILL.md`. Additive re-composition of an already-large stack grows the body monotonically, so the body overflow is the likeliest trigger. Catch both here, while the package is still in `.skf-tmp`.
|
|
230
|
+
|
|
231
|
+
Resolve `{frontmatterValidator}` from `{frontmatterValidatorProbeOrder}` (first existing path wins). Run it against the **staged** `SKILL.md`, passing the real skill name so the directory-match check is not fooled by the `.skf-tmp` staging suffix, and `--max-body-lines 500` to assert the skill-check body limit pre-commit:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
uv run {frontmatterValidator} {skill_staging}/SKILL.md --skill-dir-name {project_name}-stack --max-body-lines 500
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
The validator emits JSON: `status` (`pass`/`warn`/`fail`), `issues[]` (each with `severity` ∈ `high|medium|low`, `field`, `message`), `body_lines` (the counted body size), and `summary`. Disposition:
|
|
238
|
+
|
|
239
|
+
- **`status` is `fail`, OR any `issues[]` entry has `severity` `high` or `medium`** — a hard violation that `npx skill-check` (step 8) would reject and `--fix` cannot auto-correct. HALT-to-fix **in staging**, then re-run the validator until it clears. Remediate by `field`:
|
|
240
|
+
- `description` / `name` / `compatibility` — trim/correct `{skill_staging}/SKILL.md` (e.g. shorten `description` to ≤ 1024 chars).
|
|
241
|
+
- `body` (`body lines N exceeds max 500`) — reduce the staged body: prefer a **selective split** of the largest Tier-2 section(s) into `{skill_staging}/references/`, keeping Tier-1 content inline (mirrors `validate.md` §3); or trim redundant content. Re-run the gate until `body_lines ≤ 500`.
|
|
242
|
+
|
|
243
|
+
Do NOT proceed to §9 commit-dir with an unresolved high/medium issue. Note: an over-long `description` is rated `medium` and exits `0`, so key the HALT on the issue severities above — not on the exit code.
|
|
244
|
+
- **Only `low`-severity issues (e.g. an unexpected field)** — record each as a WARNING in the evidence report and proceed; these do not block the commit.
|
|
245
|
+
|
|
246
|
+
**If `{frontmatterValidator}` does not resolve** (neither probe path exists) **or the invocation cannot run**, emit a WARNING ("pre-commit frontmatter + body-size gate skipped — validator unavailable") and proceed. Step 8 (`validate.md`) remains the post-commit backstop (including the `body.max_lines` split path in its §3); this gate is a best-effort early catch, never a new hard dependency.
|
|
247
|
+
|
|
248
|
+
### 9. Commit Staging Directory
|
|
209
249
|
|
|
210
250
|
After all staged writes in sections 2–6 completed successfully, atomically swap the staging dir into place:
|
|
211
251
|
|
|
@@ -215,7 +255,7 @@ python3 {atomicWriteHelper} commit-dir --target {skill_package}
|
|
|
215
255
|
|
|
216
256
|
The helper moves any existing `{skill_package}` aside to a `.skf-rollback-<pid>` dir before the swap. On failure the helper restores the prior target and exits non-zero — in that case invoke the rollback contract from §1 and HALT.
|
|
217
257
|
|
|
218
|
-
###
|
|
258
|
+
### 10. Flip Active Symlink
|
|
219
259
|
|
|
220
260
|
ONLY AFTER `commit-dir` succeeds, flip the `{skill_group}/active` symlink to point at `{version}`:
|
|
221
261
|
|
|
@@ -227,7 +267,7 @@ The helper holds an flock on `{skill_group}/active.skf-lock` and refuses to repl
|
|
|
227
267
|
|
|
228
268
|
If `flip-link` fails, emit a warning (the committed package is still valid), note the symlink-flip failure in the evidence report, and continue.
|
|
229
269
|
|
|
230
|
-
###
|
|
270
|
+
### 11. Display Write Summary
|
|
231
271
|
|
|
232
272
|
"**Output files written.**
|
|
233
273
|
|
|
@@ -248,7 +288,7 @@ If `flip-link` fails, emit a warning (the committed package is still valid), not
|
|
|
248
288
|
|
|
249
289
|
**Proceeding to validation...**"
|
|
250
290
|
|
|
251
|
-
###
|
|
291
|
+
### 12. Auto-Proceed to Next Step
|
|
252
292
|
|
|
253
293
|
Load, read the full file and then execute `{nextStepFile}`.
|
|
254
294
|
|