Skip to content

INS-356: PoC: Full-stack preview for e2e verify#166

Closed
CarmenDou wants to merge 13 commits into
mainfrom
worktree-agent-e2e-preview-poc
Closed

INS-356: PoC: Full-stack preview for e2e verify#166
CarmenDou wants to merge 13 commits into
mainfrom
worktree-agent-e2e-preview-poc

Conversation

@CarmenDou

@CarmenDou CarmenDou commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary by cubic

Adds experimental, hidden insforge verify probes (RLS isolation, backend‑truth, and loud‑error capture) and auto‑configures the Playwright browser MCP (@playwright/mcp) during insforge link so insforge-verify can drive the UI for INS‑356. Tightens safety by enforcing read‑only SQL (now also blocks DML in CTEs) and treating non‑2xx responses as failures.

  • New Features

    • Hidden verify rls checks cross‑user isolation (B can’t read A; A can read own), sets exit codes, and records evidence.
    • Hidden verify truth compares a DB read to what the UI showed; flags false_pass and records evidence.
    • Hidden verify finding records loud errors (HTTP 4xx/5xx, console exceptions) seen during drives.
    • link configures a global playwright MCP server for supported agents (Claude Code via claude mcp add, Cursor ~/.cursor/mcp.json, Codex ~/.codex/config.toml); prints manual steps if not detected. Added tests for MCP config and probe classifiers; deploys now exclude test-results, playwright-report, and .playwright-mcp.
  • Bug Fixes

    • verify truth runs only read‑only SQL (starts with SELECT/WITH, single statement) and blocks destructive DML hidden inside a CTE; all probe fetches must return 2xx, preventing destructive queries and false passes.
    • Browser MCP config merge is idempotent and recovers malformed or non‑object JSON; analytics finding properties are normalized.

Written for commit 1452c86. Summary will update on new commits.

Review in cubic

Note

Add hidden preview create and preview teardown CLI commands for full-stack e2e preview branches

  • Adds preview create <name> which provisions a full-stack preview branch via the platform API, polls until ready, and writes a local manifest under .insforge/previews/.
  • Adds --wire-env [file] option to preview create that rewrites a frontend env file (default .env.local) to point NEXT_PUBLIC_INSFORGE_URL at the preview backend, backing up any existing file.
  • Adds preview teardown <name> which deletes the remote branch and restores or removes any wired env file and the local manifest.
  • Adds overwriteEnvFile in env-writer.ts to deterministically overwrite or append env vars in .env-style files.
  • Both commands are registered as hidden (excluded from --help) via registerPreviewCommands.

Changes since #166 opened

  • Added validation to reject preview creation when a manifest with the same name already exists in the current directory [aa43a5b]
  • Modified deleteBranchApi to optionally tolerate HTTP 404 responses and updated teardown command to ignore missing remote branches [aa43a5b]
  • Implemented automatic rollback by deleting the provisioned branch when polling times out before reaching ready state [aa43a5b]
  • Exported assertSafeName validation function and integrated it into preview creation flow [aa43a5b]
  • Protected existing environment file backups from being overwritten during preview creation [aa43a5b]
  • Added final readiness check after polling timeout in branch provisioning [aa43a5b]
  • Updated preview creation telemetry and added default wire-env behavior tests [aa43a5b]
  • Added preview name validation and conditional JSON-only output to the preview teardown command [3e1323f]
  • Tightened TypeScript type definition for the preview create command options parameter [3e1323f]
  • Added optional Playwright Test Agents installation to the installSkills function [21c8689]
  • Added --with-test-agents flag to the link command and threaded it through all installation code paths [21c8689]
  • Updated test expectations for installSkills calls to match the new function signature [21c8689]
  • Added documentation for experimental insforge preview commands [3dd19ac]
  • Replaced preview command infrastructure with new verify command system [517a2a9]
  • Replaced Playwright Test Agents installation with browser MCP server configuration [517a2a9]
  • Removed preview manifest library [517a2a9]
  • Removed overwriteEnvFile function from env-writer module and deleted associated test file [26d56bf]
  • Changed installSkills function signature to accept single argument in projects.link command [26d56bf]
  • Added isReadOnlyQuery validation to verify truth command and introduced assertOk HTTP error handling across authentication, database, and data API clients [29acf6c]
  • Fixed mergeJsonMcp to handle valid JSON that is not an object [29acf6c]
  • Removed optional 404 pass-through handling from deleteBranchApi [29acf6c]
  • Reordered object spread in trackVerifyFinding for field precedence [29acf6c]
  • Added regex-based validation to reject queries containing DML/DDL keywords anywhere in the query string [1452c86]

Macroscope summarized 48d0d38.

Summary by CodeRabbit

  • New Features
    • Added a hidden experimental preview CLI with create and teardown for isolated full-stack previews, including safe naming, preview manifest tracking, and optional --wire-env env wiring.
    • link now supports --with-test-agents to install Playwright Test Agents during skill installation.
  • Improvements
    • Improved env file rewriting: overwrites all occurrences, appends missing keys, preserves comments/other lines, and treats $ values literally.
    • Deployment packaging now excludes common Playwright/test result directories.
  • Tests
    • Expanded preview/teardown end-to-end coverage and added unit tests for env writing and preview manifest persistence/validation.
  • Documentation
    • Updated insforge preview command documentation.

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This pull request introduces a complete preview environment provisioning system with manifest persistence, environment file mutation utilities, hidden preview command group with create and teardown subcommands for provisioning and managing isolated full-stack preview branches, extends the link command with optional Playwright Test Agents installation during skill setup, and updates the deploy command to exclude test artifacts and playwright configuration files from deployments.

Changes

Preview Environment Provisioning System

