Skip to content

Features v0.2.2 - #1

Merged
jsh562 merged 100 commits into
mainfrom
features-v0.2.2
Jul 30, 2026
Merged

jsh562 merged 100 commits into
mainfrom
features-v0.2.2

Conversation

@jsh562

@jsh562 jsh562 commented Jul 30, 2026

Copy link
Copy Markdown
Owner

What & why

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Docs
  • Build / CI

Screenshots / clips

Checklist

  • npm run typecheck passes (the de-facto CI gate).
  • npm run build succeeds.
  • Any new UI derives from DESIGN.md / tokens.ts — no ad-hoc colors, spacing, or fonts.
  • If I added art, it's my own or compatibly licensed, and listed in ATTRIBUTION.md.
  • PR is focused on a single change.

jsh562 and others added 30 commits June 8, 2026 11:08
read_file / write_file / edit_file	Read, create/overwrite, and exact-string edit (with replace_all, unique-match enforcement, literal $)
list_dir / grep	Directory listing + bounded regex content search (skips node_modules/.git/binaries)
bash	Shell in the desk's cwd — opt-in, off by default
write_memory	Appends durable notes to memory.md via new HiveManager.appendMemory() — the exact gap behind "memory not working"
Host side of the native web_search tool: a Brave Search API impl (src/main/webSearch.ts) injected as the searchWeb toolkit dep, gated by a new webSearchEnabled config flag with the key stored under a reserved, redacted 'web-search' credentials id. Adds a Settings 'Web search' section (toggle + key input) and walks the web_search tool to the web-portal glyph. Governance (control gate + breaker) applies unchanged via executeToolFor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…picker

Settings now has a 'Native shell (bash)' on/off toggle (mirrors the web-search toggle), so the executor's 'enable in Settings -> native bash' message is finally true. The model picker now distinguishes provider-native capability tags (tooltip 'hosted by <provider>'s API') from harness-provided chips shown for native (non-Claude) models: '+web search' (lit when webSearchEnabled + a Brave key), '+files . grep' (always on), '+shell' (lit when nativeBashEnabled). The picker reads config on mount (same pattern as credential presence). Resolves the 'web search struck-through but I enabled it' confusion. Host-only; no package changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…reamble

Native desks' bash tool ran cmd.exe on Windows, so ls/head/pipes failed. resolveBashEnv() now detects a real shell (Git Bash, deliberately NOT System32 WSL bash which would remap the cwd) and feeds it as the bashShell dep. A one-line environment briefing (platform + shell + command style) is threaded host->worker via env and appended to the native preamble, so each desk uses the right commands first try. Falls back to PowerShell/cmd with a matching note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ection

The terminal used the WebGL renderer only because VT323 (incomplete glyphs, no real bold) sheared under the DOM renderer — but WebGL's selection-during-scroll is unreliable. Bundle Departure Mono, a COMPLETE retro pixel monospace (box-drawing 128/128, block elements 32/32) with bold mapped to the regular face, and drop the WebGL addon. The DOM renderer now keeps the grid aligned AND gives reliable select / drag-through-scroll / copy. Font is local (CSP 'self').

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Part 1 — native trace is now a narrative. Instead of read_file in {"path":"CLAUDE.md"}, each tool call now reads as a collapsible card:

Human summary + status icon + duration: ✓ Read CLAUDE.md · 3ms, ✗ Ran find . -type f | head -60 failed, ⟳ Ran npm test running… — driven by the new toolSummary.ts (covers every toolkit tool + mcp__* + unknown fallback, unit-tested).
Raw args/output collapsed by default, expand on click — and failures auto-expand so the error is always visible (with an effect handling the live pending→failed edge).
Turn dividers — a subtle "turn N" separator between turns (foldEvents.ts emits a turn-divider entry), so the flat transcript reads as grouped turns.
Prose stands out now that tool mechanics are collapsed by contrast.
The structured tab reuses the same summarizer, so both views speak the same language.
Virtualization, stick-to-bottom, and the measured-height infra all still work (cards re-measure on expand like the thinking rows already did).
Root cause of the blank Michael terminal: with a DeepSeek fleet default, god is routed to the native runtime (no Claude PTY), but the god lifecycle was Claude-PTY-only. Now: (1) a native god is booted via cth.nativeSend (not submitToPty/writePty, which has no PTY to write to) with a native-flavored orientation; (2) the orchestrator ROLE is injected into the native god's system prompt host-side (workerEnv is now per-agent; NATIVE_GOD_PROMPT for the isGod desk; the worker prepends it). Messaging already routes to the 'god' worker via native:send. The assistant (Dwight) is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fault

The main spawn router resolves a desk's model as (agent.model ?? config.defaultModel ?? role), so a model-less god routes to the native runtime when the fleet default is native (DeepSeek) — and runs fine. But the renderer's runtime-kind check (isNativeRuntimeDesk) used ONLY agent.model, so a model-less god classified as Claude → rendered the blank Claude PTY view (no 'native ·' trace) and the composer routed messages to the dead writePty path. Mirror the router's fallback: resolve agent.model || fleetDefaultModel (now in the store, set at config load) in both AgentDetailPanel and MessageQueueComposer. A native god now shows its transcript and accepts messages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A native god (non-anthropic fleet default) has no real PTY, but its agent record carried ptyId: GOD_PTY — so the live-PTY reconcile dropped it on every reload, and the auto-respawn then failed because the native worker still exists in MAIN ('native worker exists'), so god was never re-added and vanished. Now: the god record carries NO ptyId when native (reconcile keeps it; runtime kind is derived from the model), and a respawn that reports 'worker exists' RESTORES the record (refreshing model) + marks ready without re-kicking-off, instead of failing. This also refreshes a stale empty-model record that previously rendered the blank Claude-PTY view.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jsh562 and others added 28 commits June 14, 2026 20:27
…authoring); per-task branches; scale safety

Corrects the worktree model so the integrator/reviewer aren't isolated (fixing the
phyllis `addWorktree failed` error) and a worker's tasks don't pile on one branch.

Isolation (Part 1): only code AUTHORS (worker/planner/qc) get a per-desk worktree.
The integrator (merges into the trunk host-side) and reviewer (read-only) run in the
base tree on the trunk. New roleAuthorsCode predicate; pty:spawn gates isolation on
it; roster reports a branch only for authors.

Integrator = gate+merge, not author (Part 3): new roleCanWriteFiles (authors only)
gates write_file/edit_file (incl. SDDP specs/); roleCanEditCode still gates bash (the
integrator keeps bash for its test gate + hive_integrate). Split applied in the native
deny-list, agentTools resolveWrite (canWriteFiles dep, falls back to canEditCode), and
the Claude PreToolUse hook. So an integrator can bash + merge but cannot author the trunk.

Per-task branches (Part 2): a card's `branch` is the deterministic agentTaskBranch
(`agent/<id>-<cardId>`, hyphen-joined to avoid a D/F conflict with the desk's home
branch), stamped on add + reassign. Worker prompts (native + Claude, std + SDDP) tell
desks to author each card on its own branch off the trunk; the integrator merges that
branch. Replaces the per-desk branchFor stamping (dep removed).

Recovery (Part 4): resetBaseToTrunk now stashes a dirty base tree before checking out
the trunk (single recovery action); the migrate-to-worktree path/IPC/button is removed
(a freed branch re-isolates on next spawn). Settings → Worktrees not-isolated row → Reset.

Scale (Part 5): merges into a repo are serialized (per-repo merge lock) so multiple
integrators can't race the index; a clean merge deletes the merged branch
(deleteMergedBranch, best-effort). A feature's planner/QC kickoff routes to a SINGLE
holder (multiple would clobber the shared base specs/<feature>/).

Docs (Part 6): orchestration-model.md updated. SDDP specs/ sharing is already handled by
the existing redirect, so planner/qc isolation stays safe.

(won-agent-core source — roleAuthorsCode/roleCanWriteFiles/agentTaskBranch + hiveTools/
agentTools + tests — is its own repo; commit it there. App consumes it live via the alias.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Give native (DeepSeek) desks the sddp27 sub-agents for real: a spawn_subagent
tool forks an ephemeral one-shot worker with the named specialist's prompt, runs
it caller-scoped, and returns its final text — the native equivalent of Claude's
Task tool. Feature epic cards carry a milestone checklist, each step hard-gated
on its real specs/<feature>/ artifact.

Host side (main repo):
- subAgentRunner.ts: runOneShotSubAgent — reuses NativeAgentWorker over the
  electron transport; captures final text on terminal stop, kills the idle
  process; nesting guard + concurrency cap + wall-clock timeout. Plus the pure
  resolveSubAgentModel helper.
- index.ts: agentToolDeps.spawnSubAgent (child inherits caller provider+model,
  runs under executeNativeToolFor(callerId) so specs/ redirect + worktree resolve
  to the caller; deny-list incl. nesting/integrate/board-mutation); extracted
  executeNativeToolFor; featureArtifactExists + deskHoldsFeature deps; agentModels
  map so a sub-agent inherits the caller's exact model; child token cost rolls up
  to the caller via the telemetry collector.
- agentWorker.ts: consume NATIVE_AGENT_SUBAGENT_PROMPT + optional per-spawn caps env.
- toolGating.ts: deny spawn_subagent unless the desk holds an orchestrating role.
- config.ts: sddpSubAgentModel (same-provider override, else caller model).
- TasksKanban.tsx: read-only milestone-pill row on the feature banner.

Tests: toolGating spawn_subagent advertising; subAgentRunner over a fake transport
(capture/kill/nesting/timeout/token-rollup/model-resolution). Full suite 442 green;
typecheck + lint clean.

NOTE: the bulk of the logic (subAgents registry, FeatureMilestone/advanceMilestone
gate, spawn_subagent tool + specs feature-scope gate) lives in packages/won-agent-core,
which is gitignored here and has its own (currently uncommitted) repo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Complete the SDDP feature pipeline on top of the native sub-agent runtime:

- index.ts: parseFeatureTasks dep — parse a feature's specs/<feature>/tasks.md
  `- [ ] T###` lines (stripping [P]/[US#]/{FR-###}/[after:…] to the description)
  so hive_import_tasks can turn the plan into implement cards; updated the SDDP god
  prompt to call hive_import_tasks directly.
- docs/orchestration-model.md: document the Authors vs Coordinators role classes,
  the native sub-agent runtime (spawn_subagent), the milestone pipeline + optional
  steps, and hive_import_tasks; refreshed the implementation map + deferred list.

Package side (won-agent-core, gitignored here): FeatureMilestone.optional + the full
SDDP_MILESTONE_TEMPLATE (spec→clarify→research→data-model→contracts→adrs→plan→
checklist→tasks→implement→qc); the advanceMilestone gate skips the artifact check for
optional steps + activates the next-in-order milestone; hive_import_tasks tool +
parseFeatureTasks dep; Authors/Coordinators doc comment.

Full suite 446 green; typecheck + lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…host side)

P1–P3 built + tested the sub-agent runtime + milestone pipeline, but NOTHING drove
it: no role prompt mentioned spawn_subagent, no card ever got milestones, no prompt
mentioned advanceMilestone — so SDDP mode ran the old melded-prompt behavior and the
machinery sat inert. P4 wires the orchestration layer:

- sddpPrompts.ts (new, extracted from index.ts so it's unit-testable): the SDDP role
  prompts now INSTRUCT the planner/qc/worker to spawn_subagent their specialists per
  phase (database-administrator/api-designer/adr-author/wbs-generator/spec-validator
  for the planner; qc-auditor/story-verifier for qc; developer for the worker) and to
  advanceMilestone after each artifact; the god creates the feature EPIC card
  (hive_add_task {epic:true}) and runs hive_import_tasks. A shared SDDP_SUBAGENTS note
  is added only for spawn-capable roles (roleCanSpawnSubagents).
- index.ts: imports the prompts (removed the inline consts).
- hive.ts: Claude god parity — sddpRoleLine now creates the epic card + hive_import_tasks.
- sddpPrompts.test.ts (new): regression guard — asserts the prompts keep naming
  spawn_subagent + the specialists + advanceMilestone + epic, so this can't rot back inert.
- docs/orchestration-model.md: the runtime/milestone sections now describe how it's
  DRIVEN + an end-to-end feature walkthrough; map + deferred refreshed.

Package side (won-agent-core, gitignored here): hive_add_task {epic} seeds
defaultMilestones(); the implement-only doing-gate exempts the epic card; tool
descriptions name the spawn->advance flow.

Full suite 453 green; typecheck + lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… side)

The milestone checklist becomes the program; a host engine is the interpreter, so
the SDDP workflow is deterministic host code rather than "trust a desk to follow a
prompt." P1 ships the engine + the mechanical host-step chain.

- sddpPipeline.ts (new): SddpPipeline.advanceFeature — find the feature epic, validate
  it has a usable native owner desk (creds+model; escalate loudly if not), and for the
  active HOST milestone (research/data-model/contracts/adrs/checklist/tasks) run its
  specialist AS the owner, gate on the real artifact, advance (which re-triggers the
  engine to chain); skip an optional step that produces nothing; escalate once when a
  required artifact never lands. Debounced schedule + per-step in-flight lock.
- hive.ts: setPipelineTrigger (fired per feature from notifyTaskTransitions, fire-and-
  forget) + advanceMilestone(epicId,key) (reuses the shared advanceMilestones helper,
  writes via writeTasks which re-triggers the engine).
- index.ts: construct SddpPipeline (owner-usable = spawned non-Claude desk w/ model;
  reuses agentToolDeps.spawnSubAgent/featureArtifactExists) + wire the trigger.

Package side (won-agent-core, gitignored): advanceMilestones + milestoneDriver +
HOST_DRIVEN_MILESTONES in types.ts; hive_update_task's advance gate refactored onto
the shared advanceMilestones (no behavior change).

Tests: sddpPipeline (run/gate/advance/chain, optional-skip, desk-skip, owner-escalate,
sddp-off, in-flight lock) + the advanceMilestones/milestoneDriver helpers. Full suite
463 green; typecheck + lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The engine now drives the whole Plan→Tasks chain, authoring artifacts into sddp27's
real structure instead of "from memory."

- sddpPipeline.ts: buildStepInput prepends the step's TEMPLATE (when one exists) as
  "author into THIS structure", and adds the `plan` step (read spec + the design
  artifacts → assemble plan.md). Templates are supplied as data, not baked into prompts.
- docs/orchestration-model.md: documents the host-driven engine (program=milestone
  checklist, interpreter=sddpPipeline; owner-validate → run host step → gate → advance →
  chain; templates-as-data; escalation) + plan now host-driven.

Package side (won-agent-core, gitignored): new sddpTemplates.ts (condensed plan + tasks
templates + templateForStep); plan-author sub-agent (assembles plan.md into the template,
every requirement in the Coverage Map); SDDP_MILESTONE_TEMPLATE plan → subAgent
plan-author + `plan` added to HOST_DRIVEN_MILESTONES (so the chain no longer stalls at plan).

Tests: plan-author registry + gateArtifact, templateForStep(plan/tasks) structure + null
for single-artifact steps, milestoneDriver(plan)=host, the engine runs plan-author for the
plan step (template rides in the input) + advances on plan.md. Full suite 467 green;
typecheck + lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…topilot) + Human-Gates Registry

The engine now drives Specify→Tasks end-to-end, and the Clarify human gate is flippable.

- sddpPipeline.ts: `spec` runs as a host step (spec-author drafts spec.md from the epic's
  description = the feature request, into the spec template). New runHumanGated branch for
  Clarify: autopilot ON → spec-author resolves the [NEEDS CLARIFICATION] markers with
  documented defaults + advance; autopilot OFF (default) → run requirements-scanner once,
  askHuman (needs_human), and PAUSE until the operator answers + advances. New deps
  autopilot()/askHuman; buildStepInput gains the spec + clarify cases + a ctx.request param.
- config.ts: sddpAutopilot (OFF by default) — the master flag for the Human-Gates Registry.
- index.ts: wire autopilot()=readConfig().sddpAutopilot + askHuman → hive.send(needs_human).
- sddpPrompts.ts (native god): epic carries the feature request in `description`; on a
  Clarify needs_human message, answer + advance the clarify milestone.
- docs/orchestration-model.md: the Human-Gates Registry (every human-in-the-loop point +
  its autopilot path; engine owns #2 Clarify today, #3-5 attach to the same flag in P4).

Package side (won-agent-core, gitignored): spec template in sddpTemplates; spec-author
sub-agent; SDDP_MILESTONE_TEMPLATE spec→spec-author + `spec` host-driven; FeatureMilestone
.humanGated + clarify humanGated:true.

Tests: spec-author registry + spec template; milestoneDriver(spec)=host + clarify humanGated;
engine spec-step (request+template in input → advance), Clarify autopilot-OFF (ask once, no
advance, dedup) + autopilot-ON (spec-author resolves → advance, no ask). Full suite 471 green;
typecheck + lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t side)

Completes the engine's reach honestly. Implement/QC/Integrate are distributed, branch-
based work (workers on per-card branches → the notifier-pinged qc desk writes .qc-passed →
the integrator merges) — not "one sub-agent → one artifact", and a host-driven qc-auditor
would deadlock (the .qc-passed gate blocks integrate, but QC needs the merged code). So the
engine FEEDS + TRACKS the tail instead of running it:

- sddpPipeline.ts: trackDistributed — at `implement` it SEEDS the implement cards from
  tasks.md once (deterministic; new seedImplementCards dep) so the work exists, then advances
  the implement/qc pills when the real markers (.completed/.qc-passed) land. No sub-agent;
  the distributed flow does the work. Escalates if tasks.md has no tasks to import.
- index.ts: seedImplementCards — parseFeatureTasks → one feature-tagged todo card per task
  (unassigned; the god routes them) → writeTasks.
- sddpPrompts.ts + hive.ts: the god no longer runs hive_import_tasks (the engine seeds); it
  ASSIGNS the seeded cards to workers. QC + integrate proceed via the existing flow.
- docs: the engine authors spec→tasks + seeds + tracks; the distributed flow does
  implement/QC/integrate; registry #3 (spec/plan review) is not an engine gate, #4/#5 are the
  per-card board flow.

Deferred (documented): true host-driven QC (needs a QC-integration worktree to dodge the
merge-order deadlock); auto-assign of seeded cards; a poll for crisper pill tracking.

Tests: implement seeds when no cards (no advance) / escalates on 0 tasks; implement+qc advance
on .completed/.qc-passed; qc waits while the marker is absent. Full suite 476 green; typecheck
+ lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…use/stop (host side)

Operator control + observability on the engine, plus it now assigns work (not just seeds).

Per-step control (rides the milestone object + the board pills):
- sddpPipeline reads the active step's control first: paused = hold (in-flight finishes);
  stopped = ABORT the in-flight sub-agent (AbortSignal kills the ephemeral worker) + hold;
  manual = engine skips the run, advances when the artifact appears (a desk/human does it);
  auto = drive as designed. Plus a live per-feature status (running/waiting/paused/stopped/
  manual/blocked) the engine maintains + exposes via pipeline:status.
- subAgentRunner: runOneShotSubAgent accepts an AbortSignal (kill + resolve aborted);
  spawnSubAgentFor extracted in index.ts so the tool (no signal) + the engine (with signal) share it.

Driven + assigned:
- the engine auto-assigns the epic to a usable planner desk (resolveOwner -> findDeskForRole +
  assignCard) and round-robin-assigns the seeded implement cards to worker desks
  (seedImplementCards, per-card branch stamping). usableDesksForRole/desksForRole helpers.

UI: the milestone pills become interactive — pause/stop/switch(manual)/resume buttons that set
milestone.control + persist via hive:writeTasks, plus a live status glyph from pipeline:status.
parseTasks parses control; the board polls pipeline:status alongside tasks.

Package side (won-agent-core, gitignored): FeatureMilestone.control field.

Tests: subAgentRunner abort (in-flight kill + pre-aborted no-fork); sddpPipeline control
(paused waits, manual skips+advances-on-artifact, stopped aborts in-flight) + auto-assign
(epic->planner, escalate when none). Full suite 483 green; typecheck + lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ree)

An isolated author/qc desk runs cargo (etc.) inside its git worktree, so `target/`
churned under harnessHome (S:\munderdiff\worktrees\<id>) — heavy, executable, and
real-time-scanned by antivirus, which LOCKED the files and failed the build:
  could not execute … build-script-build … Access is denied. (os error 5)
  cargo clean … being used by another process. (os error 32)
(This was the source of the earlier "rust build in S:\munderdiff" AV ping — the QC desk.)

Fix: inject a build-tool env (today CARGO_TARGET_DIR) pointing at ONE cache root —
config.buildCacheDir, else <harnessHome>/build-cache — subdivided per working tree
(buildCacheKey = <basename>-<hash(normalizedCwd)>, one builder per dir so cargo's
per-target lock never contends; the hash discriminates same-named repos). Net: the
operator excludes that single folder from antivirus; nothing builds into a worktree
or the repo (non-isolated base-tree desks get a repo-keyed dir too).

- paths.ts: buildCacheKey(cwd) (pure, normalized-path keyed).
- index.ts: buildCacheRoot()/buildCacheEnvFor(cwd) (mkdir + CARGO_TARGET_DIR); wired as
  the agentToolDeps.bashEnv (native bash runs in MAIN) AND merged into opts.env at
  pty:spawn after the worktree override (Claude author + base-tree desks).
- config.ts (+ preload/renderer mirrors): buildCacheDir?: string.
- docs/orchestration-model.md: build-output-redirect bullet.

Package side (won-agent-core, gitignored): AgentToolDeps.bashEnv?(id) +
runBash(command, cwd, shell, env?) merging env over process.env (absent ⇒ inherit).

Tests: buildCacheKey (deterministic, collision-safe, win32 case-fold) + bash env
merge/inherit. Full suite 489 green; typecheck + lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rent folder)

Generalizes the hardcoded CARGO_TARGET_DIR redirect (9c14fc6) into a small token-templated
env table the user edits in Settings: define ONE parent folder and the per-tool/per-worktree
structure is filled in automatically.

- src/shared/buildEnv.ts (NEW, renderer-safe): BuildEnvEntry, DEFAULT_BUILD_ENV
  (CARGO_TARGET_DIR=${buildRoot}/cargo/${worktreeKey}), BUILD_ENV_TOKENS, and expandTokens()
  (pure ${token} replace — known tokens substituted, unknown left intact).
- src/main/buildEnv.ts (NEW): resolveBuildEnv(entries, root, vars) -> { env, dirs } — expands
  per desk, normalizes absolute values to platform separators, returns under-root dirs to mkdir;
  skips blank-name rows and ${buildRoot} rows when there's no root.
- index.ts: deskBuildEnvFor(cwd, agentId) replaces buildCacheEnvFor — reads config.buildEnv ??
  DEFAULT_BUILD_ENV, expands against {buildRoot, worktreeKey, cwd, agentId, harnessHome}, mkdirs,
  injected at BOTH seams (native bashEnv + Claude opts.env), now passing the desk id.
- config.ts (+ preload + renderer store mirrors): buildEnv?: BuildEnvEntry[].
- SettingsModal: a Build-cache folder picker (the one parent folder) + a full Build-env TABLE
  editor (add/edit/remove rows, token hint, per-row live preview via expandTokens; commit on
  blur, add/remove/reset immediate).
- docs/orchestration-model.md: rewrote the build-output bullet for the generic mechanism + tokens.

Tokens are host-side only; the package bashEnv already takes a Record (no package change).
Default cargo path moves from <root>/<key> to <root>/cargo/<key> (one-time recompile).

Tests: shared expandTokens (substitution/unknown/repeat/missing) + DEFAULT shape; main
resolveBuildEnv (default-under-root, external-not-created, blank-name skip, null-root behavior,
multi-tool). Full suite 500 green; typecheck + lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The token-templated buildEnv table (45cc0a6) is already general (any env var, not just
build dirs). This adds an optional PER-PROJECT-REPO override layer: for a desk whose project
repo matches, the per-repo table merges ON TOP of the global table (same name wins). The
output folder (buildCacheDir) stays global — one AV exclusion; ${worktreeKey} already
separates output per repo.

- shared/buildEnv.ts: mergeBuildEnv(base, override) — override wins by name, new names
  appended, base order preserved; empty override ⇒ base unchanged. (Pure; used by the
  resolver + the renderer preview.)
- main/buildEnv.ts: perRepoEntriesFor(map, repo) — the override entries for a desk's repo,
  matched case/separator-insensitively via normalizeRepoPath (a small scan).
- index.ts: deskBuildEnvFor now layers mergeBuildEnv(base, perRepoEntriesFor(cfg.buildEnvByRepo,
  repoForId(agentId))) before resolveBuildEnv. No seam changes.
- config.ts (+ preload + renderer store): buildEnvByRepo?: Record<string, BuildEnvEntry[]>.
- SettingsModal: extracted the row editor into a reusable BuildEnvRows component; the global
  table uses it, plus a new Per-repo env section (repo <select> + the same editor bound to
  buildEnvByRepo[repo]; "•" marks repos with overrides; empty arrays pruned on persist).
- docs/orchestration-model.md: noted the table is general + the per-repo override layer.

Host-side only (the package bashEnv already takes a Record — no won-agent-core change).

Tests: mergeBuildEnv (replace/append/mix/blank/empty) + perRepoEntriesFor (normalized
match across sep/case/trailing-slash, miss/undefined, override-wins-when-merged). Full suite
509 green; typecheck + lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…I polish

The env table is general (any var), so rename it to reflect that, add a third override layer,
and make build-dir vs plain-value obvious in the editor.

