Skip to content

fix(agents): build every onboarding prompt from one discovery-first builder - #322

Merged
roodboi merged 9 commits into
nextfrom
wf/e2-onboarding-builder-b14
Oct 14, 2026
Merged

roodboi merged 9 commits into
nextfrom
wf/e2-onboarding-builder-b14

Conversation

@roodboi

@roodboi roodboi commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Final behavior

Issue: HACK-1137 (unit E2).

  • hack init --with, hack agent onboard, hack agent init and the hack-init MCP prompt all call one builder in src/agents/onboarding-prompt.ts (buildOnboardingPrompt). Each one embeds the same repo inventory from buildInitAssistantReport (the corrected W12/fix(agents): correct onboarding inventory and harden onboarding prompt #291 inventory: bun.lock/bun.lockb, nested workspace lockfiles and package-manager conflicts, non-JS runtimes, runtime pins by name, and explicit discovery limits).
  • Discovery first: a ## Repo inventory (pre-computed by Hack) section comes before Phase 1. Phase 1 now starts by confirming that inventory.
  • Each prompt is exactly frame.before + core + frame.after. renderOnboardingCore produces the core, which is byte-identical for every entry point on the same repo. renderOnboardingFrame owns the only per-entry text. That text is fixed and never repeats repo values:
    • init --with: one leading line that says whether this run scaffolded .hack/ or skipped init because .hack/ already existed.
    • agent init: keeps its trailing hack agent patterns pointer.
    • agent onboard and MCP: no framing. The MCP message envelope is unchanged.
  • Repo state alone sets mode, project name and dev host: existing-project when a .hack/ project is found (values from its config), otherwise new-project. The repo root is the enclosing hack project root, or the hack init root when there is no project.
  • init-assistant.ts no longer assembles a prompt. It exports renderInitAssistantInventory, and only the builder calls it.
  • MCP hack-init gains an optional repoRoot argument. Relative values resolve against the session cwd. With repoRoot, the prompt matches hack agent onboard for that repo, and mode/projectName/devHost are ignored. Without it, nothing is scanned (the server cwd may be HOME or /) and the core states Not run: … named no repoRoot. Inventory failures return closed, value-free protocol errors that never echo the path, config values or the underlying message: E_PROJECT_NOT_FOUND: Path not found., or <HackErrorCode>: hack-init could not inventory the repo..

Behavior changes to review

  • hack init --with after a fresh scaffold now hands off in existing-project mode, with the header "adopt the existing hack setup" and the instruction "refine .hack/, do not re-run init". Before, it said "stand up hack" and told the agent to run hack init --auto. The framing line says the scaffold is new. A post-init handoff always runs with .hack/ present, so this is the only way it can share a core with agent onboard.
  • An existing setup with no configured dev_host now shows the placeholder <dev_host> instead of a guessed <slug>.hack or the new-project <project>.hack.local default. The removed resolveExistingInitHost was the only producer of the guess.
  • hack agent init now reads .hack/ config (name/dev host), so a malformed config fails it the same way it already failed hack agent onboard.
  • agent onboard, init --with and the MCP prompt now include package.json script commands (the inventory's dev and setup scripts), which only agent init showed before.

No canonical guidance render changed (the /hack-init skill, plugins and instruction sources are untouched), so plugin generation, fingerprints and the revision are unchanged. The CLI surface is unchanged, so docs/reference/cli.md was not regenerated. Docs updated: docs/guides/agent-first-setup.md and docs/agent-guidance.md.

Validation

  • New tests/onboarding-entry-parity.test.ts:
    • The CLI entry points run as source-CLI children through privateCliEnv (private HOME, isolated Docker client, empty PATH, HACK_NO_INTERACTIVE=1). The MCP prompt runs in-process over InMemoryTransport, so it also runs in CI.
    • On a fixture repo before init, agent onboard, agent init and MCP (absolute and relative repoRoot) all equal frame + renderOnboardingCore(collectOnboardingContext(repo)).
    • After init --auto --with claude (scaffolded) and a second init --auto --with codex (existing), both handoffs plus agent onboard, agent init --path apps/web and MCP (with conflicting caller mode, name and host) equal one shared core.
    • Content: the corrected inventory lines and explicit limits are present. Pin values and env-file contents are absent.
    • MCP parity without repoRoot (the not-run core). Closed MCP refusals (missing path; native marker) never echo the path.
    • Static guard: only the builder module renders the core or inventory, and the three callers use buildOnboardingPrompt(.
  • Updated tests: onboarding-prompt, init-assistant, setup, init-with-command.
  • bun test on 15 focused files: onboarding, init-assistant, setup, init-with-command, agent-onboard-command, mcp-prompts, onboarding-handoff, integration-ownership, cli-spawn-env-isolation, agent-instruction-source, agent-plugin-generation, mcp, mcp-session-context, mcp-inspect-tools. Result: 163 pass, 0 fail.
  • bun run --cwd packages/cli typecheck: 0 errors. bun run --cwd packages/cli check: 0 errors (the existing warnings are not in changed files). The changed tests were linted explicitly and are clean.

Negative controls (source mutations, each restored afterward)

Each mutation below was killed by the parity test file:

  • agent onboard renders without the inventory (the pre-change shape): parity test fails, and so does the static guard.
  • agent init wraps the shared prompt in its own title: parity test fails.
  • init --with frames an existing .hack/ as freshly scaffolded: parity test fails.
  • MCP ignores repoRoot: parity and refusal tests fail.
  • MCP caller projectName leaks into the repo-derived core: parity test fails.
  • An MCP refusal echoes the caller path: the refusal test fails.

The in-file negative-control test also checks that a dropped inventory signal, a missing inventory, a leaked frame, or a missing frame each produce a parity issue.

One mutation survived, as expected. Having agent init pass the hack init root instead of startDir is equivalent behavior: the builder resolves the enclosing project root itself.

Remaining gaps

  • The full bun test suite (CI mode) had not finished locally when this PR opened, so hosted CI is the gate for the rest of the suite.
  • No real agent client (Claude Code or Codex) was launched with the new prompt. The tests cover only the printed fallback and the MCP protocol response.
  • MCP repoRoot scanning is bounded only by the inventory's existing scan caps. A caller who passes / or HOME gets the same scan cost that hack agent init --path / has today.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

roodboi and others added 8 commits October 12, 2026 22:27
…mpt and add the env secrets threat model

The `hack global dns activate|deactivate` authorize closures move into
createNativeDnsAuthorizer (src/lib/native-domain-dns-authorization.ts) with
injectable interactivity, confirmation and administrator prompt. A failed
sudo -v now throws NativeDnsAdministratorAuthorizationError (closed
outcome "authorization-failed" and failure class; message unchanged).

Tests wire the authorizer into the real activation/deactivation flows and
host effect boundary and prove: sudo -v runs before every privileged and
file effect, a failed prompt is authorization-failed (not denied) and
changes nothing, and a decline or a non-interactive run never reaches sudo.

Adds docs/security/env-secrets-threat-model.md (HACK-1127), linked from
docs/env.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…efaults

tests/global-dns-authorizer-wiring.test.ts checks, on the TypeScript
syntax tree of src/commands/global.ts, that handleGlobalDnsActivate and
handleGlobalDnsDeactivate each build exactly one authorizer with
createNativeDnsAuthorizer({ action: <own action>, domain }), pass it to
createNativeDnsHostDependencies, and run their flow with those
dependencies. The only other authorizer allowed is the decline-only
`async () => false` on the deactivate verify-only path.

tests/native-domain-dns-authorization.test.ts now also covers the
production defaults: the default interactivity check requires a stdin
TTY before confirming, and the default confirmation declines an
explicitly non-interactive run before sudo. The administrator prompt
stays a fake, so no test reaches real sudo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uilder

`hack init --with`, `hack agent onboard`, `hack agent init` and the
`hack-init` MCP prompt now call one builder in
src/agents/onboarding-prompt.ts. It runs the corrected repo inventory
(bun.lock, nested workspace lockfiles, non-JS runtimes, runtime pins,
explicit discovery limits) and renders it before the phases. Each prompt
is exactly frame.before + core + frame.after; the core is byte-identical
for the same repo and only a fixed, value-free framing line differs.

Mode, project name and dev host now follow repo state, so the init
handoff always adopts the `.hack/` it runs after; its framing says
whether this run scaffolded it. The MCP prompt takes an optional
repoRoot; without it nothing is scanned and the core says no inventory
ran. MCP inventory failures return closed, value-free errors.

HACK-1137

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Authored .hack/hack.project.json projects now receive the same HACK_*
runtime host metadata as Compose-file and native image projects, on the
authored native path (up/restart; exec and run inherit) and the authored
Compose renderer.

The config compiler accepts optional env_metadata.runtime values for
declared workloads (closed reserved key set, 64 KiB per value, no NUL)
and reports them as public `runtime` bindings below managed values and
every authored directive. Host entries never receive them. The values
come from the shared runtime-host-metadata builder, fed with the
compiler's effective routing hosts. runtime-core adds runtime bindings
to public workload environment, never to private delivery.

HACK-1263

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…re guards

Add a prepareCommand-level regression test: authored Compose `up` through the
source CLI renders the reserved runtime metadata keys, and HACK_COMPOSE_PROJECT
equals the generation store's hack-nc-... project for the default and a
--branch instance. A checkout replaced between metadata selection and store
open refuses before any Compose effect.

Cover the TS wire guards: runtime metadata with non-reserved keys, undeclared
workloads or non-text values refuses before the compiler spawns, and host
reports carrying runtime bindings are rejected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…no-shell clients

- Pin the value-free hack-init MCP catch: an injected builder failure whose
  message carries a path and a credential value must surface only the closed
  `<code>: hack-init could not inventory the repo.` text.
- Pin the `<dev_host>` placeholder for an existing setup with no dev_host.
- Give interactive `hack init` a terminal seam (TTY check, prompts, service
  wizard, agent-step prompts) and test that its `--with` handoff frames a
  scaffolded run as `created` and a kept `.hack/` as `existing`.
- The /hack-init skill and canonical agent-setup guidance tell no-shell
  clients to pass `repoRoot` to the hack-init MCP prompt so it includes the
  repo inventory; renders, plugins, fingerprints and revision regenerated.
- Trim repoRoot once so the presence check and the resolved path agree.

HACK-1137

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tadata-b14

Conflicted files: none. The two change sets touch disjoint files, so
git merged them automatically.

Regenerated surfaces after the merge (no resulting diff):
- bun run generate:agent-plugins
- bun run docs:cli-reference
- checked-in renders (.codex/skills/hack-cli, .codex/skills/hack-init,
  .claude/skills/hack-init, .cursor/rules/hack.mdc) compared byte-equal
  with their render functions.

integration-revision.ts and KNOWN_RENDER_FINGERPRINTS keep the values
from wf/e1-authored-metadata-b14; agent-instruction-source and
integration-ownership tests pass unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…der-b14

Conflicted files and resolutions:
- src/agents/integration-revision.ts: set to bd15ee2d251b, the value
  tests/agent-instruction-source.test.ts computes for the merged
  instruction source.
- src/agents/integration-ownership.ts: KNOWN_RENDER_FINGERPRINTS is the
  append-only union of both sides, plus the merged renders requested by
  tests/integration-ownership.test.ts (hack-cli-skill 88900097f7eb18a752de31bb,
  cursor-rules 07e49d3c2b05b93b6c49adfd).
- plugins/hack/rules/hack.mdc, plugins/hack/skills/hack-branches/SKILL.md,
  plugins/hack/skills/hack-cli/SKILL.md,
  plugins/hack/skills/hack-diagnose/SKILL.md,
  plugins/hack/skills/hack-env/SKILL.md: regenerated with
  bun run generate:agent-plugins.
- .codex/skills/hack-cli/SKILL.md, .cursor/rules/hack.mdc: re-rendered
  through installCodexSkill/installCursorRules with an explicit project
  root; byte-equal to the renderers.
- examples/basic/AGENTS.md, examples/basic/CLAUDE.md: re-rendered through
  upsertAgentDocs with an explicit project root.

src/agents/instruction-source.ts merged cleanly. .claude/skills/hack-init
and .codex/skills/hack-init re-rendered as no-ops. docs/reference/cli.md
regenerated with no diff.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi
roodboi changed the base branch from next to wf/e1-authored-metadata-b14 October 13, 2026 04:47
Base automatically changed from wf/e1-authored-metadata-b14 to next October 14, 2026 02:56
Re-parents onto next after the previous stacked PR squashed in; next's tree equals 6710d52, which this branch already contains, so the branch tree is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi
roodboi marked this pull request as ready for review October 14, 2026 02:57
@roodboi
roodboi merged commit bccaa23 into next Oct 14, 2026
31 checks passed
@roodboi
roodboi deleted the wf/e2-onboarding-builder-b14 branch October 14, 2026 02:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant