projscan 4.2.0 → 4.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +93 -26
  2. package/dist/cli/commands/missionProof.d.ts +1 -0
  3. package/dist/cli/commands/missionProof.js +478 -0
  4. package/dist/cli/commands/missionProof.js.map +1 -0
  5. package/dist/cli/commands/start.js +18 -0
  6. package/dist/cli/commands/start.js.map +1 -1
  7. package/dist/cli/index.js +2 -0
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/core/missionOutcome.d.ts +2 -0
  10. package/dist/core/missionOutcome.js +219 -0
  11. package/dist/core/missionOutcome.js.map +1 -0
  12. package/dist/core/missionProof.d.ts +6 -0
  13. package/dist/core/missionProof.js +92 -0
  14. package/dist/core/missionProof.js.map +1 -0
  15. package/dist/core/missionProofBaseline.d.ts +10 -0
  16. package/dist/core/missionProofBaseline.js +131 -0
  17. package/dist/core/missionProofBaseline.js.map +1 -0
  18. package/dist/core/missionProofMarkdown.d.ts +2 -0
  19. package/dist/core/missionProofMarkdown.js +80 -0
  20. package/dist/core/missionProofMarkdown.js.map +1 -0
  21. package/dist/core/missionProofSummary.d.ts +2 -0
  22. package/dist/core/missionProofSummary.js +16 -0
  23. package/dist/core/missionProofSummary.js.map +1 -0
  24. package/dist/core/start.d.ts +1 -0
  25. package/dist/core/start.js +9 -4
  26. package/dist/core/start.js.map +1 -1
  27. package/dist/index.d.ts +6 -1
  28. package/dist/index.js +5 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/mcp/tools/start.js +5 -0
  31. package/dist/mcp/tools/start.js.map +1 -1
  32. package/dist/projscan-sbom.cdx.json +6 -6
  33. package/dist/tool-manifest.json +6 -2
  34. package/dist/types.d.ts +91 -0
  35. package/dist/utils/formatSupport.d.ts +1 -0
  36. package/dist/utils/formatSupport.js +1 -0
  37. package/dist/utils/formatSupport.js.map +1 -1
  38. package/docs/GUIDE.md +23 -1
  39. package/docs/demos/projscan-4-1-demo.html +78 -94
  40. package/docs/demos/projscan-mission-control.tape +13 -0
  41. package/docs/demos/projscan-mission-proof.tape +25 -0
  42. package/docs/projscan-mission-control.gif +0 -0
  43. package/docs/projscan-mission-control.png +0 -0
  44. package/docs/projscan-mission-proof.gif +0 -0
  45. package/docs/projscan-proof-router.png +0 -0
  46. package/package.json +9 -2
  47. package/scripts/capture-vhs-demos.mjs +80 -0
@@ -422,34 +422,34 @@
422
422
  <section class="hero" aria-label="projscan Mission Control">
423
423
  <div class="intro">
424
424
  <div>
425
- <p class="eyebrow">Developer-life upgrade</p>
426
- <h1>Tell projscan what you are doing.</h1>
425
+ <p class="eyebrow">Mission Outcome Loop</p>
426
+ <h1>Resume from real proof.</h1>
427
427
  <p class="lead">
428
- projscan routes a developer goal to the next safe command, the
429
- MCP call an agent can run, and the proof that makes a handoff
430
- reviewable.
428
+ projscan routes a developer goal, saves the mission, reads the
429
+ proof state, and tells the next agent what changed, what remains,
430
+ and whether the work is ready for version review.
431
431
  </p>
432
432
  <div class="pills" aria-label="Product capabilities">
433
433
  <span class="pill">Mission Control</span>
434
434
  <span class="pill">Local-first</span>
435
435
  <span class="pill">MCP-ready</span>
436
- <span class="pill">Copyable handoff</span>
437
- <span class="pill">Review gate</span>
436
+ <span class="pill">Outcome resume</span>
437
+ <span class="pill">Proof report</span>
438
438
  </div>
439
439
  </div>
440
440
 
441
441
  <div class="metric-row" aria-label="Release candidate signals">
442
442
  <div class="metric">
443
- <strong>1 call</strong>
444
- <span>intent to workflow</span>
443
+ <strong>1 bundle</strong>
444
+ <span>mission plus proof</span>
445
445
  </div>
446
446
  <div class="metric">
447
- <strong>1 JSON</strong>
448
- <span>next MCP call</span>
447
+ <strong>45</strong>
448
+ <span>MCP tools</span>
449
449
  </div>
450
450
  <div class="metric">
451
- <strong>0</strong>
452
- <span>source uploads</span>
451
+ <strong>11 / 12</strong>
452
+ <span>AST adapters / languages</span>
453
453
  </div>
454
454
  </div>
455
455
  </div>
@@ -459,83 +459,68 @@
459
459
  <span class="dot red"></span>
460
460
  <span class="dot amber"></span>
461
461
  <span class="dot green"></span>
462
- <span class="terminal-title">projscan start shortcuts</span>
462
+ <span class="terminal-title">projscan start --mission</span>
463
463
  </div>
464
464
  <div class="terminal-body">
465
465
  <span class="line"
466
466
  ><span class="prompt">$</span>
467
467
  <span class="cmd"
468
- >projscan start --intent "what breaks if I rename the auth
469
- token loader"</span
468
+ >projscan start --mission .projscan/mission</span
470
469
  ></span
471
470
  >
472
471
  <span class="line dim">ProjScan Mission Control</span>
473
- <span class="line">Intent: what breaks if I rename the auth token loader</span>
474
- <span class="line">Status: <span class="warn">needs_attention</span></span>
472
+ <span class="line">Mission: .projscan/mission</span>
473
+ <span class="line">Status: <span class="success">passed</span></span>
475
474
  <span class="line"
476
- >Route:
477
- <span class="notice">Impact analysis via projscan_search</span></span
475
+ >Outcome:
476
+ <span class="notice">proof passed after 3 commands</span></span
478
477
  >
479
478
  <span class="line dim"
480
- >confidence: high; matched: breaks, rename</span
479
+ >read proof-logs/summary.json and status.jsonl</span
481
480
  >
482
481
 
483
482
  <div class="term-section">
484
- <span class="line term-heading">Run Cursor</span>
483
+ <span class="line term-heading">What Changed</span>
485
484
  <span class="line"
486
- >command: projscan search "auth token loader" --format
487
- json</span
485
+ >- Mission proof passed after 3 command(s).</span
488
486
  >
489
487
  <span class="line"
490
- >MCP call: projscan_search {"query":"auth token loader"}</span
488
+ >- 1 reviewer decision recorded.</span
491
489
  >
492
- <span class="line">unlocks: symbol input, file input</span>
490
+ <span class="line">- 0 failed gates remain.</span>
493
491
  </div>
494
492
 
495
493
  <div class="term-section">
496
- <span class="line term-heading">Copyable Shortcuts</span>
494
+ <span class="line term-heading">What Remains</span>
497
495
  <span class="line success"
498
- >projscan start --shortcuts --intent "..."</span
496
+ >Run ./review.sh and choose a reviewer reply.</span
499
497
  >
500
498
  <span class="line success"
501
- >projscan start --next-command --intent "..."</span
502
- >
503
- <span class="line success"
504
- >projscan start --next-tool-call --intent "..."</span
505
- >
506
- <span class="line success"
507
- >projscan start --task-card --intent "..."</span
499
+ >Version candidate: review_candidate</span
508
500
  >
501
+ </div>
502
+
503
+ <div class="term-section">
504
+ <span class="line term-heading">Outcome Commands</span>
509
505
  <span class="line success"
510
- >projscan start --mission-script --intent "..."</span
506
+ >projscan start --mission .projscan/mission</span
511
507
  >
512
508
  <span class="line success"
513
- >projscan start --review-gate --intent "..."</span
509
+ >projscan mission-proof --mission .projscan/mission</span
514
510
  >
515
511
  <span class="line success"
516
- >projscan start --save-mission .projscan/mission --intent
517
- "..."</span
512
+ >projscan mission-proof --baseline manual-runs.json</span
518
513
  >
519
- <span class="line dim">writes task card, review gate, mission.sh</span>
514
+ <span class="line dim">all source stays local</span>
520
515
  </div>
521
516
 
522
517
  <div class="term-section">
523
- <span class="line term-heading">Ready Proof</span>
518
+ <span class="line term-heading">Proof Evidence</span>
524
519
  <span class="line success"
525
- >- projscan preflight --mode before_edit --format json</span
520
+ >proof-logs/summary.json: passed</span
526
521
  >
527
522
  <span class="line success"
528
- >- projscan understand --view verify --format json</span
529
- >
530
- </div>
531
-
532
- <div class="term-section">
533
- <span class="line term-heading">Done When</span>
534
- <span class="line"
535
- >- Search returns an exact symbol or file path</span
536
- >
537
- <span class="line"
538
- >- Impact is reviewed before code edits</span
523
+ >proof-logs/status.jsonl: 3 rows</span
539
524
  >
540
525
  </div>
541
526
 
@@ -543,8 +528,8 @@
543
528
  <span class="line term-heading">Review Gate</span>
544
529
  <span class="line">capture: git status --short</span>
545
530
  <span class="line">capture: git diff --stat</span>
546
- <span class="line success">reply: Approved, one bounded slice</span>
547
- <span class="line warn">stop before another slice or release</span>
531
+ <span class="line success">reply: review version candidate</span>
532
+ <span class="line warn">do not publish until approved</span>
548
533
  </div>
549
534
  </div>
550
535
  </section>
@@ -553,7 +538,7 @@
553
538
  <section class="grid" aria-label="New developer workflows">
554
539
  <article class="card">
555
540
  <span class="label green">Goal routing</span>
556
- <h2>Ask in human language.</h2>
541
+ <h2>Ask in plain language.</h2>
557
542
  <p>
558
543
  Route privacy, merge readiness, refactor risk, local setup,
559
544
  ownership, dependency, release, and handoff questions to the right
@@ -561,21 +546,21 @@
561
546
  </p>
562
547
  </article>
563
548
  <article class="card">
564
- <span class="label blue">Repo context</span>
565
- <h2>Start from cited evidence.</h2>
549
+ <span class="label blue">Outcome resume</span>
550
+ <h2>Start from saved proof.</h2>
566
551
  <p>
567
- Repo maps now include read-first files, package contracts, runtime
568
- flows, public exports, proof tiers, setup commands, and unknowns
569
- that need human input.
552
+ <code>projscan start --mission</code> reads the bundle proof state
553
+ and gives the next agent a focused "what changed / what remains"
554
+ handoff.
570
555
  </p>
571
556
  </article>
572
557
  <article class="card">
573
- <span class="label amber">Review proof</span>
574
- <h2>Make PRs easier to approve.</h2>
558
+ <span class="label amber">Proof report</span>
559
+ <h2>Measure the saved work.</h2>
575
560
  <p>
576
- Mission Control carries the original intent into preflight,
577
- verification, session memory, evidence packs, and reviewer-ready
578
- next commands.
561
+ <code>projscan mission-proof</code> reports proof completion,
562
+ reviewer approvals, reruns, failed gates, time saved, and local
563
+ risk avoided.
579
564
  </p>
580
565
  </article>
581
566
  </section>
@@ -583,13 +568,13 @@
583
568
  <section class="proof" id="proof" aria-label="Proof and dependency view">
