uni-harness 0.1.0 → 0.1.2

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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: harness-init
3
- description: Scan the repository of a new project to fill in the PROJECT section of CLAUDE.md and .harness/commands.env, and propose initial RULES/ANTI-PATTERNS. Run once right after installing the harness, or whenever CLAUDE.md is still in placeholder state. Verifies detected commands by actually running them before applying.
3
+ description: Scan the repository to fill in the PROJECT section of CLAUDE.md and .harness/commands.env, and propose initial RULES/ANTI-PATTERNS. Run once right after installing the harness, or whenever CLAUDE.md is still in placeholder state. Verifies detected commands by actually running them before applying. If the repo has no project code yet (greenfield), offers to scaffold a minimal project so the sensors start active from the first line of code.
4
4
  ---
5
5
 
6
6
  # Harness Init — Initialize the Harness by Scanning the Repo
@@ -11,6 +11,16 @@ running them.
11
11
 
12
12
  ## Step 1: Scan
13
13
 
14
+ - **Exclude the harness's own files first.** Everything the installer put
15
+ here — `.claude/`, `.harness/`, `harness/` — is the kit's machinery, not
16
+ the project. Never let it drive language detection, and never propose the
17
+ kit's self-test (`harness/tests/test_hooks.sh`) as the project's TEST_CMD:
18
+ it verifies the harness, not the user's code, and pointing the stop gate
19
+ at it would "pass" every turn regardless of what broke.
20
+ - The scan decides which mode you are in: project code exists → continue
21
+ with Step 2 (and the brownfield section if the project predates the
22
+ harness). Nothing but harness files → switch to the **New Projects
23
+ (greenfield)** section below. Do not stop at "nothing to configure".
14
24
  - Language/version: from manifests (package.json, pyproject.toml, go.mod,
15
25
  Cargo.toml, *.csproj, ...). Prefer versions pinned in lockfiles/config.
16
26
  - BUILD / TEST / LINT candidates: collect from manifest scripts, Makefile,
@@ -38,6 +48,32 @@ running them.
38
48
  truisms ("write good code") are forbidden — only things observable in
39
49
  this repository.
40
50
 
51
+ ## New Projects (greenfield — the harness was installed before any code)
52
+
53
+ If the scan finds no project code at all (no manifest, no sources, no CI),
54
+ the user is starting a project *with* the harness — the whole point is
55
+ that sensors guard the very first code written. Ending with "come back
56
+ later" leaves them off during the riskiest phase. Instead, offer to
57
+ bootstrap:
58
+
59
+ 1. **Ask, don't guess:** what is being built, which language/runtime,
60
+ which framework and test runner (offer the ecosystem defaults, e.g.
61
+ pytest / vitest / go test).
62
+ 2. **Propose a minimal scaffold** and apply it only after approval: the
63
+ manifest (package.json / pyproject.toml / go.mod ...), a source entry
64
+ point, a test setup with **one real passing test**, and a
65
+ linter/formatter config if the ecosystem has a standard one. Announce
66
+ any dependency installs with the reason before running them.
67
+ 3. If it is not a git repository, offer `git init` (the guard hooks and
68
+ future ANTI-PATTERNS mining assume git history).
69
+ 4. Then continue with Step 2 exactly as usual: run the scaffold's
70
+ BUILD/TEST/LINT commands, confirm they pass, and fill CLAUDE.md and
71
+ commands.env with the **verified** commands.
72
+
73
+ The exit criterion for greenfield: sensors ACTIVE against a real (if tiny)
74
+ test suite. If the user declines the scaffold, leave everything blank as
75
+ before and tell them to re-run /harness-init once code exists.
76
+
41
77
  ## Existing Projects (brownfield)
42
78
 
43
79
  - If CLAUDE.md existed before the harness was installed, never replace it.
package/bin/cli.js CHANGED
@@ -27,6 +27,10 @@ const { spawnSync } = require('child_process');
27
27
 