Rename (buildEnv* → deskEnv*): src/shared/buildEnv.ts → deskEnv.ts, src/main/buildEnv.ts →
deskEnv.ts (+ the two test files). BuildEnvEntry→DeskEnvEntry, BuildEnvVars→DeskEnvVars,
DEFAULT_BUILD_ENV→DEFAULT_DESK_ENV, BUILD_ENV_TOKENS→DESK_ENV_TOKENS, mergeBuildEnv→mergeDeskEnv,
resolveBuildEnv→resolveDeskEnv, perRepoEntriesFor→perRepoDeskEnv, deskBuildEnvFor→deskEnvFor.
Config fields buildEnv→deskEnv, buildEnvByRepo→deskEnvByRepo. KEPT buildCacheDir, buildCacheKey,
and the ${buildRoot}/${worktreeKey} tokens (build-specific). readConfig migrates legacy
buildEnv/buildEnvByRepo so existing configs carry over.

Per-agent layer: config deskEnvByAgent (keyed by agent id). deskEnvFor now layers
mergeDeskEnv(mergeDeskEnv(base, repoOverride), agentOverride) — precedence agent > repo > global.
perAgentDeskEnv lookup added.

${env:VAR} token: expandTokens(template, vars, envLookup?) resolves ${env:NAME} via the host's
process.env (renderer omits the lookup → literal in preview), so PATH=/extra:${env:PATH} appends
safely instead of clobbering. resolveDeskEnv now normalizes + auto-creates ONLY values under the
build root; every other value (PATH lists, flags, external paths) is set RAW (no separator mangling).

UI (SettingsModal): BuildEnvRows → DeskEnvRows with token-insert chips (insert into the focused
row) + a "· created" marker when a value resolves under the build cache; a Per-agent env section
(desk <select> from the live fleet) mirroring Per-repo; "Build env" label → "Desk env".

Deferred (surfaced): secret-backed values (these are plaintext in config.json — not for secrets)
and proxy/CA runtime env (would need a worker-process/PTY seam reaching the model-API call).
Host-side only (no won-agent-core change; bashEnv already takes a Record).

Tests: ${env:NAME} expansion, 3-layer merge precedence, plain-value + ${env:PATH}-raw resolve,
perAgentDeskEnv. Full suite 514 green; typecheck + lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the env model with two siblings of the desk-env table, plus a shared ${secret:} token.

Secrets vault (encrypted at rest):
- src/main/secrets.ts (NEW): safeStorage codec (enc:/raw: prefix, plaintext fallback) +
  set/get/clear/secretNames over config.secrets. Decrypted only in main; values never cross IPC.
- credentials.ts: SafeConfig += secretNames (Omit secrets); redactConfig strips secrets → names only.
- index.ts: secrets:list/set/clear IPC (mirror credentials:*). preload window.cth.secrets.*.
- Referenced anywhere via ${secret:NAME}.

${secret:}/${env:} token:
- shared/deskEnv.ts: expandTokens(template, vars, lookup?) where lookup(prefix,name) resolves
  prefixed tokens (env:/secret:); unknown/no-lookup → left literal (renderer preview never sees values).
- main/deskEnv.ts: resolveDeskEnv forwards the lookup. index.ts wires tokenLookup = env→process.env,
  secret→getSecretValue.

Runtime env (proxy / custom CA for the agent's OWN calls):
- config.runtimeEnv (global). index.ts runtimeEnvResolved() injected into the native worker process,
  the Claude PTY, AND bash (so the model call, Claude CLI, and git/curl all honor it).
- Native fetch is undici (ignores HTTPS_PROXY): agentWorker.ts routes selectAdapter's fetch through an
  undici ProxyAgent when a proxy is set; NODE_EXTRA_CA_CERTS works via env (Node TLS). +undici dep.
  (NO_PROXY not honored for that one native call.)

UI (SettingsModal): three labeled cards — Secrets (masked add/clear, names-only), Desk env (existing
global/repo/agent), Runtime env (DeskEnvRows with env:/secret: chips, no "created" marker). DeskEnvRows
gains a ${secret:} chip + an "unknown secret" warning when a referenced name isn't in the vault.

config.ts: secrets + runtimeEnv fields. Docs updated (three cards, ${secret:}, proxy/CA caveats).

Tests: expandTokens prefix lookup (env+secret), resolveDeskEnv secret resolve + PATH-raw, redactConfig
strips the vault. Full suite 515 green; typecheck + lint clean. (safeStorage round-trip + worker
ProxyAgent are runtime — verified in-app.)

Host-side only (no won-agent-core change). Deferred: migrate provider keys to safeStorage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…gate)

Adds the missing 7th lifecycle phase (sddp27), host-driven: the engine runs the analysis and
enforces the gate, rather than trusting a desk to.

- New `analyze` milestone between Tasks and Implement (optional; gateArtifact analysis-report.md).
- sddpPipeline.runAnalyze (special path like Clarify): (1) MECHANICAL requirement→task coverage in
  code (analyzeCoverage, pure, won-agent-core) — an uncovered requirement is CRITICAL; (2) runs the
  registered-but-previously-unused validators policy-auditor (vs project-instructions.md) +
  spec-validator; (3) writes analysis-report.md with `critical: <n>` frontmatter; (4) GATE: critical>0
  holds + escalates (delete the report or set Analyze manual to re-run); autopilot advances with a
  note; clean advances. A pre-existing report gates without re-spawning the validators.
- New engine deps (index.ts): analyzeFeature (coverage), featureArtifactText, writeFeatureArtifact
  (all path-safe to specs/<feature>/ via featureFilePath).

Package side (won-agent-core, gitignored): analyze.ts (analyzeCoverage) + export; analysis-report
template (sddpTemplates); the `analyze` milestone (types). Tests: analyzeCoverage (4) + runAnalyze
gate (5: clean-advances, uncovered-holds, policy-FAIL-holds, autopilot-advances, pre-existing-gates);
existing milestone-order tests updated (tasks→analyze). Full suite 524 green; typecheck + lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Enforces the rule already in AGENTS.md ("if checklists/ exists, all items complete before Implement").