584
569
  <div class="proof-header">
585
570
  <div>
586
- <p class="eyebrow">Intent, graph, and handoff intelligence</p>
587
- <h2>Copy the next move.</h2>
571
+ <p class="eyebrow">Proof, review, and adoption evidence</p>
572
+ <h2>Close the loop.</h2>
588
573
  </div>
589
574
  <p>
590
- Developers and agents can list the shortcut menu, pull the next
591
- shell command, fetch the MCP call, or copy the checklist and
592
- Markdown runbook without reading the full report.
575
+ Developers and agents can resume from a saved mission bundle,
576
+ summarize pass/fail evidence, and compare local proof against a
577
+ manual baseline without sending source code anywhere.
593
578
  </p>
594
579
  </div>
595
580
 
@@ -599,33 +584,32 @@
599
584
  <span class="dot red"></span>
600
585
  <span class="dot amber"></span>
601
586
  <span class="dot green"></span>
602
- <span class="terminal-title">projscan start --next-tool-call</span>
587
+ <span class="terminal-title">projscan mission-proof</span>
603
588
  </div>
604
589
  <div class="terminal-body">
605
590
  <span class="line"
606
591
  ><span class="prompt">$</span>
607
592
  <span class="cmd"
608
- >projscan start --next-tool-call --intent "what breaks if I
609
- rename the auth token loader"</span
593
+ >projscan mission-proof --mission .projscan/mission --format
594
+ json</span
610
595
  ></span
611
596
  >
612
- <span class="line dim">Current cursor as MCP JSON</span>
597
+ <span class="line dim">Local proof summary</span>
613
598
  <span class="line">&nbsp;</span>
614
- <span class="line term-heading">{"tool":"projscan_search",</span>
599
+ <span class="line term-heading">{"passed":1,"failed":0,</span>
615
600
  <span class="line success"
616
- >&nbsp;"args":{"query":"auth token loader"}}</span
601
+ >&nbsp;"reruns":0,"reviewerApprovals":1}</span
617
602
  >
618
603
  <span class="line">&nbsp;</span>
619
- <span class="line term-heading">Checklist handoff</span>
604
+ <span class="line term-heading">Risk avoided</span>
620
605
  <span class="line success"
621
- >- [ready] run_current ready-1</span
606
+ >- proof gate passed before release</span
622
607
  >
623
608
  <span class="line success"
624
- >- [blocked] resolve_input input-1</span
609
+ >- version review is safe to request</span
625
610
  >
626
- <span class="line success">- [ready] run_proof proof-2</span>
627
611
  <span class="line notice"
628
- >Review gate: capture status, diff, then wait.</span
612
+ >Next: projscan start --mission .projscan/mission</span
629
613
  >
630
614
  </div>
631
615
  </section>
@@ -634,33 +618,33 @@
634
618
  <div class="signal">
635
619
  <span class="tag green">Verify</span>
636
620
  <span>
637
- <strong>Proof commands</strong>
638
- <code>--proof-commands</code> returns the remaining
639
- verification commands, one per line.
621
+ <strong>Outcome resume</strong>
622
+ <code>--mission</code> reads <code>summary.json</code>, status
623
+ rows, and reviewer decisions.
640
624
  </span>
641
625
  </div>
642
626
  <div class="signal">
643
627
  <span class="tag blue">MCP</span>
644
628
  <span>
645
- <strong>MCP call shortcut</strong>
646
- <code>--next-tool-call</code> returns the cursor tool and args
647
- as compact JSON.
629
+ <strong>MCP start input</strong>
630
+ <code>mission_dir</code> carries the same proof outcome to
631
+ agent clients.
648
632
  </span>
649
633
  </div>
650
634
  <div class="signal">
651
635
  <span class="tag amber">List</span>
652
636
  <span>
