Worktree-first development + agent experience overhaul#54
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All agent instruction surfaces (AGENTS.md/CLAUDE.md snippet, Codex skill, Cursor rules, primer) now render from src/agents/instruction-source.ts. setup sync --check detects stale marker content, not just marker presence. Tickets removed from default instructions; linked-worktree guidance added. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Secret keys never silently diverge (shared-location writes, primary-key adoption, loud fallback warnings). hack up/down/restart/ps/logs/open default to a branch instance named for the worktree branch (opt out: worktree.auto_branch=false or explicit --branch). hack doctor gains divergent-key and cross-checkout instance checks. Registry tracks sibling worktree checkouts, shown in hack projects --details/--json. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Isolated (HACK_HOME) end-to-end suite driving the working-tree CLI: tier 1 (init, env secrets, worktree key sharing, registry dedupe, branch defaulting, docs sync drift, doctor, non-TTY hygiene) and an opt-in docker tier (up/down, lifecycle hooks, parallel primary+worktree branch instances). bun run test:e2e:local / test:e2e:local:docker. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9873feb6eb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return; | ||
| } | ||
|
|
||
| await ensureHackDirGitignore({ projectDir: project.projectDir }); |
There was a problem hiding this comment.
Add the root ignore when untracking the env key
When hack doctor --fix finds .hack.secret.key tracked, this fix only untracks it and ensures the nested .hack/.gitignore; that file cannot ignore the root-level secret key. The git rm --cached leaves the leaked key on disk as an unignored ?? .hack.secret.key, so after committing the removal it can be accidentally re-added. The repair should also add the root .gitignore entry for .hack.secret.key before reporting the generated-file fix complete.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in d6456a1: doctor --fix now ensures the .hack.secret.key entry in the root .gitignore when untracking a leaked key (the nested .hack/.gitignore can't cover repo-root files), with a regression test asserting git check-ignore passes and the key no longer appears as untracked.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e9e9ae0fbf
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "macos:ghostty:bundle": "cd ../.. && bun run scripts/macos-ghostty-bundle.ts", | ||
| "typecheck": "bunx tsc -p tsconfig.json --noEmit", | ||
| "test": "cd ../.. && bun run scripts/run-tests-deterministic.ts", | ||
| "test": "cd ../.. && bun test", |
There was a problem hiding this comment.
Restore deterministic test isolation
Changing the package test script to raw bun test makes the suite run shared-process tests concurrently; bun test --help reports --max-concurrency defaults to 20, while many existing tests mutate process.env, PATH, HOME, and stdout/stderr capture globally. I reproduced this by running bun test tests/cli-result.test.ts tests/lifecycle-json.test.ts tests/interactivity.test.ts tests/hack-gitignore.test.ts tests/run-exec-branch-default.test.ts tests/doctor-generated-files.test.ts tests/projects-registry-worktrees.test.ts, which fails the linked-worktree hack run tests because their stubbed PATH/HOME are raced by other files. The deleted deterministic runner existed to isolate exactly this kind of cross-file bleed, so bun run test/CI is now flaky or failing.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Checked and refuting this one: --max-concurrency (default 20) only applies to test.concurrent() tests — plain bun test runs files and tests serially in a single process (bun --help: '--concurrent: Treat all tests as test.concurrent() tests'; the repo uses no test.concurrent anywhere). The serial single-process model is exactly why file-scope mock.module used to poison the whole run. The deterministic runner existed to isolate those mock.module files into separate processes; that root cause was removed (scoped dispatching mocks in tests/helpers/scoped-module-mock.ts), and the full suite now passes repeatedly under shuffled file order (783+/0). Env-mutation hygiene is enforced by order-independence, not process isolation.
| if (jsonRequested) { | ||
| emitCliResult({ result: errorResultFromUnknown({ error }) }); | ||
| return 1; |
There was a problem hiding this comment.
Preserve JSON envelopes for usage errors
This JSON fallback is bypassed for CliUsageError because the earlier catch branch prints the styled error/help and returns first. For example, hack up --json --definitely-bad writes non-JSON help text to stdout, even though the CLI reference says commands with --json emit a {ok, data, error} envelope on stdout. Automation that parses JSON from up/down/restart/doctor --json will break on option typos or validation errors, so usage errors need to be rendered through the JSON path when --json was requested.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 7da751e: usage errors under --json now emit {ok:false, error:{code:"E_USAGE", message}} on stdout (help text skipped, message still on stderr), with a regression test asserting pure-JSON stdout for hack up --json --definitely-not-a-flag.
resolveGlobalHackDir() in config-paths is now the single seam for ~/.hack resolution (HACK_HOME env > HOME > homedir), migrated across 26 files: registries, daemon paths, local CA/PKI, secret store, bundled binaries, caddy/cloudflared state, logs. Non-hack home paths (ssh, tmux, ~/.claude, ~/.cursor, launchd plist) intentionally untouched. Unset HACK_HOME is byte-identical to prior behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… docs phase) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
File-scope mock.module poisons every file in a bun test run (module scopes all evaluate before any test executes). Replaced all 10 uses with a scoped dispatching mock helper (activate/deactivate per file), fixed isTTY stub restoration leaks, and reset the gum path cache between tests. Full suite: 642 pass/41 fail -> 683 pass/0 fail, stable under shuffled file order. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ting
--json on up/down/restart/doctor with {ok,data,error:{code,message}}
envelope and typed HackErrorCode union. Global --no-interactive flag +
HACK_NO_INTERACTIVE with fail-fast E_INTERACTIVE_REQUIRED and safe
defaults. Daemon autostart no longer hangs non-TTY callers (detached
spawn, bounded wait, docker fallback). NO_COLOR honored across gum/
logger/help; doctor spinner TTY-only. Experimental (Beta) commands
hidden behind 'hack help --all' with stderr banner. hack run/exec get
linked-worktree branch defaulting. Registry records worktree siblings
on the projects/status touch path. Instruction source gains the
'Running things' decision guide.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AGENTS.md/CLAUDE.md marker blocks re-synced from the instruction source (worktrees + running-things guides, no tickets). Tickets extension disabled in project config; tickets doc blocks and project codex skill removed. Default-scope phase-out of the tickets integration lands separately. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…erated files Machine-local generated files (.internal/, .branch/, .env, .env.state.json, local env overlays) are ignored via a committed, self-healing .hack/.gitignore that every clone and worktree inherits. Env-local ignores no longer write to machine-local .git/info/exclude. hack doctor flags generated files tracked in git (tracked secret key escalates with rotation advice) and doctor --fix untracks them. E2E scenarios updated: nested gitignore assertion, worktree registry recording now a hard assertion. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tickets integrations (skill install/check, doc upsert) now apply only when the tickets extension is enabled for the scope; hack setup tickets notes optional/legacy status. hack projects prune removes registry entries with missing repo roots and works with docker down; --json emits the standard envelope. findExecutableInPath honors runtime PATH. Deterministic test runner deleted (plain bun test is order-independent now). Planet animation data lazily imported out of the startup module graph. Stale github/linear extension entries disabled in this repo's config. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
hack init --with claude|codex|both hands a canonical onboarding prompt to the agent CLI (or prints a copy-paste block when unavailable or non-interactive). hack agent onboard emits it for existing projects. Thin /hack-init skills install with hack setup claude|codex; MCP serves a hack-init prompt. Content is one module: inventory, compose + caddy setup, deps-container and ops-container patterns, running-things guide (referenced from the instruction source), verification loop. docs/guides/agent-first-setup.md carries the bootstrap block. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
docs/reference/cli.md renders from CLI_SPEC (bun run docs:cli-reference); a test fails whenever the committed reference no longer matches the spec, so flag/command docs cannot rot. Experimental commands listed but not expanded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Interface/behavior changes must update docs in the same patch; CLI reference regenerates via bun run docs:cli-reference; agent-facing phrasing changes go through instruction-source + setup sync. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Audit-driven rewrite of 27 files: docs-ia and cli.md rebuilt (cli.md defers exhaustive flags to the generated reference), 7 architecture stale claims fixed, env/sessions/lifecycle corrected (secret-key inheritance order, tmux-only daemon sessions, envelope error codes), experimental banner standardized across all remote/gateway docs, tickets reframed as opt-in. Every relative link and documented command verified against the generated CLI reference. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Lead with the concrete promise (every project at its own local HTTPS URL, one command to run any repo), five what-you-get pillars, tight quick start and everyday commands. Deep material moves to docs/; tickets removed from the README entirely (opt-in extension, covered in docs). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comparison table: the duct-tape stack hack replaces (domains+TLS, secrets, run scripts, tunnels, persisted logs, parallel branches, workspaces) and why the pieces composing around one project model is the point. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Portless framing woven in ('the URL is just the front door'), the
one-project-model claim as the table capstone, and a new 'Your
environment, anywhere' section: committed + isolated environments make
remote coding sandboxes (Codex, Claude Code, Cursor, custom) zero-work
— install hack, pass HACK_ENV_SECRET_KEY, hack up.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Audit fixes: tickets opt-in (setup enables it; removed from the intro promise), smoke-script mechanics corrected (temp fixture + CI-built image), installer described accurately (two-stage, pinning knobs, HACK_ASSETS_DIR, image env defaults), remote-node pointer marked unsupported experimental. Added --no-interactive/NO_COLOR/--json guidance for container scripts, worktree key-inheritance cross-link, full slim-gated global list, and bun version read from .tool-versions instead of a hardcoded pin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t key Bun.spawn without an explicit env resolves argv[0] against the startup PATH snapshot — CI (no real docker) failed where dev machines masked it. buildSpawnEnv now always passes the live process env. That exposed a real test leak: remote-caddy-routes restored HOME from an unset per-test capture, unsetting HOME for the rest of the run. doctor --fix now also ensures the root .gitignore entry when untracking a leaked .hack.secret.key (nested .hack/.gitignore cannot cover a repo-root file) — from PR review. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Headline no longer leads with the URL (which invited the port-tool pigeonhole); the isolation frame moves into the intro. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
8760e9f to
d82ae58
Compare
CliUsageError returned before the jsonRequested check, printing styled
help to stdout. Usage errors now emit {ok:false, error:{code:E_USAGE}}
with the message on stderr; error handling extracted to a helper.
From PR review.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Logger backends write to stdout on hosts without gum, corrupting the parseable envelope (caught by CI, invisible on dev machines). The envelope already carries the message. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ed08dc0d79
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const detach = args.options.detach; | ||
| // `--json` implies detach: machine-readable output requires the command to | ||
| // return instead of streaming compose logs on stdout. | ||
| const detach = args.options.detach || json; |
There was a problem hiding this comment.
Keep compose output out of JSON stdout
When hack up --json reaches this new detached path, it still calls composeRuntimeBackend.up, and the detached backend delegates to run() whose stdout is inherited (src/backends/runtime-backend.ts -> src/lib/shell.ts). Docker Compose's normal progress/status lines are therefore written to stdout before emitLifecycleResult prints the envelope; down --json and restart --json take the same inherited-output paths. Any automation expecting stdout to be a single JSON object will fail even on successful lifecycle commands.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed in PR #59 (or as noted): src/lib/shell.ts run() gains fd-2 stdout routing, threaded through composeRuntimeBackend and all four lifecycle call sites so up/down/restart --json keep stdout as a single parseable envelope. Plumbing pinned by tests in tests/runtime-backend.test.ts.
| return { branch: null, source: "none", gitBranch: null }; | ||
| } | ||
|
|
||
| const slug = sanitizeBranchSlug(gitBranch); |
There was a problem hiding this comment.
Avoid colliding auto branch slugs
When two linked worktrees are on branches such as feature/api and feature-api, both sanitize to feature-api here, so hack up/run/exec defaults them to the same compose project and branch host instead of separate branch instances. That breaks parallel worktree usage and can target or stop the other checkout's containers; the automatic default needs a collision-resistant suffix/detection path, or it should require an explicit unique --branch when the sanitized slug collides.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed in PR #59 (or as noted): resolveEffectiveBranch now detects sanitization collisions against sibling worktrees' branches and deterministically suffixes both sides with a 4-hex hash of the raw branch name (feature/api -> feature-api-XXXX); non-colliding slugs unchanged, stable across invocations. Tested with a real two-worktree fixture.
## 3.1.0 (2026-07-07) * Merge pull request #52 from hack-dance/blacksmith-migration-6707f0f ([6774244](6774244)), closes [#52](#52) * Merge pull request #54 from hack-dance/feat/worktree-ax-overhaul ([1fc0715](1fc0715)), closes [#54](#54) * Migrate workflows to Blacksmith ([fa30afb](fa30afb)) * Rewrite README for the v3 local-first product ([7da1cc2](7da1cc2)) * fix: spawn resolves runtime PATH; doctor --fix root-ignores the secret key ([fd7eb27](fd7eb27)) * fix(cli): usage errors emit the JSON envelope under --json ([7da751e](7da751e)) * fix(cli): usage-error JSON envelope emits without logger output ([ed08dc0](ed08dc0)) * docs: carry the positioning line into the docs index ([99a4b96](99a4b96)) * docs: extend overhaul plan (run/exec defaulting, onboarding surfaces, docs phase) ([edf7de9](edf7de9)) * docs: full accuracy pass against the v3 surface ([93ae8c0](93ae8c0)) * docs: non-negotiable docs-currency rule across instruction surfaces ([e9e013e](e9e013e)) * docs: plan for worktree DX + agent experience overhaul ([d32e743](d32e743)) * docs(cli): generated command reference with drift enforcement ([4709d89](4709d89)) * docs(env): deprecation marker on legacy v2 env migrator ([aa1d305](aa1d305)) * docs(guides): refresh managed-environments guide for the v3 surface ([47ca25a](47ca25a)) * docs(readme): differentiate the environment, not the port ([3c3f502](3c3f502)) * docs(readme): lead with the whole-environment claim ([d82ae58](d82ae58)) * docs(readme): rewrite around the core value proposition ([a83c0fb](a83c0fb)) * docs(readme): sharpen differentiation and add the portability story ([99e08fd](99e08fd)) * feat(agents): agent-assisted onboarding across CLI, skills, and MCP ([644f818](644f818)) * feat(agents): single instruction source with drift detection ([3660c27](3660c27)) * feat(cli): machine-first surface — JSON envelope, non-interactive, gating ([346b2c8](346b2c8)) * feat(core): add HACK_HOME override for the global hack directory ([58a023c](58a023c)) * feat(project): committed .hack/.gitignore + doctor untracking for generated files ([b575b78](b575b78)) * feat(worktree): first-class linked-worktree development ([abc695d](abc695d)) * chore: refresh agent docs and de-register tickets for this repo ([9c150b6](9c150b6)) * chore: tickets opt-in by default, registry prune, CI + perf cleanup ([1dd2b2f](1dd2b2f)) * test: make suite order-independent via scoped module mocks ([b36a312](b36a312)) * test(e2e): real CLI harness with turborepo fixture + worktree scenarios ([83f3e17](83f3e17))
What this is
A full pass on making hack excellent for worktree-based, agent-driven development: worktree workflows that just work, a single drift-enforced source for agent instructions, a machine-first CLI surface, agent-assisted onboarding, tickets phase-out, a real e2e harness, and a docs overhaul with generated reference.
Highlights
Linked worktrees are first-class
HACK_ENV_SECRET_KEYfor CIhack up/down/restart/ps/logs/open/run/execin a linked worktree default to a branch instance named for the worktree branch (worktree.auto_branch=falseor explicit--branchto opt out)hack doctorflags divergent keys, cross-checkout dev_host collisions, and generated files tracked in git (doctor --fixuntracks)hack projects --details)Machine-first CLI (AX)
--jsononup/down/restart/doctorwith a{ok, data, error: {code, message}}envelope and typedE_*codes--no-interactive/HACK_NO_INTERACTIVE; safe defaults or fail-fastE_INTERACTIVE_REQUIREDNO_COLORhonored; no ANSI leaks to piped stdouthack help --all+ warning bannerOne source of truth for agent instructions
src/agents/instruction-source.tsrenders AGENTS.md/CLAUDE.md snippets, Codex skill, Cursor rules, and the primersetup sync --checknow detects stale content, not just marker presence; drift tests validate every documented command againstCLI_SPECAgent-assisted onboarding
hack init --with claude|codex|both,hack agent onboard,/hack-initskills, and an MCP prompt — all from one prompt module (inventory → compose+caddy setup → deps-container/ops-container patterns → running-things guide → verification loop)Housekeeping
.hack/.gitignoreends generated-file leaks into git historyHACK_HOMEoverride for full global-state isolation;hack projects prune; unit suite made order-independent (642/41 → 783/0); deterministic CI runner deleteddocs/reference/cli.mdwith a drift test + non-negotiable docs-currency ruleVerification
tests/e2e/): 11/11 including docker tier — real up/down, lifecycle singleton adoption, and primary+worktree running in parallel as separate branch instances — against the dev wrapper installed globallydoctor --jsonenvelope, 0 errors;projects --jsonnon-TTY 0.37shack setup sync --all-scopes --check: cleanFollow-ups (non-blocking)
docs/guides/codex-managed-environments.mdminor-stale refresh (task chip filed)tickets gitcheck should skip when the extension is disabled--jsonspreads to more commands🤖 Generated with Claude Code