bmad-module-skill-forge 2.0.2 → 2.1.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/404.md +4 -4
- package/docs/_data/pinned.yaml +1 -1
- package/docs/_internal/RELEASING.md +7 -4
- package/docs/agents.md +2 -2
- package/docs/architecture.md +1 -1
- package/docs/bmad-synergy.md +13 -13
- package/docs/campaign.md +3 -3
- package/docs/concepts.md +3 -3
- package/docs/examples.md +9 -9
- package/docs/forge-auto.md +6 -6
- package/docs/getting-started.md +12 -12
- package/docs/how-it-works.md +7 -7
- package/docs/index.md +7 -1
- package/docs/skill-model.md +2 -2
- package/docs/troubleshooting.md +7 -7
- package/docs/why-skf.md +8 -8
- package/docs/workflows.md +5 -5
- package/package.json +7 -3
- package/src/shared/scripts/schemas/skill-brief.v1.json +6 -0
- package/src/shared/scripts/skf-detect-language.py +20 -0
- package/src/shared/scripts/skf-detect-tools.py +34 -0
- package/src/shared/scripts/skf-emit-result-envelope.py +20 -0
- package/src/shared/scripts/skf-language-corpora.py +20 -0
- package/src/shared/scripts/skf-merge-doc-urls.py +20 -0
- package/src/shared/scripts/skf-qmd-classify-collections.py +44 -2
- package/src/shared/scripts/skf-recommend-scope-type.py +20 -0
- package/src/shared/scripts/skf-render-metadata-stats.py +1 -1
- package/src/shared/scripts/skf-shape-detect.py +20 -0
- package/src/skf-analyze-source/assets/skill-brief-schema.md +2 -1
- package/src/skf-analyze-source/references/recommend.md +1 -1
- package/src/skf-audit-skill/references/re-index.md +1 -1
- package/src/skf-brief-skill/assets/description-voice-examples.md +5 -3
- package/src/skf-brief-skill/assets/skill-brief-schema.md +2 -2
- package/src/skf-brief-skill/references/analyze-target.md +2 -2
- package/src/skf-brief-skill/references/gather-intent.md +4 -2
- package/src/skf-campaign/references/step-05-skill-loop.md +3 -1
- package/src/skf-create-skill/assets/compile-assembly-rules.md +3 -2
- package/src/skf-create-skill/assets/skill-sections.md +1 -1
- package/src/skf-create-skill/references/compile.md +12 -3
- package/src/skf-create-skill/references/extract.md +1 -1
- package/src/skf-create-skill/references/generate-artifacts.md +5 -3
- package/src/skf-create-skill/references/step-doc-rot.md +23 -6
- package/src/skf-create-skill/scripts/scan-doc-rot.py +191 -27
- package/src/skf-create-stack-skill/references/detect-integrations.md +1 -1
- package/src/skf-setup/references/ccc-index.md +2 -2
- package/src/skf-setup/references/report.md +1 -1
- package/src/skf-test-skill/references/coverage-check.md +11 -5
- package/src/skf-test-skill/references/score.md +7 -1
- package/src/skf-test-skill/scripts/aggregate-coherence.py +13 -3
- package/src/skf-test-skill/scripts/compute-score.py +17 -4
- package/src/skf-test-skill/scripts/reconcile-coverage.py +36 -9
- package/tools/cli/lib/installer.js +31 -1
- package/tools/validate-docs-links.js +665 -0
package/docs/why-skf.md
CHANGED
|
@@ -44,7 +44,7 @@ For docs-only skills, the audit shape is the same — `provenance-map.json` stil
|
|
|
44
44
|
|
|
45
45
|
If it doesn't, **that's a bug.** [Open an issue](https://github.com/armelhbobdad/bmad-module-skill-forge/issues/new/choose) and SKF republishes the skill with a new commit SHA and a new provenance map. No other AI-skills tool treats disagreement between claim and source as a defect. SKF does.
|
|
46
46
|
|
|
47
|
-
See the [Verifying a Skill](
|
|
47
|
+
See the [Verifying a Skill](/docs/verifying-a-skill.md) page for the full three-step audit on real skills, the test reports that log *exactly* where coverage falls short, and the scoring formula behind the 80% pass threshold.
|
|
48
48
|
|
|
49
49
|
---
|
|
50
50
|
|
|
@@ -53,27 +53,27 @@ See the [Verifying a Skill](../verifying-a-skill/) page for the full three-step
|
|
|
53
53
|
### The curious developer
|
|
54
54
|
|
|
55
55
|
Your agent just hallucinated a method that doesn't exist, again. You want this to stop, and you don't want to read a long architecture reference before running your first command.
|
|
56
|
-
→ Start with [Getting Started](
|
|
56
|
+
→ Start with [Getting Started](/docs/getting-started.md).
|
|
57
57
|
|
|
58
58
|
### The BMAD user
|
|
59
59
|
|
|
60
60
|
You already use BMAD Method, BMM phases, TEA, or BMB, and you want to know where SKF fits.
|
|
61
|
-
→ Read [BMAD Synergy](
|
|
61
|
+
→ Read [BMAD Synergy](/docs/bmad-synergy.md) for the phase-by-phase integration playbook.
|
|
62
62
|
|
|
63
63
|
### The skeptic
|
|
64
64
|
|
|
65
65
|
"AI docs for AI" sounds like the problem pretending to be the solution. You want receipts before you install anything.
|
|
66
|
-
→ Start with [Verifying a Skill](
|
|
66
|
+
→ Start with [Verifying a Skill](/docs/verifying-a-skill.md) — the three-step audit on real skills, including the 1% that fails.
|
|
67
67
|
|
|
68
68
|
### The OSS maintainer
|
|
69
69
|
|
|
70
70
|
You want to ship verified skills alongside your library releases — `npx skills publish`-ready, drift-detectable, version-pinned.
|
|
71
|
-
→ See [Examples → OSS Maintainer Publishing Official Skills](
|
|
71
|
+
→ See [Examples → OSS Maintainer Publishing Official Skills](/docs/examples.md#scenario-h-oss-maintainer-publishing-official-skills).
|
|
72
72
|
|
|
73
73
|
### The team lead evaluating adoption
|
|
74
74
|
|
|
75
75
|
You're considering running SKF across a brownfield platform. You need to know about rollback safety, `[MANUAL]` section preservation, and the health-check feedback loop before committing.
|
|
76
|
-
→ Start with [Architecture](
|
|
76
|
+
→ Start with [Architecture](/docs/architecture.md), then [Workflows → Workflow Health Check](/docs/workflows.md#terminal-step-health-check).
|
|
77
77
|
|
|
78
78
|
---
|
|
79
79
|
|
|
@@ -89,6 +89,6 @@ Everything else is downstream of one question: *are the instructions your AI rea
|
|
|
89
89
|
|
|
90
90
|
## Next
|
|
91
91
|
|
|
92
|
-
- **[Install SKF](
|
|
93
|
-
- **[Audit a skill in 60 seconds](
|
|
92
|
+
- **[Install SKF](/docs/getting-started.md#install)** — Node ≥ 22, Python ≥ 3.10, `uv`, one `npx` command
|
|
93
|
+
- **[Audit a skill in 60 seconds](/docs/verifying-a-skill.md)** — see the receipts before you install
|
|
94
94
|
- **[Browse real skills](https://github.com/armelhbobdad/oh-my-skills)** — four Deep-tier skills, all shipping their audit trails
|
package/docs/workflows.md
CHANGED
|
@@ -3,9 +3,9 @@ title: Workflows
|
|
|
3
3
|
description: All 15 SKF workflows with commands, steps, and connection diagram
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Trigger workflows by typing commands to [Ferris](
|
|
6
|
+
Trigger workflows by typing commands to [Ferris](/docs/agents.md). See [Concepts](/docs/concepts.md) for definitions.
|
|
7
7
|
|
|
8
|
-
> Already using BMAD? See [BMAD Synergy](
|
|
8
|
+
> Already using BMAD? See [BMAD Synergy](/docs/bmad-synergy.md) for when to invoke each SKF workflow during BMM phases and alongside TEA, BMB, and GDS.
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -83,7 +83,7 @@ Trigger workflows by typing commands to [Ferris](../agents/). See [Concepts](../
|
|
|
83
83
|
|
|
84
84
|
**Purpose:** Brief-less fast skill with package-to-repo resolution.
|
|
85
85
|
|
|
86
|
-
**Note:** QS is **tier-unaware** — it always runs at community tier and does not use ast-grep, CCC, or QMD even when your forge is configured at Forge+/Deep. For tier-aware compilation, use `BS → CS`, `forge`, or `forge-auto`. See [Skill Model](
|
|
86
|
+
**Note:** QS is **tier-unaware** — it always runs at community tier and does not use ast-grep, CCC, or QMD even when your forge is configured at Forge+/Deep. For tier-aware compilation, use `BS → CS`, `forge`, or `forge-auto`. See [Skill Model](/docs/skill-model.md).
|
|
87
87
|
|
|
88
88
|
**When to Use:** When you need a skill quickly — no brief needed. Accepts package names or GitHub URLs. Append `@version` to target a specific version (e.g., `@Ferris QS cognee@1.0.0`).
|
|
89
89
|
|
|
@@ -164,7 +164,7 @@ Trigger workflows by typing commands to [Ferris](../agents/). See [Concepts](../
|
|
|
164
164
|
|
|
165
165
|
**Key Steps:** Load skill → Detect mode → Coverage check → Coherence check → External validation (skill-check, tessl) → Hard gate → Score → Gap report
|
|
166
166
|
|
|
167
|
-
**Scored Categories:** Export Coverage (36%), Signature Accuracy (22%), Type Coverage (14%), Coherence (18%), External Validation (10%). Default pass threshold: **80%** (per-pipeline defaults: forge-auto 90%, forge 80%). Pass routes to Export Skill; fail routes to Update Skill with a gap report. See [Completeness Scoring](
|
|
167
|
+
**Scored Categories:** Export Coverage (36%), Signature Accuracy (22%), Type Coverage (14%), Coherence (18%), External Validation (10%). Default pass threshold: **80%** (per-pipeline defaults: forge-auto 90%, forge 80%). Pass routes to Export Skill; fail routes to Update Skill with a gap report. See [Completeness Scoring](/docs/verifying-a-skill.md#how-the-score-is-computed) for the full formula and tier adjustments.
|
|
168
168
|
|
|
169
169
|
**Agent:** Ferris (Audit mode)
|
|
170
170
|
|
|
@@ -315,7 +315,7 @@ flowchart TD
|
|
|
315
315
|
EXPORT --> MAINT[Maintenance + Campaign Report]
|
|
316
316
|
```
|
|
317
317
|
|
|
318
|
-
> **One workflow per session** (unless using pipeline mode or campaign). Each arrow in the standard and compose-mode diagrams represents a new conversation session. Campaign manages its own multi-session orchestration internally — see [Campaign Orchestration](
|
|
318
|
+
> **One workflow per session** (unless using pipeline mode or campaign). Each arrow in the standard and compose-mode diagrams represents a new conversation session. Campaign manages its own multi-session orchestration internally — see [Campaign Orchestration](/docs/campaign.md). Clear your context between workflows for best results — or use pipeline mode to chain them automatically. See [Pipeline Mode](#pipeline-mode) below.
|
|
319
319
|
|
|
320
320
|
---
|
|
321
321
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "bmad-module-skill-forge",
|
|
4
|
-
"version": "2.0
|
|
4
|
+
"version": "2.1.0",
|
|
5
5
|
"description": "BMAD module — Turn code and docs into instructions AI agents can actually follow. Progressive capability tiers (Quick/Forge/Forge+/Deep).",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"bmad",
|
|
@@ -44,18 +44,21 @@
|
|
|
44
44
|
"lint:fix": "eslint . --ext .js,.cjs,.mjs,.yaml --fix",
|
|
45
45
|
"lint:md": "markdownlint-cli2 \"**/*.md\"",
|
|
46
46
|
"prepare": "husky || exit 0",
|
|
47
|
-
"quality": "npm run format:check && npm run lint && npm run lint:md && npm run test:schemas && npm run test:install && npm run test:cli && npm run test:workflow && npm run test:python && npm run test:knowledge && npm run validate:schemas && npm run validate:skills && npm run validate:refs && npm run docs:validate-drift",
|
|
47
|
+
"quality": "npm run format:check && npm run lint && npm run lint:md && npm run test:schemas && npm run test:install && npm run test:cli && npm run test:workflow && npm run test:python && npm run test:rehype && npm run test:docs-links-tool && npm run test:knowledge && npm run validate:schemas && npm run validate:skills && npm run validate:refs && npm run validate:docs-links && npm run docs:validate-drift",
|
|
48
48
|
"skf:install": "node tools/cli/skf-cli.js install",
|
|
49
49
|
"skf:status": "node tools/cli/skf-cli.js status",
|
|
50
50
|
"skf:uninstall": "node tools/cli/skf-cli.js uninstall",
|
|
51
51
|
"skf:update": "node tools/cli/skf-cli.js update",
|
|
52
|
-
"test": "npm run test:schemas && npm run test:install && npm run test:cli && npm run test:workflow && npm run test:python && npm run test:knowledge && npm run validate:schemas && npm run validate:skills && npm run validate:refs && npm run lint && npm run lint:md && npm run format:check",
|
|
52
|
+
"test": "npm run test:schemas && npm run test:install && npm run test:cli && npm run test:workflow && npm run test:python && npm run test:rehype && npm run test:docs-links-tool && npm run test:knowledge && npm run validate:schemas && npm run validate:skills && npm run validate:refs && npm run validate:docs-links && npm run lint && npm run lint:md && npm run format:check",
|
|
53
53
|
"test:cli": "node test/test-cli-integration.js",
|
|
54
|
+
"test:docs-links-tool": "node test/test-validate-docs-links.js",
|
|
54
55
|
"test:install": "node test/test-installation-components.js",
|
|
55
56
|
"test:knowledge": "node test/test-knowledge-base.js",
|
|
56
57
|
"test:python": "uv run --with pytest --with pyyaml --with jsonschema pytest test/test-compute-score-contract.py test/test-skf-preflight.py test/test-skf-skill-inventory.py test/test-skf-validate-output.py test/test-skf-validate-frontmatter.py test/test-skf-manifest-ops.py test/test-skf-rebuild-managed-sections.py test/test-skf-atomic-write.py test/test-skf-severity-classify.py test/test-skf-structural-diff.py test/test-skf-detect-tools.py test/test-skf-forge-tier-rw.py test/test-skf-emit-result-envelope.py test/test-skf-qmd-classify-collections.py test/test-skf-merge-ccc-exclusions.py test/test-skf-resolve-package.py test/test-skf-extract-public-api.py test/test-skf-render-quick-metadata.py test/test-skf-validate-brief-inputs.py test/test-skf-emit-brief-result-envelope.py test/test-skf-write-skill-brief.py test/test-skf-detect-workspaces.py test/test-skf-recommend-scope-type.py test/test-skf-detect-language.py test/test-skf-language-corpora.py test/test-skf-merge-doc-urls.py test/test-skf-derive-assembly-shape.py test/test-skf-description-guard.py test/test-skf-detect-scripts-assets.py test/test-skf-hash-content.py test/test-skf-validate-brief-schema.py test/test-skf-check-workspace-drift.py test/test-skf-update-active-symlink.py test/test-skf-build-change-manifest.py test/test-skf-provenance-gap-dispatch.py test/test-skf-resolve-authoritative-files.py test/test-skf-scan-manifests.py test/test-skf-pair-intersect.py test/test-skf-enumerate-stack-skills.py test/test-skf-compare-file-hashes.py test/test-skf-load-provenance.py test/test-skf-scan-skill-md-structure.py test/test-skf-disqualify-candidates.py test/test-skf-chain-reachability.py test/test-skf-shape-detect.py test/test-skf-detect-docs.py test/test-skf-step-doc-sources.py test/test-skf-step-doc-drift.py test/test-skf-step-hard-gate.py test/test-skf-per-pipeline-thresholds.py test/test-skf-evidence-report-fallback.py test/test-skf-validate-pins.py test/test-skf-auto-shard.py test/test-skf-preapply.py test/test-skf-step-doc-rot.py test/test-skf-campaign-state.py test/test-skf-campaign-pins.py test/test-skf-campaign-deps.py test/test-skf-campaign-report.py test/test-skf-campaign-stepfiles.py test/test-skf-campaign-validate-state.py test/test-skf-campaign-provenance.py test/test-skf-campaign-parse-manifest.py test/test-skf-campaign-render-kickoff.py test/test-skf-campaign-render-batch.py test/test-skf-shard-body.py test/test-skf-render-metadata-stats.py test/test-skf-comention-pairs.py test/test-skf-count-tokens.py test/test-skf-reconcile-coverage.py test/test-skf-verify-provenance-completeness.py test/test-skf-find-cycles.py test/test-skf-validate-feasibility-report.py test/test-skf-rewrite-skill-name.py test/test-skf-verify-no-trace.py test/test-skf-validate-inventory.py test/test-skf-check-metadata-coherence.py test/test-skf-scan-doc-rot.py test/test-skf-aggregate-coherence.py test/test-skf-dir-sizes.py test/test-skf-parse-pipeline.py test/test-skf-validate-rename-name.py test/test-skf-verify-declared-numerator.py test/test-skf-combine-external-scores.py test/test-skf-new-file-diff.py test/test-skf-coverage-tally.py test/test-skf-report-delta.py test/test-skf-verdict-rollup.py -v",
|
|
58
|
+
"test:rehype": "node test/test-rehype-markdown-links.js",
|
|
57
59
|
"test:schemas": "node test/test-agent-schema.js",
|
|
58
60
|
"test:workflow": "node test/test-workflow-state.js",
|
|
61
|
+
"validate:docs-links": "node tools/validate-docs-links.js --strict",
|
|
59
62
|
"validate:refs": "node tools/validate-file-refs.js --strict",
|
|
60
63
|
"validate:schemas": "node test/validate-agent-schema.js",
|
|
61
64
|
"validate:skills": "node tools/validate-skills.js --strict"
|
|
@@ -111,6 +114,7 @@
|
|
|
111
114
|
"prettier-plugin-packagejson": "^2.5.19",
|
|
112
115
|
"semver": "^7.6.3",
|
|
113
116
|
"sharp": "^0.33.5",
|
|
117
|
+
"unist-util-visit": "^5.1.0",
|
|
114
118
|
"wrap-ansi": "^7.0.0",
|
|
115
119
|
"xml2js": "^0.6.2",
|
|
116
120
|
"yaml": "^2.7.0",
|
|
@@ -84,6 +84,12 @@
|
|
|
84
84
|
"enum": ["full-library", "specific-modules", "public-api", "component-library", "reference-app", "docs-only"]
|
|
85
85
|
},
|
|
86
86
|
"include": { "type": "array", "items": { "type": "string" } },
|
|
87
|
+
"tier_a_include": {
|
|
88
|
+
"type": "array",
|
|
89
|
+
"description": "Optional narrower tier-A include list. When present it becomes the coverage-denominator basis instead of the coarse scope.include, and it exempts the brief from the denominator deflation guard in skf-test-skill references/source-access-protocol.md — so an empty or blank-item list must not be accepted: it would silence that guard while narrowing nothing.",
|
|
90
|
+
"minItems": 1,
|
|
91
|
+
"items": { "type": "string", "minLength": 1 }
|
|
92
|
+
},
|
|
87
93
|
"exclude": { "type": "array", "items": { "type": "string" } },
|
|
88
94
|
"notes": { "type": "string" },
|
|
89
95
|
"rationale": {
|
|
@@ -378,7 +378,27 @@ def _parse_argv(argv: list[str]) -> dict:
|
|
|
378
378
|
return payload
|
|
379
379
|
|
|
380
380
|
|
|
381
|
+
def _force_utf8(*streams) -> None:
|
|
382
|
+
"""Reconfigure JSON-carrying streams to UTF-8 (issue #465).
|
|
383
|
+
|
|
384
|
+
A default Windows console decodes stdio as cp1252, which cannot carry
|
|
385
|
+
non-ASCII JSON (ensure_ascii=False output, raw UTF-8 input). Preserves
|
|
386
|
+
each stream's existing error handler — reconfigure(encoding=...) alone
|
|
387
|
+
would reset it to 'strict', downgrading e.g. an already-UTF-8 stderr on
|
|
388
|
+
Linux. For stdin this must run before the first read. Skips in-process
|
|
389
|
+
test doubles without reconfigure().
|
|
390
|
+
"""
|
|
391
|
+
for stream in streams:
|
|
392
|
+
if hasattr(stream, "reconfigure"):
|
|
393
|
+
errors = getattr(stream, "errors", None)
|
|
394
|
+
if errors is None:
|
|
395
|
+
stream.reconfigure(encoding="utf-8")
|
|
396
|
+
else:
|
|
397
|
+
stream.reconfigure(encoding="utf-8", errors=errors)
|
|
398
|
+
|
|
399
|
+
|
|
381
400
|
def main(argv: list[str]) -> int:
|
|
401
|
+
_force_utf8(sys.stdin, sys.stdout)
|
|
382
402
|
payload = _parse_argv(argv)
|
|
383
403
|
result = detect(payload)
|
|
384
404
|
json.dump(result, sys.stdout, ensure_ascii=False)
|
|
@@ -142,6 +142,29 @@ def _ok(payload: dict) -> None:
|
|
|
142
142
|
print(json.dumps(payload))
|
|
143
143
|
|
|
144
144
|
|
|
145
|
+
def _resolve_outside_cwd(command: str) -> str | None:
|
|
146
|
+
"""shutil.which with a CWD-shim guard. Returns the resolved path or None.
|
|
147
|
+
|
|
148
|
+
shutil.which on Windows searches the current directory ahead of PATH,
|
|
149
|
+
and CWD here is the repo under analysis — a bare-name lookup resolving
|
|
150
|
+
into CWD would execute a repo-planted shim (e.g. ast-grep.cmd). Such a
|
|
151
|
+
resolution is treated as not-found. Explicit paths supplied by callers
|
|
152
|
+
(containing a separator) are honored as-is. Keep identical to the
|
|
153
|
+
sibling guard in skf-qmd-classify-collections.py.
|
|
154
|
+
"""
|
|
155
|
+
resolved = shutil.which(command)
|
|
156
|
+
if resolved is None:
|
|
157
|
+
return None
|
|
158
|
+
if os.sep in command or (os.altsep and os.altsep in command):
|
|
159
|
+
return resolved
|
|
160
|
+
resolved_dir = os.path.dirname(resolved)
|
|
161
|
+
if resolved_dir:
|
|
162
|
+
cwd = os.path.normcase(os.path.abspath(os.getcwd()))
|
|
163
|
+
if os.path.normcase(os.path.abspath(resolved_dir)) == cwd:
|
|
164
|
+
return None
|
|
165
|
+
return resolved
|
|
166
|
+
|
|
167
|
+
|
|
145
168
|
def _run(cmd: list[str], timeout: int = PROBE_TIMEOUT_SEC) -> tuple[int, str, str]:
|
|
146
169
|
"""Run a subprocess. Return (returncode, stdout, stderr). Never raises.
|
|
147
170
|
|
|
@@ -149,6 +172,13 @@ def _run(cmd: list[str], timeout: int = PROBE_TIMEOUT_SEC) -> tuple[int, str, st
|
|
|
149
172
|
CalledProcessError) as a failed probe — returns rc=127 and an empty
|
|
150
173
|
stdout/stderr. Tool detection should never crash the workflow.
|
|
151
174
|
|
|
175
|
+
cmd[0] is resolved through shutil.which() before spawning (with the
|
|
176
|
+
CWD-shim guard in _resolve_outside_cwd): Windows CreateProcess only
|
|
177
|
+
ever appends .exe to a bare name, so npm-installed shims
|
|
178
|
+
(ast-grep.CMD, qmd.CMD) would raise FileNotFoundError and read as
|
|
179
|
+
"tool absent". An unresolvable command returns rc=127 without
|
|
180
|
+
spawning anything.
|
|
181
|
+
|
|
152
182
|
The child is wrapped in the OS `timeout(1)` utility (when available) so a
|
|
153
183
|
daemon-backed probe blocked in an uninterruptible syscall (e.g.
|
|
154
184
|
`qmd status` waiting on its daemon socket) is reaped at the OS level —
|
|
@@ -158,6 +188,10 @@ def _run(cmd: list[str], timeout: int = PROBE_TIMEOUT_SEC) -> tuple[int, str, st
|
|
|
158
188
|
`start_new_session=True` isolates the child's process group; the
|
|
159
189
|
Python-level timeout is a secondary net set slightly above the OS one.
|
|
160
190
|
"""
|
|
191
|
+
resolved = _resolve_outside_cwd(cmd[0])
|
|
192
|
+
if resolved is None:
|
|
193
|
+
return 127, "", ""
|
|
194
|
+
cmd = [resolved, *cmd[1:]]
|
|
161
195
|
if _TIMEOUT_BIN:
|
|
162
196
|
run_cmd = [_TIMEOUT_BIN, "--kill-after=2", str(timeout), *cmd]
|
|
163
197
|
py_timeout = timeout + 5
|
|
@@ -496,7 +496,27 @@ def cmd_emit_blocked() -> None:
|
|
|
496
496
|
print(emit_envelope_line(envelope))
|
|
497
497
|
|
|
498
498
|
|
|
499
|
+
def _force_utf8(*streams) -> None:
|
|
500
|
+
"""Reconfigure JSON-carrying streams to UTF-8 (issue #465).
|
|
501
|
+
|
|
502
|
+
A default Windows console decodes stdio as cp1252, which cannot carry
|
|
503
|
+
non-ASCII JSON (ensure_ascii=False output, raw UTF-8 input). Preserves
|
|
504
|
+
each stream's existing error handler — reconfigure(encoding=...) alone
|
|
505
|
+
would reset it to 'strict', downgrading e.g. an already-UTF-8 stderr on
|
|
506
|
+
Linux. For stdin this must run before the first read. Skips in-process
|
|
507
|
+
test doubles without reconfigure().
|
|
508
|
+
"""
|
|
509
|
+
for stream in streams:
|
|
510
|
+
if hasattr(stream, "reconfigure"):
|
|
511
|
+
errors = getattr(stream, "errors", None)
|
|
512
|
+
if errors is None:
|
|
513
|
+
stream.reconfigure(encoding="utf-8")
|
|
514
|
+
else:
|
|
515
|
+
stream.reconfigure(encoding="utf-8", errors=errors)
|
|
516
|
+
|
|
517
|
+
|
|
499
518
|
def main() -> None:
|
|
519
|
+
_force_utf8(sys.stdin, sys.stdout)
|
|
500
520
|
parser = argparse.ArgumentParser(
|
|
501
521
|
description=__doc__,
|
|
502
522
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
@@ -80,7 +80,27 @@ def corpora_for(language: str) -> list[dict]:
|
|
|
80
80
|
return out
|
|
81
81
|
|
|
82
82
|
|
|
83
|
+
def _force_utf8(*streams) -> None:
|
|
84
|
+
"""Reconfigure JSON-carrying streams to UTF-8 (issue #465).
|
|
85
|
+
|
|
86
|
+
A default Windows console decodes stdio as cp1252, which cannot carry
|
|
87
|
+
non-ASCII JSON (ensure_ascii=False output, raw UTF-8 input). Preserves
|
|
88
|
+
each stream's existing error handler — reconfigure(encoding=...) alone
|
|
89
|
+
would reset it to 'strict', downgrading e.g. an already-UTF-8 stderr on
|
|
90
|
+
Linux. For stdin this must run before the first read. Skips in-process
|
|
91
|
+
test doubles without reconfigure().
|
|
92
|
+
"""
|
|
93
|
+
for stream in streams:
|
|
94
|
+
if hasattr(stream, "reconfigure"):
|
|
95
|
+
errors = getattr(stream, "errors", None)
|
|
96
|
+
if errors is None:
|
|
97
|
+
stream.reconfigure(encoding="utf-8")
|
|
98
|
+
else:
|
|
99
|
+
stream.reconfigure(encoding="utf-8", errors=errors)
|
|
100
|
+
|
|
101
|
+
|
|
83
102
|
def main(argv: list[str]) -> int:
|
|
103
|
+
_force_utf8(sys.stdout, sys.stderr)
|
|
84
104
|
parser = argparse.ArgumentParser(
|
|
85
105
|
description="Look up canonical companion prose corpora for a language.",
|
|
86
106
|
)
|
|
@@ -213,7 +213,27 @@ def merge_doc_urls(
|
|
|
213
213
|
return kept, suppressed
|
|
214
214
|
|
|
215
215
|
|
|
216
|
+
def _force_utf8(*streams) -> None:
|
|
217
|
+
"""Reconfigure JSON-carrying streams to UTF-8 (issue #465).
|
|
218
|
+
|
|
219
|
+
A default Windows console decodes stdio as cp1252, which cannot carry
|
|
220
|
+
non-ASCII JSON (ensure_ascii=False output, raw UTF-8 input). Preserves
|
|
221
|
+
each stream's existing error handler — reconfigure(encoding=...) alone
|
|
222
|
+
would reset it to 'strict', downgrading e.g. an already-UTF-8 stderr on
|
|
223
|
+
Linux. For stdin this must run before the first read. Skips in-process
|
|
224
|
+
test doubles without reconfigure().
|
|
225
|
+
"""
|
|
226
|
+
for stream in streams:
|
|
227
|
+
if hasattr(stream, "reconfigure"):
|
|
228
|
+
errors = getattr(stream, "errors", None)
|
|
229
|
+
if errors is None:
|
|
230
|
+
stream.reconfigure(encoding="utf-8")
|
|
231
|
+
else:
|
|
232
|
+
stream.reconfigure(encoding="utf-8", errors=errors)
|
|
233
|
+
|
|
234
|
+
|
|
216
235
|
def main() -> int:
|
|
236
|
+
_force_utf8(sys.stdin, sys.stdout)
|
|
217
237
|
raw = sys.stdin.read()
|
|
218
238
|
if not raw or not raw.strip():
|
|
219
239
|
sys.stderr.write("skf-merge-doc-urls: empty stdin (expected JSON object)\n")
|
|
@@ -79,7 +79,9 @@ from __future__ import annotations
|
|
|
79
79
|
|
|
80
80
|
import argparse
|
|
81
81
|
import json
|
|
82
|
+
import os
|
|
82
83
|
import re
|
|
84
|
+
import shutil
|
|
83
85
|
import sys
|
|
84
86
|
from pathlib import Path
|
|
85
87
|
|
|
@@ -92,6 +94,10 @@ FOREIGN_SAMPLE_CAP = 5
|
|
|
92
94
|
# Header line emitted by newer qmd builds, e.g. "Collections (56):".
|
|
93
95
|
_QMD_HEADER_RE = re.compile(r"^Collections \(\d+\):\s*$")
|
|
94
96
|
|
|
97
|
+
# Empty-state message from `qmd collection list`, e.g.
|
|
98
|
+
# "No collections found. Run 'qmd collection add .' to create one."
|
|
99
|
+
_QMD_EMPTY_STATE_PREFIX = "No collections found"
|
|
100
|
+
|
|
95
101
|
|
|
96
102
|
def _die(code: int, message: str) -> None:
|
|
97
103
|
print(json.dumps({"status": "error", "message": message}), file=sys.stderr)
|
|
@@ -199,7 +205,12 @@ def parse_collection_list_output(raw: str) -> list[str]:
|
|
|
199
205
|
the trailing ` (qmd://name/)` URI and is a no-op for the bare-name form.
|
|
200
206
|
Without this, suffixed entries fail the `is_forge_owned` suffix check and
|
|
201
207
|
every forge collection is mis-classified as foreign.
|
|
208
|
+
|
|
209
|
+
The empty-state message ("No collections found. …") is recognized before
|
|
210
|
+
per-line tokenizing — it would otherwise parse as a collection named "No".
|
|
202
211
|
"""
|
|
212
|
+
if raw.lstrip().startswith(_QMD_EMPTY_STATE_PREFIX):
|
|
213
|
+
return []
|
|
203
214
|
names: list[str] = []
|
|
204
215
|
for line in raw.splitlines():
|
|
205
216
|
if not line.strip():
|
|
@@ -212,6 +223,29 @@ def parse_collection_list_output(raw: str) -> list[str]:
|
|
|
212
223
|
return names
|
|
213
224
|
|
|
214
225
|
|
|
226
|
+
def _resolve_outside_cwd(command: str) -> str | None:
|
|
227
|
+
"""shutil.which with a CWD-shim guard. Returns the resolved path or None.
|
|
228
|
+
|
|
229
|
+
shutil.which on Windows searches the current directory ahead of PATH,
|
|
230
|
+
and CWD here is the repo under analysis — a bare-name lookup resolving
|
|
231
|
+
into CWD would execute a repo-planted shim (e.g. qmd.cmd). Such a
|
|
232
|
+
resolution is treated as not-found. Explicit paths supplied by callers
|
|
233
|
+
(containing a separator) are honored as-is. Keep identical to the
|
|
234
|
+
sibling guard in skf-detect-tools.py.
|
|
235
|
+
"""
|
|
236
|
+
resolved = shutil.which(command)
|
|
237
|
+
if resolved is None:
|
|
238
|
+
return None
|
|
239
|
+
if os.sep in command or (os.altsep and os.altsep in command):
|
|
240
|
+
return resolved
|
|
241
|
+
resolved_dir = os.path.dirname(resolved)
|
|
242
|
+
if resolved_dir:
|
|
243
|
+
cwd = os.path.normcase(os.path.abspath(os.getcwd()))
|
|
244
|
+
if os.path.normcase(os.path.abspath(resolved_dir)) == cwd:
|
|
245
|
+
return None
|
|
246
|
+
return resolved
|
|
247
|
+
|
|
248
|
+
|
|
215
249
|
def fetch_live_names_from_qmd() -> tuple[list[str], str | None]:
|
|
216
250
|
"""Invoke `qmd collection list` and return (names, error).
|
|
217
251
|
|
|
@@ -221,11 +255,19 @@ def fetch_live_names_from_qmd() -> tuple[list[str], str | None]:
|
|
|
221
255
|
counts as a "live collection name".
|
|
222
256
|
"""
|
|
223
257
|
import subprocess
|
|
258
|
+
# Resolve before spawning: on Windows qmd ships from npm as a .CMD
|
|
259
|
+
# shim, which a bare-name subprocess.run cannot launch (WinError 2).
|
|
260
|
+
qmd = _resolve_outside_cwd("qmd")
|
|
261
|
+
if qmd is None:
|
|
262
|
+
return [], "qmd not found on PATH"
|
|
224
263
|
try:
|
|
264
|
+
# qmd emits UTF-8; a locale-default decode (cp1252 on Windows)
|
|
265
|
+
# mojibakes names or raises UnicodeDecodeError on unmapped bytes.
|
|
225
266
|
result = subprocess.run(
|
|
226
|
-
[
|
|
267
|
+
[qmd, "collection", "list"],
|
|
227
268
|
capture_output=True,
|
|
228
|
-
|
|
269
|
+
encoding="utf-8",
|
|
270
|
+
errors="replace",
|
|
229
271
|
timeout=30,
|
|
230
272
|
check=False,
|
|
231
273
|
)
|
|
@@ -356,7 +356,27 @@ def _parse_argv(argv: list[str]) -> dict:
|
|
|
356
356
|
return payload
|
|
357
357
|
|
|
358
358
|
|
|
359
|
+
def _force_utf8(*streams) -> None:
|
|
360
|
+
"""Reconfigure JSON-carrying streams to UTF-8 (issue #465).
|
|
361
|
+
|
|
362
|
+
A default Windows console decodes stdio as cp1252, which cannot carry
|
|
363
|
+
non-ASCII JSON (ensure_ascii=False output, raw UTF-8 input). Preserves
|
|
364
|
+
each stream's existing error handler — reconfigure(encoding=...) alone
|
|
365
|
+
would reset it to 'strict', downgrading e.g. an already-UTF-8 stderr on
|
|
366
|
+
Linux. For stdin this must run before the first read. Skips in-process
|
|
367
|
+
test doubles without reconfigure().
|
|
368
|
+
"""
|
|
369
|
+
for stream in streams:
|
|
370
|
+
if hasattr(stream, "reconfigure"):
|
|
371
|
+
errors = getattr(stream, "errors", None)
|
|
372
|
+
if errors is None:
|
|
373
|
+
stream.reconfigure(encoding="utf-8")
|
|
374
|
+
else:
|
|
375
|
+
stream.reconfigure(encoding="utf-8", errors=errors)
|
|
376
|
+
|
|
377
|
+
|
|
359
378
|
def main(argv: list[str]) -> int:
|
|
379
|
+
_force_utf8(sys.stdin, sys.stdout)
|
|
360
380
|
payload = _parse_argv(argv)
|
|
361
381
|
result = recommend(payload)
|
|
362
382
|
json.dump(result, sys.stdout, ensure_ascii=False)
|
|
@@ -64,7 +64,7 @@ Judgment payload (stdin JSON, compute mode)::
|
|
|
64
64
|
{
|
|
65
65
|
"exports_public_api": 40, # int — public-entry-point export count (judgment)
|
|
66
66
|
"exports_internal": 19, # int — non-underscore internal export count (judgment)
|
|
67
|
-
"effective_denominator": 12, # int|null — optional,
|
|
67
|
+
"effective_denominator": 12, # int|null — optional, curated-subset scopes only
|
|
68
68
|
"pattern_surfaces_documented": 8, # int — reference-app only (exports_documented proxy)
|
|
69
69
|
"exports_documented": 0, # int — stack only (own-barrel count, usually 0)
|
|
70
70
|
"scripts": [ ... ], # list — scripts[] array (or "scripts_count": int)
|
|
@@ -1075,7 +1075,27 @@ def path_to_manifest_name(ecosystem: str) -> str:
|
|
|
1075
1075
|
# CLI wiring
|
|
1076
1076
|
# ---------------------------------------------------------------------------
|
|
1077
1077
|
|
|
1078
|
+
def _force_utf8(*streams) -> None:
|
|
1079
|
+
"""Reconfigure JSON-carrying streams to UTF-8 (issue #465).
|
|
1080
|
+
|
|
1081
|
+
A default Windows console decodes stdio as cp1252, which cannot carry
|
|
1082
|
+
non-ASCII JSON (ensure_ascii=False output, raw UTF-8 input). Preserves
|
|
1083
|
+
each stream's existing error handler — reconfigure(encoding=...) alone
|
|
1084
|
+
would reset it to 'strict', downgrading e.g. an already-UTF-8 stderr on
|
|
1085
|
+
Linux. For stdin this must run before the first read. Skips in-process
|
|
1086
|
+
test doubles without reconfigure().
|
|
1087
|
+
"""
|
|
1088
|
+
for stream in streams:
|
|
1089
|
+
if hasattr(stream, "reconfigure"):
|
|
1090
|
+
errors = getattr(stream, "errors", None)
|
|
1091
|
+
if errors is None:
|
|
1092
|
+
stream.reconfigure(encoding="utf-8")
|
|
1093
|
+
else:
|
|
1094
|
+
stream.reconfigure(encoding="utf-8", errors=errors)
|
|
1095
|
+
|
|
1096
|
+
|
|
1078
1097
|
def main(argv: list[str]) -> int:
|
|
1098
|
+
_force_utf8(sys.stdout, sys.stderr)
|
|
1079
1099
|
parser = argparse.ArgumentParser(
|
|
1080
1100
|
description="Classify a repo into a known skill shape from its manifest files.",
|
|
1081
1101
|
)
|
|
@@ -13,7 +13,7 @@ Defines the output contract for skill-brief.yaml files generated by analyze-sour
|
|
|
13
13
|
| source_repo | string | GitHub URL or local path | Repository or project root (optional when `source_type: "docs-only"`) |
|
|
14
14
|
| language | string | Recognized language | Primary programming language |
|
|
15
15
|
| scope | object | See Scope Object below | Boundary definition |
|
|
16
|
-
| description | string | 1-3 sentences | What the skill covers
|
|
16
|
+
| description | string | 1-3 sentences | What the skill covers. Must contain a literal `Use when` clause — validators test for that exact phrase. |
|
|
17
17
|
| forge_tier | string | `Quick` / `Forge` / `Forge+` / `Deep` | Inherited from forge-tier.yaml (Title Case) |
|
|
18
18
|
| created | string | ISO date `YYYY-MM-DD` | Generation date |
|
|
19
19
|
| created_by | string | user_name from config | Who generated the brief |
|
|
@@ -31,6 +31,7 @@ Defines the output contract for skill-brief.yaml files generated by analyze-sour
|
|
|
31
31
|
| `constituent_refs` | object | map of path→ref strings | Optional. Per-constituent git ref overrides for composite (multi-repo or multi-ref) sources. Keys must match entries in the analyze-source `project_paths` array. When absent, a single `target_ref` (or auto-detected ref) applies to all paths. Mutually exclusive with `target_ref`. |
|
|
32
32
|
| `source_authority` | string | `official` / `community` / `internal` | Default `community`. Set to `official` only when the skill creator is the library maintainer. Forced to `community` when `source_type: "docs-only"`. |
|
|
33
33
|
| `source_ref` | string | Git ref (tag/branch/HEAD) | Resolved git ref used for source access. Set automatically during tag resolution — do not set manually. |
|
|
34
|
+
| `scope.tier_a_include` | array | Glob patterns (min 1, non-empty) | Optional narrower tier-A include list for a curated-subset skill — stratified monorepo packages, single-crate `specific-modules` scopes, and multi-entry exports-map packages. When present, `skf-test-skill` re-derives the coverage denominator from this list instead of the coarse `scope.include`. **It also exempts the brief from the denominator deflation guard**, so supply it only when it genuinely narrows the surface — never as an empty list, and never as a way to widen. See `skf-test-skill/references/source-access-protocol.md`. |
|
|
34
35
|
|
|
35
36
|
When `source_type: "docs-only"`:
|
|
36
37
|
- `source_repo` becomes optional (set to doc site URL for reference)
|
|
@@ -59,7 +59,7 @@ For each qualifying unit, prepare a recommendation card:
|
|
|
59
59
|
- name: {suggested kebab-case name}
|
|
60
60
|
- scope.type: {full-library / specific-modules / public-api / component-library / reference-app / docs-only}
|
|
61
61
|
- scope.include: {suggested glob patterns}
|
|
62
|
-
- description: {suggested 1-3 sentence description}
|
|
62
|
+
- description: {suggested 1-3 sentence description, containing a literal `Use when` clause}
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
### 3. Present Recommendations
|
|
@@ -143,7 +143,7 @@ Skip this section. Temporal context requires Deep tier.
|
|
|
143
143
|
|
|
144
144
|
For each export in the skill baseline that was NOT found at its recorded file path during re-extraction (potential "deleted" export):
|
|
145
145
|
|
|
146
|
-
1. Run `ccc_bridge.search("{export_name}", source_root, top_k=5)` — **Tool resolution:** Use `/ccc` skill search (Claude Code), ccc MCP server (Cursor), or `ccc search "{export_name}"
|
|
146
|
+
1. Run `ccc_bridge.search("{export_name}", source_root, top_k=5)` — **Tool resolution:** Use `/ccc` skill search (Claude Code), ccc MCP server (Cursor), or `cd {source_root} && ccc search --limit 5 "{export_name}"` (CLI) — to find candidate current locations. `ccc search` reads the index in the current working directory and has no project-selector flag (`--path` is a file-path glob filter *within* the index, and the result cap is `--limit`, not `--top`) — see `knowledge/ccc-bridge.md`.
|
|
147
147
|
2. If CCC returns files containing the export name:
|
|
148
148
|
- Run ast-grep verification on each candidate file
|
|
149
149
|
- If verified at a new location: reclassify from "deleted" to "moved" with the new file:line reference
|
|
@@ -6,9 +6,9 @@ Loaded by step 1 §7b only. The five examples below show the *range* of acceptab
|
|
|
6
6
|
|
|
7
7
|
> Render Markdown to HTML using the marked library. Use when the user pastes raw Markdown and wants formatted output, or asks how to convert MD files in a build pipeline.
|
|
8
8
|
|
|
9
|
-
> Stripe API client for Node.js — payment intents, subscriptions, customer portal, webhooks.
|
|
9
|
+
> Stripe API client for Node.js — payment intents, subscriptions, customer portal, webhooks. Use when the task involves Stripe-managed payments, subscription billing, or webhook event handling.
|
|
10
10
|
|
|
11
|
-
> Charts and visualizations powered by D3.js.
|
|
11
|
+
> Charts and visualizations powered by D3.js. Use when the user asks to plot data, build interactive graphs, or wants bare D3 control instead of a React-charts abstraction.
|
|
12
12
|
|
|
13
13
|
> Lint Python code with Ruff. Use when the user wants to add or configure Ruff in a Python project, debug rule selectors, or understand why a specific check fired.
|
|
14
14
|
|
|
@@ -16,4 +16,6 @@ Loaded by step 1 §7b only. The five examples below show the *range* of acceptab
|
|
|
16
16
|
|
|
17
17
|
## Notes on Voice
|
|
18
18
|
|
|
19
|
-
Each example leads differently (verb / noun / "Charts and..." / verb / noun-phrase)
|
|
19
|
+
Each example leads differently (verb / noun / "Charts and..." / verb / noun-phrase) rather than copy-pasting a single template. Compose in that spirit using the gathered material — the target repo, the user's intent, the version if set, and any scope hints — but **do not template-stamp**.
|
|
20
|
+
|
|
21
|
+
The **lead** is where the voice varies; the **trigger clause** is fixed. Every description must contain a literal `Use when` clause somewhere, because validators test for that exact phrase — `skill-check`'s `description.use_when_phrase` rule scores a description below 100 without it. Phrasings like "Triggers on…" or "Reach for this when…" read well but do not satisfy it on their own, so keep them for additional text rather than as the only trigger.
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
| source_repo | string | GitHub URL or local path | Repository or project root (optional when `source_type: "docs-only"`) |
|
|
10
10
|
| language | string | Recognized language | Primary programming language |
|
|
11
11
|
| scope | object | See Scope Object below | Boundary definition |
|
|
12
|
-
| description | string | 1-3 sentences | What the skill covers
|
|
12
|
+
| description | string | 1-3 sentences | What the skill covers. Must contain a literal `Use when` clause — validators test for that exact phrase. |
|
|
13
13
|
| forge_tier | string | `Quick` / `Forge` / `Forge+` / `Deep` | Inherited from forge-tier.yaml (Title Case) |
|
|
14
14
|
| created | string | ISO date `YYYY-MM-DD` | Generation date |
|
|
15
15
|
| created_by | string | user_name from config | Who generated the brief |
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
| `target_ref` | string | Git ref (tag or branch) | Optional. Explicit git ref used verbatim as the resolved `source_ref`, bypassing version-to-tag matching. Escape hatch for monorepo crate tags whose prefix differs from the skill name (e.g. tag `livekit/v0.7.42` for skill `livekit-rust`). Remote sources only. |
|
|
27
27
|
| `source_authority` | string | `official` / `community` / `internal` | Default `community`. Set to `official` only when the skill creator is the library maintainer. Forced to `community` when `source_type: "docs-only"`. |
|
|
28
28
|
| `source_ref` | string | Git ref (tag/branch/HEAD) | Resolved git ref used for source access. Set automatically during tag resolution — do not set manually. |
|
|
29
|
-
| `scope.tier_a_include` | array | Glob patterns
|
|
29
|
+
| `scope.tier_a_include` | array | Glob patterns (min 1, non-empty) | Optional. Narrower tier-A include list for a curated-subset skill — stratified monorepo packages, single-crate `specific-modules` scopes, and multi-entry exports-map packages. When present, `skf-test-skill` re-derives the coverage denominator from this list instead of the coarse `scope.include`, so the denominator reflects the authoring surface rather than incidentally-matched internal infrastructure. **It also exempts the brief from the denominator deflation guard**, so supply it only when it genuinely narrows the surface — never as an empty list, and never as a way to widen (listing files outside `scope.include` inverts its meaning). See `skf-test-skill/references/source-access-protocol.md` stratified-scope resolution. |
|
|
30
30
|
|
|
31
31
|
When `source_type: "docs-only"`:
|
|
32
32
|
- `source_repo` becomes optional (set to doc site URL for reference)
|
|
@@ -206,9 +206,9 @@ Fall back to ad-hoc inspection — `Gemfile` / `*.csproj` / `*.sln` / `Package.s
|
|
|
206
206
|
If `tools.ccc` is true in forge-tier.yaml, supplement the module listing with a semantic discovery pass:
|
|
207
207
|
|
|
208
208
|
**CCC Semantic Discovery:**
|
|
209
|
-
- **Claude Code:** Use `/ccc search "{repo_name} public API exports modules" {source_path}`
|
|
209
|
+
- **Claude Code:** Use `/ccc search "{repo_name} public API exports modules"` from `{source_path}` — the query is variadic, so a trailing path is swallowed into the search string rather than selecting a project
|
|
210
210
|
- **Cursor:** Use `ccc` MCP server `search` tool with query `"{repo_name} public API exports modules"` and path `{source_path}`
|
|
211
|
-
- **CLI fallback:** `ccc search "{repo_name} public API exports modules"
|
|
211
|
+
- **CLI fallback:** `cd {source_path} && ccc search --limit 10 "{repo_name} public API exports modules"` — `ccc search` reads the index in the current working directory and has no project-selector flag (`--path` is a file-path glob filter *within* the index)
|
|
212
212
|
|
|
213
213
|
See `knowledge/tool-resolution.md` for full bridge-to-tool mapping.
|
|
214
214
|
|
|
@@ -352,7 +352,9 @@ The schema's `description` field is 1-3 sentences and surfaces in skill registri
|
|
|
352
352
|
|
|
353
353
|
Compose a candidate 1-3 sentence description from the gathered material. **Write like a human library maintainer would** — what does an agent get from this skill, and when should it route here? Two facts must come through (what the skill is, when to use it); everything else is voice. Resist filling in the same skeleton every time.
|
|
354
354
|
|
|
355
|
-
Load `{descriptionVoiceExamplesPath}` for the five voice examples (range of acceptable leads
|
|
355
|
+
Load `{descriptionVoiceExamplesPath}` for the five voice examples (range of acceptable leads and structures) and the "do not template-stamp" guidance, then compose in that spirit. The asset documents what "in that spirit" means; the gathered material to draw on is the target repo, the user's intent, the version if set, and any scope hints.
|
|
356
|
+
|
|
357
|
+
**Whatever lead you choose, the description must contain a literal `Use when` clause somewhere** — validators test for that exact phrase, so alternatives like "Triggers on…" or "Reach for this when…" do not satisfy it on their own. The clause need not lead: a descriptive opener followed by `Use when …` is both good voice and validator-clean.
|
|
356
358
|
|
|
357
359
|
Present:
|
|
358
360
|
|
|
@@ -360,7 +362,7 @@ Present:
|
|
|
360
362
|
|
|
361
363
|
> {synthesized description}
|
|
362
364
|
|
|
363
|
-
This is the text agents read when deciding whether to route to your skill — it sits in the registry row alongside dozens of other skills.
|
|
365
|
+
This is the text agents read when deciding whether to route to your skill — it sits in the registry row alongside dozens of other skills. A specific 'use when…' trigger helps agents match real user requests; generic descriptions blend in and get skipped. Edit, replace, or accept as-is."
|
|
364
366
|
|
|
365
367
|
Wait for user confirmation or alternative.
|
|
366
368
|
|
|
@@ -81,7 +81,9 @@ For each ready Tier A skill:
|
|
|
81
81
|
- `{{directive_content}}` — raw content of the file at `campaign.directive_path`, or "No directive configured" if unset/missing.
|
|
82
82
|
Present the completed kickoff message as the context for the skill's pipeline run.
|
|
83
83
|
- **AN → BS → CS → TS** — standard forge pipeline for this skill. When BS (Brief Synthesis) produces the skill's brief, set `skills[current].brief_path` to the brief path from the BS result envelope so the field the schema declares is populated and available for resume and reporting.
|
|
84
|
-
- **Doc-rot check** —
|
|
84
|
+
- **Doc-rot check** — **record** what CS step 5c already produced; do not re-derive it. Because the pipeline body runs inline (see the §5 note above), that step's context is directly readable here: `corrections_added`, `correction_matches` (the enriched match records — `source`, `pattern`, `category`, `context_line`, `affected`), `corrections_deduped` and `corrections_capped`. Append one `[doc-rot]`-prefixed entry per `correction_matches` record to the skill's `workarounds_applied` array so they survive state write and are available for §6 propagation. When step 5c took its skip branch (`doc_rot_triggered: false`, `corrections_added: 0`), there is nothing to record — append nothing and move on.
|
|
85
|
+
|
|
86
|
+
**Do not hand-grep the feeder artifacts here, and do not re-run the scan.** `skf-create-skill/references/step-doc-rot.md` §2 requires the scan to run in its helper precisely so identical feeders yield identical matches; a second pass in this loop is free to disagree with the corrections CS actually wrote. It would also be self-contaminating: step 5c has already written `## CORRECTION` blocks into the compiled SKILL.md, and the helper's exclusion windows cover only that file's frontmatter and its Migration & Deprecation Warnings section — so re-scanning it would re-match the blocks 5c itself authored and record them as fresh findings.
|
|
85
87
|
3. **Record results:**
|
|
86
88
|
- On success: set `status` to `"completed"`, set `completed_at` to current ISO-8601 with timezone, record `quality_score`. Backup and write state.
|
|
87
89
|
- On failure: set `status` to `"failed"`. Log the failure reason (the sub-skill's `halt_reason`/exit code, or "unparseable result envelope") to the decision log so `campaign status` and the report surface *why* a skill failed without the operator opening sub-skill logs. Backup and write state. Downstream skills whose `depends_on` does NOT include the failed skill continue processing normally; those that DO depend on it are blocked at §4's dependency gate.
|