feat(crit): PR review integration (v1, flag-gated) - #22

Merged
Ecko95 merged 19 commits into
gitsfrom
feat/crit-pr-review-integration
Jun 7, 2026
Merged

feat(crit): PR review integration (v1, flag-gated)#22
Ecko95 merged 19 commits into
gitsfrom
feat/crit-pr-review-integration

Conversation

@Ecko95

Copy link
Copy Markdown
Owner

Crit PR Review Integration (v1 — flag-gated, experimental)

Embeds the crit review binary as a GITS-managed sidecar and surfaces its review UI inside the PR view. "Send to agent" routes into the active GITS thread (so the user's default provider — codex — answers), rather than crit spawning its own agent.

Design: docs/superpowers/specs/2026-06-07-crit-pr-review-integration-design.md
Plan: docs/superpowers/plans/2026-06-07-crit-pr-review-integration.md

Architecture

apps/server spawns one crit process per workspace and sets crit's agent_cmd to a thin GITS wrapper CLI. The wrapper reads crit's comment on stdin, POSTs a thread.turn.start to /api/orchestration/dispatch, blocks while polling /api/orchestration/snapshot until the turn completes, then prints the assistant reply on stdout for crit to thread. apps/web renders crit's loopback URL in an <iframe> panel that replaces the sidebar diff for PR review only, behind an off-by-defaultcritReviewEnabled flag with native-DiffPanel fallback.

Why crit's missing codex support is a non-issue: crit's agent_cmd points at the GITS wrapper, never at codex. The wrapper satisfies crit's stdin→stdout contract; the actual codex run happens inside GITS via the codex app-server. crit never spawns an agent.

What's included

LayerComponent
sharedreview-comment-block.ts — builds the <review_comment> block the existing web parser reads
server (CLI)crit-agent-cli.ts — the agent_cmd bridge (parse → thread.turn.start → block-and-return poll → reply)
servercrit-binary-resolver.ts, crit-sidecar-manager.ts (Effect service: spawn + HTTP health-check + ref-count + Scope teardown), crit-sidecar-request.ts
contractscrit.ts schemas + crit.ensureSidecar/crit.sidecarStatus RPC, critReviewEnabled setting
client/webEnvironmentApi.crit, CritReviewPanel.tsx, PR-view route wiring behind the flag

Verification

  • ~27 unit tests passing (builder 4, wrapper CLI 5, resolver 3, sidecar manager 4, contracts 7, request helper 3, panel 1).
  • Typecheck clean across packages/{shared,contracts,client-runtime} and apps/{server,web} for all touched files.
  • End-to-end RPC identity verified: client transport.request(WS_METHODS.critEnsureSidecar) ↔ server ws.ts handler share the same Rpc.make contract.
  • Flag-off default is safe — with critReviewEnabled false, the native diff/PR path is unchanged and no crit code runs.
  • Two-stage review per task caught & fixed: a parser-truncation bug (escaping <), the wrapper's typing/lint issues, and a sidecar crash path that threw at runtime + a non-load-bearing test.

⚠️ Deferred (require building crit; Go was unavailable in the build env)

Tracked in docs/crit-integration-notes.md, marked PENDING in-code:

  • 0b real crit CLI flags — crit-sidecar-manager.ts uses placeholder --repo/--branch/--host/--port/--agent-cmd.
  • 0c crit's agent_cmd stdin format — crit-agent-cli.ts assumes JSON {comment,quoted,filePath,startLine,endLine} with a plain-text fallback.
  • Phase 7 per-platform binary bundling (electron-builder extraResources) + the wrapper's production build path (GITS_CRIT_AGENT_CMD env override for now).
  • Phase 8 end-to-end test against a live crit.

🔎 Follow-ups from final holistic review (not blockers for this flag-gated v1)

  1. Block-and-return stale-reply race — after dispatch, latestTurn doesn't flip to running synchronously, so a fast first poll can return the prior turn's reply. Fix: baseline the pre-dispatch turn id and only accept a new completed turn.
  2. Token is role:"owner", never revoked — deviates from the design's "workspace-scoped, dispatch-only, revoked on teardown." Currently necessary because dispatch/snapshot are owner-gated; needs a scoped-dispatch capability + revocation.
  3. release_sidecar not called in production — process/token leak; wire it to CritReviewPanel cleanup via a crit.releaseSidecar WS method (or release on WS disconnect).

🤖 Generated with Claude Code

Ecko95and others added 18 commits June 7, 2026 15:15
Embed crit binary as a GITS-managed sidecar, render in the PR view, and wire agent_cmd to a wrapper CLI that injects review comments into the active GITS thread via /api/orchestration/dispatch (block-and-return). Bundle per-platform binaries; sidecar owned by apps/server.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task 0a complete. CLI flags (0b) and stdin format (0c) pending — Go not available to build crit; later phases use marked assumptions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The web parser extracts body text raw (no unescaping), so applying
escape_attribute to the body caused users/agents to see literal &quot;
in rendered comments. Attributes are still escaped correctly.
Fix the test that incorrectly asserted &quot; in the body, and add an
attribute-escaping assertion using a sectionTitle with &, ", and <.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add ./crit/review-comment-block to the @t3tools/shared exports map so the
crit-agent-cli in apps/server can resolve the shared block builder.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add parse_crit_payload with TDD — normalises crit's JSON stdin
{ comment, quoted, filePath, startLine, endLine } into a NormalizedComment,
falling back to treating raw stdin as the comment text when JSON parse fails.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…al timeoutMs typing
Wrapper is a dependency-light child-process CLI, not an Effect program — disable globalFetch/globalDate/globalTimers/nodeBuiltinImport diagnostics (sanctioned per GitsCapacityMonitor/bin.test precedent). Fix exactOptionalPropertyTypes failure on timeoutMs via mutable override object.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…turn
Baseline the thread's latestTurn id before dispatch; only accept a turn whose id differs, so a fast first poll can't surface the previous (completed) turn's reply. Adds a load-bearing test for the race.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Ecko95

Copy link
Copy Markdown
OwnerAuthor

Follow-up #1 (block-and-return stale-reply race) is now fixed in 0bf5d30b: the wrapper baselines the thread's latestTurn id before dispatch and only accepts a turn whose id differs, so a fast first poll can no longer surface the previous (completed) turn's reply. Added a load-bearing test that reproduces the race (fails against the old code). Follow-ups #2 (owner-token scoping/revocation) and #3 (release_sidecar wiring) remain open.

… with revoke-on-teardown
Change A: add a crit.releaseSidecar RPC end-to-end (contracts rpc/crit/ipc,
client-runtime, web environmentApi) and call it fire-and-forget from the
CritReviewPanel useEffect cleanup so unmount/input-change releases the refCount
the matching ensureSidecar took. The ws handler maps release_sidecar to
{ released: true }.
Change B: move the wrapper bearer-token lifecycle into CritSidecarManager. The
manager now depends on AuthControlPlane and mints an owner-scoped session ONLY
on the spawn path (reuse no longer leaks a fresh token), with a bounded 2h TTL,
and registers a Scope finalizer that revokes the session on every teardown path
(release-at-0, readiness timeout, crash, spawn failure). EnsureCritSidecarInput
and build_ensure_sidecar_input drop `token`; the ws handler stops minting. The
lifecycle test wires a stub AuthControlPlane and asserts revocation is
load-bearing across reuse/crash/timeout.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Ecko95

Copy link
Copy Markdown
OwnerAuthor

Follow-ups #2 and #3 landed in c7f1d152:

  • feat(gits): review pipeline (mechanical gate → semantic verifier → triage) #3 — sidecar release wiring (leak fixed): added a crit.releaseSidecar WS method end-to-end (contracts/rpc → client-runtime → EnvironmentApi → ws.ts handler). CritReviewPanel's effect cleanup now calls it, so unmount/input-change releases the refCount its ensureSidecar took.
  • feat(gits): acceptance-criteria plumbing for the verifier-critic #2 — token lifecycle (revoke + no-leak): token issuance moved intoCritSidecarManager — minted only on actual spawn (reuse path mints nothing, fixing the per-call leak), with a bounded 2h TTL, and revoked on every teardown path (release-to-0, crash, readiness-timeout) via a finalizer on the per-sidecar scope. Load-bearing tests assert: no second mint on reuse, not-revoked-while-alive, and revoked-on-teardown (normal + crash).

Verification: 16 server-crit + 9 web + contracts tests pass; typecheck clean across contracts/client-runtime/apps-server/apps-web.

Remaining (deliberately not in this change): true least-privilege scoping (owner→thread-bound). /api/orchestration/{dispatch,snapshot} are owner-gated and accept any command, so real scoping needs a dedicated crit endpoint (thread-bound, client-role) + repointing the wrapper — a focused, security-reviewed change better done once crit is buildable for E2E. The 2h-TTL + revoke-on-teardown is the v1 mitigation (loopback-only, flag-off).

Sign up for freeto 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

@Ecko95
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(crit): PR review integration (v1, flag-gated) - #22

Merged
Ecko95 merged 19 commits into
gitsfrom
feat/crit-pr-review-integration
Jun 7, 2026
Merged

feat(crit): PR review integration (v1, flag-gated)#22
Ecko95 merged 19 commits into
gitsfrom
feat/crit-pr-review-integration

Conversation

@Ecko95

Copy link
Copy Markdown
Owner

Crit PR Review Integration (v1 — flag-gated, experimental)

Embeds the crit review binary as a GITS-managed sidecar and surfaces its review UI inside the PR view. "Send to agent" routes into the active GITS thread (so the user's default provider — codex — answers), rather than crit spawning its own agent.

Design: docs/superpowers/specs/2026-06-07-crit-pr-review-integration-design.md
Plan: docs/superpowers/plans/2026-06-07-crit-pr-review-integration.md

Architecture

apps/server spawns one crit process per workspace and sets crit's agent_cmd to a thin GITS wrapper CLI. The wrapper reads crit's comment on stdin, POSTs a thread.turn.start to /api/orchestration/dispatch, blocks while polling /api/orchestration/snapshot until the turn completes, then prints the assistant reply on stdout for crit to thread. apps/web renders crit's loopback URL in an <iframe> panel that replaces the sidebar diff for PR review only, behind an off-by-defaultcritReviewEnabled flag with native-DiffPanel fallback.

Why crit's missing codex support is a non-issue: crit's agent_cmd points at the GITS wrapper, never at codex. The wrapper satisfies crit's stdin→stdout contract; the actual codex run happens inside GITS via the codex app-server. crit never spawns an agent.

What's included

LayerComponent
sharedreview-comment-block.ts — builds the <review_comment> block the existing web parser reads
server (CLI)crit-agent-cli.ts — the agent_cmd bridge (parse → thread.turn.start → block-and-return poll → reply)
servercrit-binary-resolver.ts, crit-sidecar-manager.ts (Effect service: spawn + HTTP health-check + ref-count + Scope teardown), crit-sidecar-request.ts
contractscrit.ts schemas + crit.ensureSidecar/crit.sidecarStatus RPC, critReviewEnabled setting
client/webEnvironmentApi.crit, CritReviewPanel.tsx, PR-view route wiring behind the flag

Verification

  • ~27 unit tests passing (builder 4, wrapper CLI 5, resolver 3, sidecar manager 4, contracts 7, request helper 3, panel 1).
  • Typecheck clean across packages/{shared,contracts,client-runtime} and apps/{server,web} for all touched files.
  • End-to-end RPC identity verified: client transport.request(WS_METHODS.critEnsureSidecar) ↔ server ws.ts handler share the same Rpc.make contract.
  • Flag-off default is safe — with critReviewEnabled false, the native diff/PR path is unchanged and no crit code runs.
  • Two-stage review per task caught & fixed: a parser-truncation bug (escaping <), the wrapper's typing/lint issues, and a sidecar crash path that threw at runtime + a non-load-bearing test.

⚠️ Deferred (require building crit; Go was unavailable in the build env)

Tracked in docs/crit-integration-notes.md, marked PENDING in-code:

  • 0b real crit CLI flags — crit-sidecar-manager.ts uses placeholder --repo/--branch/--host/--port/--agent-cmd.
  • 0c crit's agent_cmd stdin format — crit-agent-cli.ts assumes JSON {comment,quoted,filePath,startLine,endLine} with a plain-text fallback.
  • Phase 7 per-platform binary bundling (electron-builder extraResources) + the wrapper's production build path (GITS_CRIT_AGENT_CMD env override for now).
  • Phase 8 end-to-end test against a live crit.

🔎 Follow-ups from final holistic review (not blockers for this flag-gated v1)

  1. Block-and-return stale-reply race — after dispatch, latestTurn doesn't flip to running synchronously, so a fast first poll can return the prior turn's reply. Fix: baseline the pre-dispatch turn id and only accept a new completed turn.
  2. Token is role:"owner", never revoked — deviates from the design's "workspace-scoped, dispatch-only, revoked on teardown." Currently necessary because dispatch/snapshot are owner-gated; needs a scoped-dispatch capability + revocation.
  3. release_sidecar not called in production — process/token leak; wire it to CritReviewPanel cleanup via a crit.releaseSidecar WS method (or release on WS disconnect).

🤖 Generated with Claude Code

Ecko95and others added 18 commits June 7, 2026 15:15
Embed crit binary as a GITS-managed sidecar, render in the PR view, and wire agent_cmd to a wrapper CLI that injects review comments into the active GITS thread via /api/orchestration/dispatch (block-and-return). Bundle per-platform binaries; sidecar owned by apps/server.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task 0a complete. CLI flags (0b) and stdin format (0c) pending — Go not available to build crit; later phases use marked assumptions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The web parser extracts body text raw (no unescaping), so applying
escape_attribute to the body caused users/agents to see literal &quot;
in rendered comments. Attributes are still escaped correctly.
Fix the test that incorrectly asserted &quot; in the body, and add an
attribute-escaping assertion using a sectionTitle with &, ", and <.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add ./crit/review-comment-block to the @t3tools/shared exports map so the
crit-agent-cli in apps/server can resolve the shared block builder.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add parse_crit_payload with TDD — normalises crit's JSON stdin
{ comment, quoted, filePath, startLine, endLine } into a NormalizedComment,
falling back to treating raw stdin as the comment text when JSON parse fails.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…al timeoutMs typing
Wrapper is a dependency-light child-process CLI, not an Effect program — disable globalFetch/globalDate/globalTimers/nodeBuiltinImport diagnostics (sanctioned per GitsCapacityMonitor/bin.test precedent). Fix exactOptionalPropertyTypes failure on timeoutMs via mutable override object.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…turn
Baseline the thread's latestTurn id before dispatch; only accept a turn whose id differs, so a fast first poll can't surface the previous (completed) turn's reply. Adds a load-bearing test for the race.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Ecko95

Copy link
Copy Markdown
OwnerAuthor

Follow-up #1 (block-and-return stale-reply race) is now fixed in 0bf5d30b: the wrapper baselines the thread's latestTurn id before dispatch and only accepts a turn whose id differs, so a fast first poll can no longer surface the previous (completed) turn's reply. Added a load-bearing test that reproduces the race (fails against the old code). Follow-ups #2 (owner-token scoping/revocation) and #3 (release_sidecar wiring) remain open.

… with revoke-on-teardown
Change A: add a crit.releaseSidecar RPC end-to-end (contracts rpc/crit/ipc,
client-runtime, web environmentApi) and call it fire-and-forget from the
CritReviewPanel useEffect cleanup so unmount/input-change releases the refCount
the matching ensureSidecar took. The ws handler maps release_sidecar to
{ released: true }.
Change B: move the wrapper bearer-token lifecycle into CritSidecarManager. The
manager now depends on AuthControlPlane and mints an owner-scoped session ONLY
on the spawn path (reuse no longer leaks a fresh token), with a bounded 2h TTL,
and registers a Scope finalizer that revokes the session on every teardown path
(release-at-0, readiness timeout, crash, spawn failure). EnsureCritSidecarInput
and build_ensure_sidecar_input drop `token`; the ws handler stops minting. The
lifecycle test wires a stub AuthControlPlane and asserts revocation is
load-bearing across reuse/crash/timeout.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Ecko95

Copy link
Copy Markdown
OwnerAuthor

Follow-ups #2 and #3 landed in c7f1d152:

  • feat(gits): review pipeline (mechanical gate → semantic verifier → triage) #3 — sidecar release wiring (leak fixed): added a crit.releaseSidecar WS method end-to-end (contracts/rpc → client-runtime → EnvironmentApi → ws.ts handler). CritReviewPanel's effect cleanup now calls it, so unmount/input-change releases the refCount its ensureSidecar took.
  • feat(gits): acceptance-criteria plumbing for the verifier-critic #2 — token lifecycle (revoke + no-leak): token issuance moved intoCritSidecarManager — minted only on actual spawn (reuse path mints nothing, fixing the per-call leak), with a bounded 2h TTL, and revoked on every teardown path (release-to-0, crash, readiness-timeout) via a finalizer on the per-sidecar scope. Load-bearing tests assert: no second mint on reuse, not-revoked-while-alive, and revoked-on-teardown (normal + crash).

Verification: 16 server-crit + 9 web + contracts tests pass; typecheck clean across contracts/client-runtime/apps-server/apps-web.

Remaining (deliberately not in this change): true least-privilege scoping (owner→thread-bound). /api/orchestration/{dispatch,snapshot} are owner-gated and accept any command, so real scoping needs a dedicated crit endpoint (thread-bound, client-role) + repointing the wrapper — a focused, security-reviewed change better done once crit is buildable for E2E. The 2h-TTL + revoke-on-teardown is the v1 mitigation (loopback-only, flag-off).

Sign up for freeto 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

@Ecko95
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(crit): PR review integration (v1, flag-gated) - #22

Merged
Ecko95 merged 19 commits into
gitsfrom
feat/crit-pr-review-integration
Jun 7, 2026
Merged

feat(crit): PR review integration (v1, flag-gated)#22
Ecko95 merged 19 commits into
gitsfrom
feat/crit-pr-review-integration

Conversation

@Ecko95

Copy link
Copy Markdown
Owner

Crit PR Review Integration (v1 — flag-gated, experimental)

Embeds the crit review binary as a GITS-managed sidecar and surfaces its review UI inside the PR view. "Send to agent" routes into the active GITS thread (so the user's default provider — codex — answers), rather than crit spawning its own agent.

Design: docs/superpowers/specs/2026-06-07-crit-pr-review-integration-design.md
Plan: docs/superpowers/plans/2026-06-07-crit-pr-review-integration.md

Architecture

apps/server spawns one crit process per workspace and sets crit's agent_cmd to a thin GITS wrapper CLI. The wrapper reads crit's comment on stdin, POSTs a thread.turn.start to /api/orchestration/dispatch, blocks while polling /api/orchestration/snapshot until the turn completes, then prints the assistant reply on stdout for crit to thread. apps/web renders crit's loopback URL in an <iframe> panel that replaces the sidebar diff for PR review only, behind an off-by-defaultcritReviewEnabled flag with native-DiffPanel fallback.

Why crit's missing codex support is a non-issue: crit's agent_cmd points at the GITS wrapper, never at codex. The wrapper satisfies crit's stdin→stdout contract; the actual codex run happens inside GITS via the codex app-server. crit never spawns an agent.

What's included

LayerComponent
sharedreview-comment-block.ts — builds the <review_comment> block the existing web parser reads
server (CLI)crit-agent-cli.ts — the agent_cmd bridge (parse → thread.turn.start → block-and-return poll → reply)
servercrit-binary-resolver.ts, crit-sidecar-manager.ts (Effect service: spawn + HTTP health-check + ref-count + Scope teardown), crit-sidecar-request.ts
contractscrit.ts schemas + crit.ensureSidecar/crit.sidecarStatus RPC, critReviewEnabled setting
client/webEnvironmentApi.crit, CritReviewPanel.tsx, PR-view route wiring behind the flag

Verification

  • ~27 unit tests passing (builder 4, wrapper CLI 5, resolver 3, sidecar manager 4, contracts 7, request helper 3, panel 1).
  • Typecheck clean across packages/{shared,contracts,client-runtime} and apps/{server,web} for all touched files.
  • End-to-end RPC identity verified: client transport.request(WS_METHODS.critEnsureSidecar) ↔ server ws.ts handler share the same Rpc.make contract.
  • Flag-off default is safe — with critReviewEnabled false, the native diff/PR path is unchanged and no crit code runs.
  • Two-stage review per task caught & fixed: a parser-truncation bug (escaping <), the wrapper's typing/lint issues, and a sidecar crash path that threw at runtime + a non-load-bearing test.

⚠️ Deferred (require building crit; Go was unavailable in the build env)

Tracked in docs/crit-integration-notes.md, marked PENDING in-code:

  • 0b real crit CLI flags — crit-sidecar-manager.ts uses placeholder --repo/--branch/--host/--port/--agent-cmd.
  • 0c crit's agent_cmd stdin format — crit-agent-cli.ts assumes JSON {comment,quoted,filePath,startLine,endLine} with a plain-text fallback.
  • Phase 7 per-platform binary bundling (electron-builder extraResources) + the wrapper's production build path (GITS_CRIT_AGENT_CMD env override for now).
  • Phase 8 end-to-end test against a live crit.

🔎 Follow-ups from final holistic review (not blockers for this flag-gated v1)

  1. Block-and-return stale-reply race — after dispatch, latestTurn doesn't flip to running synchronously, so a fast first poll can return the prior turn's reply. Fix: baseline the pre-dispatch turn id and only accept a new completed turn.
  2. Token is role:"owner", never revoked — deviates from the design's "workspace-scoped, dispatch-only, revoked on teardown." Currently necessary because dispatch/snapshot are owner-gated; needs a scoped-dispatch capability + revocation.
  3. release_sidecar not called in production — process/token leak; wire it to CritReviewPanel cleanup via a crit.releaseSidecar WS method (or release on WS disconnect).

🤖 Generated with Claude Code

Ecko95and others added 18 commits June 7, 2026 15:15
Embed crit binary as a GITS-managed sidecar, render in the PR view, and wire agent_cmd to a wrapper CLI that injects review comments into the active GITS thread via /api/orchestration/dispatch (block-and-return). Bundle per-platform binaries; sidecar owned by apps/server.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task 0a complete. CLI flags (0b) and stdin format (0c) pending — Go not available to build crit; later phases use marked assumptions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The web parser extracts body text raw (no unescaping), so applying
escape_attribute to the body caused users/agents to see literal &quot;
in rendered comments. Attributes are still escaped correctly.
Fix the test that incorrectly asserted &quot; in the body, and add an
attribute-escaping assertion using a sectionTitle with &, ", and <.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add ./crit/review-comment-block to the @t3tools/shared exports map so the
crit-agent-cli in apps/server can resolve the shared block builder.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add parse_crit_payload with TDD — normalises crit's JSON stdin
{ comment, quoted, filePath, startLine, endLine } into a NormalizedComment,
falling back to treating raw stdin as the comment text when JSON parse fails.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…al timeoutMs typing
Wrapper is a dependency-light child-process CLI, not an Effect program — disable globalFetch/globalDate/globalTimers/nodeBuiltinImport diagnostics (sanctioned per GitsCapacityMonitor/bin.test precedent). Fix exactOptionalPropertyTypes failure on timeoutMs via mutable override object.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…turn
Baseline the thread's latestTurn id before dispatch; only accept a turn whose id differs, so a fast first poll can't surface the previous (completed) turn's reply. Adds a load-bearing test for the race.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Ecko95

Copy link
Copy Markdown
OwnerAuthor

Follow-up #1 (block-and-return stale-reply race) is now fixed in 0bf5d30b: the wrapper baselines the thread's latestTurn id before dispatch and only accepts a turn whose id differs, so a fast first poll can no longer surface the previous (completed) turn's reply. Added a load-bearing test that reproduces the race (fails against the old code). Follow-ups #2 (owner-token scoping/revocation) and #3 (release_sidecar wiring) remain open.

… with revoke-on-teardown
Change A: add a crit.releaseSidecar RPC end-to-end (contracts rpc/crit/ipc,
client-runtime, web environmentApi) and call it fire-and-forget from the
CritReviewPanel useEffect cleanup so unmount/input-change releases the refCount
the matching ensureSidecar took. The ws handler maps release_sidecar to
{ released: true }.
Change B: move the wrapper bearer-token lifecycle into CritSidecarManager. The
manager now depends on AuthControlPlane and mints an owner-scoped session ONLY
on the spawn path (reuse no longer leaks a fresh token), with a bounded 2h TTL,
and registers a Scope finalizer that revokes the session on every teardown path
(release-at-0, readiness timeout, crash, spawn failure). EnsureCritSidecarInput
and build_ensure_sidecar_input drop `token`; the ws handler stops minting. The
lifecycle test wires a stub AuthControlPlane and asserts revocation is
load-bearing across reuse/crash/timeout.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Ecko95

Copy link
Copy Markdown
OwnerAuthor

Follow-ups #2 and #3 landed in c7f1d152:

  • feat(gits): review pipeline (mechanical gate → semantic verifier → triage) #3 — sidecar release wiring (leak fixed): added a crit.releaseSidecar WS method end-to-end (contracts/rpc → client-runtime → EnvironmentApi → ws.ts handler). CritReviewPanel's effect cleanup now calls it, so unmount/input-change releases the refCount its ensureSidecar took.
  • feat(gits): acceptance-criteria plumbing for the verifier-critic #2 — token lifecycle (revoke + no-leak): token issuance moved intoCritSidecarManager — minted only on actual spawn (reuse path mints nothing, fixing the per-call leak), with a bounded 2h TTL, and revoked on every teardown path (release-to-0, crash, readiness-timeout) via a finalizer on the per-sidecar scope. Load-bearing tests assert: no second mint on reuse, not-revoked-while-alive, and revoked-on-teardown (normal + crash).

Verification: 16 server-crit + 9 web + contracts tests pass; typecheck clean across contracts/client-runtime/apps-server/apps-web.

Remaining (deliberately not in this change): true least-privilege scoping (owner→thread-bound). /api/orchestration/{dispatch,snapshot} are owner-gated and accept any command, so real scoping needs a dedicated crit endpoint (thread-bound, client-role) + repointing the wrapper — a focused, security-reviewed change better done once crit is buildable for E2E. The 2h-TTL + revoke-on-teardown is the v1 mitigation (loopback-only, flag-off).

Sign up for freeto 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

@Ecko95
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(crit): PR review integration (v1, flag-gated) - #22

Merged
Ecko95 merged 19 commits into
gitsfrom
feat/crit-pr-review-integration
Jun 7, 2026
Merged

feat(crit): PR review integration (v1, flag-gated)#22
Ecko95 merged 19 commits into
gitsfrom
feat/crit-pr-review-integration

Conversation

@Ecko95

Copy link
Copy Markdown
Owner

Crit PR Review Integration (v1 — flag-gated, experimental)

Embeds the crit review binary as a GITS-managed sidecar and surfaces its review UI inside the PR view. "Send to agent" routes into the active GITS thread (so the user's default provider — codex — answers), rather than crit spawning its own agent.

Design: docs/superpowers/specs/2026-06-07-crit-pr-review-integration-design.md
Plan: docs/superpowers/plans/2026-06-07-crit-pr-review-integration.md

Architecture

apps/server spawns one crit process per workspace and sets crit's agent_cmd to a thin GITS wrapper CLI. The wrapper reads crit's comment on stdin, POSTs a thread.turn.start to /api/orchestration/dispatch, blocks while polling /api/orchestration/snapshot until the turn completes, then prints the assistant reply on stdout for crit to thread. apps/web renders crit's loopback URL in an <iframe> panel that replaces the sidebar diff for PR review only, behind an off-by-defaultcritReviewEnabled flag with native-DiffPanel fallback.

Why crit's missing codex support is a non-issue: crit's agent_cmd points at the GITS wrapper, never at codex. The wrapper satisfies crit's stdin→stdout contract; the actual codex run happens inside GITS via the codex app-server. crit never spawns an agent.

What's included

LayerComponent
sharedreview-comment-block.ts — builds the <review_comment> block the existing web parser reads
server (CLI)crit-agent-cli.ts — the agent_cmd bridge (parse → thread.turn.start → block-and-return poll → reply)
servercrit-binary-resolver.ts, crit-sidecar-manager.ts (Effect service: spawn + HTTP health-check + ref-count + Scope teardown), crit-sidecar-request.ts
contractscrit.ts schemas + crit.ensureSidecar/crit.sidecarStatus RPC, critReviewEnabled setting
client/webEnvironmentApi.crit, CritReviewPanel.tsx, PR-view route wiring behind the flag

Verification

  • ~27 unit tests passing (builder 4, wrapper CLI 5, resolver 3, sidecar manager 4, contracts 7, request helper 3, panel 1).
  • Typecheck clean across packages/{shared,contracts,client-runtime} and apps/{server,web} for all touched files.
  • End-to-end RPC identity verified: client transport.request(WS_METHODS.critEnsureSidecar) ↔ server ws.ts handler share the same Rpc.make contract.
  • Flag-off default is safe — with critReviewEnabled false, the native diff/PR path is unchanged and no crit code runs.
  • Two-stage review per task caught & fixed: a parser-truncation bug (escaping <), the wrapper's typing/lint issues, and a sidecar crash path that threw at runtime + a non-load-bearing test.

⚠️ Deferred (require building crit; Go was unavailable in the build env)

Tracked in docs/crit-integration-notes.md, marked PENDING in-code:

  • 0b real crit CLI flags — crit-sidecar-manager.ts uses placeholder --repo/--branch/--host/--port/--agent-cmd.
  • 0c crit's agent_cmd stdin format — crit-agent-cli.ts assumes JSON {comment,quoted,filePath,startLine,endLine} with a plain-text fallback.
  • Phase 7 per-platform binary bundling (electron-builder extraResources) + the wrapper's production build path (GITS_CRIT_AGENT_CMD env override for now).
  • Phase 8 end-to-end test against a live crit.

🔎 Follow-ups from final holistic review (not blockers for this flag-gated v1)

  1. Block-and-return stale-reply race — after dispatch, latestTurn doesn't flip to running synchronously, so a fast first poll can return the prior turn's reply. Fix: baseline the pre-dispatch turn id and only accept a new completed turn.
  2. Token is role:"owner", never revoked — deviates from the design's "workspace-scoped, dispatch-only, revoked on teardown." Currently necessary because dispatch/snapshot are owner-gated; needs a scoped-dispatch capability + revocation.
  3. release_sidecar not called in production — process/token leak; wire it to CritReviewPanel cleanup via a crit.releaseSidecar WS method (or release on WS disconnect).

🤖 Generated with Claude Code

Ecko95and others added 18 commits June 7, 2026 15:15
Embed crit binary as a GITS-managed sidecar, render in the PR view, and wire agent_cmd to a wrapper CLI that injects review comments into the active GITS thread via /api/orchestration/dispatch (block-and-return). Bundle per-platform binaries; sidecar owned by apps/server.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task 0a complete. CLI flags (0b) and stdin format (0c) pending — Go not available to build crit; later phases use marked assumptions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The web parser extracts body text raw (no unescaping), so applying
escape_attribute to the body caused users/agents to see literal &quot;
in rendered comments. Attributes are still escaped correctly.
Fix the test that incorrectly asserted &quot; in the body, and add an
attribute-escaping assertion using a sectionTitle with &, ", and <.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add ./crit/review-comment-block to the @t3tools/shared exports map so the
crit-agent-cli in apps/server can resolve the shared block builder.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add parse_crit_payload with TDD — normalises crit's JSON stdin
{ comment, quoted, filePath, startLine, endLine } into a NormalizedComment,
falling back to treating raw stdin as the comment text when JSON parse fails.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…al timeoutMs typing
Wrapper is a dependency-light child-process CLI, not an Effect program — disable globalFetch/globalDate/globalTimers/nodeBuiltinImport diagnostics (sanctioned per GitsCapacityMonitor/bin.test precedent). Fix exactOptionalPropertyTypes failure on timeoutMs via mutable override object.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…turn
Baseline the thread's latestTurn id before dispatch; only accept a turn whose id differs, so a fast first poll can't surface the previous (completed) turn's reply. Adds a load-bearing test for the race.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Ecko95

Copy link
Copy Markdown
OwnerAuthor

Follow-up #1 (block-and-return stale-reply race) is now fixed in 0bf5d30b: the wrapper baselines the thread's latestTurn id before dispatch and only accepts a turn whose id differs, so a fast first poll can no longer surface the previous (completed) turn's reply. Added a load-bearing test that reproduces the race (fails against the old code). Follow-ups #2 (owner-token scoping/revocation) and #3 (release_sidecar wiring) remain open.

… with revoke-on-teardown
Change A: add a crit.releaseSidecar RPC end-to-end (contracts rpc/crit/ipc,
client-runtime, web environmentApi) and call it fire-and-forget from the
CritReviewPanel useEffect cleanup so unmount/input-change releases the refCount
the matching ensureSidecar took. The ws handler maps release_sidecar to
{ released: true }.
Change B: move the wrapper bearer-token lifecycle into CritSidecarManager. The
manager now depends on AuthControlPlane and mints an owner-scoped session ONLY
on the spawn path (reuse no longer leaks a fresh token), with a bounded 2h TTL,
and registers a Scope finalizer that revokes the session on every teardown path
(release-at-0, readiness timeout, crash, spawn failure). EnsureCritSidecarInput
and build_ensure_sidecar_input drop `token`; the ws handler stops minting. The
lifecycle test wires a stub AuthControlPlane and asserts revocation is
load-bearing across reuse/crash/timeout.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Ecko95

Copy link
Copy Markdown
OwnerAuthor

Follow-ups #2 and #3 landed in c7f1d152:

  • feat(gits): review pipeline (mechanical gate → semantic verifier → triage) #3 — sidecar release wiring (leak fixed): added a crit.releaseSidecar WS method end-to-end (contracts/rpc → client-runtime → EnvironmentApi → ws.ts handler). CritReviewPanel's effect cleanup now calls it, so unmount/input-change releases the refCount its ensureSidecar took.
  • feat(gits): acceptance-criteria plumbing for the verifier-critic #2 — token lifecycle (revoke + no-leak): token issuance moved intoCritSidecarManager — minted only on actual spawn (reuse path mints nothing, fixing the per-call leak), with a bounded 2h TTL, and revoked on every teardown path (release-to-0, crash, readiness-timeout) via a finalizer on the per-sidecar scope. Load-bearing tests assert: no second mint on reuse, not-revoked-while-alive, and revoked-on-teardown (normal + crash).

Verification: 16 server-crit + 9 web + contracts tests pass; typecheck clean across contracts/client-runtime/apps-server/apps-web.

Remaining (deliberately not in this change): true least-privilege scoping (owner→thread-bound). /api/orchestration/{dispatch,snapshot} are owner-gated and accept any command, so real scoping needs a dedicated crit endpoint (thread-bound, client-role) + repointing the wrapper — a focused, security-reviewed change better done once crit is buildable for E2E. The 2h-TTL + revoke-on-teardown is the v1 mitigation (loopback-only, flag-off).

Sign up for freeto 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

@Ecko95
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(crit): PR review integration (v1, flag-gated) - #22

Merged
Ecko95 merged 19 commits into
gitsfrom
feat/crit-pr-review-integration
Jun 7, 2026
Merged

feat(crit): PR review integration (v1, flag-gated)#22
Ecko95 merged 19 commits into
gitsfrom
feat/crit-pr-review-integration

Conversation

@Ecko95

Copy link
Copy Markdown
Owner

Crit PR Review Integration (v1 — flag-gated, experimental)

Embeds the crit review binary as a GITS-managed sidecar and surfaces its review UI inside the PR view. "Send to agent" routes into the active GITS thread (so the user's default provider — codex — answers), rather than crit spawning its own agent.

Design: docs/superpowers/specs/2026-06-07-crit-pr-review-integration-design.md
Plan: docs/superpowers/plans/2026-06-07-crit-pr-review-integration.md

Architecture

apps/server spawns one crit process per workspace and sets crit's agent_cmd to a thin GITS wrapper CLI. The wrapper reads crit's comment on stdin, POSTs a thread.turn.start to /api/orchestration/dispatch, blocks while polling /api/orchestration/snapshot until the turn completes, then prints the assistant reply on stdout for crit to thread. apps/web renders crit's loopback URL in an <iframe> panel that replaces the sidebar diff for PR review only, behind an off-by-defaultcritReviewEnabled flag with native-DiffPanel fallback.

Why crit's missing codex support is a non-issue: crit's agent_cmd points at the GITS wrapper, never at codex. The wrapper satisfies crit's stdin→stdout contract; the actual codex run happens inside GITS via the codex app-server. crit never spawns an agent.

What's included

LayerComponent
sharedreview-comment-block.ts — builds the <review_comment> block the existing web parser reads
server (CLI)crit-agent-cli.ts — the agent_cmd bridge (parse → thread.turn.start → block-and-return poll → reply)
servercrit-binary-resolver.ts, crit-sidecar-manager.ts (Effect service: spawn + HTTP health-check + ref-count + Scope teardown), crit-sidecar-request.ts
contractscrit.ts schemas + crit.ensureSidecar/crit.sidecarStatus RPC, critReviewEnabled setting
client/webEnvironmentApi.crit, CritReviewPanel.tsx, PR-view route wiring behind the flag

Verification

  • ~27 unit tests passing (builder 4, wrapper CLI 5, resolver 3, sidecar manager 4, contracts 7, request helper 3, panel 1).
  • Typecheck clean across packages/{shared,contracts,client-runtime} and apps/{server,web} for all touched files.
  • End-to-end RPC identity verified: client transport.request(WS_METHODS.critEnsureSidecar) ↔ server ws.ts handler share the same Rpc.make contract.
  • Flag-off default is safe — with critReviewEnabled false, the native diff/PR path is unchanged and no crit code runs.
  • Two-stage review per task caught & fixed: a parser-truncation bug (escaping <), the wrapper's typing/lint issues, and a sidecar crash path that threw at runtime + a non-load-bearing test.

⚠️ Deferred (require building crit; Go was unavailable in the build env)

Tracked in docs/crit-integration-notes.md, marked PENDING in-code:

  • 0b real crit CLI flags — crit-sidecar-manager.ts uses placeholder --repo/--branch/--host/--port/--agent-cmd.
  • 0c crit's agent_cmd stdin format — crit-agent-cli.ts assumes JSON {comment,quoted,filePath,startLine,endLine} with a plain-text fallback.
  • Phase 7 per-platform binary bundling (electron-builder extraResources) + the wrapper's production build path (GITS_CRIT_AGENT_CMD env override for now).
  • Phase 8 end-to-end test against a live crit.

🔎 Follow-ups from final holistic review (not blockers for this flag-gated v1)

  1. Block-and-return stale-reply race — after dispatch, latestTurn doesn't flip to running synchronously, so a fast first poll can return the prior turn's reply. Fix: baseline the pre-dispatch turn id and only accept a new completed turn.
  2. Token is role:"owner", never revoked — deviates from the design's "workspace-scoped, dispatch-only, revoked on teardown." Currently necessary because dispatch/snapshot are owner-gated; needs a scoped-dispatch capability + revocation.
  3. release_sidecar not called in production — process/token leak; wire it to CritReviewPanel cleanup via a crit.releaseSidecar WS method (or release on WS disconnect).

🤖 Generated with Claude Code

Ecko95and others added 18 commits June 7, 2026 15:15
Embed crit binary as a GITS-managed sidecar, render in the PR view, and wire agent_cmd to a wrapper CLI that injects review comments into the active GITS thread via /api/orchestration/dispatch (block-and-return). Bundle per-platform binaries; sidecar owned by apps/server.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task 0a complete. CLI flags (0b) and stdin format (0c) pending — Go not available to build crit; later phases use marked assumptions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The web parser extracts body text raw (no unescaping), so applying
escape_attribute to the body caused users/agents to see literal &quot;
in rendered comments. Attributes are still escaped correctly.
Fix the test that incorrectly asserted &quot; in the body, and add an
attribute-escaping assertion using a sectionTitle with &, ", and <.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add ./crit/review-comment-block to the @t3tools/shared exports map so the
crit-agent-cli in apps/server can resolve the shared block builder.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add parse_crit_payload with TDD — normalises crit's JSON stdin
{ comment, quoted, filePath, startLine, endLine } into a NormalizedComment,
falling back to treating raw stdin as the comment text when JSON parse fails.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…al timeoutMs typing
Wrapper is a dependency-light child-process CLI, not an Effect program — disable globalFetch/globalDate/globalTimers/nodeBuiltinImport diagnostics (sanctioned per GitsCapacityMonitor/bin.test precedent). Fix exactOptionalPropertyTypes failure on timeoutMs via mutable override object.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…turn
Baseline the thread's latestTurn id before dispatch; only accept a turn whose id differs, so a fast first poll can't surface the previous (completed) turn's reply. Adds a load-bearing test for the race.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Ecko95

Copy link
Copy Markdown
OwnerAuthor

Follow-up #1 (block-and-return stale-reply race) is now fixed in 0bf5d30b: the wrapper baselines the thread's latestTurn id before dispatch and only accepts a turn whose id differs, so a fast first poll can no longer surface the previous (completed) turn's reply. Added a load-bearing test that reproduces the race (fails against the old code). Follow-ups #2 (owner-token scoping/revocation) and #3 (release_sidecar wiring) remain open.

… with revoke-on-teardown
Change A: add a crit.releaseSidecar RPC end-to-end (contracts rpc/crit/ipc,
client-runtime, web environmentApi) and call it fire-and-forget from the
CritReviewPanel useEffect cleanup so unmount/input-change releases the refCount
the matching ensureSidecar took. The ws handler maps release_sidecar to
{ released: true }.
Change B: move the wrapper bearer-token lifecycle into CritSidecarManager. The
manager now depends on AuthControlPlane and mints an owner-scoped session ONLY
on the spawn path (reuse no longer leaks a fresh token), with a bounded 2h TTL,
and registers a Scope finalizer that revokes the session on every teardown path
(release-at-0, readiness timeout, crash, spawn failure). EnsureCritSidecarInput
and build_ensure_sidecar_input drop `token`; the ws handler stops minting. The
lifecycle test wires a stub AuthControlPlane and asserts revocation is
load-bearing across reuse/crash/timeout.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Ecko95

Copy link
Copy Markdown
OwnerAuthor

Follow-ups #2 and #3 landed in c7f1d152:

  • feat(gits): review pipeline (mechanical gate → semantic verifier → triage) #3 — sidecar release wiring (leak fixed): added a crit.releaseSidecar WS method end-to-end (contracts/rpc → client-runtime → EnvironmentApi → ws.ts handler). CritReviewPanel's effect cleanup now calls it, so unmount/input-change releases the refCount its ensureSidecar took.
  • feat(gits): acceptance-criteria plumbing for the verifier-critic #2 — token lifecycle (revoke + no-leak): token issuance moved intoCritSidecarManager — minted only on actual spawn (reuse path mints nothing, fixing the per-call leak), with a bounded 2h TTL, and revoked on every teardown path (release-to-0, crash, readiness-timeout) via a finalizer on the per-sidecar scope. Load-bearing tests assert: no second mint on reuse, not-revoked-while-alive, and revoked-on-teardown (normal + crash).

Verification: 16 server-crit + 9 web + contracts tests pass; typecheck clean across contracts/client-runtime/apps-server/apps-web.

Remaining (deliberately not in this change): true least-privilege scoping (owner→thread-bound). /api/orchestration/{dispatch,snapshot} are owner-gated and accept any command, so real scoping needs a dedicated crit endpoint (thread-bound, client-role) + repointing the wrapper — a focused, security-reviewed change better done once crit is buildable for E2E. The 2h-TTL + revoke-on-teardown is the v1 mitigation (loopback-only, flag-off).

Sign up for freeto 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

@Ecko95
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(crit): PR review integration (v1, flag-gated) - #22

Merged
Ecko95 merged 19 commits into
gitsfrom
feat/crit-pr-review-integration
Jun 7, 2026
Merged

feat(crit): PR review integration (v1, flag-gated)#22
Ecko95 merged 19 commits into
gitsfrom
feat/crit-pr-review-integration

Conversation

@Ecko95

Copy link
Copy Markdown
Owner

Crit PR Review Integration (v1 — flag-gated, experimental)

Embeds the crit review binary as a GITS-managed sidecar and surfaces its review UI inside the PR view. "Send to agent" routes into the active GITS thread (so the user's default provider — codex — answers), rather than crit spawning its own agent.

Design: docs/superpowers/specs/2026-06-07-crit-pr-review-integration-design.md
Plan: docs/superpowers/plans/2026-06-07-crit-pr-review-integration.md

Architecture

apps/server spawns one crit process per workspace and sets crit's agent_cmd to a thin GITS wrapper CLI. The wrapper reads crit's comment on stdin, POSTs a thread.turn.start to /api/orchestration/dispatch, blocks while polling /api/orchestration/snapshot until the turn completes, then prints the assistant reply on stdout for crit to thread. apps/web renders crit's loopback URL in an <iframe> panel that replaces the sidebar diff for PR review only, behind an off-by-defaultcritReviewEnabled flag with native-DiffPanel fallback.

Why crit's missing codex support is a non-issue: crit's agent_cmd points at the GITS wrapper, never at codex. The wrapper satisfies crit's stdin→stdout contract; the actual codex run happens inside GITS via the codex app-server. crit never spawns an agent.

What's included

LayerComponent
sharedreview-comment-block.ts — builds the <review_comment> block the existing web parser reads
server (CLI)crit-agent-cli.ts — the agent_cmd bridge (parse → thread.turn.start → block-and-return poll → reply)
servercrit-binary-resolver.ts, crit-sidecar-manager.ts (Effect service: spawn + HTTP health-check + ref-count + Scope teardown), crit-sidecar-request.ts
contractscrit.ts schemas + crit.ensureSidecar/crit.sidecarStatus RPC, critReviewEnabled setting
client/webEnvironmentApi.crit, CritReviewPanel.tsx, PR-view route wiring behind the flag

Verification

  • ~27 unit tests passing (builder 4, wrapper CLI 5, resolver 3, sidecar manager 4, contracts 7, request helper 3, panel 1).
  • Typecheck clean across packages/{shared,contracts,client-runtime} and apps/{server,web} for all touched files.
  • End-to-end RPC identity verified: client transport.request(WS_METHODS.critEnsureSidecar) ↔ server ws.ts handler share the same Rpc.make contract.
  • Flag-off default is safe — with critReviewEnabled false, the native diff/PR path is unchanged and no crit code runs.
  • Two-stage review per task caught & fixed: a parser-truncation bug (escaping <), the wrapper's typing/lint issues, and a sidecar crash path that threw at runtime + a non-load-bearing test.

⚠️ Deferred (require building crit; Go was unavailable in the build env)

Tracked in docs/crit-integration-notes.md, marked PENDING in-code:

  • 0b real crit CLI flags — crit-sidecar-manager.ts uses placeholder --repo/--branch/--host/--port/--agent-cmd.
  • 0c crit's agent_cmd stdin format — crit-agent-cli.ts assumes JSON {comment,quoted,filePath,startLine,endLine} with a plain-text fallback.
  • Phase 7 per-platform binary bundling (electron-builder extraResources) + the wrapper's production build path (GITS_CRIT_AGENT_CMD env override for now).
  • Phase 8 end-to-end test against a live crit.

🔎 Follow-ups from final holistic review (not blockers for this flag-gated v1)

  1. Block-and-return stale-reply race — after dispatch, latestTurn doesn't flip to running synchronously, so a fast first poll can return the prior turn's reply. Fix: baseline the pre-dispatch turn id and only accept a new completed turn.
  2. Token is role:"owner", never revoked — deviates from the design's "workspace-scoped, dispatch-only, revoked on teardown." Currently necessary because dispatch/snapshot are owner-gated; needs a scoped-dispatch capability + revocation.
  3. release_sidecar not called in production — process/token leak; wire it to CritReviewPanel cleanup via a crit.releaseSidecar WS method (or release on WS disconnect).

🤖 Generated with Claude Code

Ecko95and others added 18 commits June 7, 2026 15:15
Embed crit binary as a GITS-managed sidecar, render in the PR view, and wire agent_cmd to a wrapper CLI that injects review comments into the active GITS thread via /api/orchestration/dispatch (block-and-return). Bundle per-platform binaries; sidecar owned by apps/server.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task 0a complete. CLI flags (0b) and stdin format (0c) pending — Go not available to build crit; later phases use marked assumptions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The web parser extracts body text raw (no unescaping), so applying
escape_attribute to the body caused users/agents to see literal &quot;
in rendered comments. Attributes are still escaped correctly.
Fix the test that incorrectly asserted &quot; in the body, and add an
attribute-escaping assertion using a sectionTitle with &, ", and <.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add ./crit/review-comment-block to the @t3tools/shared exports map so the
crit-agent-cli in apps/server can resolve the shared block builder.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add parse_crit_payload with TDD — normalises crit's JSON stdin
{ comment, quoted, filePath, startLine, endLine } into a NormalizedComment,
falling back to treating raw stdin as the comment text when JSON parse fails.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…al timeoutMs typing
Wrapper is a dependency-light child-process CLI, not an Effect program — disable globalFetch/globalDate/globalTimers/nodeBuiltinImport diagnostics (sanctioned per GitsCapacityMonitor/bin.test precedent). Fix exactOptionalPropertyTypes failure on timeoutMs via mutable override object.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…turn
Baseline the thread's latestTurn id before dispatch; only accept a turn whose id differs, so a fast first poll can't surface the previous (completed) turn's reply. Adds a load-bearing test for the race.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Ecko95

Copy link
Copy Markdown
OwnerAuthor

Follow-up #1 (block-and-return stale-reply race) is now fixed in 0bf5d30b: the wrapper baselines the thread's latestTurn id before dispatch and only accepts a turn whose id differs, so a fast first poll can no longer surface the previous (completed) turn's reply. Added a load-bearing test that reproduces the race (fails against the old code). Follow-ups #2 (owner-token scoping/revocation) and #3 (release_sidecar wiring) remain open.

… with revoke-on-teardown
Change A: add a crit.releaseSidecar RPC end-to-end (contracts rpc/crit/ipc,
client-runtime, web environmentApi) and call it fire-and-forget from the
CritReviewPanel useEffect cleanup so unmount/input-change releases the refCount
the matching ensureSidecar took. The ws handler maps release_sidecar to
{ released: true }.
Change B: move the wrapper bearer-token lifecycle into CritSidecarManager. The
manager now depends on AuthControlPlane and mints an owner-scoped session ONLY
on the spawn path (reuse no longer leaks a fresh token), with a bounded 2h TTL,
and registers a Scope finalizer that revokes the session on every teardown path
(release-at-0, readiness timeout, crash, spawn failure). EnsureCritSidecarInput
and build_ensure_sidecar_input drop `token`; the ws handler stops minting. The
lifecycle test wires a stub AuthControlPlane and asserts revocation is
load-bearing across reuse/crash/timeout.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Ecko95

Copy link
Copy Markdown
OwnerAuthor

Follow-ups #2 and #3 landed in c7f1d152:

  • feat(gits): review pipeline (mechanical gate → semantic verifier → triage) #3 — sidecar release wiring (leak fixed): added a crit.releaseSidecar WS method end-to-end (contracts/rpc → client-runtime → EnvironmentApi → ws.ts handler). CritReviewPanel's effect cleanup now calls it, so unmount/input-change releases the refCount its ensureSidecar took.
  • feat(gits): acceptance-criteria plumbing for the verifier-critic #2 — token lifecycle (revoke + no-leak): token issuance moved intoCritSidecarManager — minted only on actual spawn (reuse path mints nothing, fixing the per-call leak), with a bounded 2h TTL, and revoked on every teardown path (release-to-0, crash, readiness-timeout) via a finalizer on the per-sidecar scope. Load-bearing tests assert: no second mint on reuse, not-revoked-while-alive, and revoked-on-teardown (normal + crash).

Verification: 16 server-crit + 9 web + contracts tests pass; typecheck clean across contracts/client-runtime/apps-server/apps-web.

Remaining (deliberately not in this change): true least-privilege scoping (owner→thread-bound). /api/orchestration/{dispatch,snapshot} are owner-gated and accept any command, so real scoping needs a dedicated crit endpoint (thread-bound, client-role) + repointing the wrapper — a focused, security-reviewed change better done once crit is buildable for E2E. The 2h-TTL + revoke-on-teardown is the v1 mitigation (loopback-only, flag-off).

Sign up for freeto 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

@Ecko95
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(crit): PR review integration (v1, flag-gated) - #22

Merged
Ecko95 merged 19 commits into
gitsfrom
feat/crit-pr-review-integration
Jun 7, 2026
Merged

feat(crit): PR review integration (v1, flag-gated)#22
Ecko95 merged 19 commits into
gitsfrom
feat/crit-pr-review-integration

Conversation

@Ecko95

Copy link
Copy Markdown
Owner

Crit PR Review Integration (v1 — flag-gated, experimental)

Embeds the crit review binary as a GITS-managed sidecar and surfaces its review UI inside the PR view. "Send to agent" routes into the active GITS thread (so the user's default provider — codex — answers), rather than crit spawning its own agent.

Design: docs/superpowers/specs/2026-06-07-crit-pr-review-integration-design.md
Plan: docs/superpowers/plans/2026-06-07-crit-pr-review-integration.md

Architecture

apps/server spawns one crit process per workspace and sets crit's agent_cmd to a thin GITS wrapper CLI. The wrapper reads crit's comment on stdin, POSTs a thread.turn.start to /api/orchestration/dispatch, blocks while polling /api/orchestration/snapshot until the turn completes, then prints the assistant reply on stdout for crit to thread. apps/web renders crit's loopback URL in an <iframe> panel that replaces the sidebar diff for PR review only, behind an off-by-defaultcritReviewEnabled flag with native-DiffPanel fallback.

Why crit's missing codex support is a non-issue: crit's agent_cmd points at the GITS wrapper, never at codex. The wrapper satisfies crit's stdin→stdout contract; the actual codex run happens inside GITS via the codex app-server. crit never spawns an agent.

What's included

LayerComponent
sharedreview-comment-block.ts — builds the <review_comment> block the existing web parser reads
server (CLI)crit-agent-cli.ts — the agent_cmd bridge (parse → thread.turn.start → block-and-return poll → reply)
servercrit-binary-resolver.ts, crit-sidecar-manager.ts (Effect service: spawn + HTTP health-check + ref-count + Scope teardown), crit-sidecar-request.ts
contractscrit.ts schemas + crit.ensureSidecar/crit.sidecarStatus RPC, critReviewEnabled setting
client/webEnvironmentApi.crit, CritReviewPanel.tsx, PR-view route wiring behind the flag

Verification

  • ~27 unit tests passing (builder 4, wrapper CLI 5, resolver 3, sidecar manager 4, contracts 7, request helper 3, panel 1).
  • Typecheck clean across packages/{shared,contracts,client-runtime} and apps/{server,web} for all touched files.
  • End-to-end RPC identity verified: client transport.request(WS_METHODS.critEnsureSidecar) ↔ server ws.ts handler share the same Rpc.make contract.
  • Flag-off default is safe — with critReviewEnabled false, the native diff/PR path is unchanged and no crit code runs.
  • Two-stage review per task caught & fixed: a parser-truncation bug (escaping <), the wrapper's typing/lint issues, and a sidecar crash path that threw at runtime + a non-load-bearing test.

⚠️ Deferred (require building crit; Go was unavailable in the build env)

Tracked in docs/crit-integration-notes.md, marked PENDING in-code:

  • 0b real crit CLI flags — crit-sidecar-manager.ts uses placeholder --repo/--branch/--host/--port/--agent-cmd.
  • 0c crit's agent_cmd stdin format — crit-agent-cli.ts assumes JSON {comment,quoted,filePath,startLine,endLine} with a plain-text fallback.
  • Phase 7 per-platform binary bundling (electron-builder extraResources) + the wrapper's production build path (GITS_CRIT_AGENT_CMD env override for now).
  • Phase 8 end-to-end test against a live crit.

🔎 Follow-ups from final holistic review (not blockers for this flag-gated v1)

  1. Block-and-return stale-reply race — after dispatch, latestTurn doesn't flip to running synchronously, so a fast first poll can return the prior turn's reply. Fix: baseline the pre-dispatch turn id and only accept a new completed turn.
  2. Token is role:"owner", never revoked — deviates from the design's "workspace-scoped, dispatch-only, revoked on teardown." Currently necessary because dispatch/snapshot are owner-gated; needs a scoped-dispatch capability + revocation.
  3. release_sidecar not called in production — process/token leak; wire it to CritReviewPanel cleanup via a crit.releaseSidecar WS method (or release on WS disconnect).

🤖 Generated with Claude Code

Ecko95and others added 18 commits June 7, 2026 15:15
Embed crit binary as a GITS-managed sidecar, render in the PR view, and wire agent_cmd to a wrapper CLI that injects review comments into the active GITS thread via /api/orchestration/dispatch (block-and-return). Bundle per-platform binaries; sidecar owned by apps/server.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task 0a complete. CLI flags (0b) and stdin format (0c) pending — Go not available to build crit; later phases use marked assumptions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The web parser extracts body text raw (no unescaping), so applying
escape_attribute to the body caused users/agents to see literal &quot;
in rendered comments. Attributes are still escaped correctly.
Fix the test that incorrectly asserted &quot; in the body, and add an
attribute-escaping assertion using a sectionTitle with &, ", and <.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add ./crit/review-comment-block to the @t3tools/shared exports map so the
crit-agent-cli in apps/server can resolve the shared block builder.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add parse_crit_payload with TDD — normalises crit's JSON stdin
{ comment, quoted, filePath, startLine, endLine } into a NormalizedComment,
falling back to treating raw stdin as the comment text when JSON parse fails.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…al timeoutMs typing
Wrapper is a dependency-light child-process CLI, not an Effect program — disable globalFetch/globalDate/globalTimers/nodeBuiltinImport diagnostics (sanctioned per GitsCapacityMonitor/bin.test precedent). Fix exactOptionalPropertyTypes failure on timeoutMs via mutable override object.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…turn
Baseline the thread's latestTurn id before dispatch; only accept a turn whose id differs, so a fast first poll can't surface the previous (completed) turn's reply. Adds a load-bearing test for the race.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Ecko95

Copy link
Copy Markdown
OwnerAuthor

Follow-up #1 (block-and-return stale-reply race) is now fixed in 0bf5d30b: the wrapper baselines the thread's latestTurn id before dispatch and only accepts a turn whose id differs, so a fast first poll can no longer surface the previous (completed) turn's reply. Added a load-bearing test that reproduces the race (fails against the old code). Follow-ups #2 (owner-token scoping/revocation) and #3 (release_sidecar wiring) remain open.

… with revoke-on-teardown
Change A: add a crit.releaseSidecar RPC end-to-end (contracts rpc/crit/ipc,
client-runtime, web environmentApi) and call it fire-and-forget from the
CritReviewPanel useEffect cleanup so unmount/input-change releases the refCount
the matching ensureSidecar took. The ws handler maps release_sidecar to
{ released: true }.
Change B: move the wrapper bearer-token lifecycle into CritSidecarManager. The
manager now depends on AuthControlPlane and mints an owner-scoped session ONLY
on the spawn path (reuse no longer leaks a fresh token), with a bounded 2h TTL,
and registers a Scope finalizer that revokes the session on every teardown path
(release-at-0, readiness timeout, crash, spawn failure). EnsureCritSidecarInput
and build_ensure_sidecar_input drop `token`; the ws handler stops minting. The
lifecycle test wires a stub AuthControlPlane and asserts revocation is
load-bearing across reuse/crash/timeout.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Ecko95

Copy link
Copy Markdown
OwnerAuthor

Follow-ups #2 and #3 landed in c7f1d152:

  • feat(gits): review pipeline (mechanical gate → semantic verifier → triage) #3 — sidecar release wiring (leak fixed): added a crit.releaseSidecar WS method end-to-end (contracts/rpc → client-runtime → EnvironmentApi → ws.ts handler). CritReviewPanel's effect cleanup now calls it, so unmount/input-change releases the refCount its ensureSidecar took.
  • feat(gits): acceptance-criteria plumbing for the verifier-critic #2 — token lifecycle (revoke + no-leak): token issuance moved intoCritSidecarManager — minted only on actual spawn (reuse path mints nothing, fixing the per-call leak), with a bounded 2h TTL, and revoked on every teardown path (release-to-0, crash, readiness-timeout) via a finalizer on the per-sidecar scope. Load-bearing tests assert: no second mint on reuse, not-revoked-while-alive, and revoked-on-teardown (normal + crash).

Verification: 16 server-crit + 9 web + contracts tests pass; typecheck clean across contracts/client-runtime/apps-server/apps-web.

Remaining (deliberately not in this change): true least-privilege scoping (owner→thread-bound). /api/orchestration/{dispatch,snapshot} are owner-gated and accept any command, so real scoping needs a dedicated crit endpoint (thread-bound, client-role) + repointing the wrapper — a focused, security-reviewed change better done once crit is buildable for E2E. The 2h-TTL + revoke-on-teardown is the v1 mitigation (loopback-only, flag-off).

Sign up for freeto 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

@Ecko95
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(crit): PR review integration (v1, flag-gated) - #22

Merged
Ecko95 merged 19 commits into
gitsfrom
feat/crit-pr-review-integration
Jun 7, 2026
Merged

feat(crit): PR review integration (v1, flag-gated)#22
Ecko95 merged 19 commits into
gitsfrom
feat/crit-pr-review-integration

Conversation

@Ecko95

Copy link
Copy Markdown
Owner

Crit PR Review Integration (v1 — flag-gated, experimental)

Embeds the crit review binary as a GITS-managed sidecar and surfaces its review UI inside the PR view. "Send to agent" routes into the active GITS thread (so the user's default provider — codex — answers), rather than crit spawning its own agent.

Design: docs/superpowers/specs/2026-06-07-crit-pr-review-integration-design.md
Plan: docs/superpowers/plans/2026-06-07-crit-pr-review-integration.md

Architecture

apps/server spawns one crit process per workspace and sets crit's agent_cmd to a thin GITS wrapper CLI. The wrapper reads crit's comment on stdin, POSTs a thread.turn.start to /api/orchestration/dispatch, blocks while polling /api/orchestration/snapshot until the turn completes, then prints the assistant reply on stdout for crit to thread. apps/web renders crit's loopback URL in an <iframe> panel that replaces the sidebar diff for PR review only, behind an off-by-defaultcritReviewEnabled flag with native-DiffPanel fallback.

Why crit's missing codex support is a non-issue: crit's agent_cmd points at the GITS wrapper, never at codex. The wrapper satisfies crit's stdin→stdout contract; the actual codex run happens inside GITS via the codex app-server. crit never spawns an agent.

What's included

LayerComponent
sharedreview-comment-block.ts — builds the <review_comment> block the existing web parser reads
server (CLI)crit-agent-cli.ts — the agent_cmd bridge (parse → thread.turn.start → block-and-return poll → reply)
servercrit-binary-resolver.ts, crit-sidecar-manager.ts (Effect service: spawn + HTTP health-check + ref-count + Scope teardown), crit-sidecar-request.ts
contractscrit.ts schemas + crit.ensureSidecar/crit.sidecarStatus RPC, critReviewEnabled setting
client/webEnvironmentApi.crit, CritReviewPanel.tsx, PR-view route wiring behind the flag

Verification

  • ~27 unit tests passing (builder 4, wrapper CLI 5, resolver 3, sidecar manager 4, contracts 7, request helper 3, panel 1).
  • Typecheck clean across packages/{shared,contracts,client-runtime} and apps/{server,web} for all touched files.
  • End-to-end RPC identity verified: client transport.request(WS_METHODS.critEnsureSidecar) ↔ server ws.ts handler share the same Rpc.make contract.
  • Flag-off default is safe — with critReviewEnabled false, the native diff/PR path is unchanged and no crit code runs.
  • Two-stage review per task caught & fixed: a parser-truncation bug (escaping <), the wrapper's typing/lint issues, and a sidecar crash path that threw at runtime + a non-load-bearing test.

⚠️ Deferred (require building crit; Go was unavailable in the build env)

Tracked in docs/crit-integration-notes.md, marked PENDING in-code:

  • 0b real crit CLI flags — crit-sidecar-manager.ts uses placeholder --repo/--branch/--host/--port/--agent-cmd.
  • 0c crit's agent_cmd stdin format — crit-agent-cli.ts assumes JSON {comment,quoted,filePath,startLine,endLine} with a plain-text fallback.
  • Phase 7 per-platform binary bundling (electron-builder extraResources) + the wrapper's production build path (GITS_CRIT_AGENT_CMD env override for now).
  • Phase 8 end-to-end test against a live crit.

🔎 Follow-ups from final holistic review (not blockers for this flag-gated v1)

  1. Block-and-return stale-reply race — after dispatch, latestTurn doesn't flip to running synchronously, so a fast first poll can return the prior turn's reply. Fix: baseline the pre-dispatch turn id and only accept a new completed turn.
  2. Token is role:"owner", never revoked — deviates from the design's "workspace-scoped, dispatch-only, revoked on teardown." Currently necessary because dispatch/snapshot are owner-gated; needs a scoped-dispatch capability + revocation.
  3. release_sidecar not called in production — process/token leak; wire it to CritReviewPanel cleanup via a crit.releaseSidecar WS method (or release on WS disconnect).

🤖 Generated with Claude Code

Ecko95and others added 18 commits June 7, 2026 15:15
Embed crit binary as a GITS-managed sidecar, render in the PR view, and wire agent_cmd to a wrapper CLI that injects review comments into the active GITS thread via /api/orchestration/dispatch (block-and-return). Bundle per-platform binaries; sidecar owned by apps/server.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Task 0a complete. CLI flags (0b) and stdin format (0c) pending — Go not available to build crit; later phases use marked assumptions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The web parser extracts body text raw (no unescaping), so applying
escape_attribute to the body caused users/agents to see literal &quot;
in rendered comments. Attributes are still escaped correctly.
Fix the test that incorrectly asserted &quot; in the body, and add an
attribute-escaping assertion using a sectionTitle with &, ", and <.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add ./crit/review-comment-block to the @t3tools/shared exports map so the
crit-agent-cli in apps/server can resolve the shared block builder.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add parse_crit_payload with TDD — normalises crit's JSON stdin
{ comment, quoted, filePath, startLine, endLine } into a NormalizedComment,
falling back to treating raw stdin as the comment text when JSON parse fails.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…al timeoutMs typing
Wrapper is a dependency-light child-process CLI, not an Effect program — disable globalFetch/globalDate/globalTimers/nodeBuiltinImport diagnostics (sanctioned per GitsCapacityMonitor/bin.test precedent). Fix exactOptionalPropertyTypes failure on timeoutMs via mutable override object.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…turn
Baseline the thread's latestTurn id before dispatch; only accept a turn whose id differs, so a fast first poll can't surface the previous (completed) turn's reply. Adds a load-bearing test for the race.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Ecko95

Copy link
Copy Markdown
OwnerAuthor

Follow-up #1 (block-and-return stale-reply race) is now fixed in 0bf5d30b: the wrapper baselines the thread's latestTurn id before dispatch and only accepts a turn whose id differs, so a fast first poll can no longer surface the previous (completed) turn's reply. Added a load-bearing test that reproduces the race (fails against the old code). Follow-ups #2 (owner-token scoping/revocation) and #3 (release_sidecar wiring) remain open.

… with revoke-on-teardown
Change A: add a crit.releaseSidecar RPC end-to-end (contracts rpc/crit/ipc,
client-runtime, web environmentApi) and call it fire-and-forget from the
CritReviewPanel useEffect cleanup so unmount/input-change releases the refCount
the matching ensureSidecar took. The ws handler maps release_sidecar to
{ released: true }.
Change B: move the wrapper bearer-token lifecycle into CritSidecarManager. The
manager now depends on AuthControlPlane and mints an owner-scoped session ONLY
on the spawn path (reuse no longer leaks a fresh token), with a bounded 2h TTL,
and registers a Scope finalizer that revokes the session on every teardown path
(release-at-0, readiness timeout, crash, spawn failure). EnsureCritSidecarInput
and build_ensure_sidecar_input drop `token`; the ws handler stops minting. The
lifecycle test wires a stub AuthControlPlane and asserts revocation is
load-bearing across reuse/crash/timeout.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Ecko95

Copy link
Copy Markdown
OwnerAuthor

Follow-ups #2 and #3 landed in c7f1d152:

  • feat(gits): review pipeline (mechanical gate → semantic verifier → triage) #3 — sidecar release wiring (leak fixed): added a crit.releaseSidecar WS method end-to-end (contracts/rpc → client-runtime → EnvironmentApi → ws.ts handler). CritReviewPanel's effect cleanup now calls it, so unmount/input-change releases the refCount its ensureSidecar took.
  • feat(gits): acceptance-criteria plumbing for the verifier-critic #2 — token lifecycle (revoke + no-leak): token issuance moved intoCritSidecarManager — minted only on actual spawn (reuse path mints nothing, fixing the per-call leak), with a bounded 2h TTL, and revoked on every teardown path (release-to-0, crash, readiness-timeout) via a finalizer on the per-sidecar scope. Load-bearing tests assert: no second mint on reuse, not-revoked-while-alive, and revoked-on-teardown (normal + crash).

Verification: 16 server-crit + 9 web + contracts tests pass; typecheck clean across contracts/client-runtime/apps-server/apps-web.

Remaining (deliberately not in this change): true least-privilege scoping (owner→thread-bound). /api/orchestration/{dispatch,snapshot} are owner-gated and accept any command, so real scoping needs a dedicated crit endpoint (thread-bound, client-role) + repointing the wrapper — a focused, security-reviewed change better done once crit is buildable for E2E. The 2h-TTL + revoke-on-teardown is the v1 mitigation (loopback-only, flag-off).

Sign up for freeto 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

@Ecko95