Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
271519e
fix(engine): engine-protect the intent-main→stage enforcement merge (…
jwaldrip May 28, 2026
2380d07
fix(outputs): system journals never surface as unit outputs (action-l…
jwaldrip May 28, 2026
7c26425
perf(browse): lean GitLab list-view loader — batch main branches, dro…
jwaldrip May 28, 2026
a3e049c
perf(browse): lean GitHub list-view loader — batch main branches (par…
jwaldrip May 28, 2026
3da9eea
feat(engine): hold intents at pending_seal until merged into the defa…
jwaldrip May 28, 2026
fe40bc5
fix(browse): GitLab list — surface raw-GraphQL errors + fall back so …
jwaldrip May 28, 2026
3385446
Merge feat/pending-seal-until-merged into fix/engine-fm-merge-and-bro…
jwaldrip May 28, 2026
5aca1fc
fix(browse): paginate the GitLab intent-branch query (intents past br…
jwaldrip May 28, 2026
15adfef
fix(browse): paginate the GitHub haiku-branch query (parity with GitL…
jwaldrip May 28, 2026
bf39c21
fix(pending-seal): reinterpret a sealed stamp against merge state at …
jwaldrip May 28, 2026
35b93b2
fix(browse): resolve unmerged-intent artifacts + make code inputs/out…
jwaldrip May 28, 2026
9525235
feat(browse): link review/approval roles to studio defs + render raw …
jwaldrip May 28, 2026
99da18c
feat(browse): show filing agent on FB badge + collapse hat/fix histor…
jwaldrip May 28, 2026
d8fd5fa
fix(pending-seal): a sealed stamp only short-circuits once the work i…
jwaldrip May 28, 2026
7f7d427
feat(browse): open review/approval agent defs in a modal instead of l…
jwaldrip May 28, 2026
5e6eca4
chore(pending-seal): note branch-sync in the prompt + rebuild the bundle
jwaldrip May 28, 2026
b3ecad8
feat(browse): open stage-view outputs in a modal + tame hat-history b…
jwaldrip May 28, 2026
86c45ec
feat(browse): render hat/fix history as a horizontal stepper
jwaldrip May 28, 2026
2756b5d
feat(browse): shared sign-off component + every role opens its defini…
jwaldrip May 28, 2026
1eebc22
feat(browse): show active time on a unit (sum of hat-dispatch durations)
jwaldrip May 28, 2026
5f4e399
fix(browse): wire unit quick-stats to real data + drop irrelevant tiles
jwaldrip May 28, 2026
9986112
feat(browse): show created date on list-view intent cards
jwaldrip May 28, 2026
bf7a884
fix(browse): stop Relay duplicate-id warning on the intent tree fetch
jwaldrip May 28, 2026
15698af
feat(browse): path-shaped loading skeletons + /unit/ path keyword
jwaldrip May 28, 2026
3696672
fix(review): address PR #374 review + biome lint
jwaldrip May 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ When modifying any component, check if other components need corresponding updat
| Feedback Assessor | Fix Loop | Terminal hat in `fix_hats` that independently decides closure — `plugin/studios/{name}/stages/{stage}/hats/feedback-assessor.md` | orchestrator.ts |
| Studio Review Agent | Intent-Completion Review | `plugin/studios/{name}/intent-review-agents/{agent}.md` (NOT per-stage). Walked by the cursor's intent-completion walk: `intentReviewRoles(mode, studioAgents)` appends each `intent-review-agents/*.md` after the engine roles (spec, continuity, cross-stage-consistency), deduped against them, before the terminal `user` gate; `intent_review/index.ts` resolves the mandate body via `readStudioReviewAgentPaths`. (Pre-2026-05-21 the append was severed — only `cross-stage-consistency`, promoted to an engine role, fired; `runtime-verifier` silently never ran at intent completion. The drift fix restored the general append.) Resolved by a TWO-tier cascade (2026-05-21): a GLOBAL tier at `plugin/intent-review-agents/*.md` (sibling of `studios/`, same shape as global `hats/`/`review-agents/`) is read first, then the per-studio `plugin/studios/{name}/intent-review-agents/*.md` overrides by agent name; project `.haiku/` overrides plugin at each tier. `delivery-verifier` lives at the global tier so EVERY studio runs it; software additionally ships `runtime-verifier` (drives the integrated app) + `cross-stage-consistency`. Opt out with `intent_completion_review: false` on intent frontmatter. Directory was renamed from `review-agents/` 2026-05-18 to free the studio-tier slot in the stage-review cascade | cursor.ts (`intentReviewRoles`, `walkIntentTrack`), `intent_review/index.ts`, `readStudioReviewAgentPaths` |
| Delivery Verifier | Intent-Completion Review | `plugin/intent-review-agents/delivery-verifier.md` — the GLOBAL-tier intent-review-agent, so **every studio** runs it at intent close (2026-05-21; was software-only). Confirms the intent is *deliverable*: waits for the delivery PR's CI to settle (`gh pr checks --watch`), files one intent-scope FB per failure (concrete: failing check + command + error + file), AND checks that CI is *meaningful* not just green — it reads the intent's quality-gate union (`haiku_unit_list` + `haiku_unit_get { field: "quality_gates" }`) as the reference set and files a finding when a declared gate isn't run by any CI job, when a green check is a no-op (`exit 0` / "0 tests" / never-triggered conditional), or when code shipped to a remote with no pipeline enforcing the work's own bar. Also reads/replies-to/resolves the PR review conversation. Signs off only when CI is green AND meaningful AND the PR is **mergeable** (not draft, no conflicts — `gh pr view --json isDraft,mergeable`) AND no actionable review thread is open; otherwise findings drive the studio fix-hat loop and it re-audits until clean. **Provider-agnostic by design — assumes NO authed CLI/provider** (2026-05-25): (1) it FIRST checks the provider-free merge signal via local git — if `haiku/<intent>/main` is already an ancestor of mainline (`git merge-base --is-ancestor`), the host's branch protection already gated CI+review, so the merge IS proof → sign off; (2) no remote at all → genuine SKIP; (3) remote + PR exists + provider access → verify as above; (4) remote + unmerged + NO provider access (no/unauthed CLI) → it is **blind to a gate that exists**, which is NOT a SKIP — it files ONE `severity: medium` intent FB escalating to the human (confirm CI green + merge, or grant CLI access) that HOLDS the `delivery-verifier` stamp (open invalidating FB → `dispatch-stamps.ts` skips the stamp) WITHOUT spinning the fix-hat loop (medium < fix threshold; a fixer can't authenticate a CLI), and re-audits via merge-proof / live-CI on the next pass. Never a silent false-green seal on an unverifiable delivery. Carries the `PR_INTERACTION_ROLES` scope carve-out (may mutate the PR via `gh`/`glab` — reply + resolve — but never edits source; code fixes flow through findings). Independent of `runtime-verifier` (boots-locally ≠ CI-green) | delivery-verifier.md, `_shared/index.ts` (`PR_INTERACTION_ROLES`), `intent_review/subagent.eta.md` |
| Pending seal (terminal hold) | Quality Enforcement | An intent is NOT sealed (terminal write-lock `sealed_at`) until its work has landed on the repo's default branch. After every intent-scope approval is signed and reflection is done, the cursor's terminal gate checks `intentDeliveryState(slug)` (squash-merge-aware `isBranchMerged(haiku/<slug>/main, getMainlineBranch())`): if the hub branch is NOT an ancestor of the default branch it emits `pending_seal` and HOLDS — `sealed_at` stays null and the engine NEVER merges (honors "never merge unless asked"); the next tick re-checks and seals once the merge lands (local merge OR merged PR/MR both count). Inapplicable in filesystem mode / when no default branch resolves / when already merged → seals immediately as before. On `/haiku:haiku-pickup` of a held intent, `haiku_run_next` re-opens the `PR_INTERACTION_ROLES` approvals (`delivery-verifier`) so it re-audits the open change request and files feedback for any new review comments before sealing — honors cascade overrides (keyed on the role-class set, not a hardcoded name). Surfaced as derived status `pending_seal` on `haiku_intent_list`, `current_state.seal_status` + `awaiting_merge_into` on the SPA wire (rendered by `IntentCompleteView`), the `pending_seal` statusline phase, and the architecture-map post-intent card. NOT surfaced in `haiku_capacity` (throughput report stays sealed_at-only: held intents count as in-flight) | orchestrator/workflow/intent-delivery.ts, orchestrator/workflow/cursor.ts (terminal gate + `pending_seal` action), tools/orchestrator/haiku_run_next.ts (pickup re-open), prompts/intent/seal/pending_seal, current-state.ts, statusline/{state,render}.ts |
| Pre-tick clean-tree gate | Quality Enforcement | `haiku_run_next` blocks the tick when the working tree has uncommitted work OUTSIDE the engine's `.haiku/` bookkeeping and returns `dirty_tree_blocking_tick`, telling the agent to commit its own work with logical, story-telling messages (the engine no longer authors the agent's commits under a generic "wip" message). Runs after the mid-merge detector. Engine-owned `.haiku/` writes stay the engine's job (committed at lifecycle points; `autoCommitDirtyTree` fallbacks cover mid-merge edges). Classifier: `uncommittedAgentWork()`. Paired with the end-of-tick auto-push (active stage branch, or intent main during the completion phase) so CI + the delivery PR stay current | git-worktree.ts (`uncommittedAgentWork`, auto-push), tools/orchestrator/haiku_run_next.ts |
| Studio Fix Hat | Intent-Completion Review | Two-tier cascade (global → studio, more-specific wins; project `.haiku/` overrides plugin at each tier): `plugin/fix-hats/{hat}.md` → `plugin/studios/{name}/fix-hats/{hat}.md`. NOT per-stage. Dispatched against intent-scope feedback. The shared `validator` (terminal fix verifier) and `reconciler` (cross-stage alignment) live at the GLOBAL tier so every studio inherits ONE copy; a studio drops a same-named file to override (e.g. `hwdev` ships a hardware-specific `reconciler`). Chain ORDER comes from `resolveStudioFixHats` (STUDIO.md `fix_hats:` if declared, else alphabetical over the cascade) — the SAME ordered source the cursor, `haiku_feedback_advance_hat`, and `_reject_hat` all use | orchestrator.ts (`intent_completion_fix` action), studio.ts (`resolveStudioFixHats`), studio-reader.ts (`readStudioFixHatPaths`/`readStudioFixHatDefs`, `fixHatDirCascade`) |
| Feedback Triage | Quality Enforcement | `triaged_at:` on feedback frontmatter — pre-tick gate (`feedback-triage-gate.ts`) classifies open FBs before any handler dispatch; cross-stage findings are relocated via `haiku_feedback_move` rather than carrying a stage hint | state-tools.ts, orchestrator/workflow/feedback-triage-gate.ts |
Expand Down
11 changes: 11 additions & 0 deletions packages/haiku-api/src/schemas/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,17 @@ export const IntentCurrentStateSchema = z
* `milestones.length` when every milestone is done. */
progress_index: z.number().int().nonnegative().optional(),
progress_total: z.number().int().nonnegative().optional(),
/** Terminal seal state — present only once every stage has merged
* (the intent is past all stages). `"sealed"` once the terminal
* write-lock is stamped; `"pending_seal"` when the work is built,
* signed, and reflected but its hub branch hasn't yet landed on the
* repo's default branch (held awaiting merge — the engine never
* merges; the human or the host does). Omitted while any stage is
* still in flight. */
seal_status: z.enum(["sealed", "pending_seal"]).optional(),
/** When `seal_status === "pending_seal"`, the default branch the work
* is waiting to land on (e.g. `main`). */
awaiting_merge_into: z.string().optional(),
})
.describe(
"Unified current-state snapshot — derived fresh per request from per-unit frontmatter and branch-merge state. The single source of truth for 'where is this intent right now?'.",
Expand Down
2 changes: 1 addition & 1 deletion packages/haiku-ui/budget.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"bundleGzipMaxBytes": 1048576,
"lastKnownGzipBytes": 857047,
"lastKnownGzipBytes": 857277,
"notes": [
"Pre-move bundle measured at 929.8 KB gzipped (inlined HTML blob at stages/development/artifacts/bundle-baseline.html). Post-move blob was 884.9 KB gzipped (-44.8 KB from pre-move relocation).",
"FB-21: vite.config.ts had `minify: false`, shipping a ~919 KB un-minified inlined SPA. Flipping to `minify: \"esbuild\"` (Vite default) dropped the inlined gzipped size to 693 KB (-24.6% / ~220 KB saved) with no bundle-shape changes — single inline HTML blob still required by the MCP embedder.",
Expand Down
41 changes: 32 additions & 9 deletions packages/haiku-ui/src/pages/review/IntentCompleteView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ export interface IntentCompleteViewProps {
* explicit URL — common when the user opens the PR via `gh pr
* create` or the GitHub UI directly. */
discoveredReviewUrl?: DiscoveredReviewUrl | null
/** Terminal seal state from the cursor (`current_state.seal_status`).
* `"sealed"` → the work landed on the default branch and the intent is
* write-locked. `"pending_seal"` → built + signed + reflected but the
* hub branch hasn't merged into the default branch yet, so the intent
* is HELD (the engine never merges; the human/host does). Null on older
* payloads → fall back to the deprecated `status` frontmatter shim. */
sealStatus?: "sealed" | "pending_seal" | null
/** When `sealStatus === "pending_seal"`, the default branch the work is
* waiting to land on (e.g. `main`). */
awaitingMergeInto?: string | null
/** Output artifacts across every stage. Surfaced as a click-out
* list at the final intent gate so reviewers can walk through
* the deliverables before approving the merge. */
Expand Down Expand Up @@ -84,6 +94,8 @@ export function IntentCompleteView({
stageOrder,
deliveryReviewUrl,
discoveredReviewUrl,
sealStatus,
awaitingMergeInto,
outputArtifacts,
outputDeclaredBy,
}: IntentCompleteViewProps): React.ReactElement {
Expand All @@ -104,11 +116,18 @@ export function IntentCompleteView({
)
const phase = (intentFrontmatter.phase as string | undefined) ?? ""
const status = intentFrontmatter.status ?? ""
const isFullyComplete = status === "completed"
const headlineLabel = isFullyComplete
// Prefer the cursor's authoritative seal_status; fall back to the
// deprecated v3 `status` shim only when the wire didn't carry it.
const isSealed =
sealStatus === "sealed" || (sealStatus == null && status === "completed")
const isPendingSeal = sealStatus === "pending_seal"
const headlineLabel = isSealed
? "Intent complete"
: "All stages reviewed"
: isPendingSeal
? "Pending seal"
: "All stages reviewed"
const intentMainBranch = `haiku/${intentSlug}/main`
const mergeTarget = awaitingMergeInto || "the repo's default branch"

return (
<div
Expand All @@ -117,7 +136,11 @@ export function IntentCompleteView({
>
<header className="rounded-lg border-2 border-teal-300 dark:border-teal-700 bg-teal-50 dark:bg-teal-900/20 px-6 py-5">
<div className="flex flex-wrap items-center gap-2 mb-2">
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-bold uppercase tracking-wider bg-teal-700 text-white">
<span
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-bold uppercase tracking-wider text-white ${
isPendingSeal ? "bg-violet-600" : "bg-teal-700"
}`}
>
{headlineLabel}
</span>
{phase && (
Expand All @@ -141,13 +164,13 @@ export function IntentCompleteView({
.
</p>
)}
{!isFullyComplete && (
{!isSealed && (
<p className="text-sm text-stone-700 dark:text-stone-300 mt-2">
Awaiting merge of{" "}
<code className="font-mono">{intentMainBranch}</code> into the
repo's mainline. The merge is the only remaining action — no further{" "}
<code className="font-mono">haiku_run_next</code> tick needed to
seal.
<code className="font-mono">{intentMainBranch}</code> into{" "}
<code className="font-mono">{mergeTarget}</code>. The intent is built,
signed, and reflected — but it won't seal until that merge lands. The
engine never merges it for you; merging is the human/host's call.
</p>
)}
</header>
Expand Down
4 changes: 4 additions & 0 deletions packages/haiku-ui/src/routes/review/$sessionId/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,10 @@ function ReviewLayoutLoaded({
stageOrder={stageNamesOrdered}
deliveryReviewUrl={deliveryReviewUrl}
discoveredReviewUrl={discoveredReviewUrl}
sealStatus={session.current_state?.seal_status ?? null}
awaitingMergeInto={
session.current_state?.awaiting_merge_into ?? null
}
outputArtifacts={session.output_artifacts}
outputDeclaredBy={session.output_declared_by}
/>
Expand Down
Loading
Loading