Layer / File(s) Summary
Preview manifest persistence and validation
src/lib/preview-manifest.ts, src/lib/preview-manifest.test.ts
Defines PreviewManifest with name, branchId, appkey, createdAt, optional wiredEnvFile and wiredEnvCreated. Adds assertSafeName validator (rejecting paths, git-refs, traversal patterns). Provides writePreviewManifest, readPreviewManifest, and deletePreviewManifest with round-trip, missing file, and deletion tests.
Environment file mutation utilities
src/lib/env-writer.ts, src/lib/env-writer.overwrite.test.ts
Adds overwriteEnvFile to replace or append KEY=value entries, tracking changed and added keys and conditionally persisting. Tests cover overwrite, append, comment/line preservation, literal $ in values, and duplicate-key updates.
Platform API enhancement
src/lib/api/platform.ts
deleteBranchApi gains optional opts.ignoreNotFound parameter; when set, passes passThroughStatuses: [404] to avoid throwing on missing branches, enabling graceful cleanup during teardown.
Preview command group wiring
src/commands/preview/index.ts, src/commands/preview/index.test.ts, src/index.ts
Registers hidden preview command group (marked experimental) and wires create and teardown subcommands. Tests assert registration and that preview is excluded from help. CLI startup imports and registers the group.
Preview create command
src/commands/preview/create.ts, src/commands/preview/create.test.ts
Implements preview create <name>: auth validation, project/branch checks, name safety validation, createBranchApi call with analytics, pollUntilReady loop until ready (throws on deleted/conflicted/timeout), backend URL computation, manifest write, optional env file backup and NEXT_PUBLIC_INSFORGE_URL overwrite using overwriteEnvFile, manifest rewrite with wiring metadata. Outputs JSON or human-readable status. Tests verify basic creation, env wiring with backup, default .env.local wiring, failure rollback with branch cleanup, and duplicate-name rejection.
Preview teardown command
src/commands/preview/teardown.ts, src/commands/preview/teardown.test.ts
Implements preview teardown <name>: manifest loading, deleteBranchApi with ignoreNotFound for graceful cleanup, defensive env file restoration (from .preview-bak backup or removal if wiredEnvCreated), manifest deletion, error warnings (non-JSON), and guaranteed analytics shutdown. Tests cover branch deletion, restore-from-backup, removal of created env files, and strict-JSON output.
Preview command documentation
src/commands/preview/README.md
Describes preview create/teardown workflows, manifest-first behavior, reversible env wiring, retry and cleanup semantics, integration with insforge-verify loop, module/file structure mapping, deployment gotchas in branch context, and usage example.

Test Agents Installation Extension

Layer / File(s) Summary
Link command test agents option
src/commands/projects/link.ts, src/commands/projects/link.test.ts
Adds --with-test-agents flag to link command. Propagates Boolean(opts.withTestAgents) as third parameter to installSkills across all link flows (skills-only, direct-link with/without template, templated/non-templated projects). Updates test assertions for new signature.
installSkills test agents installation
src/lib/skills.ts
Updates installSkills signature to add optional withTestAgents parameter (default false). When enabled, after existing skill installations and AGENTS.md generation, runs npx playwright init-agents --loop=claude with timeout handling; logs success with restart reminder or failure with install guidance.

Deployment File Exclusion Patterns

Layer / File(s) Summary
Deploy source exclusion list updates
src/commands/deployments/deploy.ts
Extends EXCLUDE_PATTERNS list with test-results, playwright-report, and .playwright-mcp to prevent test artifacts and playwright configuration from being collected or packaged in deployments.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • InsForge/CLI#119: Extends installSkills behavior and calling patterns across multiple link flows, building on test agent installation capability.

Suggested reviewers

  • tonychang04
  • jwfing

Poem

🐰 I dug a tunnel where preview branches bloom and thrive,
Manifests like little maps keep memories alive.
I nibble env files gently, back them up with care,
Create and teardown waltz, hidden, light as air.
Hop, hop—Playwright agents test, then fade to the drive. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'INS-356: PoC: Full-stack preview for e2e verify' directly and clearly summarizes the main addition: a proof-of-concept full-stack preview environment feature for end-to-end verification workflows, which is the primary focus of all changes in this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-agent-e2e-preview-poc

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 12, 2026

Copy link
Copy Markdown

Greptile Summary