653
- <strong>Checklist shortcut</strong>
654
- <code>--checklist</code> returns current, blocked, follow-up,
655
- proof, and done rows.
637
+ <strong>Proof summary</strong>
638
+ <code>mission-proof</code> reports completion, reruns, failed
639
+ gates, and approvals.
656
640
  </span>
657
641
  </div>
658
642
  <div class="signal">
659
643
  <span class="tag red">Gate</span>
660
644
  <span>
661
- <strong>Review gate</strong>
662
- <code>--review-gate</code> returns the stop checklist before
663
- another slice, release, publish, or deploy.
645
+ <strong>Version review</strong>
646
+ Outcome data says whether to request review or keep fixing
647
+ failed proof.
664
648
  </span>
665
649
  </div>
666
650
  </div>
@@ -0,0 +1,13 @@
1
+ Output docs/projscan-mission-control.gif
2
+
3
+ Set Shell "bash"
4
+ Set FontSize 16
5
+ Set Width 1200
6
+ Set Height 720
7
+ Set Theme "Catppuccin Mocha"
8
+ Set TypingSpeed 35ms
9
+ Set Padding 16
10
+
11
+ Type "projscan start --shortcuts --intent 'is it safe to commit this change?'"
12
+ Enter
13
+ Sleep 12s
@@ -0,0 +1,25 @@
1
+ Output docs/projscan-mission-proof.gif
2
+
3
+ Set Shell "bash"
4
+ Set FontSize 16
5
+ Set Width 1200
6
+ Set Height 720
7
+ Set Theme "Catppuccin Mocha"
8
+ Set TypingSpeed 35ms
9
+ Set Padding 16
10
+
11
+ Type "rm -rf .projscan/vhs-mission"
12
+ Enter
13
+ Sleep 500ms
14
+
15
+ Type "projscan start --save-mission .projscan/vhs-mission --intent 'is it safe to commit this change?'"
16
+ Enter
17
+ Sleep 10s
18
+
19
+ Type "projscan mission-proof --mission .projscan/vhs-mission --format markdown | sed -n '1,38p'"
20
+ Enter
21
+ Sleep 8s
22
+
23
+ Type "projscan start --mission .projscan/vhs-mission --handoff-prompt | sed -n '1,8p'"
24
+ Enter
25
+ Sleep 16s
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "projscan",
3
3
  "mcpName": "io.github.abhiyoheswaran1/projscan",
4
- "version": "4.2.0",
5
- "description": "Agent-first code intelligence. MCP server (2025-03-26) with AST parsing for JavaScript, TypeScript, Python, Go, Java, Ruby, Rust, PHP, C#, Kotlin, Swift, and C++; repo understanding maps (projscan_understand), stable v3 semantic graph (projscan_semantic_graph), dataflow risk engine with bridge-helper detection (projscan_dataflow), code graph, file + per-function AST cyclomatic complexity, per-function fan-in + fan-out, coupling + cycle detection, structural PR diff with HTML reporter, coverage report with HTML reporter, intent-grounded one-call PR review (projscan_review with optional `intent` arg, new taint flows, contract changes, and newDataflowRisks) and long-running PR-watch mode with structured per-bucket deltas (projscan_review_watch), first-60-seconds workflow orientation (projscan_start), agent workplans (projscan_workplan), bug-hunt queues (projscan_bug_hunt), product-line planning (projscan_release_train), evidence packs (projscan_evidence_pack), regression planning (projscan_regression_plan), agent briefs (projscan_agent_brief), quality scorecards (projscan_quality_scorecard), and preflight with supply-chain IOC evidence, rule-driven fix suggestions + mechanical apply layer with rollback (projscan_apply_fix, projscan_fix_suggest, projscan_explain_issue), source-to-sink taint analysis (projscan_taint) with truncation reporting, transitive blast-radius analysis with cross-repo mode (projscan_impact for files and symbols), cross-repo workspace registration + intelligence (projscan_workspace_graph), per-function semantic search chunks (sub-file embeddings), per-rule confidence + severity drift + cost-summary analytics with live streaming (projscan_cost_summary), stable local analyzer + reporter plugin API (projscan_plugin, CLI --reporter, opt-in via PROJSCAN_PLUGINS_PREVIEW=1), monorepo workspace awareness with cross-package import policy + per-package dependencies / outdated / audit, BM25 + optional semantic search, cursor pagination, progress notifications, context-budgeted output, and a stable-surface CI guard. CLI on the side.",
4
+ "version": "4.3.1",
5
+ "description": "Agent-first code intelligence. MCP server (2025-03-26) with 11 AST adapters covering 12 named languages: JavaScript, TypeScript, Python, Go, Java, Ruby, Rust, PHP, C#, Kotlin, Swift, and C++; repo understanding maps (projscan_understand), stable v3 semantic graph (projscan_semantic_graph), dataflow risk engine with bridge-helper detection (projscan_dataflow), code graph, file + per-function AST cyclomatic complexity, per-function fan-in + fan-out, coupling + cycle detection, structural PR diff with HTML reporter, coverage report with HTML reporter, intent-grounded one-call PR review (projscan_review with optional `intent` arg, new taint flows, contract changes, and newDataflowRisks) and long-running PR-watch mode with structured per-bucket deltas (projscan_review_watch), first-60-seconds workflow orientation (projscan_start), agent workplans (projscan_workplan), bug-hunt queues (projscan_bug_hunt), product-line planning (projscan_release_train), evidence packs (projscan_evidence_pack), regression planning (projscan_regression_plan), agent briefs (projscan_agent_brief), quality scorecards (projscan_quality_scorecard), and preflight with supply-chain IOC evidence, rule-driven fix suggestions + mechanical apply layer with rollback (projscan_apply_fix, projscan_fix_suggest, projscan_explain_issue), source-to-sink taint analysis (projscan_taint) with truncation reporting, transitive blast-radius analysis with cross-repo mode (projscan_impact for files and symbols), cross-repo workspace registration + intelligence (projscan_workspace_graph), per-function semantic search chunks (sub-file embeddings), per-rule confidence + severity drift + cost-summary analytics with live streaming (projscan_cost_summary), stable local analyzer + reporter plugin API (projscan_plugin, CLI --reporter, opt-in via PROJSCAN_PLUGINS_PREVIEW=1), monorepo workspace awareness with cross-package import policy + per-package dependencies / outdated / audit, BM25 + optional semantic search, cursor pagination, progress notifications, context-budgeted output, and a stable-surface CI guard. CLI on the side.",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
@@ -18,11 +18,16 @@
18
18
  "docs/PLUGIN-GALLERY.md",
19
19
  "docs/ROADMAP.md",
20
20
  "docs/demos/projscan-4-1-demo.html",
21
+ "docs/demos/projscan-mission-control.tape",
22
+ "docs/demos/projscan-mission-proof.tape",
21
23
  "docs/plugin.schema.json",
22
24
  "docs/projscan-mission-control.png",
25
+ "docs/projscan-mission-control.gif",
23
26
  "docs/projscan-proof-router.png",
27
+ "docs/projscan-mission-proof.gif",
24
28
  "docs/examples/plugins",
25
29
  "scripts/capture-readme-assets.mjs",
30
+ "scripts/capture-vhs-demos.mjs",
26
31
  "public/brand/baseframe-labs",
27
32
  "public/.well-known/security.txt",
28
33
  "CONTRIBUTING.md",
@@ -44,6 +49,8 @@
44
49
  "bench": "node scripts/bench.mjs",
45
50
  "bench:references": "node scripts/bench-references.mjs",
46
51
  "docs:screenshots": "node scripts/capture-readme-assets.mjs",
52
+ "docs:demos": "npm run build && node scripts/capture-vhs-demos.mjs",
53
+ "docs:assets": "npm run docs:screenshots && npm run docs:demos",
47
54
  "smoke:packed-install": "node scripts/packed-install-smoke.mjs",
