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,63 @@
1
+ #!/usr/bin/env node
2
+ // statusLine — single compact line into Claude Code's TUI status bar.
3
+ // Reads stdin (Claude Code payload), augments with kit state, emits to
4
+ // stdout. Failure mode: print nothing rather than crash.
5
+
6
+ import { readFileSync, existsSync } from "node:fs";
7
+ import { resolve } from "node:path";
8
+ import { spawnSync } from "node:child_process";
9
+
10
+ const CWD = process.env.CLAUDE_PROJECT_DIR || process.cwd();
11
+
12
+ function safeRead(rel) {
13
+ try { return readFileSync(resolve(CWD, rel), "utf8"); }
14
+ catch { return null; }
15
+ }
16
+ function safeJSON(rel) {
17
+ const raw = safeRead(rel);
18
+ if (!raw) return null;
19
+ try { return JSON.parse(raw); } catch { return null; }
20
+ }
21
+ function readStdinSync() {
22
+ try { return readFileSync(0, "utf8"); } catch { return ""; }
23
+ }
24
+
25
+ function pieces() {
26
+ const out = [];
27
+ const lock = safeJSON(".harness/installed.json");
28
+ if (lock?.version) out.push(`{kit-v${lock.version}}`);
29
+
30
+ const features = safeJSON("feature_list.json");
31
+ if (features?.features && Array.isArray(features.features)) {
32
+ const open = features.features.find((f) => f.passes === false);
33
+ out.push(open ? `feat:${open.id}` : "feat:clean");
34
+ }
35
+
36
+ try {
37
+ const br = spawnSync("git", ["branch", "--show-current"], {
38
+ cwd: CWD, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"],
39
+ });
40
+ const status = spawnSync("git", ["status", "--short"], {
41
+ cwd: CWD, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"],
42
+ });
43
+ if (br.status === 0 && br.stdout.trim()) {
44
+ const dirty = status.stdout ? status.stdout.split("\n").filter(Boolean).length : 0;
45
+ out.push(dirty > 0 ? `${br.stdout.trim()}(±${dirty})` : br.stdout.trim());
46
+ }
47
+ } catch { /* git not on PATH — skip */ }
48
+
49
+ const raw = readStdinSync();
50
+ let payload = null;
51
+ if (raw) { try { payload = JSON.parse(raw); } catch { /* ignore */ } }
52
+ if (payload?.context && typeof payload.context.percentage === "number") {
53
+ out.push(`ctx:${Math.round(payload.context.percentage)}%`);
54
+ }
55
+ if (payload?.cost && typeof payload.cost.total === "number") {
56
+ const v = payload.cost.total;
57
+ out.push(`$${v < 1 ? v.toFixed(2) : v.toFixed(1)}`);
58
+ }
59
+ return out;
60
+ }
61
+
62
+ const line = pieces().join(" • ");
63
+ if (line) process.stdout.write(line);
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env bash
2
+ # SubagentStop hook — fires when a subagent finishes its turn (Task tool).
3
+ # Triggers the same structural-test that PostToolUse(Edit) runs, because a
4
+ # subagent can edit files in batches that individually pass but jointly drift
5
+ # off-layer. Running the check at subagent boundary catches that drift early.
6
+ #
7
+ # Contract:
8
+ # - Never blocks (exit 0 even on failure — the parent Stop hook handles the
9
+ # final gate). We only emit a stderr summary that Claude reads.
10
+ # - Telemetry append to .harness/telemetry.jsonl as {event:"subagent_stop"}.
11
+ # - Skipped when harness.config.json#structuralTest.engine === "none" (the
12
+ # "structural test not yet wired" escape hatch used by polyglot scaffolds).
13
+ set -eo pipefail
14
+
15
+ INPUT=$(cat)
16
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
17
+ have_jq() {
18
+ [ "${AHK_DISABLE_JQ:-}" = "1" ] && return 1
19
+ command -v jq >/dev/null 2>&1
20
+ }
21
+ have_jp() {
22
+ have_jq && return 0
23
+ command -v node >/dev/null 2>&1 && [ -f "$SCRIPT_DIR/_lib/json-pick.mjs" ] && return 0
24
+ return 1
25
+ }
26
+ jp() {
27
+ if have_jq; then jq -r "$1"
28
+ else node "$SCRIPT_DIR/_lib/json-pick.mjs" "$1"
29
+ fi
30
+ }
31
+
32
+ SUBAGENT="(unknown)"
33
+ if have_jp; then
34
+ SUBAGENT=$(echo "$INPUT" | jp '.subagent // .session_id // "unknown"' 2>/dev/null || echo "unknown")
35
+ fi
36
+
37
+ # Telemetry first so we record every subagent boundary, even if the
38
+ # structural-test bails below.
39
+ mkdir -p .harness
40
+ TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
41
+ SHA=$(git rev-parse --short HEAD 2>/dev/null || echo 'no-git')
42
+ printf '{"ts":"%s","event":"subagent_stop","subagent":"%s","sha":"%s"}\n' \
43
+ "$TS" "$SUBAGENT" "$SHA" >> .harness/telemetry.jsonl
44
+
45
+ # Skip if structural test disabled.
46
+ if [ -f harness.config.json ] \
47
+ && grep -qE '"engine"[[:space:]]*:[[:space:]]*"none"' harness.config.json; then
48
+ exit 0
49
+ fi
50
+
51
+ # AHK_HOOK_MODE=warn → log only, don't run.
52
+ if [ "${AHK_HOOK_MODE:-}" = "warn" ]; then
53
+ exit 0
54
+ fi
55
+
56
+ # Run structural test workspace-wide. Subagents typically touch multiple
57
+ # files; per-file scoping would miss the cross-file drift case. Cap output
58
+ # to 30 lines on stderr so the parent agent sees the summary without flood.
59
+ RAN=0
60
+ if [ -f harness/structural-check.mjs ] && command -v node >/dev/null 2>&1; then
61
+ RAN=1
62
+ if ! node harness/structural-check.mjs 2>&1 | tail -30 >&2; then
63
+ echo "[ahk] subagent_stop: structural-test reported violations (see above). Continuing — parent Stop hook will gate." >&2
64
+ fi
65
+ elif command -v npm >/dev/null 2>&1 && [ -f package.json ] \
66
+ && grep -q '"harness:check"' package.json 2>/dev/null; then
67
+ RAN=1
68
+ if ! npm run --silent harness:check 2>&1 | tail -30 >&2; then
69
+ echo "[ahk] subagent_stop: structural-test reported violations (see above). Continuing — parent Stop hook will gate." >&2
70
+ fi
71
+ fi
72
+ if [ "$RAN" = "0" ]; then
73
+ # No structural-test entry point. Skip silently — already logged in telemetry.
74
+ exit 0
75
+ fi
76
+ exit 0
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env bash
2
+ # UserPromptSubmit hook — denies prompt patterns that undo harness safety
3
+ # rules. Hard guardrail replacing soft CLAUDE.md guidance.
4
+ #
5
+ # Denied patterns:
6
+ # 1. "ignore previous instructions" / "disregard above"
7
+ # 2. "disable the structural test" / "skip the structural check"
8
+ # 3. "bypass the (Stop|PreToolUse|hook) (rules?|checks?)"
9
+ # 4. "remove the .harness" / "delete .harness directory"
10
+ # 5. "set disableAllHooks: true"
11
+ #
12
+ # Escape hatch: AHK_ALLOW_BYPASS=1 logs to .harness/bypass.log + lets through.
13
+ set -eo pipefail
14
+
15
+ INPUT=$(cat)
16
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
17
+ have_jq() {
18
+ [ "${AHK_DISABLE_JQ:-}" = "1" ] && return 1
19
+ command -v jq >/dev/null 2>&1
20
+ }
21
+ have_jp() {
22
+ have_jq && return 0
23
+ command -v node >/dev/null 2>&1 && [ -f "$SCRIPT_DIR/_lib/json-pick.mjs" ] && return 0
24
+ return 1
25
+ }
26
+ jp() {
27
+ if have_jq; then
28
+ if [ -n "$2" ]; then jq -r "$1" "$2"; else jq -r "$1"; fi
29
+ else
30
+ if [ -n "$2" ]; then
31
+ node "$SCRIPT_DIR/_lib/json-pick.mjs" "$1" "$2"
32
+ else
33
+ node "$SCRIPT_DIR/_lib/json-pick.mjs" "$1"
34
+ fi
35
+ fi
36
+ }
37
+
38
+ if ! have_jp; then
39
+ exit 0
40
+ fi
41
+
42
+ PROMPT=$(echo "$INPUT" | jp '.prompt // empty')
43
+ [ -z "$PROMPT" ] && exit 0
44
+
45
+ LOWER=$(printf '%s' "$PROMPT" | tr '[:upper:]' '[:lower:]')
46
+
47
+ REASON=""
48
+
49
+ if printf '%s' "$LOWER" | grep -qE '(ignore|disregard|forget) (the|all|any|your|previous|prior|above)'; then
50
+ REASON="Prompts that ask Claude to ignore previous instructions defeat the harness's safety rules. State the actual change you need; the structural test and Stop checklist are deterministic and stay enforced."
51
+ fi
52
+
53
+ if [ -z "$REASON" ] \
54
+ && printf '%s' "$LOWER" | grep -qE '(disable|skip|turn off|bypass) (the )?(structural|layer|stop hook|stop check|precompletion|lint|harness:check)'; then
55
+ REASON="Disabling the structural test or Stop checklist is not how the kit is meant to be used. Fix the violation in code, or open an ADR if the layer rule itself needs to change."
56
+ fi
57
+
58
+ if [ -z "$REASON" ] \
59
+ && printf '%s' "$LOWER" | grep -qE 'bypass (the )?(pretooluse|posttooluse|sessionstart|sessionend|precompact|hook|hooks|rules?|checks?)'; then
60
+ REASON="Prompts that ask to bypass kit hooks defeat their purpose. If a specific hook is wrong for your workflow, edit it explicitly with a commit message; do not phrase the request as 'bypass'."
61
+ fi
62
+
63
+ if [ -z "$REASON" ] \
64
+ && printf '%s' "$LOWER" | grep -qE '(remove|delete|wipe|rm -rf|drop) (the )?(\.harness|\.claude)( |/|$|\.)'; then
65
+ REASON="Removing .harness/ or .claude/ deletes the kit's lockfile, structural baseline, skills, agents, and hooks. Use 'agent-harness-kit upgrade' to refresh installed files; do not delete by hand."
66
+ fi
67
+
68
+ if [ -z "$REASON" ] \
69
+ && printf '%s' "$LOWER" | grep -qE 'disableallhooks.*true'; then
70
+ REASON="disableAllHooks: true defeats every protection the kit installs. Remove specific hooks explicitly if needed; do not flip the master switch."
71
+ fi
72
+
73
+ if [ -z "$REASON" ]; then
74
+ exit 0
75
+ fi
76
+
77
+ if [ "${AHK_ALLOW_BYPASS:-}" = "1" ]; then
78
+ mkdir -p .harness
79
+ TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
80
+ SHA=$(git rev-parse --short HEAD 2>/dev/null || echo 'no-git')
81
+ ESCAPED_PROMPT=${PROMPT//$'\n'/ }
82
+ ESCAPED_PROMPT=${ESCAPED_PROMPT//\"/\\\"}
83
+ printf '{"ts":"%s","sha":"%s","bypass":"AHK_ALLOW_BYPASS","reason":"%s","prompt":"%s","hook":"UserPromptSubmit"}\n' \
84
+ "$TS" "$SHA" "${REASON//\"/\\\"}" "$ESCAPED_PROMPT" >> .harness/bypass.log
85
+ exit 0
86
+ fi
87
+
88
+ if command -v node >/dev/null 2>&1; then
89
+ node -e "
90
+ const reason = process.argv[1];
91
+ const out = { decision: 'block', reason };
92
+ process.stdout.write(JSON.stringify(out));
93
+ " "$REASON"
94
+ elif have_jq; then
95
+ jq -nc --arg r "$REASON" '{decision:"block", reason:$r}'
96
+ else
97
+ echo "$REASON" >&2
98
+ exit 2
99
+ fi
100
+ exit 0