Adds hidden insforge verify subcommands (rls, truth, finding) for AI-driven end-to-end security probes, and auto-configures the @playwright/mcp browser MCP server during insforge link for Claude Code, Cursor, and Codex.

  • verify rls runs a cross-user RLS isolation check (B can't read A, A can read own) using seeded test users and PostgREST, emitting a PostHog finding with exit code.
  • verify truth cross-checks DB state against what the UI claimed; enforces read-only SQL via isReadOnlyQuery (including DML-in-CTE detection) and uses assertOk on all fetches so transport errors can't masquerade as passing probes.
  • browser-mcp.ts idempotently writes @playwright/mcp@latest --headless into each agent's config file (or delegates to claude mcp add), recovering from malformed JSON/TOML without crashing link.

Confidence Score: 4/5

Safe to merge after fixing the URL encoding issue in the RLS probe.

The RLS probe builds a PostgREST filter by interpolating the user-supplied --owner column name directly into the URL query string without encoding. An &limit=0 suffix in that value forces every recordsCount call to return zero, making a leaking table report clean isolation — the exact wrong outcome for a security tool. The rest of the change is well-guarded: assertOk catches non-2xx responses, isReadOnlyQuery blocks DML inside CTEs, and the browser-MCP wiring is fully constants-only.

src/commands/verify/rls.ts — the filter construction on line 42 needs encodeURIComponent applied to opts.owner and aId.

Important Files Changed

Filename Overview
src/commands/verify/rls.ts New RLS isolation probe; --owner value is interpolated into the URL query string without encodeURIComponent, enabling parameter injection that can silently falsify the row-count results.
src/lib/verify-probe.ts Core probe helpers; assertOk now guards all fetches, isReadOnlyQuery blocks DML in CTEs via keyword scan — previous thread issues are resolved. recordsCount accepts a raw query string (expects already-encoded caller input).
src/lib/browser-mcp.ts New module to register @playwright/mcp in Claude Code, Cursor, and Codex configs; all values are constants, idempotent, best-effort per agent. No issues found.
src/commands/verify/truth.ts New backend-truth probe; enforces isReadOnlyQuery before executing, uses assertOk via rawsqlRows. No issues found.
src/lib/skills.ts Adds withBrowserMcp parameter (defaults true) to installSkills and calls configureBrowserMcp; best-effort, no issues found.
src/lib/analytics.ts Adds trackVerifyFinding helper; spreads evidence first then explicit fields so explicit properties take precedence. No issues found.
src/lib/verify-probe.test.ts Unit tests for classifiers and isReadOnlyQuery, including the CTE-DML case. Good coverage.
src/lib/browser-mcp.test.ts Tests for mergeJsonMcp and ensureCodexToml, covering idempotency, malformed JSON recovery, and non-object JSON. No issues found.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Agent as AI Agent
    participant CLI as insforge verify rls/truth
    participant Backend as oss_host API
    participant PH as PostHog

    Agent->>CLI: insforge verify rls --table t --owner user_id
    CLI->>Backend: POST /api/auth/sessions (user A + B login)
    Backend-->>CLI: JWT tokens A, B
    CLI->>Backend: POST /api/auth/tokens/anon
    Backend-->>CLI: anon key
    CLI->>Backend: POST /api/database/advance/rawsql (get user A UUID)
    Backend-->>CLI: "[{id: uuid-a}]"
    CLI->>Backend: "GET /api/database/records/t?user_id=eq.uuid-a (as user B)"
    Backend-->>CLI: rows (RLS check)
    CLI->>Backend: "GET /api/database/records/t?user_id=eq.uuid-a (as user A)"
    Backend-->>CLI: rows (positive control)
    CLI->>Backend: GET /api/database/records/t (as anon)
    Backend-->>CLI: rows (anon check)
    CLI->>PH: "trackVerifyFinding(rls_leak|rls_overrestrict|none)"
    CLI-->>Agent: exit 0/1 + JSON finding
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Agent as AI Agent
    participant CLI as insforge verify rls/truth
    participant Backend as oss_host API
    participant PH as PostHog

    Agent->>CLI: insforge verify rls --table t --owner user_id
    CLI->>Backend: POST /api/auth/sessions (user A + B login)
    Backend-->>CLI: JWT tokens A, B
    CLI->>Backend: POST /api/auth/tokens/anon
    Backend-->>CLI: anon key
    CLI->>Backend: POST /api/database/advance/rawsql (get user A UUID)
    Backend-->>CLI: "[{id: uuid-a}]"
    CLI->>Backend: "GET /api/database/records/t?user_id=eq.uuid-a (as user B)"
    Backend-->>CLI: rows (RLS check)
    CLI->>Backend: "GET /api/database/records/t?user_id=eq.uuid-a (as user A)"
    Backend-->>CLI: rows (positive control)
    CLI->>Backend: GET /api/database/records/t (as anon)
    Backend-->>CLI: rows (anon check)
    CLI->>PH: "trackVerifyFinding(rls_leak|rls_overrestrict|none)"
    CLI-->>Agent: exit 0/1 + JSON finding
Loading

Reviews (12): Last reviewed commit: "fix(verify): block destructive DML hidde..." | Re-trigger Greptile

Comment thread src/lib/env-writer.ts Outdated
Comment thread src/commands/preview/create.ts Outdated
Comment thread src/commands/preview/create.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (3)
src/lib/env-writer.overwrite.test.ts (1)

42-60: 📐 Maintainability & Code Quality | ⚡ Quick win

Add a duplicate-key regression test for overwrite semantics.

Please add a case with two NEXT_PUBLIC_INSFORGE_URL= lines and assert both are rewritten (or only one remains with the new value). That locks in the intended overwrite behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/env-writer.overwrite.test.ts` around lines 42 - 60, Add a regression
test that ensures duplicate keys are handled by overwriteEnvFile: create a new
case (or extend the existing "leaves unrelated lines and comments intact" test)
where the original .env content contains two lines starting with
NEXT_PUBLIC_INSFORGE_URL= (different values), call overwriteEnvFile(file, {
NEXT_PUBLIC_INSFORGE_URL: 'https://branch.insforge.app' }), then read the file
with readFile and assert that either both original lines were replaced with the
new value or only one remains with the new value depending on intended semantics
(but assert the chosen behavior); reference the NEXT_PUBLIC_INSFORGE_URL key,
overwriteEnvFile, writeFile, readFile and the file variable to locate and
implement the test.
src/commands/preview/create.test.ts (1)

50-90: 📐 Maintainability & Code Quality | ⚡ Quick win

Add a failure-path test for post-create wiring errors.

Please add a case where branch creation succeeds but env wiring fails (e.g., mock overwriteEnvFile to throw), and assert the manifest still exists with branchId/appkey. This guards against orphaned-preview regressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/preview/create.test.ts` around lines 50 - 90, Add a test that
simulates a failure in the env wiring step while branch creation succeeds: use
jest to spyOn/mock the overwriteEnvFile function (or the module export used by
the command) to throw an error, then run the CLI command via
registerPreviewCreateCommand (call program.parseAsync with
['preview','create','feat-likes','--wire-env', envFile] like other tests), and
finally assert the preview manifest (readPreviewManifest) still exists and
contains branchId ('branch-123') and appkey ('p1ky-x9p'); also assert the
original env file was not overwritten (or backup created) if relevant. Ensure
you restore the mock after the test.
src/commands/preview/teardown.test.ts (1)

42-71: 📐 Maintainability & Code Quality | ⚡ Quick win

Add a restore-failure regression test to lock cleanup behavior.

Add a case where backup exists but restore throws (e.g., mock copy failure), then assert manifest cleanup still happens after deleteBranchApi. This protects against stale local manifests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/preview/teardown.test.ts` around lines 42 - 71, Add a new test
that creates a wired env backup (like existing test using writePreviewManifest
and envName), then mock the file-restore operation (mock fs.copyFile or
fs.rename used by the teardown logic) to throw so restore fails; call the
teardown via registerPreviewTeardownCommand and program.parseAsync as in the
existing test; assert that deleteBranchApi (mock) is still invoked and that
readPreviewManifest(tmpBase, 'feat-wired') returns null (manifest cleaned up)
even though the restore threw, and also assert the backup file still exists to
prove restore failed but cleanup ran. Ensure you reference the same
helpers/register function names: registerPreviewTeardownCommand,
writePreviewManifest, readPreviewManifest, and the API function deleteBranchApi
when adding the test and mocks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/commands/preview/create.ts`:
- Around line 73-74: The next-step message currently tells users to run the
non-existent subcommand "insforge preview test ${name}" (see the two outputInfo
calls); update the second outputInfo to reference an actual registered preview
subcommand (replace "preview test ${name}" with the correct one, e.g. "preview
teardown ${name}" if you intend the user to stop the preview) so the guidance
matches the registered preview flow (adjust the string passed to outputInfo in
create.ts accordingly).
- Around line 39-61: Write the preview manifest immediately after
createBranchApi returns (use the created object) before calling pollUntilReady
or mutating local files so we never leave a remote branch without a local
manifest; specifically, move or add a writePreviewManifest call that uses
created.id/appkey/created metadata (instead of waiting for
ready.branch_created_at) right after createBranchApi (referencing
createBranchApi and writePreviewManifest), then proceed to pollUntilReady
(pollUntilReady) and only afterward perform env wiring (overwriteEnvFile,
copyFileSync) and update the manifest with any runtime fields from ready if
needed.

In `@src/commands/preview/index.test.ts`:
- Line 12: The test currently asserts the internal Commander field `_hidden` on
the `found` object; instead, change the assertion to verify public behavior:
call `program.helpInformation()` (or similar help output method used in this
test) and assert that the returned string does not include the preview
command/group name (e.g., "preview"), and optionally add a separate assertion
that parsing/execution of the preview command behaves as expected; update the
assertion referencing `found` to use `program.helpInformation()` and a negative
match for "preview" rather than checking `found._hidden`.

In `@src/commands/preview/teardown.ts`:
- Around line 23-35: The teardown can leave a stale local manifest if the
env-file restore (uses manifest.wiredEnvFile, existsSync, copyFileSync, rmSync)
throws after deleteBranchApi succeeded; change the control flow so
deletePreviewManifest(process.cwd(), name) always runs regardless of restore
errors by moving the restore into its own try/catch (or wrapping the restore
block in try and calling deletePreviewManifest in a finally) and log/propagate
restore errors without skipping deletePreviewManifest; keep deleteBranchApi
as-is and ensure deletePreviewManifest is invoked even when the wired-env
restore fails.

In `@src/lib/env-writer.ts`:
- Around line 99-101: The overwrite logic in overwriteEnvFile only replaces the
first match because content.replace(re, ...) is used; update overwriteEnvFile to
replace all occurrences of the key in content (or compute the effective one) by
using a global replace (ensure the RegExp re has the 'g' flag) or run a loop to
replace every match, then push the key into changed; reference the
variables/functions re, content, changed and the function overwriteEnvFile to
locate and update the replacement so duplicate KEY= lines are all rewritten.

In `@src/lib/preview-manifest.ts`:
- Around line 16-17: manifestPath currently concatenates the raw name into a
filesystem path allowing path traversal; update manifestPath to validate and
sanitize the name token before joining: normalize and reject any input that
contains path separators or resolves outside previewDir (e.g. use
path.basename(name) or a strict regex like /^[A-Za-z0-9._-]+$/ to allow only
safe filename characters), optionally throw a clear error if validation fails,
and then join the sanitized token with previewDir(baseDir) so read/write/delete
operations cannot escape .insforge/previews; keep references to manifestPath and
previewDir when locating the change.

---

Nitpick comments:
In `@src/commands/preview/create.test.ts`:
- Around line 50-90: Add a test that simulates a failure in the env wiring step
while branch creation succeeds: use jest to spyOn/mock the overwriteEnvFile
function (or the module export used by the command) to throw an error, then run
the CLI command via registerPreviewCreateCommand (call program.parseAsync with
['preview','create','feat-likes','--wire-env', envFile] like other tests), and
finally assert the preview manifest (readPreviewManifest) still exists and
contains branchId ('branch-123') and appkey ('p1ky-x9p'); also assert the
original env file was not overwritten (or backup created) if relevant. Ensure
you restore the mock after the test.

In `@src/commands/preview/teardown.test.ts`:
- Around line 42-71: Add a new test that creates a wired env backup (like
existing test using writePreviewManifest and envName), then mock the
file-restore operation (mock fs.copyFile or fs.rename used by the teardown
logic) to throw so restore fails; call the teardown via
registerPreviewTeardownCommand and program.parseAsync as in the existing test;
assert that deleteBranchApi (mock) is still invoked and that
readPreviewManifest(tmpBase, 'feat-wired') returns null (manifest cleaned up)
even though the restore threw, and also assert the backup file still exists to
prove restore failed but cleanup ran. Ensure you reference the same
helpers/register function names: registerPreviewTeardownCommand,
writePreviewManifest, readPreviewManifest, and the API function deleteBranchApi
when adding the test and mocks.

In `@src/lib/env-writer.overwrite.test.ts`:
- Around line 42-60: Add a regression test that ensures duplicate keys are
handled by overwriteEnvFile: create a new case (or extend the existing "leaves
unrelated lines and comments intact" test) where the original .env content
contains two lines starting with NEXT_PUBLIC_INSFORGE_URL= (different values),
call overwriteEnvFile(file, { NEXT_PUBLIC_INSFORGE_URL:
'https://branch.insforge.app' }), then read the file with readFile and assert
that either both original lines were replaced with the new value or only one
remains with the new value depending on intended semantics (but assert the
chosen behavior); reference the NEXT_PUBLIC_INSFORGE_URL key, overwriteEnvFile,
writeFile, readFile and the file variable to locate and implement the test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3976334c-5a3e-47ae-a4e3-5874ab856adb

📥 Commits

Reviewing files that changed from the base of the PR and between 21c8bd0 and 5108b22.

📒 Files selected for processing (11)
  • src/commands/preview/create.test.ts
  • src/commands/preview/create.ts
  • src/commands/preview/index.test.ts
  • src/commands/preview/index.ts
  • src/commands/preview/teardown.test.ts
  • src/commands/preview/teardown.ts
  • src/index.ts
  • src/lib/env-writer.overwrite.test.ts
  • src/lib/env-writer.ts
  • src/lib/preview-manifest.test.ts
  • src/lib/preview-manifest.ts

Comment thread src/commands/preview/create.ts Outdated
Comment on lines +39 to +61
const created = await createBranchApi(project.project_id, { mode: 'full', name }, apiUrl);
captureEvent(project.project_id, 'cli_preview_create', { name });
const ready = await pollUntilReady(created.id, apiUrl);

const previewUrl = `https://${ready.appkey}.${ready.region}.insforge.app`;

let wiredEnvFile: string | undefined;
if (opts.wireEnv) {
wiredEnvFile = typeof opts.wireEnv === 'string' ? opts.wireEnv : '.env.local';
const envPath = path.resolve(process.cwd(), wiredEnvFile);
if (existsSync(envPath)) {
copyFileSync(envPath, envPath + '.preview-bak');
}
overwriteEnvFile(envPath, { NEXT_PUBLIC_INSFORGE_URL: previewUrl });
}

await writePreviewManifest(process.cwd(), {
name,
branchId: ready.id,
appkey: ready.appkey,
createdAt: ready.branch_created_at,
...(wiredEnvFile ? { wiredEnvFile } : {}),
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Persist preview metadata before post-create local mutations to avoid orphaned environments.

Line 39 creates the remote branch, but the manifest is only written at Line 55 after polling and optional env-file mutation. If wiring/polling fails in between, the preview can exist remotely without a local manifest, and preview teardown won’t be able to discover it by name.

Suggested sequencing change
         const created = await createBranchApi(project.project_id, { mode: 'full', name }, apiUrl);
         captureEvent(project.project_id, 'cli_preview_create', { name });
         const ready = await pollUntilReady(created.id, apiUrl);

         const previewUrl = `https://${ready.appkey}.${ready.region}.insforge.app`;

+        // Persist baseline manifest first so teardown can always find the branch.
+        await writePreviewManifest(process.cwd(), {
+          name,
+          branchId: ready.id,
+          appkey: ready.appkey,
+          createdAt: ready.branch_created_at,
+        });
+
         let wiredEnvFile: string | undefined;
         if (opts.wireEnv) {
           wiredEnvFile = typeof opts.wireEnv === 'string' ? opts.wireEnv : '.env.local';
           const envPath = path.resolve(process.cwd(), wiredEnvFile);
           if (existsSync(envPath)) {
             copyFileSync(envPath, envPath + '.preview-bak');
           }
           overwriteEnvFile(envPath, { NEXT_PUBLIC_INSFORGE_URL: previewUrl });
+
+          // Persist wired file only after successful rewrite.
+          await writePreviewManifest(process.cwd(), {
+            name,
+            branchId: ready.id,
+            appkey: ready.appkey,
+            createdAt: ready.branch_created_at,
+            wiredEnvFile,
+          });
         }
-
-        await writePreviewManifest(process.cwd(), {
-          name,
-          branchId: ready.id,
-          appkey: ready.appkey,
-          createdAt: ready.branch_created_at,
-          ...(wiredEnvFile ? { wiredEnvFile } : {}),
-        });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const created = await createBranchApi(project.project_id, { mode: 'full', name }, apiUrl);
captureEvent(project.project_id, 'cli_preview_create', { name });
const ready = await pollUntilReady(created.id, apiUrl);
const previewUrl = `https://${ready.appkey}.${ready.region}.insforge.app`;
let wiredEnvFile: string | undefined;
if (opts.wireEnv) {
wiredEnvFile = typeof opts.wireEnv === 'string' ? opts.wireEnv : '.env.local';
const envPath = path.resolve(process.cwd(), wiredEnvFile);
if (existsSync(envPath)) {
copyFileSync(envPath, envPath + '.preview-bak');
}
overwriteEnvFile(envPath, { NEXT_PUBLIC_INSFORGE_URL: previewUrl });
}
await writePreviewManifest(process.cwd(), {
name,
branchId: ready.id,
appkey: ready.appkey,
createdAt: ready.branch_created_at,
...(wiredEnvFile ? { wiredEnvFile } : {}),
});
const created = await createBranchApi(project.project_id, { mode: 'full', name }, apiUrl);
captureEvent(project.project_id, 'cli_preview_create', { name });
const ready = await pollUntilReady(created.id, apiUrl);
const previewUrl = `https://${ready.appkey}.${ready.region}.insforge.app`;
// Persist baseline manifest first so teardown can always find the branch.
await writePreviewManifest(process.cwd(), {
name,
branchId: ready.id,
appkey: ready.appkey,
createdAt: ready.branch_created_at,
});
let wiredEnvFile: string | undefined;
if (opts.wireEnv) {
wiredEnvFile = typeof opts.wireEnv === 'string' ? opts.wireEnv : '.env.local';
const envPath = path.resolve(process.cwd(), wiredEnvFile);
if (existsSync(envPath)) {
copyFileSync(envPath, envPath + '.preview-bak');
}
overwriteEnvFile(envPath, { NEXT_PUBLIC_INSFORGE_URL: previewUrl });
// Persist wired file only after successful rewrite.
await writePreviewManifest(process.cwd(), {
name,
branchId: ready.id,
appkey: ready.appkey,
createdAt: ready.branch_created_at,
wiredEnvFile,
});
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/preview/create.ts` around lines 39 - 61, Write the preview
manifest immediately after createBranchApi returns (use the created object)
before calling pollUntilReady or mutating local files so we never leave a remote
branch without a local manifest; specifically, move or add a
writePreviewManifest call that uses created.id/appkey/created metadata (instead
of waiting for ready.branch_created_at) right after createBranchApi (referencing
createBranchApi and writePreviewManifest), then proceed to pollUntilReady
(pollUntilReady) and only afterward perform env wiring (overwriteEnvFile,
copyFileSync) and update the manifest with any runtime fields from ready if
needed.

Comment thread src/commands/preview/create.ts Outdated
Comment thread src/commands/preview/index.test.ts Outdated
Comment thread src/commands/preview/teardown.ts Outdated
Comment thread src/lib/env-writer.ts Outdated
Comment thread src/lib/preview-manifest.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 issues found across 11 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/lib/env-writer.ts Outdated
Comment thread src/lib/preview-manifest.ts Outdated
Comment thread src/commands/preview/create.ts Outdated
Comment thread src/commands/preview/teardown.ts Outdated
Comment thread src/commands/preview/create.ts Outdated
@CarmenDou CarmenDou changed the title feat(preview): experimental preview commands for isolated e2e environments INS-356: PoC: Full-stack preview for e2e verify Jun 12, 2026
Comment thread src/commands/preview/teardown.ts Outdated
Comment thread src/commands/preview/teardown.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 4 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src/commands/preview/create.ts Outdated

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Solid, well-tested PoC for hidden preview create/teardown commands, but preview-name validation runs only at manifest-write time — after the remote preview is fully provisioned — so a common git-style name like feat/likes orphans a live full-stack preview with no rollback and no teardown path.

Requirements context

No /docs/superpowers/ directory exists in this repo. The closest equivalent, docs/specs/, contains specs for diagnose and db migrations only — no spec/plan matches INS-356 / preview, so this review assesses against the PR description, DEVELOPMENT.md, and the repo's cli-development skill.

Verification performed in an isolated workspace at head f64f000: npm ci + npx vitest run466 passed, 13 skipped (pre-existing); eslint clean on all new/changed files. (Raw tsc --noEmit errors exist in payments/transactions.test.ts, config-capabilities.ts, prompts.ts — all untouched by this PR, pre-existing.)

Findings

Critical

  1. [Functionality] Name validation happens after provisioning, orphaning the remote preview on realistic input. assertSafeName (src/lib/preview-manifest.ts:17-21) rejects /, but it is only reached via writePreviewManifestmanifestPath (src/commands/preview/create.ts:55-60) — after createBranchApi succeeds and pollUntilReady completes (up to 5 minutes). The rollback try/catch wraps only the poll (create.ts:39-52), so for a git-style name like feat/likes:

    • the full-stack preview branch is created and provisioned remotely,
    • writePreviewManifest then throws a plain Error("Invalid preview name …"),
    • no deleteBranchApi rollback runs, no manifest exists, so preview teardown can never find it,
    • the error message doesn't tell the user a live branch was left behind.

    The PR description advertises "validates safe names", but the validation fires too late to protect anything except the manifest path itself. Fix: validate the name at the top of the action, before createBranchApi (export assertSafeName or a shared validator from preview-manifest.ts), and add a test for the rejected-name path.

Suggestion

  1. [Functionality] teardown deadlocks if the remote branch is already gone. deleteBranchApi is awaited before any local cleanup (src/commands/preview/teardown.ts:20-23), and platformFetch throws CLIError on any non-2xx including 404 (src/lib/api/platform.ts:83-87; deleteBranchApi passes no passThroughStatuses). If the branch was deleted by other means, teardown aborts every time and the manifest + wired env file can never be cleaned up via the CLI. Notably, create's own poll-failure message tells the user to run insforge branch delete <name> (create.ts:46-50) — following that advice sets this exact trap. Tolerate 404/not-found from the delete and continue local cleanup.

  2. [Functionality] Re-running preview create <same name> silently orphans the previous preview and can destroy the env backup. There is no guard against an existing manifest: a second create overwrites .insforge/previews/<name>.json (losing the first branchId, orphaning that branch), and with --wire-env the copyFileSync at create.ts:67-69 overwrites .preview-bak with the already-wired content — the user's original (e.g. prod) value is permanently lost, and teardown "restores" the previous preview's URL. Refuse to create when a manifest for the name exists, and don't overwrite an existing .preview-bak.

  3. [Software engineering] The rollback-on-poll-failure path — listed as a bug fix in the PR description — has no test. Moreover, the vi.mock factory in src/commands/preview/create.test.ts:9-31 doesn't stub deleteBranchApi at all, so any future test exercising that path will fail with Vitest's "No 'deleteBranchApi' export is defined on the mock". Add the stub plus a test asserting deleteBranchApi is called when polling fails. A create-side test for the --wire-env default (.env.local, no value) / wiredEnvCreated: true case would also be welcome (the teardown side is covered).

  4. [Security/conventions] Analytics event sends user-entered free text. captureEvent(project.project_id, 'cli_preview_create', { name }) (create.ts:37) sends the user-chosen preview name to PostHog. DEVELOPMENT.md §2 says properties must include only non-sensitive metadata and "Never send SQL, file contents, credentials, or user-entered free text"; branch create deliberately sends only { mode, parent_project_id } (src/commands/branch/create.ts:57-60). Drop name from the properties (or send a derived non-identifying flag).

  5. [Software engineering] pollUntilReady is duplicated from src/commands/branch/create.ts (same constants, near-identical loop). The branch version also re-checks terminal state once after timeout (branch/create.ts:129-135); the new copy doesn't. Consider extracting a shared helper — for a PoC, at minimum a comment noting the divergence.

Information

  1. [Software engineering] No progress output during the up-to-5-minute poll. branch create runs a clack spinner through the slow POST + provisioning; preview create is silent until ready in non-JSON mode. Acceptable for a hidden experimental command, but worth aligning before un-hiding.

  2. [Functionality] If overwriteEnvFile throws mid-wire (e.g. permissions), a .preview-bak is left orphaned and the manifest lacks wiredEnvFile — teardown then leaves both behind. Low blast radius.

  3. [Security] Otherwise no security-relevant concerns: assertSafeName correctly blocks path traversal in manifest paths, both commands call requireAuth, no secrets are logged, overwriteEnvFile uses a replacer function so $-patterns in values are written literally (tested), and no new dependencies are added.

  4. [Performance] No performance concerns: the poll loop is bounded (5 min / 3 s interval) and the sync file I/O matches existing CLI patterns; nothing runs per-request.

Verdict

request_changes — one Critical finding (late name validation orphaning provisioned previews). Everything else is non-blocking. The fix is small: validate the name before calling createBranchApi, plus a regression test.

…tes, tolerate 404 on teardown, drop name from analytics

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Solid, well-tested PoC adding hidden preview create/preview teardown commands that closely follow the existing branch create patterns; no blocking issues found.

Requirements context

No matching spec/plan found under docs/specs/ (only diagnose and db-migrations specs exist) — assessing against the PR description (INS-356 PoC) alone.

Verification run in a clean workspace at aa43a5b: npm run lint → 0 errors; npx vitest run → 471 passed, 0 failed (all 20 new tests pass; the deleteBranchApi signature change is backward compatible with existing callers).

Findings

Critical

(none)

Suggestion

Functionality / conventions

  1. --json output is polluted in teardownsrc/commands/preview/teardown.ts:37-48: the env-restore messages (outputInfo(' Removed …'), ' Restored …', ' ⚠ Could not restore …') are emitted unconditionally. outputInfo is console.log to stdout (src/lib/output.ts:22-24), so with --json these plain-text lines precede the outputJson({ teardown: … }) payload and break strict JSON consumers parsing stdout. create.ts gates all outputInfo calls behind !json — teardown should do the same (DEVELOPMENT.md §1: "Respect the --json flag"). Low blast radius since the command is hidden/experimental, hence non-blocking.

Functionality
2. Two previews wiring the same env file interact poorlysrc/commands/preview/create.ts:90-93: creating preview A with --wire-env backs up prod; creating preview B reuses the same file but skips the backup (it already exists, intentionally protected). Tearing down A then restores the prod values and deletes the backup while B is still active — the env file silently stops pointing at B. Consider refusing --wire-env when the target file's .preview-bak belongs to another live preview (e.g. record the backup owner in the manifest), or at least warn.

Software engineering
3. No progress feedback during the up-to-5-minute provisioning pollsrc/commands/preview/create.ts:55-60: branch create shows a @clack/prompts spinner across the identical POST + poll flow (src/commands/branch/create.ts:48-55); preview create hangs silently. Fine for a PoC, but worth spinner parity before unhiding the command.

Information

  1. Rollback deletes the branch on any poll errorsrc/commands/preview/create.ts:57-69: a transient network failure in getBranchApi mid-poll triggers deleteBranchApi on a possibly healthy branch. The error message ("If the branch still exists, remove it…") mitigates this, and aggressive cleanup is arguably right for ephemeral previews — just noting the trade-off.
  2. Teardown doesn't wrap assertSafeNameinsforge preview teardown 'feat/likes' throws the raw Error from manifestPath (src/lib/preview-manifest.ts:24-26), rendered as UNKNOWN_ERROR in --json, whereas create wraps it in CLIError (src/commands/preview/create.ts:37-41). Cosmetic inconsistency.
  3. Untyped opts in create's actionsrc/commands/preview/create.ts:25 leaves opts implicitly any; branch/create.ts:21 types it. Minor.
  4. Manifest parse is unvalidatedsrc/lib/preview-manifest.ts:42-50: a corrupt/hand-edited manifest JSON throws a raw parse error. Acceptable for a PoC.
  5. DEVELOPMENT.md §3 (agent-skills sync): presumably intentionally skipped since the command is hidden — worth a conscious decision when it graduates from experimental.

Security — no security-relevant concerns. Preview names are validated against ^[A-Za-z0-9._-]+$ before any path use (blocks traversal); the name was correctly removed from the analytics payload per DEVELOPMENT.md §2 ("never send user-entered free text"); no new dependencies; no secrets logged. passThroughStatuses is a pre-existing, documented platformFetch option used correctly.

Performance — no findings. Poll interval/timeout match branch create; file operations are small and one-shot.

Verdict

approved — zero Critical findings (informational; explicit GitHub approval remains a human action via the approve flow). Test coverage is genuinely good: rollback on failed provisioning, duplicate-name guard, default vs explicit --wire-env, $-literal env values, duplicated-key rewrite, and help-hiding are all exercised.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/commands/preview/create.test.ts`:
- Around line 47-51: The tests create a temporary directory in beforeEach
(tmpBase via fs.mkdtemp) but never delete it; add an afterEach that
asynchronously removes tmpBase (use fs.rm(tmpBase, { recursive: true, force:
true }) or fs.rmdir/tmpdir removal equivalent) and restores any mocked cwd if
needed; place the afterEach alongside beforeEach in the test file so tmpBase is
cleaned up after each test and avoid leaking temp directories.
- Line 92: The manifest stores manifest.wiredEnvFile as the raw --wire-env
string which can be relative, causing teardown.ts (which uses
path.resolve(process.cwd(), manifest.wiredEnvFile)) to resolve against the
current working directory instead of the original create-time path; change
create.ts to resolve the provided env path to an absolute path (e.g., compute
envPath = path.resolve(providedWireEnv || '.env.local')) and store that absolute
path in the manifest (replace wiredEnvFile with envPath or set
manifest.wiredEnvFile to the absolute path), update teardown.ts to use the
absolute manifest value directly, and update create.test.ts expectations to
assert the manifest contains the resolved absolute env path instead of the raw
relative value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b0e60b73-796f-4bc0-bd07-8e816650d1a7

📥 Commits

Reviewing files that changed from the base of the PR and between f64f000 and 3e1323f.

📒 Files selected for processing (7)
  • src/commands/preview/create.test.ts
  • src/commands/preview/create.ts
  • src/commands/preview/teardown.test.ts
  • src/commands/preview/teardown.ts
  • src/lib/api/platform.ts
  • src/lib/preview-manifest.test.ts
  • src/lib/preview-manifest.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/lib/preview-manifest.ts
  • src/commands/preview/teardown.test.ts
  • src/commands/preview/teardown.ts
  • src/commands/preview/create.ts

Comment thread src/commands/preview/create.test.ts Outdated
Comment thread src/commands/preview/create.test.ts Outdated

@jwfing jwfing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review — INS-356: PoC full-stack preview for e2e verify

Summary: A well-scoped, well-tested experimental PoC that adds hidden preview create/teardown commands plus an opt-in link --with-test-agents flag; no blocking issues found.

Requirements context

No matching spec/plan found under docs/specs/ for INS‑356 (only diagnose and db-migrations designs exist there) — assessed against the PR description/title alone. Behavior was cross-checked against repo conventions in DEVELOPMENT.md §2 (PostHog analytics) and §3 (agent-skills sync), and the Playwright init-agents API was verified against current docs (real command, v1.56+).

Findings

Critical

(none)

Suggestion

  • Functionality — partial provisioning has no rollback on the env-wiring path (src/commands/preview/create.ts:90-118). The polling-timeout path correctly rolls back the branch (deleteBranchApi) and writes no manifest. But once the branch is ready, the manifest is written and then copyFileSync/overwriteEnvFile run unguarded. If backup or write fails (e.g. EACCES/EROFS), the outer catch calls handleError, leaving a live preview branch and a manifest without wiredEnvFile — the env wiring is reported as failed but the branch is silently orphaned. Consider wrapping the wiring in a try/catch that at minimum warns the user the preview exists and must be torn down. Low blast radius (teardown still works), hence non-blocking.

  • Software engineering — preview teardown emits no analytics event (src/commands/preview/teardown.ts:14-58). create fires captureEvent('cli_preview_create', …) per DEVELOPMENT.md §2, and teardown imports/awaits shutdownAnalytics() in finally, but never captures a cli_preview_teardown event. For symmetry and feature-usage tracking, add one (distinct ID = branchId's parent project or manifest.appkey as available).

Information

  • overwriteEnvFile normalizes line formatting (src/lib/env-writer.ts:97-107). The replace callback rewrites a matched line to ${key}=${value}, dropping any surrounding quotes and KEY = val spacing. Fine for repointing a URL, but worth a doc note that quote/spacing style isn't preserved (contrast with upsertEnvFile, which leaves mismatched values untouched). The () => … callback correctly avoids $-pattern interpretation, and the global-regex lastIndex reset before .replace is handled — both well covered by tests.

  • Duplicate-name detection is cwd-scoped (src/commands/preview/create.ts:46-50). readPreviewManifest(process.cwd(), name) only detects a clash for previews created from the same directory. This is consistent with the local-manifest design (.insforge/previews/<name>.json), so it's intentional — just noting the limitation.

  • agent-skills sync (DEVELOPMENT.md §3). The new visible link --with-test-agents flag (and, eventually, the preview commands if they graduate from hidden) will need the insforge-cli skill in InsForge/agent-skills updated. Low urgency while preview is hidden and this is a PoC.

  • assertSafeName path-traversal check is sound. ^[A-Za-z0-9._-]+$ rejects /, and pure-dot names (..) are neutralized by the ${name}.json suffix at manifestPath, so no traversal escape — confirmed by tests.

Verdict

approved (informational; human approval via the approve flow still required). Strong test coverage across all new units (manifest round-trip, env overwrite incl. $/duplicate-key/comment-preservation, rollback, duplicate-refusal, --json stdout hygiene), correct use of the existing passThroughStatuses plumbing for the 404-tolerant delete, analytics convention followed (no preview name leaked), and the link signature change threaded through all call sites with updated tests. Security: no untrusted input reaches shell/SQL — init-agents is a fixed command string, env paths are user-local, and previewUrl is built from trusted API fields. Performance: no concerns — polling is await-based with bounded 5‑min timeout / 3s interval.

…eview-poc

# Conflicts:
#	src/commands/projects/link.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/commands/projects/link.ts (1)

118-124: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

--guard is accidentally ignored by the skills-only fast path.

On Line 118–Line 124, isSkillsOnly does not account for opts.guard.
So link --guard / link --guard off falls into the skills-only branch and returns before any project config is saved, making the guard option a no-op in that path.

Suggested fix
         const isSkillsOnly =
           opts.projectId === undefined &&
           opts.orgId === undefined &&
           opts.template === undefined &&
           opts.auth === undefined &&
           opts.apiBaseUrl === undefined &&
-          opts.apiKey === undefined;
+          opts.apiKey === undefined &&
+          opts.guard === undefined;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/projects/link.ts` around lines 118 - 124, The isSkillsOnly
condition on lines 118-124 does not account for the opts.guard option, causing
the link --guard and link --guard off commands to incorrectly fall into the
skills-only fast path and return before the guard configuration is saved. Fix
this by adding opts.guard === undefined to the list of conditions in the
isSkillsOnly check, alongside the existing checks for opts.projectId,
opts.orgId, opts.template, opts.auth, opts.apiBaseUrl, and opts.apiKey.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/commands/projects/link.ts`:
- Around line 118-124: The isSkillsOnly condition on lines 118-124 does not
account for the opts.guard option, causing the link --guard and link --guard off
commands to incorrectly fall into the skills-only fast path and return before
the guard configuration is saved. Fix this by adding opts.guard === undefined to
the list of conditions in the isSkillsOnly check, alongside the existing checks
for opts.projectId, opts.orgId, opts.template, opts.auth, opts.apiBaseUrl, and
opts.apiKey.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f5d0d665-85a4-4fb8-a0dd-d37873288aa4

📥 Commits

Reviewing files that changed from the base of the PR and between 3e1323f and 48d0d38.

📒 Files selected for processing (4)
  • src/commands/projects/link.test.ts
  • src/commands/projects/link.ts
  • src/index.ts
  • src/lib/skills.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/index.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 24 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src/lib/analytics.ts Outdated
Comment thread src/lib/browser-mcp.ts Outdated
Comment thread src/lib/verify-probe.ts
Comment thread src/commands/verify/truth.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 7 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src/lib/verify-probe.ts
Comment thread src/lib/verify-probe.ts
@cubic-dev-ai

cubic-dev-ai Bot commented Jun 18, 2026

Copy link
Copy Markdown

You're iterating quickly on this pull request. To help protect your rate limits, cubic has paused automatic reviews on new pushes for now—when you're ready for another review, comment @cubic-dev-ai review.

@CarmenDou CarmenDou closed this Jun 18, 2026
const aId = (rows[0] as { id?: string })?.id;
if (!aId) throw new CLIError(`Could not find user A (${opts.userA}) — seed it first.`);

const filter = `${opts.owner}=eq.${aId}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 The --owner column name is interpolated directly into the URL query string without encoding. PostgREST parses everything after ? as key=op.value pairs, so an agent-generated value like user_id&limit=0 would produce ?user_id&limit=0=eq.<uuid>, forcing the PostgREST endpoint to return 0 rows regardless of RLS state. That silences a real rls_leak and produces a false "isolation holds" verdict — the worst possible outcome for a security probe.

Suggested change
const filter = `${opts.owner}=eq.${aId}`;
const filter = `${encodeURIComponent(opts.owner)}=eq.${encodeURIComponent(aId)}`;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants