uni-harness 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/TEMPLATE.md +64 -0
- package/.claude/hooks/guard-pre-bash.sh +46 -0
- package/.claude/hooks/observe-log.sh +88 -0
- package/.claude/hooks/sensor-post-edit.sh +62 -0
- package/.claude/hooks/session-start.sh +86 -0
- package/.claude/hooks/stop-gate.sh +70 -0
- package/.claude/settings.json +76 -0
- package/.claude/skills/checkpoint/SKILL.md +49 -0
- package/.claude/skills/guide-audit/SKILL.md +50 -0
- package/.claude/skills/harness-init/SKILL.md +70 -0
- package/.claude/skills/ratchet/SKILL.md +72 -0
- package/.harness/commands.env +13 -0
- package/CLAUDE.md +68 -0
- package/LICENSE +21 -0
- package/README.md +212 -0
- package/bin/cli.js +371 -0
- package/harness/harness_report.py +90 -0
- package/harness/tests/test_hooks.sh +129 -0
- package/harness/tests/test_installer.sh +112 -0
- package/package.json +35 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ════════════════════════════════════════════════════════════════
|
|
3
|
+
# CLI installer (bin/cli.js) tests
|
|
4
|
+
# Run: bash harness/tests/test_installer.sh
|
|
5
|
+
# Exercises init → doctor → local-modification protection → update →
|
|
6
|
+
# uninstall in a temp directory, including the brownfield scenario
|
|
7
|
+
# (existing project with its own CLAUDE.md, settings.json, .gitignore).
|
|
8
|
+
# Requires node.
|
|
9
|
+
# ════════════════════════════════════════════════════════════════
|
|
10
|
+
set -u
|
|
11
|
+
|
|
12
|
+
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
13
|
+
CLI="$ROOT/bin/cli.js"
|
|
14
|
+
PASS=0
|
|
15
|
+
FAIL=0
|
|
16
|
+
|
|
17
|
+
check() {
|
|
18
|
+
local name="$1" expect="$2" actual="$3"
|
|
19
|
+
if [ "$expect" = "EMPTY" ]; then
|
|
20
|
+
if [ -z "$actual" ]; then PASS=$((PASS+1)); echo " ok: $name"
|
|
21
|
+
else FAIL=$((FAIL+1)); echo " FAIL: $name — expected empty, got: ${actual:0:120}"; fi
|
|
22
|
+
else
|
|
23
|
+
if printf '%s' "$actual" | grep -qF "$expect"; then PASS=$((PASS+1)); echo " ok: $name"
|
|
24
|
+
else FAIL=$((FAIL+1)); echo " FAIL: $name — missing '$expect': ${actual:0:160}"; fi
|
|
25
|
+
fi
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
command -v node >/dev/null || { echo "node not found — skipping installer tests."; exit 0; }
|
|
29
|
+
|
|
30
|
+
# ── greenfield init ──────────────────────────────────────────────
|
|
31
|
+
echo "init (greenfield)"
|
|
32
|
+
TGT=$(mktemp -d)
|
|
33
|
+
OUT=$(node "$CLI" init "$TGT" 2>&1)
|
|
34
|
+
check "installs hooks" "yes" "$([ -f "$TGT/.claude/hooks/stop-gate.sh" ] && echo yes)"
|
|
35
|
+
check "hooks are executable" "yes" "$([ -x "$TGT/.claude/hooks/stop-gate.sh" ] && echo yes)"
|
|
36
|
+
check "installs skills" "yes" "$([ -f "$TGT/.claude/skills/ratchet/SKILL.md" ] && echo yes)"
|
|
37
|
+
check "installs CLAUDE.md" "yes" "$([ -f "$TGT/CLAUDE.md" ] && echo yes)"
|
|
38
|
+
check "installs settings.json" "yes" "$([ -f "$TGT/.claude/settings.json" ] && echo yes)"
|
|
39
|
+
check "writes manifest" "yes" "$([ -f "$TGT/.harness/kit-manifest.json" ] && echo yes)"
|
|
40
|
+
check "gitignores logs" ".harness/logs/" "$(cat "$TGT/.gitignore")"
|
|
41
|
+
check "init runs hook tests" "Hook tests passed" "$OUT"
|
|
42
|
+
check "prints next step" "/harness-init" "$OUT"
|
|
43
|
+
|
|
44
|
+
# ── brownfield init: existing project files are preserved ────────
|
|
45
|
+
echo "init (brownfield)"
|
|
46
|
+
BF=$(mktemp -d)
|
|
47
|
+
echo "MY PROJECT RULES" > "$BF/CLAUDE.md"
|
|
48
|
+
echo "node_modules/" > "$BF/.gitignore"
|
|
49
|
+
mkdir -p "$BF/.claude"
|
|
50
|
+
cat > "$BF/.claude/settings.json" <<'EOF'
|
|
51
|
+
{
|
|
52
|
+
"permissions": {"allow": ["Read"]},
|
|
53
|
+
"hooks": {
|
|
54
|
+
"PreToolUse": [
|
|
55
|
+
{"matcher": "Bash", "hooks": [{"type": "command", "command": "./my-own-hook.sh"}]}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
EOF
|
|
60
|
+
OUT=$(node "$CLI" init "$BF" 2>&1)
|
|
61
|
+
check "keeps existing CLAUDE.md" "MY PROJECT RULES" "$(cat "$BF/CLAUDE.md")"
|
|
62
|
+
check "notes CLAUDE.md was kept" "existing CLAUDE.md was kept" "$OUT"
|
|
63
|
+
check "keeps user permissions" '"Read"' "$(cat "$BF/.claude/settings.json")"
|
|
64
|
+
check "keeps user's own hook" "my-own-hook.sh" "$(cat "$BF/.claude/settings.json")"
|
|
65
|
+
check "merges kit hooks in" "stop-gate.sh" "$(cat "$BF/.claude/settings.json")"
|
|
66
|
+
check "appends to existing gitignore" "node_modules/" "$(cat "$BF/.gitignore")"
|
|
67
|
+
check "gitignore gains logs entry" ".harness/logs/" "$(cat "$BF/.gitignore")"
|
|
68
|
+
OUT=$(node "$CLI" init "$BF" 2>&1)
|
|
69
|
+
N=$(grep -c "my-own-hook.sh" "$BF/.claude/settings.json")
|
|
70
|
+
check "merge is idempotent" "1" "$N"
|
|
71
|
+
|
|
72
|
+
# ── update: refresh unmodified / protect modified ────────────────
|
|
73
|
+
echo "update"
|
|
74
|
+
echo "# local customization" >> "$TGT/.claude/hooks/guard-pre-bash.sh"
|
|
75
|
+
KEEP="$TGT/.claude/hooks/observe-log.sh"
|
|
76
|
+
ORIG_HASH=$(shasum -a 256 "$KEEP" | cut -d' ' -f1)
|
|
77
|
+
OUT=$(node "$CLI" update "$TGT" 2>&1)
|
|
78
|
+
check "skips locally modified file" "guard-pre-bash.sh" "$(printf '%s' "$OUT" | grep '!' || true)"
|
|
79
|
+
check "preserves the local change" "local customization" "$(cat "$TGT/.claude/hooks/guard-pre-bash.sh")"
|
|
80
|
+
check "unmodified files stay current" "$ORIG_HASH" "$(shasum -a 256 "$KEEP" | cut -d' ' -f1)"
|
|
81
|
+
check "states owned files untouched" "were not touched" "$OUT"
|
|
82
|
+
OUT=$(node "$CLI" update "$TGT" --force 2>&1)
|
|
83
|
+
check "--force overwrites modifications" "EMPTY" "$(grep -F 'local customization' "$TGT/.claude/hooks/guard-pre-bash.sh" || true)"
|
|
84
|
+
|
|
85
|
+
# ── doctor ───────────────────────────────────────────────────────
|
|
86
|
+
echo "doctor"
|
|
87
|
+
OUT=$(node "$CLI" doctor "$TGT" 2>&1)
|
|
88
|
+
check "diagnosis passes" "Diagnosis passed" "$OUT"
|
|
89
|
+
check "warns on placeholder CLAUDE.md" "warn" "$OUT"
|
|
90
|
+
rm "$TGT/.claude/hooks/stop-gate.sh"
|
|
91
|
+
OUT=$(node "$CLI" doctor "$TGT" 2>&1); RC=$?
|
|
92
|
+
check "detects missing file" "stop-gate.sh" "$(printf '%s' "$OUT" | grep FAIL)"
|
|
93
|
+
check "exit 1 on failure" "1" "$RC"
|
|
94
|
+
node "$CLI" update "$TGT" > /dev/null 2>&1 # restore
|
|
95
|
+
|
|
96
|
+
# ── uninstall ────────────────────────────────────────────────────
|
|
97
|
+
echo "uninstall"
|
|
98
|
+
OUT=$(node "$CLI" uninstall "$TGT" 2>&1); RC=$?
|
|
99
|
+
check "refuses without --yes" "1" "$RC"
|
|
100
|
+
OUT=$(node "$CLI" uninstall "$TGT" --yes 2>&1)
|
|
101
|
+
check "removes machinery" "EMPTY" "$([ -f "$TGT/.claude/hooks/observe-log.sh" ] && echo left)"
|
|
102
|
+
check "keeps CLAUDE.md" "yes" "$([ -f "$TGT/CLAUDE.md" ] && echo yes)"
|
|
103
|
+
check "keeps settings.json" "yes" "$([ -f "$TGT/.claude/settings.json" ] && echo yes)"
|
|
104
|
+
check "removes manifest" "EMPTY" "$([ -f "$TGT/.harness/kit-manifest.json" ] && echo left)"
|
|
105
|
+
|
|
106
|
+
# ── refuses to install into the kit repo itself ──────────────────
|
|
107
|
+
OUT=$(node "$CLI" init "$ROOT" 2>&1); RC=$?
|
|
108
|
+
check "refuses self-install" "1" "$RC"
|
|
109
|
+
|
|
110
|
+
echo
|
|
111
|
+
echo "passed: $PASS, failed: $FAIL"
|
|
112
|
+
[ "$FAIL" -eq 0 ] || exit 1
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "uni-harness",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Agent harness kit for Claude Code — installs verification sensors, destructive-command guards, checkpoint recovery, observability logs, and ratchet skills into your project",
|
|
5
|
+
"bin": {
|
|
6
|
+
"uni-harness": "bin/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin",
|
|
10
|
+
"CLAUDE.md",
|
|
11
|
+
".claude",
|
|
12
|
+
".harness/commands.env",
|
|
13
|
+
"harness",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=16"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/GAFFWC/uni-harness.git"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/GAFFWC/uni-harness#readme",
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/GAFFWC/uni-harness/issues"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"claude-code",
|
|
29
|
+
"agent",
|
|
30
|
+
"harness",
|
|
31
|
+
"hooks",
|
|
32
|
+
"ai-coding"
|
|
33
|
+
],
|
|
34
|
+
"license": "MIT"
|
|
35
|
+
}
|