bmad-module-skill-forge 1.5.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/docs/_data/pinned.yaml +1 -1
- package/docs/workflows.md +1 -1
- package/package.json +2 -2
- package/src/knowledge/version-paths.md +4 -3
- package/src/shared/health-check.md +1 -1
- package/src/shared/scripts/schemas/skill-brief.v1.json +5 -0
- package/src/shared/scripts/skf-atomic-write.py +4 -1
- package/src/shared/scripts/skf-description-guard.py +33 -80
- package/src/shared/scripts/skf-detect-language.py +34 -0
- package/src/shared/scripts/skf-detect-workspaces.py +54 -0
- package/src/shared/scripts/skf-enumerate-stack-skills.py +107 -16
- package/src/shared/scripts/skf-forge-tier-rw.py +4 -1
- package/src/shared/scripts/skf-manifest-ops.py +8 -4
- package/src/shared/scripts/skf-merge-ccc-exclusions.py +4 -1
- package/src/shared/scripts/skf-qmd-classify-collections.py +29 -3
- package/src/shared/scripts/skf-rebuild-managed-sections.py +72 -7
- package/src/shared/scripts/skf-scan-skill-md-structure.py +13 -3
- package/src/shared/scripts/skf-validate-brief-inputs.py +79 -18
- package/src/shared/scripts/skf-validate-brief-schema.py +47 -1
- package/src/shared/scripts/skf-validate-frontmatter.py +60 -2
- package/src/shared/scripts/skf-write-skill-brief.py +97 -17
- package/src/skf-analyze-source/assets/skill-brief-schema.md +10 -2
- package/src/skf-analyze-source/references/continue.md +3 -1
- package/src/skf-analyze-source/references/generate-briefs.md +30 -9
- package/src/skf-analyze-source/references/recommend.md +1 -1
- package/src/skf-brief-skill/SKILL.md +4 -2
- package/src/skf-brief-skill/assets/skill-brief-schema.md +6 -1
- package/src/skf-brief-skill/references/analyze-target.md +22 -6
- package/src/skf-brief-skill/references/confirm-brief.md +4 -0
- package/src/skf-brief-skill/references/gather-intent.md +72 -3
- package/src/skf-brief-skill/references/headless-args.md +3 -2
- package/src/skf-brief-skill/references/portfolio-similarity-check.md +16 -8
- package/src/skf-brief-skill/references/scope-definition.md +20 -2
- package/src/skf-brief-skill/references/version-resolution.md +1 -1
- package/src/skf-brief-skill/references/write-brief.md +12 -2
- package/src/skf-create-skill/assets/compile-assembly-rules.md +20 -1
- package/src/skf-create-skill/assets/skill-sections.md +2 -1
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +18 -5
- package/src/skf-create-skill/references/compile.md +5 -2
- package/src/skf-create-skill/references/extract.md +8 -3
- package/src/skf-create-skill/references/extraction-patterns.md +14 -5
- package/src/skf-create-skill/references/generate-artifacts.md +5 -1
- package/src/skf-create-skill/references/source-resolution-protocols.md +5 -2
- package/src/skf-create-skill/references/validate.md +4 -3
- package/src/skf-create-stack-skill/assets/provenance-map-schema.md +3 -3
- package/src/skf-create-stack-skill/assets/stack-skill-template.md +56 -0
- package/src/skf-create-stack-skill/references/compile-stack.md +13 -2
- package/src/skf-create-stack-skill/references/compose-mode-rules.md +2 -0
- package/src/skf-create-stack-skill/references/detect-integrations.md +5 -2
- package/src/skf-create-stack-skill/references/generate-output.md +49 -9
- package/src/skf-create-stack-skill/references/validate.md +8 -5
- package/src/skf-export-skill/references/load-skill.md +5 -4
- package/src/skf-export-skill/references/manifest-rebuild.md +1 -1
- package/src/skf-export-skill/references/token-report.md +2 -2
- package/src/skf-export-skill/references/update-context.md +25 -11
- package/src/skf-quick-skill/assets/skill-template.md +2 -2
- package/src/skf-quick-skill/references/compile.md +4 -2
- package/src/skf-quick-skill/references/write-and-validate.md +2 -2
- package/src/skf-refine-architecture/SKILL.md +1 -1
- package/src/skf-refine-architecture/references/gap-analysis.md +30 -4
- package/src/skf-refine-architecture/references/init.md +2 -0
- package/src/skf-refine-architecture/references/issue-detection.md +8 -3
- package/src/skf-test-skill/SKILL.md +1 -1
- package/src/skf-test-skill/references/coherence-check.md +4 -4
- package/src/skf-test-skill/references/coverage-check.md +58 -16
- package/src/skf-test-skill/references/external-validators.md +7 -7
- package/src/skf-test-skill/references/init.md +5 -4
- package/src/skf-test-skill/references/score.md +6 -3
- package/src/skf-test-skill/references/scoring-rules.md +12 -0
- package/src/skf-test-skill/references/source-access-protocol.md +19 -3
- package/src/skf-test-skill/scripts/compute-score.py +4 -1
- package/src/skf-update-skill/references/detect-changes.md +15 -2
- package/src/skf-update-skill/references/init.md +8 -1
- package/src/skf-update-skill/references/merge.md +22 -0
- package/src/skf-update-skill/references/re-extract.md +19 -7
- package/src/skf-update-skill/references/write.md +3 -0
- package/src/skf-verify-stack/SKILL.md +2 -2
- package/src/skf-verify-stack/assets/feasibility-report-template.md +1 -1
- package/src/skf-verify-stack/references/coverage.md +35 -6
- package/src/skf-verify-stack/references/init.md +6 -2
- package/src/skf-verify-stack/references/integration-verification-rules.md +1 -1
- package/src/skf-verify-stack/references/integrations.md +7 -3
- package/src/skf-verify-stack/references/report.md +5 -1
- package/src/skf-verify-stack/references/requirements.md +3 -1
- package/src/skf-verify-stack/references/synthesize.md +11 -3
- package/tools/cli/lib/installer.js +1 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
nextStepFile: 'analyze-target.md'
|
|
3
|
+
ratifyTargetFile: 'confirm-brief.md'
|
|
3
4
|
forgeTierFile: '{sidecar_path}/forge-tier.yaml'
|
|
4
5
|
descriptionVoiceExamplesFile: 'assets/description-voice-examples.md'
|
|
5
6
|
headlessArgsFile: 'references/headless-args.md'
|
|
@@ -9,6 +10,9 @@ draftCheckpointFile: 'references/draft-checkpoint.md'
|
|
|
9
10
|
validateBriefInputsProbeOrder:
|
|
10
11
|
- '{project-root}/_bmad/skf/shared/scripts/skf-validate-brief-inputs.py'
|
|
11
12
|
- '{project-root}/src/shared/scripts/skf-validate-brief-inputs.py'
|
|
13
|
+
validateBriefSchemaProbeOrder:
|
|
14
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-validate-brief-schema.py'
|
|
15
|
+
- '{project-root}/src/shared/scripts/skf-validate-brief-schema.py'
|
|
12
16
|
emitBriefEnvelopeProbeOrder:
|
|
13
17
|
- '{project-root}/_bmad/skf/shared/scripts/skf-emit-brief-result-envelope.py'
|
|
14
18
|
- '{project-root}/src/shared/scripts/skf-emit-brief-result-envelope.py'
|
|
@@ -84,7 +88,7 @@ Let's get started."
|
|
|
84
88
|
|
|
85
89
|
### 3. Gather Target Repository
|
|
86
90
|
|
|
87
|
-
This section has
|
|
91
|
+
This section has four sub-flows. Execute exactly one branch — 3.1a *or* 3.2 *or* 3.3 — based on the user's response in 3.1, then end with the shared confirmation (3.1a is terminal for §3 and jumps directly to confirm-brief.md). Do not mix branches.
|
|
88
92
|
|
|
89
93
|
#### 3.1 Collect target
|
|
90
94
|
|
|
@@ -94,6 +98,7 @@ Provide one of:
|
|
|
94
98
|
- A **GitHub URL** (e.g., `https://github.com/org/repo`)
|
|
95
99
|
- A **local path** (e.g., `/path/to/project`)
|
|
96
100
|
- **Documentation URLs** for a docs-only skill (e.g., `https://docs.stripe.com/api`) — use this when no source code is available (SaaS, closed-source)
|
|
101
|
+
- A **path to an existing `skill-brief.yaml`** (file path or a directory containing one) — use this to ratify a brief produced by another workflow (e.g. `skf-analyze-source`) without re-deriving fields
|
|
97
102
|
|
|
98
103
|
Or type `cancel` / `exit` / `[X]` to leave without writing anything.
|
|
99
104
|
|
|
@@ -102,10 +107,65 @@ Or type `cancel` / `exit` / `[X]` to leave without writing anything.
|
|
|
102
107
|
Wait for user response. Branch on the response:
|
|
103
108
|
|
|
104
109
|
- Empty input, `cancel`, `exit`, `[X]`, `q`, or `:q` → Display `"Cancelled — no brief was written."` and HALT (exit code 6, `halt_reason: "user-cancelled"`). Cancellation here is non-destructive — no files have been written yet by step 1. Headless mode never reaches this branch (the GATE in §8 short-circuits the interactive sub-flows).
|
|
110
|
+
- Path that resolves to an existing `skill-brief.yaml` (file path ending in `skill-brief.yaml` that exists, OR a directory containing a `skill-brief.yaml`) → §3.1a
|
|
105
111
|
- Documentation URLs only (no source location) → §3.2
|
|
106
112
|
- GitHub URL or local filesystem path → §3.3
|
|
107
113
|
- Any other free-form question (e.g. "what is this?", "show me an example", "how does SKF work?") → answer briefly, re-display the prompt
|
|
108
114
|
|
|
115
|
+
#### 3.1a Branch — Ratify existing brief
|
|
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 §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
|
+
|
|
119
|
+
Resolve the path:
|
|
120
|
+
|
|
121
|
+
- If the user's input ends in `skill-brief.yaml` and points at an existing file → that is the brief path.
|
|
122
|
+
- Otherwise (input was a directory) → the brief path is `<input>/skill-brief.yaml`.
|
|
123
|
+
|
|
124
|
+
**Validate the brief against the schema** before presenting it. Resolve `{validateBriefSchemaHelper}` from `{validateBriefSchemaProbeOrder}` (first existing path wins; HALT if no candidate exists), then:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
uv run {validateBriefSchemaHelper} <resolved-brief-path>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The script returns JSON `{valid, errors[], warnings[], halt_reason, brief}`. Apply the result:
|
|
131
|
+
|
|
132
|
+
- **`valid: false`** — surface the `errors[]` messages and the `halt_reason` to the user, then re-display the §3.1 prompt for a corrected path (or another target altogether). Do not HALT — the user may simply have pointed at the wrong file. Example: `"**Brief at `{path}` is invalid:** {first error message}. Pick a different brief, or supply a repo / docs URL instead."`
|
|
133
|
+
- **`valid: true`** — proceed with the parsed `brief` payload.
|
|
134
|
+
|
|
135
|
+
Surface any non-empty `warnings[]` as a single grouped line (`"**Brief validation warnings:** {joined warnings}"`), then present the ratify menu:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
**Existing brief detected at `{path}`.**
|
|
139
|
+
|
|
140
|
+
- **Name:** {brief.name}
|
|
141
|
+
- **Target:** {brief.source_repo}
|
|
142
|
+
- **Description:** "{brief.description}"
|
|
143
|
+
- **Created:** {brief.created} by {brief.created_by}
|
|
144
|
+
- **Scope:** {brief.scope.type}
|
|
145
|
+
|
|
146
|
+
Pick one:
|
|
147
|
+
[R] Ratify — review in step 4 and write (overwriting this file once approved)
|
|
148
|
+
[F] Start fresh — discard this brief and re-prompt for a target
|
|
149
|
+
[X] Cancel and exit
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Wait for user response. Branch:
|
|
153
|
+
|
|
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
|
+
- `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)
|
|
157
|
+
- `source_repo` ← `brief.source_repo`; `source_type` ← `brief.source_type`; `source_authority` ← `brief.source_authority`; `doc_urls` ← `brief.doc_urls`
|
|
158
|
+
- `language` ← `brief.language`; `description` ← `brief.description`; `forge_tier` ← `brief.forge_tier`
|
|
159
|
+
- `created` ← `brief.created`; `created_by` ← `brief.created_by`
|
|
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)
|
|
161
|
+
- `scripts_intent` ← `brief.scripts_intent`; `assets_intent` ← `brief.assets_intent`
|
|
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.
|
|
164
|
+
|
|
165
|
+
- **[F] Start fresh** — discard the loaded brief and re-display §3.1 above (the user is now at the same point as if they had typed nothing).
|
|
166
|
+
- **[X] Cancel** — Display `"Cancelled — no brief was written."` and HALT (exit code 6, `halt_reason: "user-cancelled"`). Non-destructive.
|
|
167
|
+
- **Any other input** — treat as a fresh §3.1 response and re-evaluate the routing branches above (a typed GitHub URL after seeing the menu means "I changed my mind, brief this repo instead").
|
|
168
|
+
|
|
109
169
|
#### 3.2 Branch — Documentation URLs (docs-only)
|
|
110
170
|
|
|
111
171
|
- Set `source_type: "docs-only"` in the brief data
|
|
@@ -310,7 +370,7 @@ Display: "**Select:** [C] Continue to Target Analysis · [X] Cancel and exit"
|
|
|
310
370
|
|
|
311
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.
|
|
312
372
|
|
|
313
|
-
**Preset merge (before validation).**
|
|
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).
|
|
314
374
|
|
|
315
375
|
**Delegate validation to `{validateBriefInputsHelper}`** instead of reasoning through the table rules in prose:
|
|
316
376
|
|
|
@@ -325,7 +385,16 @@ Display: "**Select:** [C] Continue to Target Analysis · [X] Cancel and exit"
|
|
|
325
385
|
|
|
326
386
|
The script's `KNOWN_FIELDS` set must stay in sync with the table in `{headlessArgsFile}`.
|
|
327
387
|
|
|
328
|
-
**
|
|
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.
|
|
329
398
|
|
|
330
399
|
- ONLY proceed to next step when user selects 'C'
|
|
331
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
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
#
|
|
20
|
-
|
|
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
|
|
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
|
|
|
@@ -52,7 +52,11 @@ The script's atomic-write helper creates parent directories as needed (`mkdir -p
|
|
|
52
52
|
|
|
53
53
|
Before writing, check whether the resolved target path already exists.
|
|
54
54
|
|
|
55
|
-
**
|
|
55
|
+
**Ratify path (`ratify_mode: true` in workflow context):**
|
|
56
|
+
|
|
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
|
+
|
|
59
|
+
**Interactive (`{headless_mode}` is false, `ratify_mode` not set):**
|
|
56
60
|
|
|
57
61
|
If the file exists, present:
|
|
58
62
|
|
|
@@ -92,13 +96,19 @@ Assemble the brief context as a **flat** JSON object — every approved value is
|
|
|
92
96
|
"scope_exclude": ["{approved exclude patterns}"],
|
|
93
97
|
"scope_notes": "{approved scope notes or empty string}",
|
|
94
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":"..."}],
|
|
95
101
|
"doc_urls": null | [{"url": "...", "label": "..."}],
|
|
96
102
|
"scripts_intent": null | "{detect|none|free-text}",
|
|
97
103
|
"assets_intent": null | "{detect|none|free-text}",
|
|
98
|
-
"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}"
|
|
99
107
|
}
|
|
100
108
|
```
|
|
101
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
|
+
|
|
102
112
|
Pipe it into the writer script with the `--from-flat` flag:
|
|
103
113
|
|
|
104
114
|
```bash
|
|
@@ -15,7 +15,7 @@ description: >
|
|
|
15
15
|
**Frontmatter rules:**
|
|
16
16
|
|
|
17
17
|
- `name`: lowercase alphanumeric + hyphens only, must match the skill output directory name. Prefer gerund form (`processing-pdfs`, `analyzing-spreadsheets`) for clarity.
|
|
18
|
-
- `description`: non-empty, max 1024 chars, optimized for agent discovery. **MUST use third-person voice** ("Processes Excel files..." not "I can help you..." or "You can use this to..."). Inconsistent point-of-view causes discovery problems since the description is injected into the system prompt.
|
|
18
|
+
- `description`: non-empty, max 1024 chars, optimized for agent discovery. **MUST use third-person voice** ("Processes Excel files..." not "I can help you..." or "You can use this to...") AND **MUST include a trigger phrase** so agents know when to match — one of `Use when …`, `Triggers on …`, or `Reach for this when …`. When using `Use when `, follow with a **gerund** (`Use when building/processing/analyzing X…`) or a noun-phrase clause (`Use when the user requests to "X"`); never a bare indicative verb like `builds`/`processes`, because `skill-check --fix` will prepend a literal `Use when ` to a description missing the trigger phrase, producing ungrammatical output (`Use when builds X…`). Inconsistent point-of-view or a missing trigger phrase causes discovery problems since the description is injected into the system prompt. See `skf-brief-skill/assets/description-voice-examples.md` for the canonical voice palette.
|
|
19
19
|
- **`description` must NOT contain angle brackets** — neither standalone placeholders like `<name>`, `<component>`, `<path>`, nor inline generics like `` `Array<T>` `` or `` `Meta<typeof X>` ``. Both `skill-check`'s `description_field` validator and `tessl`'s deterministic description check parse the frontmatter description as a raw string and reject any `<` or `>`, regardless of markdown context (backticks do NOT protect content here). A rejected description fails the review with 0% score. When the natural phrasing would use angle brackets:
|
|
20
20
|
- Prefer the curly-brace form in prose: `{name}`, `{component-id}`, `{path}` — readable and tessl-safe.
|
|
21
21
|
- Uppercase placeholders are also acceptable: `NAME`, `COMPONENT_ID`.
|
|
@@ -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
|
|
|
@@ -18,7 +18,7 @@ description: >
|
|
|
18
18
|
**Frontmatter rules (agentskills.io specification):**
|
|
19
19
|
|
|
20
20
|
- `name`: 1-64 characters, lowercase alphanumeric + hyphens only, must match parent directory name. Prefer gerund form (`processing-pdfs`, `analyzing-spreadsheets`) for clarity; noun phrases and action-oriented forms are acceptable alternatives.
|
|
21
|
-
- `description`: 1-1024 characters, trigger-optimized for agent matching. MUST use third-person voice ("Processes..." not "I can..." or "You can...").
|
|
21
|
+
- `description`: 1-1024 characters, trigger-optimized for agent matching. MUST use third-person voice ("Processes..." not "I can..." or "You can...") AND include a trigger phrase ("Use when ...", "Triggers on ...", "Reach for this when ..."). When using "Use when ", follow with a gerund (`Use when building/processing/analyzing X…`) or noun-phrase clause — never a bare indicative verb, since `skill-check --fix` will prepend a literal "Use when " to descriptions missing the phrase. See `skf-brief-skill/assets/description-voice-examples.md`.
|
|
22
22
|
- Only 6 fields permitted: `name`, `description`, `license`, `compatibility`, `metadata`, `allowed-tools`
|
|
23
23
|
- `version` and `author` belong in metadata.json, NOT in frontmatter
|
|
24
24
|
|
|
@@ -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}",
|
|
@@ -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
|
|