bigpowers 2.73.9 → 2.74.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/.continue/rules/context7-mcp.md +0 -0
- package/.continue/rules/diagnose-stall.md +0 -0
- package/.kilocode/rules/context7-mcp.md +0 -0
- package/.kilocode/rules/diagnose-stall.md +0 -0
- package/.pi/package.json +2 -2
- package/.pi/prompts/audit-code.md +17 -13
- package/.pi/prompts/change-request.md +3 -1
- package/.pi/prompts/compose-workflow.md +15 -0
- package/.pi/prompts/context7-mcp.md +54 -0
- package/.pi/prompts/craft-skill.md +28 -0
- package/.pi/prompts/deepen-architecture.md +20 -0
- package/.pi/prompts/delegate-task.md +14 -0
- package/.pi/prompts/deploy.md +8 -0
- package/.pi/prompts/develop-tdd.md +15 -4
- package/.pi/prompts/diagnose-stall.md +53 -0
- package/.pi/prompts/dispatch-agents.md +49 -19
- package/.pi/prompts/gate-trace.md +9 -1
- package/.pi/prompts/guard-git.md +14 -0
- package/.pi/prompts/migrate-spec.md +12 -0
- package/.pi/prompts/plan-work.md +62 -2
- package/.pi/prompts/publish-package.md +11 -0
- package/.pi/prompts/release-branch.md +39 -43
- package/.pi/prompts/request-review.md +57 -17
- package/.pi/prompts/run-benchmark.md +3 -3
- package/.pi/prompts/run-evals.md +26 -6
- package/.pi/prompts/security-review.md +56 -8
- package/.pi/prompts/seed-conventions.md +61 -0
- package/.pi/prompts/session-state.md +5 -10
- package/.pi/prompts/slice-tasks.md +3 -0
- package/.pi/prompts/smoke-test.md +11 -0
- package/.pi/prompts/stocktake-skills.md +2 -1
- package/.pi/prompts/validate-contracts.md +12 -0
- package/.pi/prompts/validate-fix.md +2 -0
- package/.pi/prompts/verify-work.md +31 -20
- package/.pi/prompts/wire-ci.md +11 -0
- package/.pi/prompts/write-document.md +10 -0
- package/.pi/skills/audit-code/SKILL.md +17 -13
- package/.pi/skills/change-request/SKILL.md +3 -1
- package/.pi/skills/compose-workflow/SKILL.md +15 -0
- package/.pi/skills/context7-mcp/SKILL.md +56 -0
- package/.pi/skills/craft-skill/SKILL.md +28 -0
- package/.pi/skills/deepen-architecture/SKILL.md +20 -0
- package/.pi/skills/delegate-task/SKILL.md +14 -0
- package/.pi/skills/deploy/SKILL.md +8 -0
- package/.pi/skills/develop-tdd/SKILL.md +15 -4
- package/.pi/skills/diagnose-stall/SKILL.md +55 -0
- package/.pi/skills/dispatch-agents/SKILL.md +49 -19
- package/.pi/skills/gate-trace/SKILL.md +9 -1
- package/.pi/skills/guard-git/SKILL.md +14 -0
- package/.pi/skills/migrate-spec/SKILL.md +12 -0
- package/.pi/skills/plan-work/SKILL.md +62 -2
- package/.pi/skills/publish-package/SKILL.md +11 -0
- package/.pi/skills/release-branch/SKILL.md +39 -43
- package/.pi/skills/request-review/SKILL.md +57 -17
- package/.pi/skills/run-benchmark/SKILL.md +3 -3
- package/.pi/skills/run-evals/SKILL.md +26 -6
- package/.pi/skills/security-review/SKILL.md +56 -8
- package/.pi/skills/seed-conventions/SKILL.md +61 -0
- package/.pi/skills/session-state/SKILL.md +5 -10
- package/.pi/skills/slice-tasks/SKILL.md +3 -0
- package/.pi/skills/smoke-test/SKILL.md +11 -0
- package/.pi/skills/stocktake-skills/SKILL.md +2 -1
- package/.pi/skills/validate-contracts/SKILL.md +12 -0
- package/.pi/skills/validate-fix/SKILL.md +2 -0
- package/.pi/skills/verify-work/SKILL.md +31 -20
- package/.pi/skills/wire-ci/SKILL.md +11 -0
- package/.pi/skills/write-document/SKILL.md +10 -0
- package/CHANGELOG.md +7 -0
- package/CLAUDE.md +59 -1
- package/CONVENTIONS.md +76 -0
- package/README.md +1 -1
- package/SKILL-INDEX.md +1 -1
- package/package.json +1 -1
- package/scripts/bp-churn-rank.sh +49 -0
- package/scripts/check-import-boundaries.sh +64 -0
- package/scripts/compile-rule-matrix.sh +114 -0
- package/scripts/hooks/rtk-rewrite.sh +11 -0
- package/scripts/hooks/token-mgmt-pre-tool-use.sh +53 -0
- package/scripts/install.sh +6 -0
- package/scripts/lib/completeness-critic.sh +62 -0
- package/scripts/lib/doc-fetch-cache.sh +71 -0
- package/scripts/lib/golden-suite-gates.sh +3 -1
- package/scripts/lib/parallel-review-worktrees.sh +34 -0
- package/scripts/lib/plan-consistency-check.sh +88 -0
- package/scripts/lib/run-benchmark.py +210 -0
- package/scripts/lib/sync-context-engineering-ref.sh +19 -0
- package/scripts/references-manifest.yaml +19 -0
- package/scripts/run-benchmark.sh +6 -0
- package/scripts/sync-references.sh +25 -0
- package/scripts/validate-skill-catalog.sh +147 -0
- package/scripts/validate-skill-description.sh +70 -0
- package/skills/audit-code/SKILL.md +19 -13
- package/skills/change-request/SKILL.md +3 -1
- package/skills/compose-workflow/SKILL.md +15 -0
- package/skills/context7-mcp/SKILL.md +57 -0
- package/skills/craft-skill/SKILL.md +29 -0
- package/skills/deepen-architecture/SKILL.md +22 -0
- package/skills/delegate-task/SKILL.md +14 -0
- package/skills/deploy/SKILL.md +9 -0
- package/skills/develop-tdd/SKILL.md +18 -4
- package/skills/diagnose-stall/SKILL.md +56 -0
- package/skills/dispatch-agents/SKILL.md +51 -19
- package/skills/gate-trace/SKILL.md +11 -1
- package/skills/guard-git/REFERENCE.md +14 -0
- package/skills/migrate-spec/REFERENCE.md +12 -0
- package/skills/plan-work/REFERENCE.md +29 -2
- package/skills/plan-work/SKILL.md +38 -0
- package/skills/publish-package/REFERENCE.md +11 -0
- package/skills/release-branch/REFERENCE.md +14 -0
- package/skills/release-branch/SKILL.md +28 -43
- package/skills/request-review/SKILL.md +59 -17
- package/skills/run-benchmark/SKILL.md +4 -4
- package/skills/run-evals/REFERENCE.md +14 -4
- package/skills/run-evals/SKILL.md +13 -2
- package/skills/security-review/REFERENCE-false-positives.md +9 -8
- package/skills/security-review/REFERENCE-vuln-categories.md +4 -0
- package/skills/security-review/SKILL.md +45 -0
- package/skills/security-review/fixtures/CWE-79-xss-negative.js +6 -0
- package/skills/security-review/fixtures/CWE-79-xss-positive.js +6 -0
- package/skills/security-review/fixtures/CWE-89-sqli-negative.py +8 -0
- package/skills/security-review/fixtures/CWE-89-sqli-positive.py +9 -0
- package/skills/seed-conventions/REFERENCE.md +40 -0
- package/skills/seed-conventions/SKILL.md +22 -0
- package/skills/session-state/SKILL.md +5 -10
- package/skills/slice-tasks/SKILL.md +3 -0
- package/skills/smoke-test/REFERENCE.md +11 -0
- package/skills/stocktake-skills/SKILL.md +3 -1
- package/skills/validate-contracts/REFERENCE.md +12 -0
- package/skills/validate-fix/SKILL.md +3 -0
- package/skills/verify-work/REFERENCE.md +17 -0
- package/skills/verify-work/SKILL.md +18 -20
- package/skills/wire-ci/REFERENCE.md +11 -0
- package/skills/write-document/REFERENCE.md +10 -0
- package/skills-lock.json +32 -22
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# story: e45s01
|
|
3
|
+
"""Run skill benchmarks from specs/benchmarks/ with train/validation split and delta grading."""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
from datetime import date
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import yaml
|
|
14
|
+
|
|
15
|
+
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
16
|
+
BENCH_DIR = REPO_ROOT / "specs" / "benchmarks"
|
|
17
|
+
REPORTS_DIR = BENCH_DIR / "reports"
|
|
18
|
+
DEFAULT_RUNS = 3
|
|
19
|
+
GRADER_TIMEOUT = 15
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def load_definition(skill: str) -> dict:
|
|
23
|
+
path = BENCH_DIR / f"{skill}.yaml"
|
|
24
|
+
if not path.exists():
|
|
25
|
+
print(f"ERROR: no benchmark definition at {path}", file=sys.stderr)
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
with path.open() as f:
|
|
28
|
+
return yaml.safe_load(f)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def list_skills() -> list[str]:
|
|
32
|
+
return sorted(p.stem for p in BENCH_DIR.glob("*.yaml") if p.stem not in ("axes",))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def run_code_grader(command: str, cwd: Path) -> bool:
|
|
36
|
+
try:
|
|
37
|
+
result = subprocess.run(
|
|
38
|
+
["bash", "-c", command],
|
|
39
|
+
cwd=cwd,
|
|
40
|
+
capture_output=True,
|
|
41
|
+
text=True,
|
|
42
|
+
timeout=GRADER_TIMEOUT,
|
|
43
|
+
)
|
|
44
|
+
out = (result.stdout + result.stderr).strip()
|
|
45
|
+
if "PASS" in out.upper() and "FAIL" not in out.upper():
|
|
46
|
+
return True
|
|
47
|
+
return result.returncode == 0
|
|
48
|
+
except (subprocess.TimeoutExpired, OSError):
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def scenario_pass_rate(scenario: dict, runs: int, with_skill: bool) -> tuple[float, str]:
|
|
53
|
+
grader = scenario.get("grader", {})
|
|
54
|
+
gtype = grader.get("type", "code")
|
|
55
|
+
if gtype == "rubric":
|
|
56
|
+
return (-1.0, "pending_agent_eval")
|
|
57
|
+
if gtype != "code":
|
|
58
|
+
return (-1.0, "unsupported_grader")
|
|
59
|
+
command = grader.get("command", "")
|
|
60
|
+
if not command:
|
|
61
|
+
return (-1.0, "missing_command")
|
|
62
|
+
if not with_skill:
|
|
63
|
+
command = f"test -f /dev/null" # bare agent: code graders cannot pass without skill context
|
|
64
|
+
passes = sum(1 for _ in range(runs) if run_code_grader(command, REPO_ROOT))
|
|
65
|
+
return (passes / runs, "code")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def pass_at_k(scenarios: list[dict], runs: int, with_skill: bool) -> float:
|
|
69
|
+
total_weight = 0.0
|
|
70
|
+
weighted = 0.0
|
|
71
|
+
for sc in scenarios:
|
|
72
|
+
rate, status = scenario_pass_rate(sc, runs, with_skill)
|
|
73
|
+
if status == "pending_agent_eval":
|
|
74
|
+
continue
|
|
75
|
+
weight = float(sc.get("weight", 1.0))
|
|
76
|
+
total_weight += weight
|
|
77
|
+
weighted += weight * max(rate, 0.0)
|
|
78
|
+
if total_weight == 0:
|
|
79
|
+
return 0.0
|
|
80
|
+
return round(weighted / total_weight, 2)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def partition_scenarios(scenarios: list[dict]) -> tuple[list[dict], list[dict]]:
|
|
84
|
+
train, validation = [], []
|
|
85
|
+
for sc in scenarios:
|
|
86
|
+
split = sc.get("split", "validation")
|
|
87
|
+
if split == "train":
|
|
88
|
+
train.append(sc)
|
|
89
|
+
else:
|
|
90
|
+
validation.append(sc)
|
|
91
|
+
return train, validation
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def build_split_report(scenarios: list[dict], runs: int) -> dict:
|
|
95
|
+
if not scenarios:
|
|
96
|
+
return {"with_skill": 0.0, "without_skill": 0.0, "delta": 0.0, "scenarios": []}
|
|
97
|
+
with_score = pass_at_k(scenarios, runs, with_skill=True)
|
|
98
|
+
without_score = pass_at_k(scenarios, runs, with_skill=False)
|
|
99
|
+
return {
|
|
100
|
+
"with_skill": with_score,
|
|
101
|
+
"without_skill": without_score,
|
|
102
|
+
"delta": round(with_score - without_score, 2),
|
|
103
|
+
"scenarios": [sc["id"] for sc in scenarios],
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def scenario_details(scenarios: list[dict], runs: int) -> list[dict]:
|
|
108
|
+
rows = []
|
|
109
|
+
for sc in scenarios:
|
|
110
|
+
with_rate, status = scenario_pass_rate(sc, runs, with_skill=True)
|
|
111
|
+
without_rate, _ = scenario_pass_rate(sc, runs, with_skill=False)
|
|
112
|
+
if status == "pending_agent_eval":
|
|
113
|
+
rows.append(
|
|
114
|
+
{
|
|
115
|
+
"id": sc["id"],
|
|
116
|
+
"split": sc.get("split", "validation"),
|
|
117
|
+
"status": "pending_agent_eval",
|
|
118
|
+
"weight": sc.get("weight", 1.0),
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
continue
|
|
122
|
+
rows.append(
|
|
123
|
+
{
|
|
124
|
+
"id": sc["id"],
|
|
125
|
+
"split": sc.get("split", "validation"),
|
|
126
|
+
"with_pass_rate": round(max(with_rate, 0.0), 2),
|
|
127
|
+
"without_pass_rate": round(max(without_rate, 0.0), 2),
|
|
128
|
+
"delta": round(max(with_rate, 0.0) - max(without_rate, 0.0), 2),
|
|
129
|
+
"weight": sc.get("weight", 1.0),
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
return rows
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def run_benchmark(skill: str, baseline: bool = False) -> dict:
|
|
136
|
+
definition = load_definition(skill)
|
|
137
|
+
runs = int(definition.get("runs", DEFAULT_RUNS))
|
|
138
|
+
scenarios = definition.get("scenarios", [])
|
|
139
|
+
train_sc, val_sc = partition_scenarios(scenarios)
|
|
140
|
+
today = date.today().isoformat()
|
|
141
|
+
report = {
|
|
142
|
+
"skill": skill,
|
|
143
|
+
"run_date": today,
|
|
144
|
+
"runs_per_scenario": runs,
|
|
145
|
+
"train": build_split_report(train_sc, runs),
|
|
146
|
+
"validation": build_split_report(val_sc, runs),
|
|
147
|
+
"scenarios": scenario_details(scenarios, runs),
|
|
148
|
+
}
|
|
149
|
+
REPORTS_DIR.mkdir(parents=True, exist_ok=True)
|
|
150
|
+
yaml_path = REPORTS_DIR / f"BENCHMARK-{skill}-{today}.yaml"
|
|
151
|
+
json_path = REPORTS_DIR / f"benchmark-{skill}.json"
|
|
152
|
+
with yaml_path.open("w") as f:
|
|
153
|
+
yaml.dump(
|
|
154
|
+
{
|
|
155
|
+
"skill": skill,
|
|
156
|
+
"run_date": today,
|
|
157
|
+
"runs_per_scenario": runs,
|
|
158
|
+
"train": {
|
|
159
|
+
"pass_at_k_with": report["train"]["with_skill"],
|
|
160
|
+
"pass_at_k_without": report["train"]["without_skill"],
|
|
161
|
+
"delta": report["train"]["delta"],
|
|
162
|
+
},
|
|
163
|
+
"validation": {
|
|
164
|
+
"pass_at_k_with": report["validation"]["with_skill"],
|
|
165
|
+
"pass_at_k_without": report["validation"]["without_skill"],
|
|
166
|
+
"delta": report["validation"]["delta"],
|
|
167
|
+
},
|
|
168
|
+
"scenarios": report["scenarios"],
|
|
169
|
+
},
|
|
170
|
+
f,
|
|
171
|
+
default_flow_style=False,
|
|
172
|
+
)
|
|
173
|
+
with json_path.open("w") as f:
|
|
174
|
+
json.dump(report, f, separators=(",", ":"))
|
|
175
|
+
print(f"Wrote {yaml_path}")
|
|
176
|
+
print(f"Wrote {json_path}")
|
|
177
|
+
if baseline:
|
|
178
|
+
import shutil
|
|
179
|
+
|
|
180
|
+
shutil.copy(yaml_path, REPORTS_DIR / f"BASELINE-{skill}.yaml")
|
|
181
|
+
shutil.copy(json_path, REPORTS_DIR / f"baseline-{skill}.json")
|
|
182
|
+
print(f"Pinned baseline for {skill}")
|
|
183
|
+
val_delta = report["validation"]["delta"]
|
|
184
|
+
if val_delta < 0:
|
|
185
|
+
print(f"REGRESSION: validation Δ {val_delta} — do NOT ship")
|
|
186
|
+
elif val_delta < 0.05:
|
|
187
|
+
print(f"WARN: validation Δ {val_delta} — marginal contribution")
|
|
188
|
+
else:
|
|
189
|
+
print(f"OK: validation Δ {val_delta}")
|
|
190
|
+
return report
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def main() -> None:
|
|
194
|
+
parser = argparse.ArgumentParser(description="Run skill quality benchmarks")
|
|
195
|
+
parser.add_argument("skill", nargs="?", help="Skill name to benchmark")
|
|
196
|
+
parser.add_argument("--all", action="store_true", help="Benchmark all skills with definitions")
|
|
197
|
+
parser.add_argument("--baseline", action="store_true", help="Pin results as baseline")
|
|
198
|
+
args = parser.parse_args()
|
|
199
|
+
if args.all:
|
|
200
|
+
for skill in list_skills():
|
|
201
|
+
run_benchmark(skill, baseline=args.baseline)
|
|
202
|
+
elif args.skill:
|
|
203
|
+
run_benchmark(args.skill, baseline=args.baseline)
|
|
204
|
+
else:
|
|
205
|
+
parser.print_help()
|
|
206
|
+
sys.exit(1)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
if __name__ == "__main__":
|
|
210
|
+
main()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# story: e45s10
|
|
3
|
+
# Refresh context-engineering.md sync stamp from SSOT (CLAUDE.md + PRINCIPLES.md).
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
7
|
+
TARGET="$REPO_ROOT/docs/references/context-engineering.md"
|
|
8
|
+
STAMP="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
9
|
+
|
|
10
|
+
[[ -f "$TARGET" ]] || exit 0
|
|
11
|
+
|
|
12
|
+
if grep -q 'Last synced:' "$TARGET"; then
|
|
13
|
+
sed -i.bak "s/\*\*Last synced:\*\*.*/\*\*Last synced:\*\* ${STAMP} (from CLAUDE.md + PRINCIPLES.md)/" "$TARGET"
|
|
14
|
+
rm -f "${TARGET}.bak"
|
|
15
|
+
else
|
|
16
|
+
printf '\n**Last synced:** %s (from CLAUDE.md + PRINCIPLES.md)\n' "$STAMP" >> "$TARGET"
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
echo "sync-context-engineering-ref: stamped $TARGET"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# story: e45s10
|
|
2
|
+
# SSOT → docs/references distillation manifest.
|
|
3
|
+
# Each entry: source globs/files regenerate target via generator script.
|
|
4
|
+
version: 1
|
|
5
|
+
generated_at_field: docs/references/.sync-stamp
|
|
6
|
+
|
|
7
|
+
entries:
|
|
8
|
+
- id: model-profiles-catalog
|
|
9
|
+
sources:
|
|
10
|
+
- skills/*/SKILL.md
|
|
11
|
+
target: docs/references/model-profiles.md
|
|
12
|
+
generator: scripts/generate-reference-tables.sh
|
|
13
|
+
|
|
14
|
+
- id: context-engineering
|
|
15
|
+
sources:
|
|
16
|
+
- CLAUDE.md
|
|
17
|
+
- docs/PRINCIPLES.md
|
|
18
|
+
target: docs/references/context-engineering.md
|
|
19
|
+
generator: scripts/lib/sync-context-engineering-ref.sh
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# story: e45s01
|
|
3
|
+
# run-benchmark.sh — N-run with/without-skill delta grading + train/validation split.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
source "$(dirname "${BASH_SOURCE[0]}")/lib/python-env.sh"
|
|
6
|
+
exec "$PYTHON" "$(dirname "${BASH_SOURCE[0]}")/lib/run-benchmark.py" "$@"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# story: e45s10
|
|
3
|
+
# Regenerate docs/references from SSOT sources (see scripts/references-manifest.yaml).
|
|
4
|
+
# Schedule: weekly via .github/workflows/sync-references.yml; also safe to run locally.
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
8
|
+
MANIFEST="$REPO_ROOT/scripts/references-manifest.yaml"
|
|
9
|
+
STAMP_FILE="$REPO_ROOT/docs/references/.sync-stamp"
|
|
10
|
+
|
|
11
|
+
cd "$REPO_ROOT"
|
|
12
|
+
[[ -f "$MANIFEST" ]] || { echo "sync-references: missing manifest" >&2; exit 1; }
|
|
13
|
+
|
|
14
|
+
run_generator() {
|
|
15
|
+
local gen="$1"
|
|
16
|
+
[[ -x "$REPO_ROOT/$gen" || -f "$REPO_ROOT/$gen" ]] || { echo "sync-references: generator not found: $gen" >&2; exit 1; }
|
|
17
|
+
bash "$REPO_ROOT/$gen"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
# model-profiles catalog
|
|
21
|
+
run_generator scripts/generate-reference-tables.sh
|
|
22
|
+
run_generator scripts/lib/sync-context-engineering-ref.sh
|
|
23
|
+
|
|
24
|
+
date -u +%Y-%m-%dT%H:%M:%SZ > "$STAMP_FILE"
|
|
25
|
+
echo "sync-references: OK (stamp $(cat "$STAMP_FILE"))"
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# story: e45s12
|
|
3
|
+
# validate-skill-catalog.sh — code-enforced skill catalog hygiene (Hermes Curator pattern)
|
|
4
|
+
# Exit 0 when catalog passes; 1 on violations. --archive lists zero-usage skills for archiving.
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
9
|
+
cd "$REPO_ROOT"
|
|
10
|
+
|
|
11
|
+
SKILLS_ROOT="$REPO_ROOT/skills"
|
|
12
|
+
[[ -d "$SKILLS_ROOT" ]] || SKILLS_ROOT="$REPO_ROOT"
|
|
13
|
+
|
|
14
|
+
ARCHIVE_MODE=false
|
|
15
|
+
STRICT=false
|
|
16
|
+
TARGET_SKILL=""
|
|
17
|
+
VIOLATIONS=0
|
|
18
|
+
WARNINGS=0
|
|
19
|
+
|
|
20
|
+
# Documented naming exceptions (craft-skill REFERENCE.md)
|
|
21
|
+
VERB_NOUN_EXCEPTIONS="grill-me grill-with-docs context7-mcp using-bigpowers deploy"
|
|
22
|
+
|
|
23
|
+
usage() {
|
|
24
|
+
echo "Usage: bash scripts/validate-skill-catalog.sh [--archive] [--strict] [--skill <name>]"
|
|
25
|
+
exit 2
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
while [[ $# -gt 0 ]]; do
|
|
29
|
+
case "$1" in
|
|
30
|
+
--archive) ARCHIVE_MODE=true; shift ;;
|
|
31
|
+
--strict) STRICT=true; shift ;;
|
|
32
|
+
--skill) TARGET_SKILL="${2:-}"; shift 2 ;;
|
|
33
|
+
--skill=*) TARGET_SKILL="${1#*=}"; shift ;;
|
|
34
|
+
-h|--help) usage ;;
|
|
35
|
+
*) echo "Unknown flag: $1" >&2; usage ;;
|
|
36
|
+
esac
|
|
37
|
+
done
|
|
38
|
+
|
|
39
|
+
report_catalog_violation() { echo "FAIL: $1" >&2; VIOLATIONS=$((VIOLATIONS + 1)); }
|
|
40
|
+
report_catalog_warning() { echo "WARN: $1" >&2; WARNINGS=$((WARNINGS + 1)); }
|
|
41
|
+
|
|
42
|
+
check_verb_noun() {
|
|
43
|
+
local name="$1"
|
|
44
|
+
if echo " $VERB_NOUN_EXCEPTIONS " | grep -q " $name "; then
|
|
45
|
+
return 0
|
|
46
|
+
fi
|
|
47
|
+
if [[ ! "$name" =~ ^[a-z][a-z0-9]*(-[a-z][a-z0-9]*)+$ ]]; then
|
|
48
|
+
report_catalog_violation "$name: not verb-noun kebab-case"
|
|
49
|
+
return 1
|
|
50
|
+
fi
|
|
51
|
+
local segments
|
|
52
|
+
segments=$(echo "$name" | tr '-' '\n' | wc -l | tr -d ' ')
|
|
53
|
+
if [[ "$segments" -gt 2 ]]; then
|
|
54
|
+
report_catalog_violation "$name: more than two hyphen segments (verb-noun only)"
|
|
55
|
+
fi
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
check_skill() {
|
|
59
|
+
local skill_md="$1"
|
|
60
|
+
local name
|
|
61
|
+
name=$(basename "$(dirname "$skill_md")")
|
|
62
|
+
|
|
63
|
+
check_verb_noun "$name" || true
|
|
64
|
+
|
|
65
|
+
if ! grep -q 'HARD GATE' "$skill_md" 2>/dev/null; then
|
|
66
|
+
if $STRICT; then report_catalog_violation "$name: missing HARD GATE block"; else report_catalog_warning "$name: missing HARD GATE block"; fi
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
local desc
|
|
70
|
+
desc=$(awk 'BEGIN{f=0} /^---$/{f++; next} f==1{print} f==2{exit}' "$skill_md" \
|
|
71
|
+
| grep -E '^description:' | head -1 | sed 's/^description: *//; s/^> //' | tr -d '"' || true)
|
|
72
|
+
if [[ -n "$desc" && ${#desc} -gt 1024 ]]; then
|
|
73
|
+
report_catalog_violation "$name: description ${#desc} chars (max 1024)"
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
local lines
|
|
77
|
+
lines=$(wc -l < "$skill_md" | tr -d ' ')
|
|
78
|
+
if [[ "$lines" -gt 300 ]]; then
|
|
79
|
+
report_catalog_warning "$name: SKILL.md $lines lines (target ≤300; split to REFERENCE.md)"
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
local cmd
|
|
83
|
+
cmd=$(grep -E '^(> )?→ verify:' "$skill_md" 2>/dev/null | tail -1 | sed 's/^> // ; s/^→ verify: *//' || true)
|
|
84
|
+
if [[ -z "$cmd" ]]; then
|
|
85
|
+
if $STRICT; then report_catalog_violation "$name: missing → verify: command"; else report_catalog_warning "$name: missing → verify: command"; fi
|
|
86
|
+
fi
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
echo "Validating skill catalog under $SKILLS_ROOT ..."
|
|
90
|
+
if [[ -n "$TARGET_SKILL" ]]; then
|
|
91
|
+
skill_md="$SKILLS_ROOT/$TARGET_SKILL/SKILL.md"
|
|
92
|
+
[[ -f "$skill_md" ]] || { echo "FAIL: $skill_md not found" >&2; exit 1; }
|
|
93
|
+
check_skill "$skill_md"
|
|
94
|
+
else
|
|
95
|
+
for skill_md in "$SKILLS_ROOT"/*/SKILL.md; do
|
|
96
|
+
[[ -f "$skill_md" ]] || continue
|
|
97
|
+
check_skill "$skill_md"
|
|
98
|
+
done
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
if $ARCHIVE_MODE; then
|
|
102
|
+
STATE="$REPO_ROOT/specs/state.yaml"
|
|
103
|
+
echo ""
|
|
104
|
+
echo "=== Archive candidates (zero calls in metrics.skill_timings) ==="
|
|
105
|
+
if [[ ! -f "$STATE" ]]; then
|
|
106
|
+
echo " (no specs/state.yaml — skip usage scan)"
|
|
107
|
+
else
|
|
108
|
+
python3 - "$STATE" "$SKILLS_ROOT" <<'PY'
|
|
109
|
+
import sys, glob, os
|
|
110
|
+
from pathlib import Path
|
|
111
|
+
|
|
112
|
+
state_path, skills_root = sys.argv[1], sys.argv[2]
|
|
113
|
+
text = Path(state_path).read_text(encoding="utf-8")
|
|
114
|
+
timings = set()
|
|
115
|
+
in_timings = False
|
|
116
|
+
for line in text.splitlines():
|
|
117
|
+
if line.strip() == "skill_timings:":
|
|
118
|
+
in_timings = True
|
|
119
|
+
continue
|
|
120
|
+
if in_timings:
|
|
121
|
+
if line.startswith(" ") and not line.startswith(" ") and line.rstrip().endswith(":"):
|
|
122
|
+
timings.add(line.strip().rstrip(":"))
|
|
123
|
+
elif line.strip() and not line.startswith(" "):
|
|
124
|
+
in_timings = False
|
|
125
|
+
|
|
126
|
+
skills = [os.path.basename(os.path.dirname(p)) for p in glob.glob(f"{skills_root}/*/SKILL.md")]
|
|
127
|
+
zero = sorted(s for s in skills if s not in timings)
|
|
128
|
+
if zero:
|
|
129
|
+
for s in zero:
|
|
130
|
+
print(f" ARCHIVE_CANDIDATE: {s} (0 calls — move to specs/epics/archive/ or delete)")
|
|
131
|
+
else:
|
|
132
|
+
print(" (no zero-usage skills)")
|
|
133
|
+
PY
|
|
134
|
+
fi
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
echo ""
|
|
138
|
+
if [[ "$VIOLATIONS" -gt 0 ]]; then
|
|
139
|
+
echo "CATALOG INVALID: $VIOLATIONS violation(s), $WARNINGS warning(s)"
|
|
140
|
+
exit 1
|
|
141
|
+
fi
|
|
142
|
+
if $STRICT && [[ "$WARNINGS" -gt 0 ]]; then
|
|
143
|
+
echo "CATALOG INVALID (strict): $WARNINGS warning(s)"
|
|
144
|
+
exit 1
|
|
145
|
+
fi
|
|
146
|
+
echo "CATALOG OK: $WARNINGS warning(s)"
|
|
147
|
+
exit 0
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# story: e45s02
|
|
3
|
+
# CSO description discipline — max 1024 chars, no workflow-summary leakage.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
7
|
+
SKILLS_ROOT="$REPO_ROOT/skills"
|
|
8
|
+
[[ -d "$SKILLS_ROOT" ]] || SKILLS_ROOT="$REPO_ROOT"
|
|
9
|
+
|
|
10
|
+
MAX_LEN=1024
|
|
11
|
+
ERRORS=0
|
|
12
|
+
|
|
13
|
+
audit_skill_description_cso() {
|
|
14
|
+
local path="$1"
|
|
15
|
+
local skill desc len
|
|
16
|
+
|
|
17
|
+
skill="$(basename "$(dirname "$path")")"
|
|
18
|
+
desc="$(
|
|
19
|
+
awk 'BEGIN{n=0} /^---$/{n++; next} n==1{print; next} n>=2{exit}' "$path" \
|
|
20
|
+
| grep -E '^description:' | head -1 | sed 's/^description:[[:space:]]*//' | sed 's/^"//;s/"$//'
|
|
21
|
+
)"
|
|
22
|
+
|
|
23
|
+
if [[ -z "$desc" ]]; then
|
|
24
|
+
echo "FAIL: $skill — missing description frontmatter"
|
|
25
|
+
ERRORS=$((ERRORS + 1))
|
|
26
|
+
return
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
len=${#desc}
|
|
30
|
+
if (( len > MAX_LEN )); then
|
|
31
|
+
echo "FAIL: $skill — description ${len} chars (max ${MAX_LEN})"
|
|
32
|
+
ERRORS=$((ERRORS + 1))
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# Workflow-summary leakage: numbered steps, phase chains, verify prose in description
|
|
36
|
+
local leak=0
|
|
37
|
+
if echo "$desc" | grep -qE '(^|[[:space:]])([0-9]+\.|Step [0-9]+|First,|Then,|Finally,|→ verify:|HARD GATE|Process:|Workflow:)'; then
|
|
38
|
+
leak=1
|
|
39
|
+
fi
|
|
40
|
+
if echo "$desc" | grep -qE '(discover →|scope-work →|plan-work →|develop-tdd →|verify-work →|→ .* → )'; then
|
|
41
|
+
leak=1
|
|
42
|
+
fi
|
|
43
|
+
if (( leak )); then
|
|
44
|
+
echo "FAIL: $skill — workflow-summary leakage in description (keep triggers only; move steps to body)"
|
|
45
|
+
ERRORS=$((ERRORS + 1))
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
if echo "$desc" | grep -qE 'model:[[:space:]]*(haiku|sonnet|opus)'; then
|
|
49
|
+
echo "FAIL: $skill — model hint belongs in frontmatter, not description"
|
|
50
|
+
ERRORS=$((ERRORS + 1))
|
|
51
|
+
fi
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if [[ $# -gt 0 ]]; then
|
|
55
|
+
for f in "$@"; do
|
|
56
|
+
[[ -f "$f" ]] || { echo "FAIL: not found: $f" >&2; exit 1; }
|
|
57
|
+
audit_skill_description_cso "$f"
|
|
58
|
+
done
|
|
59
|
+
else
|
|
60
|
+
while IFS= read -r f; do
|
|
61
|
+
audit_skill_description_cso "$f"
|
|
62
|
+
done < <(find "$SKILLS_ROOT" -maxdepth 2 -name SKILL.md | sort)
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
if (( ERRORS > 0 )); then
|
|
66
|
+
echo "validate-skill-description: $ERRORS issue(s)" >&2
|
|
67
|
+
exit 1
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
echo "validate-skill-description: PASS"
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# story: e51s04
|
|
2
|
+
# story: e45s18
|
|
3
|
+
# story: e45s31
|
|
2
4
|
---
|
|
3
5
|
name: audit-code
|
|
4
6
|
model: haiku
|
|
@@ -14,11 +16,27 @@ Run this self-review before asking anyone else to look at the code. The goal is
|
|
|
14
16
|
|
|
15
17
|
**Distinct from `request-review`:** This is the coding agent checking its own work. No second agent is involved. Run this first; run `request-review` after this passes.
|
|
16
18
|
|
|
19
|
+
## Look-here-first (churn heuristic)
|
|
20
|
+
|
|
21
|
+
Before the checklist, rank changed files by git churn and review **high-churn hotspots first** — they carry the most latent risk regardless of diff size.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bash scripts/bp-churn-rank.sh --since 90.days --limit 15
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Apply the full checklist to churn-ranked files in descending order. Files with zero recent commits but large diffs still get reviewed; churn only sets priority, not scope.
|
|
28
|
+
|
|
17
29
|
## Modes
|
|
18
30
|
|
|
19
31
|
- Default: full checklist
|
|
20
32
|
- --quick: Run only Supply Chain and Test Coverage. Use for changes under 50 LOC.
|
|
21
33
|
- --gate: Non-interactive mode for automated CI gating (used by build-epic step 6). Exit with non-zero status code (`exit 1`) on ANY checklist failure; `exit 0` only if ALL items pass. Produces a compact pass/fail summary to stderr. On failure, list every ✗ item with reason.
|
|
34
|
+
- --parallel: Run checklist sections in **isolated git worktrees** (e45s18) so concurrent checks cannot corrupt each other's working tree. See [REFERENCE.md](REFERENCE.md) or:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
bash scripts/lib/parallel-review-worktrees.sh audit-code
|
|
38
|
+
```
|
|
39
|
+
|
|
22
40
|
|
|
23
41
|
## Checklist
|
|
24
42
|
|
|
@@ -110,24 +128,12 @@ Report the checklist with ✓ / ✗ per item. For each ✗, describe what needs
|
|
|
110
128
|
If all items pass: suggest running `request-review` for an independent second opinion.
|
|
111
129
|
If any items fail: fix them before proceeding.
|
|
112
130
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
In `--gate` mode, print one summary line per checklist section:
|
|
116
|
-
|
|
117
|
-
```
|
|
118
|
-
PASS Supply Chain & Security
|
|
119
|
-
FAIL Provenance & Metadata (2 items)
|
|
120
|
-
PASS Law of Demeter
|
|
121
|
-
...
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
Aggregate exit code: `0` if all sections PASS, `1` (non-zero) if any section FAILs. Write the full audit report to `specs/verifications/AUDIT-<epic>-<story>.md` as a permanent record.
|
|
131
|
+
In `--gate` mode, print one summary line per checklist section (`PASS Supply Chain` / `FAIL Provenance (2 items)`). Exit `0` only if all PASS. Write full report to `specs/verifications/AUDIT-<epic>-<story>.md`.
|
|
125
132
|
|
|
126
133
|
## Verify
|
|
127
134
|
|
|
128
135
|
→ verify: `test -f CONVENTIONS.md && test -d skills/enforce-first && test -d skills/request-review && echo "OK: audit-code dependencies present" || echo "FAIL"`
|
|
129
136
|
|
|
130
|
-
|
|
131
137
|
## Handoff
|
|
132
138
|
|
|
133
139
|
Gate: READY -> next: commit-message
|
|
@@ -5,6 +5,8 @@ effort: standard
|
|
|
5
5
|
description: Add a new requirement or reorder epics by WSJF against specs/release-plan.yaml and epic capsule directories. Modes Add and Reorder. Use when a new requirement arrives mid-release or the plan needs prioritization.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
# story: e45s29
|
|
9
|
+
|
|
8
10
|
# Change Request
|
|
9
11
|
|
|
10
12
|
> **HARD GATE** — `specs/release-plan.yaml` must exist before running either mode. If it doesn't, run `plan-release` first.
|
|
@@ -19,7 +21,7 @@ Intake a new requirement mid-flight without disrupting work in progress.
|
|
|
19
21
|
|
|
20
22
|
1. **Capture**: What is the change? What problem does it solve?
|
|
21
23
|
2. **Locate**: Which existing stories in `specs/epics/` does it affect or replace?
|
|
22
|
-
3. **Draft**: Add story + `tasks[]` with Gherkin-style AC in epic YAML (each task has `verify`).
|
|
24
|
+
3. **Draft**: Add story + `tasks[]` with Gherkin-style AC in epic YAML (each task has `verify`). Tag requirement deltas: `ADDED` / `MODIFIED` / `REMOVED` / `RENAMED` with before/after for non-`ADDED` changes (e45s29).
|
|
23
25
|
4. **Place**: Append story under existing epic capsule, or create `specs/epics/eNN-slug.yaml` and register in `release-plan.yaml` `epics[]`.
|
|
24
26
|
5. **Score**: Compute WSJF; note if it outranks in-progress work.
|
|
25
27
|
|
|
@@ -5,6 +5,8 @@ model: sonnet
|
|
|
5
5
|
effort: standard
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
# story: e45s27
|
|
9
|
+
|
|
8
10
|
# Compose Workflow
|
|
9
11
|
> **HARD GATE** — **HARD GATE** — Workflows are orchestration, not automation. Do NOT create workflows for tasks that should be single skills. Workflow complexity must be justified.
|
|
10
12
|
|
|
@@ -21,6 +23,19 @@ effort: standard
|
|
|
21
23
|
> **Prefer the YAML recipe format** over the legacy `specs/WORKFLOW-<name>.md` markdown format.
|
|
22
24
|
> YAML recipes are command-mappable, machine-readable, and listed in the Standard Recipe Library.
|
|
23
25
|
|
|
26
|
+
## Terminal-state taxonomy (e45s27)
|
|
27
|
+
|
|
28
|
+
Every workflow step and `/loop` tick MUST exit with exactly one terminal state:
|
|
29
|
+
|
|
30
|
+
| State | Meaning | Next action |
|
|
31
|
+
|-------|---------|-------------|
|
|
32
|
+
| `success` | Step verify passed; artifacts written | Advance to next skill in recipe |
|
|
33
|
+
| `no-op` | Nothing to do (already green / already applied) | Skip step; advance |
|
|
34
|
+
| `blocked` | External gate (approval, red CI, missing dep) | `diagnose-stall` or escalate to user |
|
|
35
|
+
| `exhausted` | Max iterations/cycles reached (review cap, dispatch cycles) | Stop; human decision required |
|
|
36
|
+
|
|
37
|
+
Record terminal state in `specs/state.yaml` `handoff.last_terminal_state` when a recipe step completes. `/loop` ticks that produce no progress for two consecutive wakes → invoke `diagnose-stall`.
|
|
38
|
+
|
|
24
39
|
## Standard Recipe Library
|
|
25
40
|
|
|
26
41
|
Pre-built recipes in `specs/workflows/` map agentic stack commands to skill chains.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: context7-mcp
|
|
3
|
+
# story: e45s19 e45s20
|
|
4
|
+
model: haiku
|
|
5
|
+
effort: light
|
|
6
|
+
description: Fetch current library docs via Context7 MCP instead of training data. Use when user asks about frameworks, APIs, setup, or code examples for React, Next.js, Prisma, etc.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Context7 MCP
|
|
10
|
+
|
|
11
|
+
> **HARD GATE** — Max **3** Context7 tool calls per user question (`resolve-library-id` + `query-docs` count toward the cap). On quota/rate-limit errors, emit an explicit **CONTEXT7_UNAVAILABLE** block — do NOT silently answer from training data.
|
|
12
|
+
>
|
|
13
|
+
> **HARD GATE** — Before HTTP fetch, check `bash scripts/lib/doc-fetch-cache.sh get "<libraryId>:<query>"`. Cache hit within TTL → use cached body (no round-trip). On ETag mismatch after conditional refresh, replace cache entry.
|
|
14
|
+
|
|
15
|
+
## When to Use
|
|
16
|
+
|
|
17
|
+
- Setup/configuration questions ("How do I configure Next.js middleware?")
|
|
18
|
+
- Code involving libraries ("Write a Prisma query for…")
|
|
19
|
+
- API references ("What are the Supabase auth methods?")
|
|
20
|
+
- User mentions specific frameworks (React, Vue, Svelte, Express, Tailwind, etc.)
|
|
21
|
+
|
|
22
|
+
## Bounded Retry (max 3x)
|
|
23
|
+
|
|
24
|
+
| Attempt | Action |
|
|
25
|
+
|---------|--------|
|
|
26
|
+
| 1 | `resolve-library-id` → pick best match |
|
|
27
|
+
| 2 | `query-docs` with selected `libraryId` |
|
|
28
|
+
| 3 | Retry `query-docs` once with refined query (narrower scope) |
|
|
29
|
+
|
|
30
|
+
After 3 failures, stop and print:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
CONTEXT7_UNAVAILABLE
|
|
34
|
+
Reason: <quota|rate-limit|no-match|timeout>
|
|
35
|
+
Action: Ask user to retry later, paste official docs URL, or run `bts docs <lib>`.
|
|
36
|
+
Do NOT substitute training-data answers without labeling them UNVERIFIED.
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Fetch Cache (ETag-revalidated)
|
|
40
|
+
|
|
41
|
+
1. **Cache key:** `"<libraryId>:<normalized-query>"` (lowercase, trimmed).
|
|
42
|
+
2. **Read:** `bash scripts/lib/doc-fetch-cache.sh get "<key>"` — exit 0 → use cached body.
|
|
43
|
+
3. **Miss / stale:** call `query-docs`; store via `doc-fetch-cache.sh put`.
|
|
44
|
+
4. **TTL:** 300s default (`DOC_CACHE_TTL`). Stale entries refresh on next fetch; honor `ETag` when MCP returns it.
|
|
45
|
+
|
|
46
|
+
`bts docs <lib>` shares the same cache helper when invoked from this skill.
|
|
47
|
+
|
|
48
|
+
## Process
|
|
49
|
+
|
|
50
|
+
1. `resolve-library-id` with `libraryName` + full user `query`.
|
|
51
|
+
2. Select match: name similarity, reputation, benchmark score; prefer version-specific IDs when user names a version.
|
|
52
|
+
3. `query-docs` with `libraryId` + specific question (one concept per call).
|
|
53
|
+
4. Answer using fetched docs; cite library/version when relevant.
|
|
54
|
+
|
|
55
|
+
## Verify
|
|
56
|
+
|
|
57
|
+
→ verify: `test -f scripts/lib/doc-fetch-cache.sh && grep -q CONTEXT7_UNAVAILABLE skills/context7-mcp/SKILL.md && echo OK || echo FAIL`
|