agent-harness-kit 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/bin/cli.mjs +26 -0
  4. package/package.json +1 -1
  5. package/src/core/doctor.mjs +47 -0
  6. package/src/core/render-templates.mjs +119 -5
  7. package/src/core/upgrade.mjs +81 -60
  8. package/src/templates/.claude/agents/api-consistency-reviewer.md.vi +37 -0
  9. package/src/templates/.claude/agents/architecture-reviewer.md.vi.hbs +45 -0
  10. package/src/templates/.claude/agents/performance-reviewer.md.vi +39 -0
  11. package/src/templates/.claude/agents/reliability-reviewer.md.vi +42 -0
  12. package/src/templates/.claude/agents/security-reviewer.md.vi +43 -0
  13. package/src/templates/.claude/hooks/hooks.json +46 -0
  14. package/src/templates/.claude/output-styles/harness-terse.md +42 -0
  15. package/src/templates/.claude/settings.json.hbs +2 -1
  16. package/src/templates/.claude/skills/add-adr/SKILL.md.vi +64 -0
  17. package/src/templates/.claude/skills/add-feature/SKILL.md.vi.hbs +50 -0
  18. package/src/templates/.claude/skills/debug-flow/SKILL.md.vi.hbs +42 -0
  19. package/src/templates/.claude/skills/doc-drift-scan/SKILL.md +15 -10
  20. package/src/templates/.claude/skills/doc-drift-scan/SKILL.md.vi +52 -0
  21. package/src/templates/.claude/skills/doc-drift-scan/scripts/scan-paths.mjs +64 -0
  22. package/src/templates/.claude/skills/eval-runner/SKILL.md.vi +59 -0
  23. package/src/templates/.claude/skills/garbage-collection/SKILL.md.hbs +14 -5
  24. package/src/templates/.claude/skills/garbage-collection/SKILL.md.vi.hbs +58 -0
  25. package/src/templates/.claude/skills/garbage-collection/scripts/gc-classify.mjs +77 -0
  26. package/src/templates/.claude/skills/i18n-add-locale/SKILL.md +52 -0
  27. package/src/templates/.claude/skills/i18n-add-locale/SKILL.md.vi +56 -0
  28. package/src/templates/.claude/skills/i18n-add-locale/scripts/locale-scaffold.mjs +120 -0
  29. package/src/templates/.claude/skills/inspect-app/SKILL.md.vi +61 -0
  30. package/src/templates/.claude/skills/inspect-module/SKILL.md.hbs +17 -14
  31. package/src/templates/.claude/skills/inspect-module/SKILL.md.vi.hbs +57 -0
  32. package/src/templates/.claude/skills/inspect-module/scripts/module-summary.mjs +144 -0
  33. package/src/templates/.claude/skills/map-domain/SKILL.md +42 -0
  34. package/src/templates/.claude/skills/map-domain/SKILL.md.vi +42 -0
  35. package/src/templates/.claude/skills/map-domain/scripts/domain-map.mjs +145 -0
  36. package/src/templates/.claude/skills/propose-harness-improvement/SKILL.md.vi +49 -0
  37. package/src/templates/.claude/skills/propose-harness-improvement/scripts/improvement-bundle.mjs +172 -0
  38. package/src/templates/.claude/skills/refactor-feature/SKILL.md +60 -0
  39. package/src/templates/.claude/skills/refactor-feature/SKILL.md.vi +64 -0
  40. package/src/templates/.claude/skills/refactor-feature/scripts/feature-diff.mjs +146 -0
  41. package/src/templates/.claude/skills/review-this-pr/SKILL.md +59 -0
  42. package/src/templates/.claude/skills/review-this-pr/SKILL.md.vi +63 -0
  43. package/src/templates/.claude/skills/review-this-pr/scripts/pr-review-driver.mjs +152 -0
  44. package/src/templates/.claude/skills/structural-test-author/SKILL.md.vi.hbs +50 -0
  45. package/src/templates/.claude/skills/write-skill/SKILL.md.vi +43 -0
  46. package/src/templates/.harness/eval/rubrics/feature-step-done.mjs +148 -0
  47. package/src/templates/.harness/eval/tasks/feature-step-done.answer.md +53 -0
  48. package/src/templates/.harness/eval/tasks/feature-step-done.json +10 -0
  49. package/src/templates/.harness/eval/tasks/feature-step-done.prompt.md +43 -0
  50. package/src/templates/.mcp.json.example +35 -0
  51. package/src/templates/CLAUDE.md.hbs +9 -5
  52. package/src/templates/CLAUDE.md.vi.hbs +9 -5
  53. package/src/templates/scripts/notify-on-block.sh.hbs +73 -0
  54. package/src/templates/scripts/pretooluse-edit-guard.sh.hbs +115 -0
  55. package/src/templates/scripts/session-end.sh.hbs +6 -0
  56. package/src/templates/scripts/session-rollup.mjs +96 -0
  57. package/src/templates/scripts/session-start.sh.hbs +25 -0
  58. package/src/templates/scripts/statusline.mjs +63 -0
  59. package/src/templates/scripts/subagent-stop.sh.hbs +76 -0
  60. package/src/templates/scripts/userprompt-guard.sh.hbs +100 -0
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: garbage-collection
3
+ description: Use this skill on Fridays, before tagging a release, or when the user mentions "cleanup", "tech debt", "AI slop", "GC", or "garbage collection". Runs the deterministic linters, structural tests, and doc-drift scans, then proposes the top-3 highest-leverage cleanups (with risk/cost/benefit) — does NOT auto-merge. This is the solo-dev shrunk version of OpenAI's Friday garbage-collection ritual.
4
+ allowed-tools: Read, Glob, Grep, Bash(npm run:*), Bash(pytest:*), Bash(ruff:*), Bash(git:*), Bash(gh:*), Bash(node .claude/skills/garbage-collection/scripts/gc-classify.mjs:*)
5
+ suggested-turns: 15
6
+ ---
7
+
8
+ ## Các bước
9
+
10
+ 1. **Lấy baseline.** Chạy toàn bộ test suite và lưu output:
11
+ - {{#if isPython}}`python -m harness.structural_test`{{else}}`npm run harness:check`{{/if}}
12
+ - {{#if isPython}}`ruff check . --output-format json`{{else}}`npm run lint -- --format json`{{/if}}
13
+ - Lưu vào `.harness/gc-<date>.json`.
14
+ 2. **Phân loại vi phạm.** Với mỗi finding:
15
+ - **Vi phạm layer** → sửa.
16
+ - **Duplicate utility** (cùng function body ở 2+ chỗ) → đề xuất
17
+ trích xuất sang `src/shared/`.
18
+ - **Dead import** → xóa.
19
+ - **Doc drift** (đường dẫn trong `docs/architecture.md` không còn tồn tại) →
20
+ gọi skill `doc-drift-scan`.
21
+ - **Helper viết tay** trùng với shared utility đã có → đề xuất thay thế.
22
+ 3. **Chấm điểm** từng candidate fix trên ba thang điểm 1–5 qua side-car
23
+ script (thay thế LLM-scored turn trước đây — deterministic và có thể
24
+ kiểm chứng):
25
+
26
+ ```bash
27
+ node .claude/skills/garbage-collection/scripts/gc-classify.mjs \
28
+ --baseline .harness/gc-<date>.json \
29
+ --history .harness/gc-history.json
30
+ ```
31
+
32
+ Script áp dụng rubric cơ học: `risk = 1 + ceil(touched/3)`,
33
+ `cost = 1 + ceil(lines/30)`, `benefit = recurrenceCount(class)`. Đọc
34
+ `candidates[]` JSON đã sort theo `(benefit desc, cost asc, risk asc)`.
35
+ 4. **Chỉ đề xuất top 3** cleanup (mức cap cho solo-dev; OpenAI làm hàng chục,
36
+ bạn làm 3). Mở thành các PR riêng bằng `gh pr create --label gc --draft`.
37
+ 5. **Append một row** vào `.harness/gc-history.json`:
38
+ `{ "date": "...", "violations_found": N, "fixes_opened": M, "total_tokens": K }`.
39
+ 6. **Dừng lại.** Không merge gì cả. Phải có human review ở scale solo.
40
+
41
+ ## Output contract
42
+
43
+ ```
44
+ ### GC run: <date>
45
+ ### Violations found: <N>
46
+ ### Top 3 fixes proposed:
47
+ 1. <slug> — risk:<1-5> cost:<1-5> benefit:<1-5> — PR #<n>
48
+ 2. ...
49
+ 3. ...
50
+ ### Fixes deferred (not in top 3): <count>
51
+ ```
52
+
53
+ ## Anti-patterns
54
+
55
+ - Không mở quá 3 PR trong một lần chạy. Cap đó là feature, không phải bug.
56
+ - Không auto-merge — toàn bộ ý nghĩa của GC ở solo scale là human review.
57
+ - Không bỏ qua các findings điểm thấp; record chúng vào
58
+ `.harness/gc-history.json` để xu hướng nổi lên theo thời gian.
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ // gc-classify.mjs — deterministic scoring step for /garbage-collection.
3
+ // Replaces "LLM-scored risk/cost/benefit" turn with mechanical rubric.
4
+ //
5
+ // Usage:
6
+ // gc-classify.mjs --baseline <gc-snapshot.json> [--history <hist.json>] [--out <file>]
7
+ //
8
+ // Rubric:
9
+ // risk = 1 + ceil(touched_files / 3) capped at 5
10
+ // cost = 1 + ceil(lines_to_change / 30) capped at 5
11
+ // benefit = recurrenceCount(class) from gc-history capped at 5
12
+
13
+ import { readFileSync, existsSync, writeFileSync } from "node:fs";
14
+ import { resolve } from "node:path";
15
+
16
+ function loadJSON(p, fallback = null) {
17
+ try { return JSON.parse(readFileSync(p, "utf8")); } catch { return fallback; }
18
+ }
19
+
20
+ function parseArgs(argv) {
21
+ const out = { baseline: null, history: ".harness/gc-history.json", out: null };
22
+ for (let i = 0; i < argv.length; i++) {
23
+ if (argv[i] === "--baseline") out.baseline = argv[++i];
24
+ else if (argv[i] === "--history") out.history = argv[++i];
25
+ else if (argv[i] === "--out") out.out = argv[++i];
26
+ }
27
+ if (!out.baseline) {
28
+ console.error("usage: gc-classify.mjs --baseline <gc-snapshot.json> [--history <hist.json>] [--out <file>]");
29
+ process.exit(2);
30
+ }
31
+ return out;
32
+ }
33
+
34
+ function recurrenceCount(history, klass) {
35
+ if (!history?.runs) return 1;
36
+ let n = 0;
37
+ for (const run of history.runs) {
38
+ if (Array.isArray(run.classes_seen) && run.classes_seen.includes(klass)) n++;
39
+ }
40
+ return n;
41
+ }
42
+
43
+ function cap5(n) { return Math.max(1, Math.min(5, n)); }
44
+
45
+ function classify(baseline, history) {
46
+ const violations = Array.isArray(baseline?.violations) ? baseline.violations : [];
47
+ return violations.map((v) => {
48
+ const touched = Number(v.files_touched) || 1;
49
+ const lines = Number(v.lines_estimate) || 5;
50
+ return {
51
+ class: v.class || "unknown",
52
+ path: v.path || "(unspecified)",
53
+ summary: v.summary || `${v.class} at ${v.path || "(unspecified)"}`,
54
+ risk: cap5(1 + Math.ceil(touched / 3)),
55
+ cost: cap5(1 + Math.ceil(lines / 30)),
56
+ benefit: cap5(recurrenceCount(history, v.class)),
57
+ };
58
+ });
59
+ }
60
+
61
+ function main() {
62
+ const { baseline, history: histPath, out } = parseArgs(process.argv.slice(2));
63
+ const base = loadJSON(resolve(baseline));
64
+ if (!base) {
65
+ console.error(`gc-classify: cannot read baseline at ${baseline}`);
66
+ process.exit(2);
67
+ }
68
+ const hist = existsSync(resolve(histPath)) ? loadJSON(resolve(histPath), { runs: [] }) : { runs: [] };
69
+ const scored = classify(base, hist);
70
+ scored.sort((a, b) => b.benefit - a.benefit || a.cost - b.cost || a.risk - b.risk);
71
+ const payload = { total: scored.length, candidates: scored };
72
+ const text = JSON.stringify(payload, null, 2);
73
+ if (out) writeFileSync(resolve(out), text + "\n");
74
+ else process.stdout.write(text + "\n");
75
+ }
76
+
77
+ main();
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: i18n-add-locale
3
+ description: Use this skill to scaffold a new human-language locale for the kit's skills/agents/CLAUDE.md. Mirrors every existing SKILL.md.hbs into a SKILL.md.<locale>.hbs stub so a translator (or LLM) can edit copy without touching machine-readable frontmatter. Default locale codes — vi, ja, fr, es, de — but accepts any 2-5 char code.
4
+ allowed-tools: Read, Write, Bash(node scripts/locale-scaffold.mjs:*)
5
+ suggested-turns: 4
6
+ ---
7
+
8
+ ## When to invoke
9
+
10
+ - Adding a new human language to the kit (or a fork's downstream).
11
+ - After upstream adds a new English skill — re-running this skill scaffolds
12
+ the locale stubs for the new file, leaving translated files untouched.
13
+
14
+ ## Steps
15
+
16
+ 1. **Pick a locale.** Two-to-five char ISO code (vi, ja, fr-CA, etc.).
17
+ 2. **Dry-run the scan.**
18
+ ```
19
+ node .claude/skills/i18n-add-locale/scripts/locale-scaffold.mjs \
20
+ --locale <code> --dry-run
21
+ ```
22
+ Lists every SKILL.md / SKILL.md.hbs that lacks a `.<locale>.hbs` sibling.
23
+ 3. **Materialize stubs.**
24
+ ```
25
+ node .claude/skills/i18n-add-locale/scripts/locale-scaffold.mjs \
26
+ --locale <code>
27
+ ```
28
+ For each missing sibling, copies the English master and prepends a
29
+ `<!-- LOCALE_TODO: translate body --> ` banner so the translator can grep
30
+ for pending work.
31
+ 4. **Register the locale.** Edit `src/core/render-templates.mjs` and add the
32
+ code to `SUPPORTED_HUMAN_LANGS`. The renderer picks the variant via
33
+ `--locale <code>` or `HARNESS_LOCALE` env.
34
+ 5. **Verify rendering** by running `agent-harness-kit init --locale <code>`
35
+ in a scratch dir and grepping the output for `LOCALE_TODO` (must be
36
+ zero before publishing).
37
+
38
+ ## Output contract
39
+
40
+ ```
41
+ locale: <code>
42
+ scaffolded: <N>
43
+ already-present: <M>
44
+ register-in: src/core/render-templates.mjs (SUPPORTED_HUMAN_LANGS)
45
+ ```
46
+
47
+ ## Anti-patterns
48
+
49
+ - Don't translate the YAML frontmatter — the renderer + Claude Code parse
50
+ it as machine-readable. Translate only the body markdown.
51
+ - Don't drop the master `.hbs` file. The locale stub *augments*; the
52
+ renderer falls back to the master when the locale variant is missing.
@@ -0,0 +1,56 @@
1
+ <!-- LOCALE_TODO: translate body to vi -->
2
+ <!-- Source: .claude/skills/i18n-add-locale/SKILL.md -->
3
+ <!-- Edit only the markdown body — keep frontmatter verbatim so the kit's renderer + Claude Code parse it identically across locales. -->
4
+
5
+ ---
6
+ name: i18n-add-locale
7
+ description: Use this skill to scaffold a new human-language locale for the kit's skills/agents/CLAUDE.md. Mirrors every existing SKILL.md.hbs into a SKILL.md.<locale>.hbs stub so a translator (or LLM) can edit copy without touching machine-readable frontmatter. Default locale codes — vi, ja, fr, es, de — but accepts any 2-5 char code.
8
+ allowed-tools: Read, Write, Bash(node scripts/locale-scaffold.mjs:*)
9
+ suggested-turns: 4
10
+ ---
11
+
12
+ ## When to invoke
13
+
14
+ - Adding a new human language to the kit (or a fork's downstream).
15
+ - After upstream adds a new English skill — re-running this skill scaffolds
16
+ the locale stubs for the new file, leaving translated files untouched.
17
+
18
+ ## Steps
19
+
20
+ 1. **Pick a locale.** Two-to-five char ISO code (vi, ja, fr-CA, etc.).
21
+ 2. **Dry-run the scan.**
22
+ ```
23
+ node .claude/skills/i18n-add-locale/scripts/locale-scaffold.mjs \
24
+ --locale <code> --dry-run
25
+ ```
26
+ Lists every SKILL.md / SKILL.md.hbs that lacks a `.<locale>.hbs` sibling.
27
+ 3. **Materialize stubs.**
28
+ ```
29
+ node .claude/skills/i18n-add-locale/scripts/locale-scaffold.mjs \
30
+ --locale <code>
31
+ ```
32
+ For each missing sibling, copies the English master and prepends a
33
+ `<!-- LOCALE_TODO: translate body --> ` banner so the translator can grep
34
+ for pending work.
35
+ 4. **Register the locale.** Edit `src/core/render-templates.mjs` and add the
36
+ code to `SUPPORTED_HUMAN_LANGS`. The renderer picks the variant via
37
+ `--locale <code>` or `HARNESS_LOCALE` env.
38
+ 5. **Verify rendering** by running `agent-harness-kit init --locale <code>`
39
+ in a scratch dir and grepping the output for `LOCALE_TODO` (must be
40
+ zero before publishing).
41
+
42
+ ## Output contract
43
+
44
+ ```
45
+ locale: <code>
46
+ scaffolded: <N>
47
+ already-present: <M>
48
+ register-in: src/core/render-templates.mjs (SUPPORTED_HUMAN_LANGS)
49
+ ```
50
+
51
+ ## Anti-patterns
52
+
53
+ - Don't translate the YAML frontmatter — the renderer + Claude Code parse
54
+ it as machine-readable. Translate only the body markdown.
55
+ - Don't drop the master `.hbs` file. The locale stub *augments*; the
56
+ renderer falls back to the master when the locale variant is missing.
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+ // locale-scaffold.mjs — deterministic step for /i18n-add-locale.
3
+ // Walks .claude/skills/* (and a small whitelist of other files) and scaffolds
4
+ // missing `.<locale>.hbs` siblings from their English masters.
5
+ //
6
+ // Usage:
7
+ // locale-scaffold.mjs --locale vi [--dry-run] [--root .claude/skills]
8
+ //
9
+ // A "master" file is either:
10
+ // - <stem>.md.hbs → scaffold sibling <stem>.md.<locale>.hbs
11
+ // - <stem>.md → scaffold sibling <stem>.md.<locale>.hbs
12
+ // (rare; only when no .hbs counterpart exists)
13
+ //
14
+ // Idempotent: if the sibling already exists, the script leaves it alone.
15
+
16
+ import { readFileSync, existsSync, writeFileSync, readdirSync, statSync } from "node:fs";
17
+ import { resolve, join, dirname, basename, relative } from "node:path";
18
+
19
+ const ROOT = process.env.CLAUDE_PROJECT_DIR || process.cwd();
20
+
21
+ function parseArgs(argv) {
22
+ const out = { locale: null, dryRun: false, roots: [".claude/skills", ".claude/agents"] };
23
+ for (let i = 0; i < argv.length; i++) {
24
+ if (argv[i] === "--locale") out.locale = argv[++i];
25
+ else if (argv[i] === "--dry-run") out.dryRun = true;
26
+ else if (argv[i] === "--root") out.roots = [argv[++i]];
27
+ }
28
+ if (!out.locale || !/^[a-z]{2,5}(-[A-Z]{2})?$/.test(out.locale)) {
29
+ console.error("usage: locale-scaffold.mjs --locale <code> [--dry-run] [--root <dir>]");
30
+ console.error(" <code> = 2-5 lowercase letters, optional region (e.g. vi, ja, fr-CA)");
31
+ process.exit(2);
32
+ }
33
+ return out;
34
+ }
35
+
36
+ function* walk(dir) {
37
+ let entries;
38
+ try { entries = readdirSync(dir, { withFileTypes: true }); }
39
+ catch { return; }
40
+ for (const e of entries) {
41
+ const full = join(dir, e.name);
42
+ if (e.isDirectory()) yield* walk(full);
43
+ else yield full;
44
+ }
45
+ }
46
+
47
+ function isMaster(path, locale) {
48
+ // English masters: *.md or *.md.hbs (but NOT *.md.<locale>.hbs already).
49
+ const name = basename(path);
50
+ if (!/\.md(?:\.hbs)?$/.test(name)) return false;
51
+ // Skip locale-specific files of any code.
52
+ if (/\.md\.[a-z]{2,5}(?:-[A-Z]{2})?\.hbs$/.test(name)) return false;
53
+ return true;
54
+ }
55
+
56
+ function siblingPath(masterPath, locale) {
57
+ // Preserve the master's Handlebars-ness in the variant. Master .md.hbs
58
+ // → variant .md.<lang>.hbs (Handlebars-active). Master .md (plain) →
59
+ // variant .md.<lang> (no Handlebars). This matters because plain-md
60
+ // masters often contain literal `{{...}}` strings as examples (e.g.
61
+ // XSS demos in security-reviewer.md); promoting them to .hbs would
62
+ // make Handlebars choke on the example text.
63
+ const name = basename(masterPath);
64
+ if (name.endsWith(".md.hbs")) {
65
+ return join(dirname(masterPath), name.replace(/\.md\.hbs$/, `.md.${locale}.hbs`));
66
+ }
67
+ if (name.endsWith(".md")) {
68
+ return join(dirname(masterPath), name.replace(/\.md$/, `.md.${locale}`));
69
+ }
70
+ return null;
71
+ }
72
+
73
+ function scaffold(masterPath, siblingPathAbs, locale, dryRun) {
74
+ if (existsSync(siblingPathAbs)) return { status: "skip", reason: "exists" };
75
+ if (dryRun) return { status: "would-create" };
76
+ const body = readFileSync(masterPath, "utf8");
77
+ const banner =
78
+ `<!-- LOCALE_TODO: translate body to ${locale} -->
79
+ <!-- Source: ${relative(ROOT, masterPath)} -->
80
+ <!-- Edit only the markdown body — keep frontmatter verbatim so the kit's renderer + Claude Code parse it identically across locales. -->
81
+
82
+ `;
83
+ writeFileSync(siblingPathAbs, banner + body);
84
+ return { status: "created" };
85
+ }
86
+
87
+ function main() {
88
+ const { locale, dryRun, roots } = parseArgs(process.argv.slice(2));
89
+ const masters = [];
90
+ for (const r of roots) {
91
+ const abs = resolve(ROOT, r);
92
+ if (!existsSync(abs)) continue;
93
+ for (const f of walk(abs)) {
94
+ if (isMaster(f, locale)) masters.push(f);
95
+ }
96
+ }
97
+ let created = 0;
98
+ let skipped = 0;
99
+ const wouldCreate = [];
100
+ for (const m of masters) {
101
+ const sib = siblingPath(m, locale);
102
+ if (!sib) continue;
103
+ const res = scaffold(m, sib, locale, dryRun);
104
+ if (res.status === "created") created++;
105
+ else if (res.status === "would-create") wouldCreate.push(relative(ROOT, sib));
106
+ else skipped++;
107
+ }
108
+ const payload = {
109
+ locale,
110
+ dry_run: dryRun,
111
+ scaffolded: dryRun ? wouldCreate.length : created,
112
+ already_present: skipped,
113
+ scanned_masters: masters.length,
114
+ would_create: dryRun ? wouldCreate : undefined,
115
+ register_in: "src/core/render-templates.mjs#SUPPORTED_HUMAN_LANGS",
116
+ };
117
+ process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
118
+ }
119
+
120
+ main();
@@ -0,0 +1,61 @@
1
+ <!-- LOCALE_TODO: translate body to vi -->
2
+ <!-- Source: .claude/skills/inspect-app/SKILL.md -->
3
+ <!-- Edit only the markdown body — keep frontmatter verbatim so the kit's renderer + Claude Code parse it identically across locales. -->
4
+
5
+ ---
6
+ name: inspect-app
7
+ description: Use this skill whenever the user asks to "test the UI", "check what the app looks like", "inspect the page", "verify the dev server is up", or before claiming a UI feature is done. Boots the dev server via scripts/dev-up.sh and drives the failing flow through Playwright MCP if installed (else falls back to curl + lightweight HTML capture). Mirrors the OpenAI Chrome-DevTools-Protocol-into-runtime pattern at solo scale — verify the running app, don't trust the type checker alone.
8
+ allowed-tools: Read, Bash(scripts/dev-up.sh), Bash(curl:*), Bash(playwright:*), Bash(node:*)
9
+ suggested-turns: 12
10
+ ---
11
+
12
+ ## When to use
13
+
14
+ The user said any of: "what does the page look like", "test the UI flow",
15
+ "is the dev server up", "before merging the UI work", or invoked this skill
16
+ explicitly via `/inspect-app`. Also auto-invokes from `/debug-flow` when the
17
+ bug is UI-shaped.
18
+
19
+ ## Steps
20
+
21
+ 1. **Detect dev server.** Read `harness.config.json` for the framework.
22
+ - If a process is already listening on the expected port (3000 / 8000 /
23
+ 5000 depending on framework), reuse it.
24
+ - Else: `bash scripts/dev-up.sh &` in the background and wait up to 30s
25
+ for the readiness probe.
26
+ 2. **Capture mode — Playwright MCP (preferred).** If `mcp__playwright__*`
27
+ tools are available:
28
+ - `mcp__playwright__browser_navigate` to the target URL
29
+ - `mcp__playwright__browser_snapshot` for accessibility tree
30
+ - `mcp__playwright__browser_take_screenshot` for a visual
31
+ - Optionally drive a single user flow (click → fill → click → wait)
32
+ 3. **Capture mode — curl fallback.** If MCP is unavailable:
33
+ - `curl -i -s -o response.body "http://localhost:$PORT$PATH"` for headers + body
34
+ - `wc -l response.body` to size-check
35
+ - `grep -E '<title>|<h1>' response.body | head -5` for sanity
36
+ 4. **Diff against expectation.** If the user gave an expected element /
37
+ text, grep for it. If not, just report what's on the page.
38
+ 5. **Cleanup.** Kill the dev server we started (don't kill ones already
39
+ running before this session).
40
+
41
+ ## Output contract
42
+
43
+ ```
44
+ ### App inspection
45
+ **URL:** http://localhost:<port><path>
46
+ **Status:** <HTTP status>
47
+ **Title:** <page title or first H1>
48
+ **Mode:** playwright-mcp | curl-fallback
49
+ **Screenshot:** <path or "n/a">
50
+ **Findings:** <bulleted list of matches/mismatches against expectation>
51
+ ```
52
+
53
+ ## Anti-patterns
54
+
55
+ - Don't claim a UI feature is done without running this skill once.
56
+ - Don't leave a dev server running after the inspection — kill what you
57
+ started.
58
+ - Don't grep for "Error" alone as a failure signal — many pages legitimately
59
+ contain that word. Match against specific expected text instead.
60
+ - Don't take screenshots of pages with secrets or test fixtures with PII —
61
+ the screenshot lands on disk.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: inspect-module
3
3
  description: Use this skill whenever the user mentions "explore", "inspect", "understand", "what does X do", "where is Y", or before adding a new feature in an unfamiliar area. Produces a structured map of one module — files, exports, dependencies, layer assignment, and recent commits — without reading the entire codebase. Always invoke this skill before editing an unfamiliar module so the agent has accurate context, not guesses.
4
- allowed-tools: Read, Glob, Grep, Bash(git log:*), Bash(git ls-tree:*), Bash(tree:*)
4
+ allowed-tools: Read, Glob, Grep, Bash(git log:*), Bash(git ls-tree:*), Bash(tree:*), Bash(node .claude/skills/inspect-module/scripts/module-summary.mjs:*)
5
5
  suggested-turns: 6
6
6
  ---
7
7
 
@@ -15,19 +15,22 @@ feature Y, what's in the area?", "explore <path>", "show me the shape of
15
15
 
16
16
  1. **Resolve the target.** If the user gave a feature name (not a path), grep
17
17
  `feature_list.json` for it. If multiple paths match, ask the user which.
18
- 2. **Recent activity.** Run `git log --oneline -20 -- <target>` and read the
19
- top 3 commit messages.
20
- 3. **Layer.** Cross-reference the path against `harness.config.json` →
21
- `domains[].layers` to determine the layer.
22
- 4. **Public surface.** List exported symbols:
23
- - TypeScript: `grep -nE "^export " <target>/**/*.ts`
24
- - Python: `grep -nE "^def |^class |^[A-Z_][A-Z0-9_]+ ?=" <target>/**/*.py`
25
- 5. **Dependencies.** List inbound imports (who depends on this module) and
26
- outbound imports (what this module depends on). Verify both respect the
27
- forward-only layer order; if not, **stop and report the violation** before
28
- proceeding with any change plan.
29
- 6. **Risks.** Flag any of: dynamic imports, eval, shell-out with
30
- interpolation, missing tests for an exported function.
18
+ 2. **One-shot summary (deterministic).** Run the side-car script bundles
19
+ exports + inbound + outbound deps + layer + recent commits into one JSON
20
+ blob, replacing three LLM turns of grep:
21
+
22
+ ```bash
23
+ node .claude/skills/inspect-module/scripts/module-summary.mjs <target>
24
+ ```
25
+
26
+ Read the JSON. If `layer` is `null`, the file is outside any configured
27
+ layer root flag that and ask whether the user wants to add it.
28
+ 3. **Forward-only check.** Walk `outbound[]` and verify each crosses layers
29
+ forward only (never backward). The structural test enforces this
30
+ mechanically too, but flagging here short-circuits a wasted write step.
31
+ 4. **Risks.** Flag any of: dynamic imports, eval, shell-out with
32
+ interpolation, missing tests for an exported function. (LLM judgment —
33
+ the side-car reports facts, not risks.)
31
34
 
32
35
  ## Output contract
33
36
 
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: inspect-module
3
+ description: Use this skill whenever the user mentions "explore", "inspect", "understand", "what does X do", "where is Y", or before adding a new feature in an unfamiliar area. Produces a structured map of one module — files, exports, dependencies, layer assignment, and recent commits — without reading the entire codebase. Always invoke this skill before editing an unfamiliar module so the agent has accurate context, not guesses.
4
+ allowed-tools: Read, Glob, Grep, Bash(git log:*), Bash(git ls-tree:*), Bash(tree:*), Bash(node .claude/skills/inspect-module/scripts/module-summary.mjs:*)
5
+ suggested-turns: 6
6
+ ---
7
+
8
+ ## Khi nào dùng
9
+
10
+ User hỏi bất cứ câu nào dạng: "X hoạt động như thế nào", "trong src/foo
11
+ có gì", "trước khi thêm feature Y, khu vực này có gì?", "explore <path>",
12
+ "hiện cho tôi shape của <module>".
13
+
14
+ ## Các bước
15
+
16
+ 1. **Resolve target.** Nếu user đưa tên feature (không phải path), grep
17
+ `feature_list.json` để tìm. Nếu nhiều path khớp, hỏi user chọn cái nào.
18
+ 2. **Tóm tắt one-shot (deterministic).** Chạy side-car script — bundles
19
+ exports + inbound + outbound deps + layer + recent commits vào một JSON
20
+ blob, thay thế ba LLM turn grep:
21
+
22
+ ```bash
23
+ node .claude/skills/inspect-module/scripts/module-summary.mjs <target>
24
+ ```
25
+
26
+ Đọc JSON. Nếu `layer` là `null`, file nằm ngoài layer root đã config —
27
+ flag điều đó và hỏi user có muốn thêm vào layer không.
28
+ 3. **Forward-only check.** Đi qua `outbound[]` và xác minh mỗi outbound
29
+ cross layer theo chiều tiến (không bao giờ ngược). Structural test sẽ
30
+ enforce điều này cơ học rồi, nhưng flag ở đây tránh được một write step
31
+ bị lãng phí.
32
+ 4. **Rủi ro.** Flag bất kỳ trường hợp nào: dynamic imports, eval, shell-out
33
+ với interpolation, missing tests cho function được export. (LLM judgment
34
+ — side-car report facts, không report risks.)
35
+
36
+ ## Output contract
37
+
38
+ Tạo Markdown report với các section sau, theo thứ tự này:
39
+
40
+ ```
41
+ ### Module: <path>
42
+ ### Layer: <layer-name>
43
+ ### Public surface: <list>
44
+ ### Inbound deps: <list of paths>
45
+ ### Outbound deps: <list of paths or external packages>
46
+ ### Recent changes: <top 3 commit messages>
47
+ ### Risks: <bulleted list, "none" nếu clean>
48
+ ```
49
+
50
+ Kết thúc bằng: "Tôi sẵn sàng thay đổi `<module>`. Architecture-reviewer
51
+ subagent sẽ được gọi khi hoàn tất."
52
+
53
+ ## Anti-patterns
54
+
55
+ - Không đọc mọi file trong module — sample exports, rồi chỉ drill in
56
+ vào nơi mà task của user chỉ tới.
57
+ - Không đề xuất changes trong skill này. Đây là read-only context-gathering.