48
55
  "check:stability": "node scripts/check-stability.mjs",
49
56
  "release:check": "node scripts/release-check.mjs",
@@ -0,0 +1,80 @@
1
+ import { chmodSync, existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
7
+ const cliPath = path.join(repoRoot, 'dist', 'cli', 'index.js');
8
+ const vhsBinDir = path.join(repoRoot, '.projscan', 'vhs-bin');
9
+ const vhsShimPath = path.join(vhsBinDir, 'projscan');
10
+ const vhsMissionDir = path.join(repoRoot, '.projscan', 'vhs-mission');
11
+
12
+ const tapes = [
13
+ {
14
+ name: 'Mission Control terminal demo',
15
+ tape: path.join(repoRoot, 'docs', 'demos', 'projscan-mission-control.tape'),
16
+ output: path.join(repoRoot, 'docs', 'projscan-mission-control.gif'),
17
+ },
18
+ {
19
+ name: 'Mission Proof terminal demo',
20
+ tape: path.join(repoRoot, 'docs', 'demos', 'projscan-mission-proof.tape'),
21
+ output: path.join(repoRoot, 'docs', 'projscan-mission-proof.gif'),
22
+ },
23
+ ];
24
+
25
+ const quoteForShell = (value) => `'${value.replaceAll("'", "'\"'\"'")}'`;
26
+
27
+ const fail = (message, status = 1) => {
28
+ console.error(message);
29
+ process.exit(status);
30
+ };
31
+
32
+ if (!existsSync(cliPath)) {
33
+ fail('Missing dist/cli/index.js. Run `npm run build` before capturing VHS demos.');
34
+ }
35
+
36
+ const vhsVersion = spawnSync('vhs', ['--version'], { cwd: repoRoot, encoding: 'utf8' });
37
+ if (vhsVersion.status !== 0) {
38
+ fail('Missing `vhs`. Install it with `brew install vhs`, then run `npm run docs:demos`.');
39
+ }
40
+
41
+ for (const tape of tapes) {
42
+ if (!existsSync(tape.tape)) {
43
+ fail(`Missing VHS tape: ${path.relative(repoRoot, tape.tape)}`);
44
+ }
45
+ }
46
+
47
+ mkdirSync(vhsBinDir, { recursive: true });
48
+ writeFileSync(vhsShimPath, `#!/usr/bin/env sh\nexec node ${quoteForShell(cliPath)} "$@"\n`);
49
+ chmodSync(vhsShimPath, 0o755);
50
+
51
+ const env = {
52
+ ...process.env,
53
+ PATH: `${vhsBinDir}${path.delimiter}${process.env.PATH ?? ''}`,
54
+ };
55
+
56
+ try {
57
+ rmSync(vhsMissionDir, { recursive: true, force: true });
58
+
59
+ for (const tape of tapes) {
60
+ console.log(`Capturing ${tape.name} -> ${path.relative(repoRoot, tape.output)}`);
61
+ const result = spawnSync('vhs', [path.relative(repoRoot, tape.tape)], {
62
+ cwd: repoRoot,
63
+ env,
64
+ stdio: 'inherit',
65
+ });
66
+
67
+ if (result.status !== 0) {
68
+ fail(`VHS capture failed for ${path.relative(repoRoot, tape.tape)}`, result.status ?? 1);
69
+ }
70
+
71
+ if (!existsSync(tape.output)) {
72
+ fail(`VHS did not write expected output: ${path.relative(repoRoot, tape.output)}`);
73
+ }
74
+ }
75
+ } finally {
76
+ rmSync(vhsMissionDir, { recursive: true, force: true });
77
+ rmSync(vhsBinDir, { recursive: true, force: true });
78
+ }
79
+
80
+ console.log('VHS demos captured.');