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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/bin/cli.mjs +26 -0
- package/package.json +1 -1
- package/src/core/doctor.mjs +47 -0
- package/src/core/render-templates.mjs +119 -5
- package/src/core/upgrade.mjs +81 -60
- package/src/templates/.claude/agents/api-consistency-reviewer.md.vi +37 -0
- package/src/templates/.claude/agents/architecture-reviewer.md.vi.hbs +45 -0
- package/src/templates/.claude/agents/performance-reviewer.md.vi +39 -0
- package/src/templates/.claude/agents/reliability-reviewer.md.vi +42 -0
- package/src/templates/.claude/agents/security-reviewer.md.vi +43 -0
- package/src/templates/.claude/hooks/hooks.json +46 -0
- package/src/templates/.claude/output-styles/harness-terse.md +42 -0
- package/src/templates/.claude/settings.json.hbs +2 -1
- package/src/templates/.claude/skills/add-adr/SKILL.md.vi +64 -0
- package/src/templates/.claude/skills/add-feature/SKILL.md.vi.hbs +50 -0
- package/src/templates/.claude/skills/debug-flow/SKILL.md.vi.hbs +42 -0
- package/src/templates/.claude/skills/doc-drift-scan/SKILL.md +15 -10
- package/src/templates/.claude/skills/doc-drift-scan/SKILL.md.vi +52 -0
- package/src/templates/.claude/skills/doc-drift-scan/scripts/scan-paths.mjs +64 -0
- package/src/templates/.claude/skills/eval-runner/SKILL.md.vi +59 -0
- package/src/templates/.claude/skills/garbage-collection/SKILL.md.hbs +14 -5
- package/src/templates/.claude/skills/garbage-collection/SKILL.md.vi.hbs +58 -0
- package/src/templates/.claude/skills/garbage-collection/scripts/gc-classify.mjs +77 -0
- package/src/templates/.claude/skills/i18n-add-locale/SKILL.md +52 -0
- package/src/templates/.claude/skills/i18n-add-locale/SKILL.md.vi +56 -0
- package/src/templates/.claude/skills/i18n-add-locale/scripts/locale-scaffold.mjs +120 -0
- package/src/templates/.claude/skills/inspect-app/SKILL.md.vi +61 -0
- package/src/templates/.claude/skills/inspect-module/SKILL.md.hbs +17 -14
- package/src/templates/.claude/skills/inspect-module/SKILL.md.vi.hbs +57 -0
- package/src/templates/.claude/skills/inspect-module/scripts/module-summary.mjs +144 -0
- package/src/templates/.claude/skills/map-domain/SKILL.md +42 -0
- package/src/templates/.claude/skills/map-domain/SKILL.md.vi +42 -0
- package/src/templates/.claude/skills/map-domain/scripts/domain-map.mjs +145 -0
- package/src/templates/.claude/skills/propose-harness-improvement/SKILL.md.vi +49 -0
- package/src/templates/.claude/skills/propose-harness-improvement/scripts/improvement-bundle.mjs +172 -0
- package/src/templates/.claude/skills/refactor-feature/SKILL.md +60 -0
- package/src/templates/.claude/skills/refactor-feature/SKILL.md.vi +64 -0
- package/src/templates/.claude/skills/refactor-feature/scripts/feature-diff.mjs +146 -0
- package/src/templates/.claude/skills/review-this-pr/SKILL.md +59 -0
- package/src/templates/.claude/skills/review-this-pr/SKILL.md.vi +63 -0
- package/src/templates/.claude/skills/review-this-pr/scripts/pr-review-driver.mjs +152 -0
- package/src/templates/.claude/skills/structural-test-author/SKILL.md.vi.hbs +50 -0
- package/src/templates/.claude/skills/write-skill/SKILL.md.vi +43 -0
- package/src/templates/.harness/eval/rubrics/feature-step-done.mjs +148 -0
- package/src/templates/.harness/eval/tasks/feature-step-done.answer.md +53 -0
- package/src/templates/.harness/eval/tasks/feature-step-done.json +10 -0
- package/src/templates/.harness/eval/tasks/feature-step-done.prompt.md +43 -0
- package/src/templates/.mcp.json.example +35 -0
- package/src/templates/CLAUDE.md.hbs +9 -5
- package/src/templates/CLAUDE.md.vi.hbs +9 -5
- package/src/templates/scripts/notify-on-block.sh.hbs +73 -0
- package/src/templates/scripts/pretooluse-edit-guard.sh.hbs +115 -0
- package/src/templates/scripts/session-end.sh.hbs +6 -0
- package/src/templates/scripts/session-rollup.mjs +96 -0
- package/src/templates/scripts/session-start.sh.hbs +25 -0
- package/src/templates/scripts/statusline.mjs +63 -0
- package/src/templates/scripts/subagent-stop.sh.hbs +76 -0
- 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
|