@profoundry-us/highball 0.3.0 → 0.3.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.
package/ONBOARDING.md CHANGED
@@ -14,7 +14,7 @@ yourself.
14
14
 
15
15
  ## 0. Preconditions
16
16
 
17
- `npx highball --help` must work (Node >= 18, package installed as a dev
17
+ `npx @profoundry-us/highball --help` must work (Node >= 18, package installed as a dev
18
18
  dependency). If it doesn't, ask your human whether to install from the npm
19
19
  registry (`npm install --save-dev @profoundry-us/highball`) or from a
20
20
  local tarball path they provide. In a repo with no `package.json`, create
@@ -36,19 +36,57 @@ Answer these by reading, not assuming:
36
36
  repo *already trust* — look at `package.json` scripts, a `justfile` or
37
37
  `Makefile`, CI workflows, README instructions. Wire what exists; invent
38
38
  no new tooling in the first pass.
39
- - **Host or container?** If the toolchain runs in Docker (compose files, a
40
- devcontainer), find the dev service's name, then verify the repo mount
41
- and working directory by running `pwd` and `ls` through
42
- `docker compose exec -T <service>`. Rules will run there; you need to
43
- know what paths they see.
39
+ - **Host or container?** Settle this before writing a single rule see the
40
+ callout immediately below. Getting it wrong makes every rule fail for the
41
+ same uninteresting reason, and it is the most common way this setup
42
+ stalls.
44
43
  - **What's fast?** Time candidate commands. Only sub-~2s commands belong
45
44
  on the per-edit path; test suites belong at turn end; anything needing a
46
45
  live server or long setup should not gate turns at all (leave it to the
47
46
  repo's existing workflow, or declare it `todo`).
48
47
 
48
+ ### If the repo's toolchain lives in Docker
49
+
50
+ Plenty of repos run *everything* through containers — the host may have no
51
+ Ruby, no Python, no database at all. Highball handles this, but only if you
52
+ declare it. **The runner itself always stays on the host** (that's where the
53
+ hooks fire and where the journal lives); only the rule commands move.
54
+
55
+ Find the dev service and confirm what it actually sees, rather than assuming
56
+ the layout:
57
+
58
+ ```bash
59
+ docker compose ps --services
60
+ docker compose exec -T <service> sh -c 'pwd && ls'
61
+ ```
62
+
63
+ Then declare the wrapper once, and every rule runs through it:
64
+
65
+ ```yaml
66
+ exec:
67
+ via: docker compose exec -T --workdir /app app
68
+ ```
69
+
70
+ Four traps, each of which has bitten a real onboarding:
71
+
72
+ 1. **`-T` is mandatory.** Hook shells have no TTY; without it commands hang
73
+ or die with "the input device is not a TTY".
74
+ 2. **Set `--workdir`** to wherever the repo is mounted (verify with `pwd`
75
+ above). Containers frequently start somewhere other than the mount root.
76
+ 3. **Self-orchestrating commands must opt out with `exec: host`.** A
77
+ `just test` / `make test` target that runs its *own* `docker compose
78
+ exec` would otherwise be double-wrapped into nonsense.
79
+ 4. **Host-only tools opt out too.** If a linter or parser exists on the host
80
+ but not in the image (`node --check` against a JS bundle, say), mark that
81
+ rule `exec: host`.
82
+
83
+ A stopped container makes every wrapped rule fail. That's correct behavior —
84
+ unverifiable is not passing — but say so plainly to your human rather than
85
+ quietly dropping the rules.
86
+
49
87
  ## 2. Scaffold
50
88
 
51
- Run `npx highball init`. It never overwrites: an existing
89
+ Run `npx @profoundry-us/highball init`. It never overwrites: an existing
52
90
  `.highball/checks.yml` is kept, and if `.claude/settings.json` already
53
91
  exists it prints the hook snippet for you to merge by hand — merge it
54
92
  without disturbing existing hooks. Otherwise it creates both files.
@@ -110,7 +148,7 @@ Decision rules:
110
148
  You must never see, type, or store a token value. Ask your human to:
111
149
 
112
150
  1. Create this project (and a token for it) in their Highball app.
113
- 2. Run `npx highball login` themselves — interactively, or piping the
151
+ 2. Run `npx @profoundry-us/highball login` themselves — interactively, or piping the
114
152
  token via `--token-stdin` to keep it out of shell history.
115
153
 
116
154
  This stores the token in `~/.highball/credentials.json` (machine-local,
@@ -119,8 +157,8 @@ vars instead. The repo tree never contains a secret.
119
157
 
120
158
  ## 5. Verify — all four proofs, not just the happy path
121
159
 
122
- 1. **Fast path:** `npx highball run --fast` exits 0, every rule passed.
123
- 2. **Full path:** `npx highball run` exits 0 (or fails honestly on real
160
+ 1. **Fast path:** `npx @profoundry-us/highball run --fast` exits 0, every rule passed.
161
+ 2. **Full path:** `npx @profoundry-us/highball run` exits 0 (or fails honestly on real
124
162
  pre-existing issues — surface those to your human rather than papering
125
163
  over them).
126
164
  3. **The guardrail:** prove exit 2 works. Create an obviously-temporary
package/README.md CHANGED
@@ -13,11 +13,10 @@ block, they just aren't recorded.
13
13
 
14
14
  Published releases: `npm install --save-dev @profoundry-us/highball`.
15
15
 
16
- Install before running anything: with the package in `node_modules`, the
17
- short `npx highball …` form resolves to this runner's binary. Without it,
18
- bare `npx highball` would fetch the unrelated unscoped `highball` package
19
- from the registry for uninstalled one-offs, always use the scoped form
20
- (`npx @profoundry-us/highball <command>`).
16
+ **Always use the scoped name.** The unscoped npm name `highball` belongs to
17
+ an unrelated package, so a bare `npx highball` in a committed hook, a
18
+ README, or a one-off is a single uninstalled checkout away from fetching
19
+ a stranger's code and running it.
21
20
 
22
21
  From a local tarball (pre-release):
23
22
 
@@ -31,7 +30,7 @@ npm install --save-dev ../highball-runner/profoundry-us-highball-<v>.tgz
31
30
  Highball is installed *by the AI agent that will be checked by it*. After
32
31
  installing the package, tell the repo's Claude Code agent:
33
32
 
34
- > Run `npx highball onboard` and follow the instructions.
33
+ > Run `npx @profoundry-us/highball onboard` and follow the instructions.
35
34
 
36
35
  [ONBOARDING.md](ONBOARDING.md) (which that command prints) walks the agent
37
36
  through surveying the repo's real toolchain, scaffolding, writing rules that
@@ -41,8 +40,8 @@ human, and verifying all four proofs — including that exit 2 actually blocks.
41
40
  The pieces, for reference or manual setup:
42
41
 
43
42
  ```bash
44
- npx highball init # scaffolds .highball/checks.yml + Claude Code hooks
45
- npx highball login # stores this machine's project token (once per machine)
43
+ npx @profoundry-us/highball init # scaffolds checks.yml + Claude Code hooks
44
+ npx @profoundry-us/highball login # stores a project token (once per machine)
46
45
  ```
47
46
 
48
47
  `init` never overwrites an existing `checks.yml` and never edits an existing
@@ -95,9 +94,17 @@ in hosts that render Apps (Claude Desktop and friends), asking about your
95
94
  checks produces an interactive inline dashboard — click a run for per-rule
96
95
  detail with expandable command output, re-run fast or full checks from a
97
96
  button. In hosts without Apps support the same tools answer in plain text,
98
- per the extension's graceful-degradation rule. Register it as
99
- `command: npx`, `args: ["highball", "mcp"]` (or absolute paths for hosts
100
- that spawn outside your shell PATH).
97
+ per the extension's graceful-degradation rule. Register it with the scoped
98
+ name hosts spawn the server from an arbitrary directory, so it resolves
99
+ from the registry rather than a local install:
100
+
101
+ ```json
102
+ "highball": { "command": "npx", "args": ["-y", "@profoundry-us/highball", "mcp"] }
103
+ ```
104
+
105
+ The journal it reads is machine-global (`~/.highball/runs/`), so one
106
+ registration covers every repo on that machine — there is no per-repo MCP
107
+ setup.
101
108
 
102
109
  The split is capability-driven, not guesswork: the server reads the
103
110
  client's initialize capabilities (`io.modelcontextprotocol/ui`) — hosts
@@ -112,8 +119,8 @@ Desktop restart.
112
119
 
113
120
  Every run also appends to a local journal (`~/.highball/runs/<project>.jsonl`,
114
121
  pruned to the last 200) — unconditionally, whether or not reporting is
115
- configured. `npx highball runs` lists recent runs; `npx highball runs 3`
116
- shows one run's detail with failure output, and `--logs` prints every
122
+ configured. `npx @profoundry-us/highball runs` lists recent runs; adding a
123
+ number shows one run's detail with failure output, and `--logs` prints every
117
124
  rule's captured output, GitHub-Actions-style — the journal keeps the last
118
125
  10KB per rule, pass or fail, while the dashboard receives failure tails
119
126
  only. So the runner is self-sufficient out of the box: the hosted
package/bin/highball.js CHANGED
File without changes
package/lib/init.js CHANGED
@@ -36,17 +36,20 @@ checks:
36
36
  # fast: true
37
37
  `;
38
38
 
39
+ // Always the SCOPED command. The unscoped npm name belongs to an unrelated
40
+ // package, so a bare `npx highball` in a committed hook is one uninstalled
41
+ // checkout away from fetching a stranger's code and running it on every edit.
39
42
  const HOOKS_JSON = {
40
43
  hooks: {
41
44
  PostToolUse: [
42
45
  {
43
46
  matcher: "Write|Edit",
44
- hooks: [{ type: "command", command: "npx highball run --fast" }]
47
+ hooks: [{ type: "command", command: "npx @profoundry-us/highball run --fast" }]
45
48
  }
46
49
  ],
47
50
  Stop: [
48
51
  {
49
- hooks: [{ type: "command", command: "npx highball run", timeout: 900 }]
52
+ hooks: [{ type: "command", command: "npx @profoundry-us/highball run", timeout: 900 }]
50
53
  }
51
54
  ]
52
55
  }
package/lib/run.js CHANGED
@@ -137,11 +137,25 @@ export async function run(args) {
137
137
  // friends); that id groups this run with the rest of the agent's session
138
138
  // on the dashboard. A TTY means a human at a terminal — don't block on
139
139
  // read.
140
+ //
141
+ // The deadline matters: a non-TTY stdin that nobody writes to and nobody
142
+ // closes (a pipeline, a task runner, a CI step) would otherwise hang the
143
+ // runner forever waiting for EOF. Hooks write their payload immediately,
144
+ // so a short wait costs nothing and turns an indefinite hang into a run
145
+ // with no session context.
146
+ const HOOK_STDIN_DEADLINE_MS = 400;
147
+
140
148
  async function readHookPayload() {
141
149
  if (process.stdin.isTTY) return {};
142
150
  try {
143
- let text = "";
144
- for await (const chunk of process.stdin) text += chunk;
151
+ const text = await Promise.race([
152
+ (async () => {
153
+ let buffered = "";
154
+ for await (const chunk of process.stdin) buffered += chunk;
155
+ return buffered;
156
+ })(),
157
+ new Promise((resolve) => setTimeout(() => resolve(""), HOOK_STDIN_DEADLINE_MS))
158
+ ]);
145
159
  return JSON.parse(text);
146
160
  } catch {
147
161
  return {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@profoundry-us/highball",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Highball runner — local CI for AI coding agents: runs a repo's .highball/checks.yml rules, blocks the agent on failure, and reports runs to a Highball dashboard.",
5
5
  "keywords": [
6
6
  "ai",