28
28
  const PKG_ROOT = path.resolve(__dirname, '..');
29
29
  const MACHINERY_DIRS = ['.claude/hooks', '.claude/skills', '.claude/agents', 'harness'];
30
+ // Files that live in these dirs but belong to the kit repository only.
31
+ // test_installer.sh tests bin/cli.js, which is never installed into target
32
+ // projects — shipping it there guarantees a failing test suite.
33
+ const KIT_ONLY_FILES = new Set(['harness/tests/test_installer.sh']);
30
34
  const MANIFEST = '.harness/kit-manifest.json';
31
35
  const KIT_VERSION = require(path.join(PKG_ROOT, 'package.json')).version;
32
36
 
@@ -53,7 +57,9 @@ function machineryFiles() {
53
57
  const files = [];
54
58
  for (const d of MACHINERY_DIRS) {
55
59
  for (const f of walk(path.join(PKG_ROOT, d))) {
56
- files.push(path.relative(PKG_ROOT, f));
60
+ const rel = path.relative(PKG_ROOT, f).split(path.sep).join('/');
61
+ if (KIT_ONLY_FILES.has(rel)) continue;
62
+ files.push(rel);
57
63
  }
58
64
  }
59
65
  return files.sort();
@@ -40,6 +40,9 @@ check "writes manifest" "yes" "$([ -f "$TGT/.harness/kit-manifest.json" ] && ech
40
40
  check "gitignores logs" ".harness/logs/" "$(cat "$TGT/.gitignore")"
41
41
  check "init runs hook tests" "Hook tests passed" "$OUT"
42
42
  check "prints next step" "/harness-init" "$OUT"
43
+ # regression: test_installer.sh tests bin/cli.js, which only exists in the
44
+ # npm package — shipping it to targets guarantees a failing suite there
45
+ check "does not ship test_installer.sh" "EMPTY" "$([ -f "$TGT/harness/tests/test_installer.sh" ] && echo left)"
43
46
 
44
47
  # ── brownfield init: existing project files are preserved ────────
45
48
  echo "init (brownfield)"
@@ -82,6 +85,22 @@ check "states owned files untouched" "were not touched" "$OUT"
82
85
  OUT=$(node "$CLI" update "$TGT" --force 2>&1)
83
86
  check "--force overwrites modifications" "EMPTY" "$(grep -F 'local customization' "$TGT/.claude/hooks/guard-pre-bash.sh" || true)"
84
87
 
88
+ # simulate a v0.1.0 install that shipped test_installer.sh: an unmodified
89
+ # manifest-tracked copy must be cleaned up by update
90
+ cp "$ROOT/harness/tests/test_installer.sh" "$TGT/harness/tests/test_installer.sh"
91
+ python3 - "$TGT" <<'EOF'
92
+ import json, sys, hashlib, os
93
+ t = sys.argv[1]
94
+ mp = os.path.join(t, '.harness/kit-manifest.json')
95
+ m = json.load(open(mp))
96
+ p = os.path.join(t, 'harness/tests/test_installer.sh')
97
+ m['files']['harness/tests/test_installer.sh'] = hashlib.sha256(open(p, 'rb').read()).hexdigest()
98
+ json.dump(m, open(mp, 'w'), indent=2)
99
+ EOF
100
+ OUT=$(node "$CLI" update "$TGT" 2>&1)
101
+ check "update removes stale kit-only file" "removed from kit" "$OUT"
102
+ check "stale file is gone" "EMPTY" "$([ -f "$TGT/harness/tests/test_installer.sh" ] && echo left)"
103
+
85
104
  # ── doctor ───────────────────────────────────────────────────────
86
105
  echo "doctor"
87
106
  OUT=$(node "$CLI" doctor "$TGT" 2>&1)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uni-harness",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
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
5
  "bin": {
6
6
  "uni-harness": "bin/cli.js"