- sddpPipeline.checklistGate (run from trackDistributed at implement entry, now async): mechanical
  scan of specs/<feature>/checklists/*.md completion (checklistStatus dep, `- [ ]` vs `- [X]`); if
  incomplete, ONE auto-resolve pass via the test-evaluator sub-agent (deduped per feature), re-scan;
  still incomplete → block + escalate (the operator resolves the items, a re-trigger passes) unless
  autopilot proceeds. No checklists / already complete ⇒ proceed (seed implement cards as before).
- index.ts: checklistStatus dep (counts unchecked/checked across the checklist dir, path-safe).

Tests: no-checklist N/A seeds (no test-evaluator); incomplete holds + escalates (one auto-resolve);
test-evaluator-completes proceeds; autopilot proceeds; already-complete proceeds without test-evaluator.
Full suite 529 green; typecheck + lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… QC iterations)

Mirrors sddp27's .github/sddp-config.md as a config object + a compact Settings editor. Consumed by
the host QC + bug-loop phases (P4/P5).

- config.ts: sddpConfig?: { qcStrictness: minimal|standard|strict; coverageTarget; maxChecklist;
  maxQcIterations } (+ preload mirror).
- SettingsModal: under the SDDP toggle (shown when SDDP is on), a row of knobs — QC strictness select +
  coverage % + max checklists + max QC iterations — persisted as the whole sddpConfig object.

Defaults are built-in (standard strictness, etc.); the values wire into runQc (strictness→required
categories, coverage) and the bug loop (max iterations) in the next phases. Suite 529 green; typecheck +
lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…deadlock)

The engine now RUNS QC instead of only tracking it: it builds a throwaway worktree, merges the
feature's implement branches into it (off trunk), runs qc-auditor + story-verifier IN that merged
tree, and writes .qc-passed itself on PASS — so the suite tests the INTEGRATED code without touching
trunk, resolving the .qc-passed↔integrate deadlock.

- git.ts prepareQcTree(repo, wtPath, base, branches): detached worktree at trunk + `git merge` each
  branch (conflicts are aborted + reported, the rest still merge). removeWorktree tears it down.
- sub-agent cwd override: spawnSubAgentFor + executeNativeToolFor take an optional cwdOverride →
  the child's fs/bash resolve in the QC tree (+ it's a read root); specs/ still anchors to the base
  repo so qc-report.md lands in the shared specs/<feature>/. No change to non-SDDP runs.
- sddpPipeline.runQc (special-cased like Analyze, only when the QC-tree deps are wired — else falls
  back to the feed-track qc-desk flow): prepare tree → conflicts escalate; else run qc-auditor (build/
  lint/tests per sddpConfig.qcStrictness) + story-verifier in the tree → both VERDICT:PASS ⇒ write
  .qc-passed + advance (integrate unblocks); FAIL ⇒ hold + escalate (+ seedBugCards hook for P5).
  Idempotent (existing .qc-passed advances); tree always torn down.
- index.ts deps: prepareQcTree (branches from the feature's implement cards, base=repoTrunk, tree
  under <harnessHome>/qc-worktrees), removeQcTree, spawnSubAgentInTree, sddpPolicy. qcTreeRepo maps
  tree→repo for teardown.

Tests: runQc PASS (writes marker, advances, both sub-agents, tree removed), FAIL (holds + escalates +
bug-cards hook + tree removed), merge conflict (escalates, no sub-agents), idempotent, and the
feed-track fallback when host-QC deps are absent. Full suite 534 green; typecheck + lint clean.
(Real worktree-merge + suite-run verified in-app.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes host-driven QC: a FAIL no longer re-runs the whole suite on every board tick (a P4 gap).

- runQc bug loop: on FAIL the engine increments a per-feature attempt count and files bug-task cards
  (seedBugCards), then WAITS — it re-runs QC only once all the feature's [BUG] cards are closed
  (openBugCards == 0), so workers fix → engine re-QCs → pass or new bugs. On PASS the attempt count
  clears. Bounded by sddpConfig.maxQcIterations.
- escalation: attempt drives the tag — ≥3 ⇒ [ESCALATED], ≥maxQcIterations ⇒ [DEFERRED] — and the
  escalation message's urgency; deferred bug cards stay open so the loop pauses for manual review
  rather than spinning or auto-shipping failing QC.
- index.ts deps: seedBugCards (parse the report's [BUG:…] lines → cards, else one generic; round-robin
  to workers; attempt→tag) + openBugCards (count open [BUG] cards for the feature).
- docs: lifecycle now spec→…→tasks→ANALYZE→implement→qc; the host-engine paragraph rewritten for
  Analyze + checklist gate + host-driven QC (worktree) + the bug loop (replacing the "QC deferred" note).

Tests: FAIL files bugs (attempt 1) then waits while open; re-runs once bugs closed → PASS writes
.qc-passed + advances; attempt increments across cycles. Full suite 537 green; typecheck + lint clean.

This finishes the sddp27 lifecycle port (P1 Analyze, P2 checklist gate, P3 sddp-config, P4 host QC,
P5 bug loop) — all host-driven. (Worktree-merge + real suite-run verified in-app.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ries)

Deepens the QC bug loop to sddp27's full bug-task grammar:
[BUG:severity] [RECURRING?] [ESCALATED?] [DEFERRED?] {req?} [category] desc — file:line.

- index.ts seedBugCards: now builds titles via buildBugTitles + detects RECURRENCE — it computes the
  signatures of every prior [BUG card for the feature (done cards from earlier attempts) and tags a
  re-filed finding [RECURRING]; [ESCALATED] (attempt≥3) / [DEFERRED] (≥maxQcIterations) by attempt.
- sddpPipeline runQc: the qc-auditor prompt now emits one bug line per failure with a [category]
  (test-failure/lint-error/security-vuln/coverage-gap/requirement-gap/pi-violation/runtime-error), so
  seedBugCards parses rich findings instead of one generic card.
- docs: bug-task grammar + [RECURRING] note.

Package side (won-agent-core, gitignored): coordination/bugTasks.ts — pure bugSignature (normalized
recurrence key), bugCardTitle (canonical tag order, defaults severity, strips stale modifiers),
buildBugTitles (attempt→ESCALATED/DEFERRED + per-finding RECURRING) + barrel export. Tests (9):
signature normalization/equality, title grammar + stale-tag stripping, attempt thresholds, recurrence.

This is the last clearly-worth-it sddp27 item; preservation-enforcement / bootstrap / audit-logs /
manual-test.md remain deliberately deferred. Full suite 546 green; typecheck + lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cipe

Closes the "never run end-to-end" gap from both sides, without creating anything permanent.

- src/main/__tests__/sddpPipelineE2e.test.ts (NEW): drives the REAL engine + REAL git.ts
  (prepareQcTree = actual `git worktree add` + merges) + REAL fs against a throwaway `git init`
  repo in os.tmpdir, with a scripted no-API provider that writes each step's artifact. Runs
  spec→clarify→…→analyze→implement→host-QC and asserts: every milestone advances, analysis-report.md
  is critical:0, .qc-passed lands, the two implement branches really merge in the QC tree (conflicts:
  []), and the QC worktree is torn down — then rmSync's the temp dir. A FAIL variant asserts QC holds,
  no .qc-passed, and a [BUG] card is filed. (Exercises the git/fs plumbing every other test fakes;
  requires git on PATH; ~2s.)
- docs/sddp-smoke.md (NEW): the disposable in-app sandbox recipe for the LIVE path the test can't cover
  (real DeepSeek desk) — isolate harnessHome to a temp dir (config:changeHome 'fresh'), register a
  throwaway repo, SDDP+autopilot, run one tiny feature, watch the pills, tear down — plus a
  failure-mode table (needs-credentials, no owner, analyze CRITICAL, checklist hold, QC conflict, bug loop).

Test-only + a doc; no production code change. Full suite 548 green; typecheck + lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…path)

Replaces the 2-case happy/fail smoke with a single suite that drives EVERY engine path against REAL
git + REAL fs (real prepareQcTree/mergeBranch + real analyzeCoverage + the real bug-task grammar),
each in its own throwaway temp repo:

1. happy → integrate-to-trunk (full run to .qc-passed, then real mergeBranch each impl branch into main)
2. merge CONFLICT (two branches edit the same line → prepareQcTree conflicts → QC escalates, no marker)
3. full bug loop (FAIL→wait-while-open→close→re-QC FAIL tagged [RECURRING]→close→re-QC PASS→.qc-passed)
4. Analyze CRITICAL → recover (uncovered FR-002 holds critical:1; fix tasks.md + delete report → clean)
5. optional-step skip (research/data-model/contracts/adrs advance with no artifact)
6. Clarify autopilot OFF (requirements-scanner asks + holds; operator-advance continues)
7-9. per-step control: paused (no run), manual (skip→advance on artifact), stopped (abort in-flight run)
10. no usable owner → escalate; with a planner → auto-assign the epic
11. idempotent (pre-existing .qc-passed advances without building a tree)
12. in-flight lock (two concurrent advances → exactly one spawn)

The factory (setupE2E) is generalized with mutable state flags (qcPass/autopilot/uncovered/skipOptional/
conflict/ownerUsable/plannerDesk/gate) + helpers (setControl, closeBugCards, operatorAdvance, drive).

Honest boundary unchanged: the LIVE layer (model/worker/cwd-override/IPC/UI/god-epic/integrate-desk) is
not automatable — docs/sddp-smoke.md remains its only coverage; integrate here is a simulated mergeBranch.

Runtime: the 12 real-git tests add ~8s (each shells out to git). Full suite 558 green; typecheck + lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ly the model faked)

The exhaustive E2E mocked each sub-agent as a coarse black box (name -> write a
placeholder file): it proved the engine + real git/fs but never ran the real
sub-agent runner, the real toolkit, the cwd-override, or the deny-list together.
Those layers were unit-tested in isolation but never COMPOSED with the engine,
because the spawn glue was electron-coupled in index.ts and hardcoded the worker
transport.

Phase A — extract the spawn glue (behavior-identical). The body of
spawnSubAgentFor couples to only three host edges: the electron worker transport,
executeNativeToolFor, and the telemetry cost rollup; everything else is pure logic
over already-electron-free modules. Move that body into a new electron-free
runtime/subAgentExecutor.ts (makeSpawnSubAgent(deps)) with those three edges
injected, and rewire index.ts to construct it. Production behavior is unchanged
(full suite + typecheck verify parity).

Phase B — sddpFullStack.test.ts. Wire the engine's spawnSubAgent/spawnSubAgentInTree
to makeSpawnSubAgent over a SCRIPTED fake transport (the only fake — it stands in
for the LLM token/tool stream) + the real executeAgentTool over a throwaway git
repo. So each sub-agent's tool calls actually execute: spec/plan/tasks are authored
by a real write_file, the checklist is flipped by a real edit_file, and the
qc-auditor's bash + write_file run with the cwd OVERRIDDEN into the merged QC
worktree (process.cwd() proves it) while qc-report.md redirects to the shared
specs/. Three cases: happy path, deny-list refusal (spawn_subagent/hive_integrate
refused before the toolkit), and a QC FAIL -> bug card -> re-run PASS.

Phase C — test display. Add a `test:e2e` verbose script (one check-line per case)
and an SDDP_E2E_TRACE env gate that logs each milestone as the drive loop advances.

The live model's prompts/outputs remain un-automatable (sandbox only — see
docs/sddp-smoke.md); this fakes the model's stream, not the model.

561 tests pass; typecheck clean; lint 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ovider

Deepen sddpFullStack.test.ts so the only thing faked is the provider's token/tool
turns. The transport now runs the real `runAgentLoop` in-process (a programmable
stub provider replays each specialist's turns), so the full chain executes for real:
engine -> makeSpawnSubAgent (deny-list + cwd-override) -> runOneShotSubAgent ->
NativeAgentWorker (toolRequest/toolResult round-trip) -> real runAgentLoop (turn/hop
caps, request->tool->result cycle, usage rollup, stop) -> real executeAgentTool ->
real git/fs. Replaces the prior hand-emitted tool stream.

Closes the four coverage gaps the prior version left:
- Read path: each specialist now READS before it authors (read_file/list_dir/grep)
  per its subAgents.ts prompt, so the toolkit's read path + specs/-redirect-on-read
  run composed, not just in isolation.
- Real loop: the loop GENERATES each sub-agent's output stream (asserted via its
  turn-start/tool-start/tool-end/stop events + the token-usage rollup), instead of
  the test hand-emitting it.
- Branches: every meaningful engine path now runs through the real stack — happy,
  deny-list, QC-fail, merge-conflict, recurring-bug ([RECURRING]), analyze-CRITICAL,
  optional-skip, clarify-autopilot-OFF (requirements-scanner).
- hive/web/memory: a focused case round-trips hive_read_memory / hive_list_tasks /
  hive_feature_status / write_memory / web_search through the real toolkit.

9 cases (was 3). Still test-only; won-agent-core + production code untouched. The
live model's prompts/outputs stay un-automatable (sandbox only — docs/sddp-smoke.md);
the stub stands in at the ProviderCall seam, the realistic in-process boundary.

567 tests pass; typecheck clean; lint 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
An honest audit of sddpFullStack.test.ts found six remaining gaps; three are
closeable in vitest, three are inherently sandbox-only. Close the closeable three
and write down the ceiling.

Closeable (now covered, composed through the real loop + real toolkit):
- Real tool FAILURES + self-correction: a sub-agent hits genuine execution failures
  (read a missing file, bash non-zero, write outside the sandbox) — distinct from a
  deny-list refusal — and the real loop feeds each back so a later hop recovers and
  really writes. Previously only the success path + deny-list were composed.
- The REAL DeepSeek adapter at the wire level: one case swaps the scripted stub for
  makeDeepseekAdapter driven by a mocked fetch/SSE stream, so request-building + SSE
  parsing run COMPOSED with the loop -> real toolkit -> fs (only the socket is faked).
  Adds a providerCall-injection refactor to realLoopTransport (+ opts.providerFor).
- The integrator's real hive_integrate merge: an integrator-role desk calls
  hive_integrate with a real `integrate` dep that runs a real `git merge` into trunk
  after .qc-passed; the files land on trunk, and a non-integrator is refused by the
  gate. Previously only the deny-list refusal of hive_integrate was tested.

Documented in docs/sddp-smoke.md the three inherently sandbox-only gaps (the ceiling):
the electron utilityProcess worker, the god-creates-epic + reviewer/integrator ping
choreography, and the fact that the stub ignores each sub-agent's systemPrompt — so
only a live model tests whether the prompts actually elicit the right behavior. Also
refreshed section 1 to describe both suites + `npm run test:e2e`.

12 full-stack cases (was 9); 570 tests pass; typecheck clean; lint 0 errors. Test +
docs only; won-agent-core + production code untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A fourth "are you testing everything?" prompted a deeper skeptical audit; it found
four more composable gaps beyond the documented sandbox-only ceiling. Close them all
(test-only) and write down the complete coverage map so nothing is silently uncovered.

New composed cases in sddpFullStack.test.ts (now 16):
- Multiple tool-uses in ONE provider turn — a real model parallelizes tool calls; the
  loop now provably executes every tool from a single turn (was: one tool per turn).
- The REAL Minimax adapter at the wire level — driven over a mocked Anthropic-style
  SSE stream (tool_use assembly + final text), composed with loop -> toolkit -> fs.
  Previously only DeepSeek was composed; Minimax was unit-tested only.
- Reasoning/thinking — the DeepSeek case now streams reasoning_content and asserts a
  thinking event surfaces through the loop.
- EVERY registered sub-agent (all 20) — data-driven over SUB_AGENT_NAMES: each spawns
  and its write_file is allowed/denied per roleAuthorsCode(spec.roles), so the 5 never
  previously spawned (adversarial-scanner/checklist-reader/task-tracker/
  configuration-auditor/developer) + the role->deny-list gate are covered for the whole
  registry.
- A PERSISTENT desk (new describe) — a NativeAgentWorker driven across TWO turns via the
  real loop with a block:true drain continuation; tools route to the real toolkit and
  memory written in turn 1 is read in turn 2. Closes the persistent-loop + drain +
  cross-turn-memory gaps. (realLoopTransport gained an optional drain() param.)

docs/sddp-smoke.md: replaced the prose with a full COVERAGE MATRIX (every layer ->
COVERED-COMPOSED / UNIT-ONLY / SANDBOX-ONLY with where/why), and an explicit irreducible
ceiling: the electron utilityProcess worker + NativeRuntime.spawn lifecycle, the
god-creates-epic + reviewer/integrator ping choreography, and prompt efficacy (the stub
ignores systemPrompt — only a live model tests whether prompts work).

Honest bottom line: coverage is asymptotic, but every gap that reflects real production
behavior and is composable in vitest is now closed; the only remaining items are the
three documented sandbox-only ones + a few unit-tested-elsewhere internals.

574 tests pass; typecheck clean; lint 0 errors. Test + docs only; won-agent-core +
production code untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…an + ground the feature steps

SDDP mode drove only the per-feature specify→qc lifecycle from a hand-seeded epic
on a bare repo. Make it pick up an ALREADY-bootstrapped repo (init + PRD + system
design + devops + project-plan done) and run the feature workflow hands-off.

- Bootstrap-aware buildStepInput (sddpPipeline.ts): the grounded steps (spec / research
  / data-model / contracts / adrs / plan) now tell their sub-agent to read the project
  docs — specs/prd.md, specs/sad.md, specs/dod.md, specs/project-plan.md,
  project-instructions.md — so the feature artifacts are grounded in the bootstrap docs
  instead of ignoring them. Additive prose; locations per .claude/rules/sddp-bootstrap.md.

- Auto-derive epics (the before-Specify step): a pure parser (won-agent-core
  projectPlan.ts: parseProjectPlanEpics / featureFolderForEpic / epicCardDescription)
  turns the plan's `- [ ] E### [P#] [CAT] {SAD:..}{PRD:..} <title> — <detail>` checklist
  into ParsedEpics; SddpPipeline.seedFeaturesFromPlan(repo) reads specs/project-plan.md
  and seeds one feature epic card (defaultMilestones + derived 00001-slug feature folder
  + a description carrying the PRD/SAD refs) per pending epic, idempotently (skips ones
  already carded by E### id). New injected deps projectPlanText/seedFeatureEpic mirror
  the seedImplementCards seam. This CONSUMES a finished plan — it does not re-run
  projectplan.

- Host wiring (index.ts): implement the deps over real fs + the hive, and a thin,
  idempotent auto-seed trigger — on SDDP enable (config:update), at startup (whenReady),
  and on each board tick — over every registered repo. Gated on SDDP mode; the god can
  still create epics too.

- End-to-end proof (sddpFullStack.test.ts, case 17): a temp repo bootstrapped with
  prd/sad/dod/project-plan/project-instructions (committed, no feature work) →
  seedFeaturesFromPlan auto-creates the E001 epic (feature 00001-provider-runtime-and-
  event-bus, refs in the description) → specify→qc runs to .qc-passed under autopilot
  with ZERO escalations, and the sub-agents REALLY read specs/prd.md + specs/sad.md via
  the real toolkit (consumption proven, not just presence). Re-seeding is idempotent.
  The harness is now feature-folder-dynamic, so the prior 16 cases still resolve to
  '00001' and stay green.

582 tests pass (incl. 7 new projectPlan unit tests); typecheck clean; lint 0 errors.
Additive + SDDP-gated; the won-agent-core parser lives in its own repo (not committed
here); only main-side files staged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sekeeping

- README: "What this fork adds" high-level section
- FORK-FEATURES.md: detailed per-feature catalog of fork additions vs upstream
- LICENSE: add James Han copyright
- .gitignore / .claude/settings.json: ignore local reference copy + perms

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jsh562
jsh562 merged commit ccb82cc into main Jul 30, 2026
0 of 2 checks passed
@jsh562
jsh562 deleted the features-v0.2.2 branch July 30, 2026 00:39
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