Skip to content

feat: vibesift propose — scan, kickoff, auto-open, live page#37

Open
polrai-bg wants to merge 5 commits into
neurot1cal:mainfrom
polrai-bg:feat/propose-command
Open

feat: vibesift propose — scan, kickoff, auto-open, live page#37
polrai-bg wants to merge 5 commits into
neurot1cal:mainfrom
polrai-bg:feat/propose-command

Conversation

@polrai-bg

Copy link
Copy Markdown

Summary

  • New vibesift propose [<slug>] verb: low-friction kickoff. Scans the repo, classifies the project (node/python/rust/go/static), seeds project-appropriate constraint stubs + a starter ship task list, auto-commits a single vibesift: propose <slug> commit, and opens the rendered HTML in the default browser. Prompts only when stdin is a TTY; CI/agent flows pass everything via --title and --problem.
  • New "What's next" hero on every session page, above the pipeline graphic. One-line callout naming the single next action with a phase-tinted left border. Eight states across scope / sift / ship / deployed, including a monospace inline preview of the next ship task. data-vibesift-next attribute for future JS hooks.
  • New live auto-refresh on every session page. The page polls itself every 3s, parses the embedded JSON state out of the response, and reloads when updatedAt changes. Toggleable via a LIVE / PAUSED pill in the toolbar that persists in localStorage. Stays inside the zero-runtime-deps promise: no SSE, no WebSocket. Closes the "watch the agent work" gap for vibe-coding flows: a Claude Code agent running vibesift ship task done shows up on the page within seconds, no manual refresh.
  • SKILL.md gains a propose trigger list, propose in the canonical commands block, and an auto-ticking discipline section telling agents to mark ship tasks done inline as they complete work.

Plan-file parsing in propose was deliberately deferred. The feat/import-plan branch (separate work) carries the PRD/build-plan parser; this PR detects plan files and surfaces their paths in the propose summary, but does not parse or seed from them. Two PRs can stack cleanly later.

Files

  • New: src/scan.js, src/open.js, tests/scan.test.js, tests/cli-propose.test.js
  • Modified: src/cli.js, src/template.js, tests/template.test.js, skills/vibesift/SKILL.md, package.json

Test plan

  • All 119 tests pass: npm test (state, template, cli-no-commit, cli-index, cli-propose, scan, svg-pipeline, svg-tree).
  • Hero panel renders correctly across all eight phase states. Verified manually with a full scope → sift → ship → deploy walkthrough in /tmp/vibesift-fulltest.
  • propose does not clobber an existing session (test + manual).
  • propose --no-commit writes the file but skips the commit (test + manual).
  • propose on a non-node repo falls back to generic scaffold (test).
  • propose creates a single vibesift: propose <slug> commit + optional vibesift: index regenerated follow-up (test).
  • Live auto-refresh script does not contain a literal </script> tag in its body (test asserts the OPEN_TAG / CLOSE_TAG split).
  • Manually verify live-refresh end-to-end in a browser by running a propose, then ship task done from another terminal, watching the page reload within 3s.

Smoke test

cd /tmp && rm -rf vibesift-smoke && mkdir vibesift-smoke && cd vibesift-smoke
git init -q -b main && git commit --allow-empty -q -m initial
echo '{ "name": "demo", "engines": { "node": "20+" }, "dependencies": {} }' > package.json
git add . && git commit -q -m "add package.json"

# In a real consumer, this would be: vibesift propose blue-widget --title "..."
node /path/to/vibesift/src/cli.js propose blue-widget --title "Build the widget" --problem "Need X"

# Expected:
#   ✓ created docs/sessions/blue-widget/index.html
#   session: blue-widget · phase: scope · 2 constraints · 5 tasks
#   browser opens with "What's next: Set the approach" hero in yellow
#   LIVE pill in toolbar is green and polling

🤖 Generated with Claude Code

polrai-bg and others added 5 commits May 12, 2026 11:07
Pure repo-scan module classifies project type (node/python/rust/go/static)
and produces project-appropriate constraint stubs and starter task lists
for vibesift propose to seed sessions with. Cross-platform browser-open
helper lands the user on the rendered HTML right after creation. Both
modules are zero-dep and side-effect-free (open spawns once and unrefs).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Low-friction kickoff verb. propose [<slug>] scans the repo, auto-bootstraps
docs/sessions/ if missing, seeds project-type constraints and a starter
ship task list, writes one commit, and opens the rendered page in the
default browser. Prompts only when stdin is a TTY so CI and agent flows
that pass --title and --problem never block. main() goes async so future
verbs that need stdin can also use readline without restructuring.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
One-line callout above the pipeline graphic naming the single next action
the user (or agent) should take, with a phase-tinted left border. Eight
distinct states across scope/sift/ship/deployed, including a monospace
inline preview of the next ship task. data-vibesift-next attribute lets
future JS hook in without touching the embedded state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds vibesift propose to the canonical commands block and the trigger
list so harnesses know to invoke it for "kickoff a session" phrasing.
Auto-ticking discipline section tells agents to mark ship tasks done
inline as they complete work, not batched at the end, since the HTML
page is a live broadcast.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The HTML page polls itself every 3s, parses the embedded JSON state out
of the response, and reloads when updatedAt changes. Toggleable via a
LIVE / PAUSED pill in the toolbar that persists in localStorage. Stays
inside the zero-runtime-deps promise: no SSE, no WebSocket. Silent on
file:// where fetch is blocked.

Closes the "watch the agent work" gap for vibe-coding flows: a Claude
Code agent running ship task done shows up on the page within seconds,
no manual refresh.

Also wires tests/scan.test.js and tests/cli-propose.test.js into the
npm test script (they were skipped on the first pass).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@polrai-bg polrai-bg requested a review from neurot1cal as a code owner May 12, 2026 18:51

@neurot1cal neurot1cal left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks for this — propose, the hero, and the auto-ticking discipline are clear wins. A few inline notes below. Most are small; the README one is the only one I want resolved before this lands so the project's scope/docs stay in sync.

Comment thread src/template.js
// deps promise: no SSE, no WebSocket, no framework. The close tag for the
// embedded state block is built by string concatenation so the literal
// `</script>` never appears inside this script body.
const LIVE_POLL_SCRIPT = `

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This adds live auto-refresh, but the README still lists Real-time updates — Pages serves whatever's committed. Reload to see new state. as out-of-scope for v1 (line 251). Two options to resolve:

  1. Update README.md line 251 to admit the scope, e.g. Real-time updates land via polling (no SSE, no WebSocket); the page reloads itself when updatedAt changes.
  2. Pull the live-refresh out and land it in a follow-up PR with the README amendment.

I'm fine with (1), but the doc needs to match the code before merge.

Comment thread src/template.js
var nextState;
try { nextState = JSON.parse(raw); } catch(err) { return; }
if (nextState.updatedAt && nextState.updatedAt !== lastSeen) {
window.location.reload();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Full window.location.reload() loses scroll position and any expanded <details> state. On a long session page being reviewed on mobile, the reader can lose their place every 3s while an agent is actively working.

Not a blocker for v1 of this feature, but worth a follow-up to do something more surgical — swap the embedded JSON state and re-render just the affected section. Could also be mitigated short-term by preserving document.scrollingElement.scrollTop across the reload via sessionStorage.

Comment thread src/template.js
})
.catch(function(){});
}
setInterval(tick, INTERVAL);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Polling continues on hidden / backgrounded tabs. On a phone with a vibesift tab in the background, this keeps hitting the network every 3s. Cheap fix:

function tick(){
  if (!enabled) return;
  if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return;
  // ...rest as-is
}

Or gate the setInterval behind a visibilitychange listener that pauses/resumes. Either way, optional but worth doing.

Comment thread skills/vibesift/SKILL.md
Same shape applies to sift and ship.

For a faster start that pre-seeds the session with project-type-derived
constraints and a starter task list, use `vibesift propose <slug>` instead

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

The canonical commands block above mentions [--no-open] but this paragraph (the one agents actually read for flow guidance) doesn't tell them to use it. In a CI or headless-agent flow, default-opening a browser is at best a no-op and at worst a stray process. Suggest a line like:

In CI or any non-interactive context, pass --no-open to suppress the browser. The CLI also auto-suppresses when stdin isn't a TTY for the prompt path, but the open-in-browser call is unconditional unless the flag is set.

Comment thread tests/cli-propose.test.js
// Expected commits: initial empty + propose. Index regen may add 1 more
// if the landing index actually changed; with a single new session it
// does, so total is 3.
assert.ok(lines.length >= 2, `expected >=2 commits, got ${lines.length}: ${log.stdout}`);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Coverage notes on this PR overall, anchored here since this is the loosest of the new tests:

  1. This test is too loose. lines.length >= 2 plus the vibesift: propose line check would still pass if some future regression caused every constraint and task to fire its own commit. Tightening to assert.equal(lines.length, 2) or <= 3 (allowing the optional index-regen follow-up) would catch that.
  2. No test for openInBrowser. Fair — cross-platform browser spawn is hard to test cleanly. But a unit test that mocks spawn and asserts the right cmd + args per platform would be cheap. Not blocking.
  3. No test enumerates all 8 nextStep states explicitly. Three template tests cover live-pill / live-poll / no-literal-script-tag, but the hero's 8 phase states are only spot-checked through the integration paths. A pure unit test that calls nextStep(state) with each shape (no constraints, constraints + no decision, etc.) would lock the labels in.
  4. No ensureBootstrapped idempotence test. The function is intentionally idempotent via wx + EEXIST catch, but no test asserts that calling propose twice in a row doesn't clobber docs/index.html.

Comment thread src/cli.js
for (const c of constraintStubsFor(scan)) addConstraint(state, c);
for (const t of taskScaffoldFor(scan)) addTask(state, t);

writeFileSync(path, renderHTML(state));
Comment thread src/open.js
args = [filepath];
}
try {
const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
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.

3 participants