@justjoshn/pi-pstack 0.0.0-stage → 0.15.36-pi.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.
- package/LICENSE +21 -0
- package/README.md +95 -2
- package/agents/comment-sicko.md +32 -0
- package/agents/poteto-agent.md +9 -0
- package/automations/benny/FOR_AGENTS.md +82 -0
- package/automations/benny/README.md +20 -0
- package/automations/benny/skills/reproduce-and-fix-issues/SKILL.md +310 -0
- package/automations/benny/skills/reproduce-and-fix-issues/references/control-adapter.md +169 -0
- package/automations/benny/skills/reproduce-and-fix-issues/references/feature-map.example.md +205 -0
- package/automations/benny/skills/reproduce-and-fix-issues/references/verify-existing-fix.md +93 -0
- package/automations/benny/skills/setup-benny/SKILL.md +253 -0
- package/automations/benny/skills/triage-issue-reports/SKILL.md +240 -0
- package/automations/benny/skills/triage-issue-reports/references/routing.example.md +61 -0
- package/automations/benny/templates/configuration.example.yaml +113 -0
- package/automations/benny/templates/reproduce-automation-prompt.md +49 -0
- package/automations/benny/templates/triage-automation-prompt.md +53 -0
- package/docs/guide/01-setup.md +67 -0
- package/docs/guide/02-poteto-mode.md +139 -0
- package/docs/guide/03-understand.md +79 -0
- package/docs/guide/04-design.md +133 -0
- package/docs/guide/05-build-and-clean.md +83 -0
- package/docs/guide/06-verify-and-ship.md +162 -0
- package/docs/guide/07-overnight.md +122 -0
- package/docs/guide/08-principles.md +72 -0
- package/docs/guide/09-make-it-yours.md +100 -0
- package/docs/guide/10-recipes-and-pitfalls.md +182 -0
- package/docs/guide/README.md +38 -0
- package/docs/guide/images/design.jpg +0 -0
- package/docs/guide/images/overnight.jpg +0 -0
- package/docs/guide/images/recipes.jpg +0 -0
- package/docs/guide/images/router.jpg +0 -0
- package/docs/guide/images/understanding.jpg +0 -0
- package/docs/guide/images/verification.jpg +0 -0
- package/docs/native-gaps.md +67 -0
- package/licenses/cursor-team-kit.LICENSE +21 -0
- package/licenses/pstack.LICENSE +21 -0
- package/package.json +51 -4
- package/scripts/live-e2e.mjs +257 -0
- package/skills/architect/SKILL.md +79 -0
- package/skills/architect/references/design-red-flags.md +57 -0
- package/skills/architect/references/rationale-template.md +35 -0
- package/skills/architect/references/runner-prompt.md +20 -0
- package/skills/arena/SKILL.md +48 -0
- package/skills/automate-me/SKILL.md +104 -0
- package/skills/benchmark-checklist/SKILL.md +39 -0
- package/skills/blast-radius/SKILL.md +50 -0
- package/skills/bro/SKILL.md +7 -0
- package/skills/control-cli/SKILL.md +109 -0
- package/skills/control-ui/SKILL.md +109 -0
- package/skills/correct/SKILL.md +32 -0
- package/skills/create-formal-verification-skill/SKILL.md +43 -0
- package/skills/create-skill/SKILL.md +62 -0
- package/skills/create-verification-skill/SKILL.md +44 -0
- package/skills/create-verification-skill/references/feature-map-example/README.md +47 -0
- package/skills/create-verification-skill/references/feature-map-example/create-note.md +39 -0
- package/skills/create-verification-skill/references/feature-map-example/search.md +45 -0
- package/skills/deslop/SKILL.md +22 -0
- package/skills/figure-it-out/SKILL.md +52 -0
- package/skills/how/SKILL.md +36 -0
- package/skills/how/references/explainer-prompt.md +55 -0
- package/skills/how/references/explorer-prompt.md +35 -0
- package/skills/in-your-own-words/SKILL.md +7 -0
- package/skills/interrogate/SKILL.md +31 -0
- package/skills/ketchup/SKILL.md +43 -0
- package/skills/ketchup-video/SKILL.md +29 -0
- package/skills/maintain-formal-verification-skill/SKILL.md +58 -0
- package/skills/maintain-verification-skill/SKILL.md +39 -0
- package/skills/make-bot-ui/SKILL.md +104 -0
- package/skills/no-comments/SKILL.md +24 -0
- package/skills/poteto-help/SKILL.md +161 -0
- package/skills/poteto-help/references/prompting.md +51 -0
- package/skills/poteto-help/references/recipes.md +51 -0
- package/skills/poteto-mode/SKILL.md +151 -0
- package/skills/poteto-mode/playbooks/authoring-a-skill.md +12 -0
- package/skills/poteto-mode/playbooks/autonomous-run.md +13 -0
- package/skills/poteto-mode/playbooks/autopilot-full.md +25 -0
- package/skills/poteto-mode/playbooks/autopilot-stack.md +8 -0
- package/skills/poteto-mode/playbooks/babysit.md +23 -0
- package/skills/poteto-mode/playbooks/bug-fix.md +15 -0
- package/skills/poteto-mode/playbooks/eval.md +25 -0
- package/skills/poteto-mode/playbooks/feature.md +21 -0
- package/skills/poteto-mode/playbooks/hillclimb.md +21 -0
- package/skills/poteto-mode/playbooks/investigation.md +14 -0
- package/skills/poteto-mode/playbooks/multi-phase-plan.md +155 -0
- package/skills/poteto-mode/playbooks/opening-a-pr.md +34 -0
- package/skills/poteto-mode/playbooks/orchestrate.md +25 -0
- package/skills/poteto-mode/playbooks/pause-safely.md +10 -0
- package/skills/poteto-mode/playbooks/perf-issue.md +25 -0
- package/skills/poteto-mode/playbooks/prototype.md +14 -0
- package/skills/poteto-mode/playbooks/refactoring.md +16 -0
- package/skills/poteto-mode/playbooks/runtime-forensics.md +11 -0
- package/skills/poteto-mode/playbooks/session-pickup.md +11 -0
- package/skills/poteto-mode/playbooks/shipping.md +12 -0
- package/skills/poteto-mode/playbooks/trace-forensics.md +14 -0
- package/skills/poteto-mode/playbooks/visual-parity.md +11 -0
- package/skills/poteto-mode/playbooks/worktree-cleanup.md +12 -0
- package/skills/poteto-mode/references/bugbot-triage.md +142 -0
- package/skills/poteto-mode/scripts/bootstrap.ts +62 -0
- package/skills/poteto-mode/scripts/bun.lock +67 -0
- package/skills/poteto-mode/scripts/check-plan.mjs +205 -0
- package/skills/poteto-mode/scripts/check-plan.test.ts +110 -0
- package/skills/poteto-mode/scripts/package.json +16 -0
- package/skills/poteto-mode/scripts/watch-pr/cli.test.ts +224 -0
- package/skills/poteto-mode/scripts/watch-pr/cli.ts +223 -0
- package/skills/poteto-mode/scripts/watch-pr/fakes.test-helper.ts +129 -0
- package/skills/poteto-mode/scripts/watch-pr/github.test.ts +498 -0
- package/skills/poteto-mode/scripts/watch-pr/github.ts +726 -0
- package/skills/poteto-mode/scripts/watch-pr/policy.test.ts +482 -0
- package/skills/poteto-mode/scripts/watch-pr/policy.ts +842 -0
- package/skills/poteto-mode/scripts/watch-pr/render.ts +169 -0
- package/skills/poteto-mode/scripts/watch-pr/tsconfig.json +13 -0
- package/skills/poteto-mode/scripts/watch-pr/types.compile.ts +93 -0
- package/skills/poteto-mode/scripts/watch-pr/types.ts +414 -0
- package/skills/poteto-mode/scripts/watch-pr/watch-pr +6 -0
- package/skills/poteto-mode/scripts/worktree-audit.sh +156 -0
- package/skills/principle-attack-the-premise/SKILL.md +21 -0
- package/skills/principle-boundary-discipline/SKILL.md +34 -0
- package/skills/principle-build-the-lever/SKILL.md +23 -0
- package/skills/principle-encode-lessons-in-structure/SKILL.md +11 -0
- package/skills/principle-exhaust-the-design-space/SKILL.md +9 -0
- package/skills/principle-experience-first/SKILL.md +19 -0
- package/skills/principle-explain-the-number/SKILL.md +18 -0
- package/skills/principle-fix-root-causes/SKILL.md +21 -0
- package/skills/principle-foundational-thinking/SKILL.md +21 -0
- package/skills/principle-guard-the-context-window/SKILL.md +12 -0
- package/skills/principle-laziness-protocol/SKILL.md +10 -0
- package/skills/principle-make-operations-idempotent/SKILL.md +22 -0
- package/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +14 -0
- package/skills/principle-minimize-reader-load/SKILL.md +18 -0
- package/skills/principle-model-the-domain/SKILL.md +24 -0
- package/skills/principle-never-block-on-the-human/SKILL.md +15 -0
- package/skills/principle-outcome-oriented-execution/SKILL.md +21 -0
- package/skills/principle-prove-it-works/SKILL.md +22 -0
- package/skills/principle-redesign-from-first-principles/SKILL.md +16 -0
- package/skills/principle-separate-before-serializing-shared-state/SKILL.md +16 -0
- package/skills/principle-sequence-verifiable-units/SKILL.md +17 -0
- package/skills/principle-subtract-before-you-add/SKILL.md +18 -0
- package/skills/principle-test-behavior-not-implementation/SKILL.md +20 -0
- package/skills/principle-type-system-discipline/SKILL.md +13 -0
- package/skills/recall/SKILL.md +35 -0
- package/skills/reflect/SKILL.md +69 -0
- package/skills/reflect/references/divergent-reviewer.md +38 -0
- package/skills/reflect/references/judgment-reviewer.md +37 -0
- package/skills/reflect/references/synthesizer.md +56 -0
- package/skills/reflect/references/tooling-reviewer.md +48 -0
- package/skills/setup-pstack/SKILL.md +74 -0
- package/skills/sharpen/SKILL.md +16 -0
- package/skills/show-me-your-work/SKILL.md +82 -0
- package/skills/show-me-your-work/references/decision-log-template.tsv +1 -0
- package/skills/show-me-your-work/scripts/log.sh +42 -0
- package/skills/statechart/SKILL.md +82 -0
- package/skills/statechart/references/render.md +93 -0
- package/skills/statechart/scripts/backends/cfg.ts +33 -0
- package/skills/statechart/scripts/backends/mermaid.ts +201 -0
- package/skills/statechart/scripts/backends/plain-text.ts +72 -0
- package/skills/statechart/scripts/backends/report.ts +177 -0
- package/skills/statechart/scripts/frontends/config.ts +276 -0
- package/skills/statechart/scripts/frontends/dot.ts +74 -0
- package/skills/statechart/scripts/frontends/marks.ts +93 -0
- package/skills/statechart/scripts/frontends/messages.ts +52 -0
- package/skills/statechart/scripts/frontends/spec.ts +114 -0
- package/skills/statechart/scripts/frontends/trace.ts +24 -0
- package/skills/statechart/scripts/frontends/value.ts +166 -0
- package/skills/statechart/scripts/frontends/verdict.ts +40 -0
- package/skills/statechart/scripts/frontends/view.ts +119 -0
- package/skills/statechart/scripts/ir.ts +547 -0
- package/skills/statechart/scripts/package.json +11 -0
- package/skills/statechart/scripts/passes/annotate.ts +128 -0
- package/skills/statechart/scripts/passes/label.ts +269 -0
- package/skills/statechart/scripts/passes/lift.ts +85 -0
- package/skills/statechart/scripts/passes/loop-check.ts +68 -0
- package/skills/statechart/scripts/passes/paths.ts +60 -0
- package/skills/statechart/scripts/passes/reach.ts +135 -0
- package/skills/statechart/scripts/passes/region-check.ts +97 -0
- package/skills/statechart/scripts/passes/regions.ts +295 -0
- package/skills/statechart/scripts/pipeline.ts +76 -0
- package/skills/statechart/scripts/run.ts +481 -0
- package/skills/statechart/scripts/statechart +29 -0
- package/skills/statechart/scripts/statechart.ts +164 -0
- package/skills/statechart/scripts/test/ConfigOracle.java +63 -0
- package/skills/statechart/scripts/test/chart.test.ts +1210 -0
- package/skills/statechart/scripts/test/charts/Braces.mmd +7 -0
- package/skills/statechart/scripts/test/charts/Escapes.mmd +11 -0
- package/skills/statechart/scripts/test/charts/Phases.mmd +29 -0
- package/skills/statechart/scripts/test/cli.test.ts +1314 -0
- package/skills/statechart/scripts/test/config-fuzz.test.ts +263 -0
- package/skills/statechart/scripts/test/corpus/Counter/ChartRun.out +57 -0
- package/skills/statechart/scripts/test/corpus/Counter/Counter.cfg +3 -0
- package/skills/statechart/scripts/test/corpus/Counter/Counter.tla +10 -0
- package/skills/statechart/scripts/test/corpus/Counter/chart.mmd +9 -0
- package/skills/statechart/scripts/test/corpus/Counter/graph.dot +16 -0
- package/skills/statechart/scripts/test/corpus/Counter/report.md +7 -0
- package/skills/statechart/scripts/test/corpus/Counter/sany.out +2 -0
- package/skills/statechart/scripts/test/corpus/Counter/verdict-1.out +49 -0
- package/skills/statechart/scripts/test/corpus/CounterAction/ChartRun.out +60 -0
- package/skills/statechart/scripts/test/corpus/CounterAction/CounterAction.cfg +4 -0
- package/skills/statechart/scripts/test/corpus/CounterAction/CounterAction.tla +22 -0
- package/skills/statechart/scripts/test/corpus/CounterAction/chart.mmd +26 -0
- package/skills/statechart/scripts/test/corpus/CounterAction/graph.dot +19 -0
- package/skills/statechart/scripts/test/corpus/CounterAction/report.md +20 -0
- package/skills/statechart/scripts/test/corpus/CounterAction/sany.out +2 -0
- package/skills/statechart/scripts/test/corpus/CounterAction/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterAction/verdict-1.out +56 -0
- package/skills/statechart/scripts/test/corpus/CounterAction/verdict-2.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterAction/verdict-2.out +82 -0
- package/skills/statechart/scripts/test/corpus/CounterAction/verdict-3.out +57 -0
- package/skills/statechart/scripts/test/corpus/CounterDeadlock/ChartRun.out +59 -0
- package/skills/statechart/scripts/test/corpus/CounterDeadlock/CounterDeadlock.cfg +3 -0
- package/skills/statechart/scripts/test/corpus/CounterDeadlock/CounterDeadlock.tla +11 -0
- package/skills/statechart/scripts/test/corpus/CounterDeadlock/chart.mmd +22 -0
- package/skills/statechart/scripts/test/corpus/CounterDeadlock/graph.dot +16 -0
- package/skills/statechart/scripts/test/corpus/CounterDeadlock/report.md +17 -0
- package/skills/statechart/scripts/test/corpus/CounterDeadlock/sany.out +2 -0
- package/skills/statechart/scripts/test/corpus/CounterDeadlock/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterDeadlock/verdict-1.out +56 -0
- package/skills/statechart/scripts/test/corpus/CounterDeadlock/verdict-2.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterDeadlock/verdict-2.out +77 -0
- package/skills/statechart/scripts/test/corpus/CounterFan/ChartRun.out +69 -0
- package/skills/statechart/scripts/test/corpus/CounterFan/CounterFan.cfg +2 -0
- package/skills/statechart/scripts/test/corpus/CounterFan/CounterFan.tla +11 -0
- package/skills/statechart/scripts/test/corpus/CounterFan/chart.mmd +85 -0
- package/skills/statechart/scripts/test/corpus/CounterFan/graph.dot +70 -0
- package/skills/statechart/scripts/test/corpus/CounterFan/report.md +12 -0
- package/skills/statechart/scripts/test/corpus/CounterFan/sany.out +2 -0
- package/skills/statechart/scripts/test/corpus/CounterFan/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterFan/verdict-1.out +67 -0
- package/skills/statechart/scripts/test/corpus/CounterFan/verdict-2.out +57 -0
- package/skills/statechart/scripts/test/corpus/CounterGuard/ChartRun.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterGuard/ChartRun.out +85 -0
- package/skills/statechart/scripts/test/corpus/CounterGuard/CounterGuard.cfg +6 -0
- package/skills/statechart/scripts/test/corpus/CounterGuard/CounterGuard.tla +13 -0
- package/skills/statechart/scripts/test/corpus/CounterGuard/chart.mmd +5 -0
- package/skills/statechart/scripts/test/corpus/CounterGuard/graph.dot +10 -0
- package/skills/statechart/scripts/test/corpus/CounterGuard/report.md +31 -0
- package/skills/statechart/scripts/test/corpus/CounterGuard/sany.out +2 -0
- package/skills/statechart/scripts/test/corpus/CounterGuard/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterGuard/verdict-1.out +64 -0
- package/skills/statechart/scripts/test/corpus/CounterGuard/verdict-2.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterGuard/verdict-2.out +77 -0
- package/skills/statechart/scripts/test/corpus/CounterInitPast/ChartRun.out +55 -0
- package/skills/statechart/scripts/test/corpus/CounterInitPast/CounterInitPast.cfg +3 -0
- package/skills/statechart/scripts/test/corpus/CounterInitPast/CounterInitPast.tla +10 -0
- package/skills/statechart/scripts/test/corpus/CounterInitPast/chart.mmd +12 -0
- package/skills/statechart/scripts/test/corpus/CounterInitPast/graph.dot +6 -0
- package/skills/statechart/scripts/test/corpus/CounterInitPast/report.md +12 -0
- package/skills/statechart/scripts/test/corpus/CounterInitPast/sany.out +2 -0
- package/skills/statechart/scripts/test/corpus/CounterInitPast/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterInitPast/verdict-1.out +33 -0
- package/skills/statechart/scripts/test/corpus/CounterInitPast/verdict-2.out +54 -0
- package/skills/statechart/scripts/test/corpus/CounterInitPastHolds/ChartRun.out +54 -0
- package/skills/statechart/scripts/test/corpus/CounterInitPastHolds/CounterInitPastHolds.cfg +3 -0
- package/skills/statechart/scripts/test/corpus/CounterInitPastHolds/CounterInitPastHolds.tla +10 -0
- package/skills/statechart/scripts/test/corpus/CounterInitPastHolds/chart.mmd +1 -0
- package/skills/statechart/scripts/test/corpus/CounterInitPastHolds/graph.dot +6 -0
- package/skills/statechart/scripts/test/corpus/CounterInitPastHolds/report.md +7 -0
- package/skills/statechart/scripts/test/corpus/CounterInitPastHolds/sany.out +2 -0
- package/skills/statechart/scripts/test/corpus/CounterInitPastHolds/verdict-1.out +46 -0
- package/skills/statechart/scripts/test/corpus/CounterInstance/ChartRun.out +59 -0
- package/skills/statechart/scripts/test/corpus/CounterInstance/CounterInstance.cfg +2 -0
- package/skills/statechart/scripts/test/corpus/CounterInstance/CounterInstance.tla +10 -0
- package/skills/statechart/scripts/test/corpus/CounterInstance/CounterSteps.tla +6 -0
- package/skills/statechart/scripts/test/corpus/CounterInstance/chart.mmd +17 -0
- package/skills/statechart/scripts/test/corpus/CounterInstance/graph.dot +15 -0
- package/skills/statechart/scripts/test/corpus/CounterInstance/report.md +14 -0
- package/skills/statechart/scripts/test/corpus/CounterInstance/sany.out +3 -0
- package/skills/statechart/scripts/test/corpus/CounterInstance/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterInstance/verdict-1.out +78 -0
- package/skills/statechart/scripts/test/corpus/CounterJump/ChartRun.out +59 -0
- package/skills/statechart/scripts/test/corpus/CounterJump/CounterJump.cfg +3 -0
- package/skills/statechart/scripts/test/corpus/CounterJump/CounterJump.tla +19 -0
- package/skills/statechart/scripts/test/corpus/CounterJump/chart.mmd +26 -0
- package/skills/statechart/scripts/test/corpus/CounterJump/graph.dot +22 -0
- package/skills/statechart/scripts/test/corpus/CounterJump/report.md +12 -0
- package/skills/statechart/scripts/test/corpus/CounterJump/sany.out +2 -0
- package/skills/statechart/scripts/test/corpus/CounterJump/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterJump/verdict-1.out +58 -0
- package/skills/statechart/scripts/test/corpus/CounterJump/verdict-2.out +57 -0
- package/skills/statechart/scripts/test/corpus/CounterLoop/ChartRun.out +57 -0
- package/skills/statechart/scripts/test/corpus/CounterLoop/CounterLoop.cfg +2 -0
- package/skills/statechart/scripts/test/corpus/CounterLoop/CounterLoop.tla +11 -0
- package/skills/statechart/scripts/test/corpus/CounterLoop/chart.mmd +17 -0
- package/skills/statechart/scripts/test/corpus/CounterLoop/graph.dot +15 -0
- package/skills/statechart/scripts/test/corpus/CounterLoop/report.md +14 -0
- package/skills/statechart/scripts/test/corpus/CounterLoop/sany.out +2 -0
- package/skills/statechart/scripts/test/corpus/CounterLoop/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterLoop/verdict-1.out +76 -0
- package/skills/statechart/scripts/test/corpus/CounterOrder/ChartRun.out +59 -0
- package/skills/statechart/scripts/test/corpus/CounterOrder/CounterOrder.cfg +3 -0
- package/skills/statechart/scripts/test/corpus/CounterOrder/CounterOrder.tla +11 -0
- package/skills/statechart/scripts/test/corpus/CounterOrder/chart.mmd +19 -0
- package/skills/statechart/scripts/test/corpus/CounterOrder/graph.dot +10 -0
- package/skills/statechart/scripts/test/corpus/CounterOrder/report.md +15 -0
- package/skills/statechart/scripts/test/corpus/CounterOrder/sany.out +2 -0
- package/skills/statechart/scripts/test/corpus/CounterOrder/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterOrder/verdict-1.out +56 -0
- package/skills/statechart/scripts/test/corpus/CounterOrder/verdict-2.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterOrder/verdict-2.out +67 -0
- package/skills/statechart/scripts/test/corpus/CounterRules/ChartRun.out +62 -0
- package/skills/statechart/scripts/test/corpus/CounterRules/CounterRules.cfg +6 -0
- package/skills/statechart/scripts/test/corpus/CounterRules/CounterRules.tla +24 -0
- package/skills/statechart/scripts/test/corpus/CounterRules/Moves.tla +5 -0
- package/skills/statechart/scripts/test/corpus/CounterRules/chart.mmd +22 -0
- package/skills/statechart/scripts/test/corpus/CounterRules/graph.dot +10 -0
- package/skills/statechart/scripts/test/corpus/CounterRules/report.md +23 -0
- package/skills/statechart/scripts/test/corpus/CounterRules/sany.out +3 -0
- package/skills/statechart/scripts/test/corpus/CounterRules/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterRules/verdict-1.out +58 -0
- package/skills/statechart/scripts/test/corpus/CounterRules/verdict-2.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterRules/verdict-2.out +66 -0
- package/skills/statechart/scripts/test/corpus/CounterRules/verdict-3.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterRules/verdict-3.out +74 -0
- package/skills/statechart/scripts/test/corpus/CounterRules/verdict-4.out +59 -0
- package/skills/statechart/scripts/test/corpus/CounterStart/ChartRun.out +58 -0
- package/skills/statechart/scripts/test/corpus/CounterStart/CounterStart.cfg +3 -0
- package/skills/statechart/scripts/test/corpus/CounterStart/CounterStart.tla +15 -0
- package/skills/statechart/scripts/test/corpus/CounterStart/chart.mmd +18 -0
- package/skills/statechart/scripts/test/corpus/CounterStart/graph.dot +13 -0
- package/skills/statechart/scripts/test/corpus/CounterStart/report.md +11 -0
- package/skills/statechart/scripts/test/corpus/CounterStart/sany.out +2 -0
- package/skills/statechart/scripts/test/corpus/CounterStart/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterStart/verdict-1.out +33 -0
- package/skills/statechart/scripts/test/corpus/CounterStart/verdict-2.out +57 -0
- package/skills/statechart/scripts/test/corpus/CounterStop/ChartRun.out +57 -0
- package/skills/statechart/scripts/test/corpus/CounterStop/CounterStop.cfg +3 -0
- package/skills/statechart/scripts/test/corpus/CounterStop/CounterStop.tla +15 -0
- package/skills/statechart/scripts/test/corpus/CounterStop/chart.mmd +7 -0
- package/skills/statechart/scripts/test/corpus/CounterStop/graph.dot +13 -0
- package/skills/statechart/scripts/test/corpus/CounterStop/report.md +6 -0
- package/skills/statechart/scripts/test/corpus/CounterStop/sany.out +2 -0
- package/skills/statechart/scripts/test/corpus/CounterStop/verdict-1.out +49 -0
- package/skills/statechart/scripts/test/corpus/CounterTie/ChartRun.out +61 -0
- package/skills/statechart/scripts/test/corpus/CounterTie/CounterTie.cfg +4 -0
- package/skills/statechart/scripts/test/corpus/CounterTie/CounterTie.tla +14 -0
- package/skills/statechart/scripts/test/corpus/CounterTie/chart.mmd +30 -0
- package/skills/statechart/scripts/test/corpus/CounterTie/graph.dot +19 -0
- package/skills/statechart/scripts/test/corpus/CounterTie/report.md +27 -0
- package/skills/statechart/scripts/test/corpus/CounterTie/sany.out +2 -0
- package/skills/statechart/scripts/test/corpus/CounterTie/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterTie/verdict-1.out +56 -0
- package/skills/statechart/scripts/test/corpus/CounterTie/verdict-2.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterTie/verdict-2.out +64 -0
- package/skills/statechart/scripts/test/corpus/CounterTie/verdict-3.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterTie/verdict-3.out +87 -0
- package/skills/statechart/scripts/test/corpus/CounterTie/verdict-4.out +57 -0
- package/skills/statechart/scripts/test/corpus/CounterTieInGraph/ChartRun.out +60 -0
- package/skills/statechart/scripts/test/corpus/CounterTieInGraph/CounterTieInGraph.cfg +3 -0
- package/skills/statechart/scripts/test/corpus/CounterTieInGraph/CounterTieInGraph.tla +12 -0
- package/skills/statechart/scripts/test/corpus/CounterTieInGraph/chart.mmd +23 -0
- package/skills/statechart/scripts/test/corpus/CounterTieInGraph/graph.dot +19 -0
- package/skills/statechart/scripts/test/corpus/CounterTieInGraph/report.md +11 -0
- package/skills/statechart/scripts/test/corpus/CounterTieInGraph/sany.out +2 -0
- package/skills/statechart/scripts/test/corpus/CounterTieInGraph/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterTieInGraph/verdict-1.out +56 -0
- package/skills/statechart/scripts/test/corpus/CounterTieInGraph/verdict-2.out +57 -0
- package/skills/statechart/scripts/test/corpus/CounterView/ChartRun.out +61 -0
- package/skills/statechart/scripts/test/corpus/CounterView/CounterView.cfg +4 -0
- package/skills/statechart/scripts/test/corpus/CounterView/CounterView.tla +14 -0
- package/skills/statechart/scripts/test/corpus/CounterView/chart.mmd +23 -0
- package/skills/statechart/scripts/test/corpus/CounterView/graph.dot +19 -0
- package/skills/statechart/scripts/test/corpus/CounterView/report.md +27 -0
- package/skills/statechart/scripts/test/corpus/CounterView/sany.out +2 -0
- package/skills/statechart/scripts/test/corpus/CounterView/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterView/verdict-1.out +56 -0
- package/skills/statechart/scripts/test/corpus/CounterView/verdict-2.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterView/verdict-2.out +64 -0
- package/skills/statechart/scripts/test/corpus/CounterView/verdict-3.exit +1 -0
- package/skills/statechart/scripts/test/corpus/CounterView/verdict-3.out +87 -0
- package/skills/statechart/scripts/test/corpus/CounterView/verdict-4.out +57 -0
- package/skills/statechart/scripts/test/corpus/CounterView/view.mjs +1 -0
- package/skills/statechart/scripts/test/corpus/DieHard/ChartRun.out +59 -0
- package/skills/statechart/scripts/test/corpus/DieHard/DieHard.cfg +4 -0
- package/skills/statechart/scripts/test/corpus/DieHard/DieHard.tla +38 -0
- package/skills/statechart/scripts/test/corpus/DieHard/chart.mmd +92 -0
- package/skills/statechart/scripts/test/corpus/DieHard/graph.dot +126 -0
- package/skills/statechart/scripts/test/corpus/DieHard/report.md +17 -0
- package/skills/statechart/scripts/test/corpus/DieHard/sany.out +2 -0
- package/skills/statechart/scripts/test/corpus/DieHard/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/DieHard/verdict-1.out +91 -0
- package/skills/statechart/scripts/test/corpus/DieHard/verdict-2.out +57 -0
- package/skills/statechart/scripts/test/corpus/FirstRule/ChartRun.out +61 -0
- package/skills/statechart/scripts/test/corpus/FirstRule/FirstRule.cfg +6 -0
- package/skills/statechart/scripts/test/corpus/FirstRule/FirstRule.tla +11 -0
- package/skills/statechart/scripts/test/corpus/FirstRule/chart.mmd +22 -0
- package/skills/statechart/scripts/test/corpus/FirstRule/graph.dot +16 -0
- package/skills/statechart/scripts/test/corpus/FirstRule/report.md +25 -0
- package/skills/statechart/scripts/test/corpus/FirstRule/sany.out +2 -0
- package/skills/statechart/scripts/test/corpus/FirstRule/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/FirstRule/verdict-1.out +56 -0
- package/skills/statechart/scripts/test/corpus/FirstRule/verdict-2.exit +1 -0
- package/skills/statechart/scripts/test/corpus/FirstRule/verdict-2.out +64 -0
- package/skills/statechart/scripts/test/corpus/FirstRule/verdict-3.exit +1 -0
- package/skills/statechart/scripts/test/corpus/FirstRule/verdict-3.out +64 -0
- package/skills/statechart/scripts/test/corpus/FirstRule/verdict-4.exit +1 -0
- package/skills/statechart/scripts/test/corpus/FirstRule/verdict-4.out +77 -0
- package/skills/statechart/scripts/test/corpus/HiddenClock/ChartRun.out +57 -0
- package/skills/statechart/scripts/test/corpus/HiddenClock/HiddenClock.cfg +4 -0
- package/skills/statechart/scripts/test/corpus/HiddenClock/HiddenClock.tla +11 -0
- package/skills/statechart/scripts/test/corpus/HiddenClock/chart.mmd +13 -0
- package/skills/statechart/scripts/test/corpus/HiddenClock/graph.dot +12 -0
- package/skills/statechart/scripts/test/corpus/HiddenClock/report.md +11 -0
- package/skills/statechart/scripts/test/corpus/HiddenClock/sany.out +2 -0
- package/skills/statechart/scripts/test/corpus/HiddenClock/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/HiddenClock/verdict-1.out +72 -0
- package/skills/statechart/scripts/test/corpus/Login/ChartRun.out +57 -0
- package/skills/statechart/scripts/test/corpus/Login/Login.cfg +3 -0
- package/skills/statechart/scripts/test/corpus/Login/Login.tla +62 -0
- package/skills/statechart/scripts/test/corpus/Login/chart.mmd +27 -0
- package/skills/statechart/scripts/test/corpus/Login/graph.dot +40 -0
- package/skills/statechart/scripts/test/corpus/Login/report.md +7 -0
- package/skills/statechart/scripts/test/corpus/Login/sany.out +1 -0
- package/skills/statechart/scripts/test/corpus/Login/verdict-1.out +47 -0
- package/skills/statechart/scripts/test/corpus/Login/view.mjs +1 -0
- package/skills/statechart/scripts/test/corpus/LoginGuarded/ChartRun.out +57 -0
- package/skills/statechart/scripts/test/corpus/LoginGuarded/LoginGuarded.cfg +2 -0
- package/skills/statechart/scripts/test/corpus/LoginGuarded/LoginGuarded.tla +64 -0
- package/skills/statechart/scripts/test/corpus/LoginGuarded/chart.mmd +28 -0
- package/skills/statechart/scripts/test/corpus/LoginGuarded/graph.dot +37 -0
- package/skills/statechart/scripts/test/corpus/LoginGuarded/report.md +8 -0
- package/skills/statechart/scripts/test/corpus/LoginGuarded/sany.out +1 -0
- package/skills/statechart/scripts/test/corpus/LoginGuarded/verdict-1.out +47 -0
- package/skills/statechart/scripts/test/corpus/LoginGuarded/view.mjs +1 -0
- package/skills/statechart/scripts/test/corpus/LoginParallel/ChartRun.out +60 -0
- package/skills/statechart/scripts/test/corpus/LoginParallel/LoginParallel.cfg +3 -0
- package/skills/statechart/scripts/test/corpus/LoginParallel/LoginParallel.tla +64 -0
- package/skills/statechart/scripts/test/corpus/LoginParallel/chart.mmd +44 -0
- package/skills/statechart/scripts/test/corpus/LoginParallel/graph.dot +58 -0
- package/skills/statechart/scripts/test/corpus/LoginParallel/report.md +17 -0
- package/skills/statechart/scripts/test/corpus/LoginParallel/sany.out +1 -0
- package/skills/statechart/scripts/test/corpus/LoginParallel/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/LoginParallel/verdict-1.out +83 -0
- package/skills/statechart/scripts/test/corpus/LoginParallel/verdict-2.out +57 -0
- package/skills/statechart/scripts/test/corpus/LoginParallel/view.mjs +1 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayer/ChartRun.out +60 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayer/MediaPlayer.cfg +3 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayer/MediaPlayer.tla +103 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayer/chart.mmd +43 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayer/graph.dot +93 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayer/report.md +14 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayer/sany.out +1 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayer/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayer/verdict-1.out +59 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayer/verdict-2.out +72 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayer/view.mjs +3 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerFixed/ChartRun.out +57 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerFixed/MediaPlayerFixed.cfg +2 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerFixed/MediaPlayerFixed.tla +100 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerFixed/chart.mmd +31 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerFixed/graph.dot +85 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerFixed/report.md +9 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerFixed/sany.out +1 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerFixed/verdict-1.out +47 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerFixed/view.mjs +3 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerProps/ChartRun.out +59 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerProps/MediaPlayer.tla +103 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerProps/MediaPlayerProps.cfg +3 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerProps/MediaPlayerProps.tla +6 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerProps/chart.mmd +42 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerProps/graph.dot +93 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerProps/report.md +19 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerProps/sany.out +2 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-1.out +61 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-2.out +59 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-3.out +61 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-4.exit +1 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-4.out +66 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-5.exit +1 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-5.out +58 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-6.out +49 -0
- package/skills/statechart/scripts/test/corpus/MediaPlayerProps/view.mjs +3 -0
- package/skills/statechart/scripts/test/corpus/PickWorker/ChartRun.out +57 -0
- package/skills/statechart/scripts/test/corpus/PickWorker/PickWorker.cfg +6 -0
- package/skills/statechart/scripts/test/corpus/PickWorker/PickWorker.tla +11 -0
- package/skills/statechart/scripts/test/corpus/PickWorker/chart.mmd +5 -0
- package/skills/statechart/scripts/test/corpus/PickWorker/graph.dot +11 -0
- package/skills/statechart/scripts/test/corpus/PickWorker/report.md +7 -0
- package/skills/statechart/scripts/test/corpus/PickWorker/sany.out +5 -0
- package/skills/statechart/scripts/test/corpus/PickWorker/verdict-1.out +70 -0
- package/skills/statechart/scripts/test/corpus/Shadow/ChartRun.out +61 -0
- package/skills/statechart/scripts/test/corpus/Shadow/Shadow.cfg +4 -0
- package/skills/statechart/scripts/test/corpus/Shadow/Shadow.tla +11 -0
- package/skills/statechart/scripts/test/corpus/Shadow/ShadowLib.tla +6 -0
- package/skills/statechart/scripts/test/corpus/Shadow/ShadowSub.tla +3 -0
- package/skills/statechart/scripts/test/corpus/Shadow/chart.mmd +9 -0
- package/skills/statechart/scripts/test/corpus/Shadow/graph.dot +16 -0
- package/skills/statechart/scripts/test/corpus/Shadow/report.md +8 -0
- package/skills/statechart/scripts/test/corpus/Shadow/sany.out +4 -0
- package/skills/statechart/scripts/test/corpus/Shadow/verdict-1.out +65 -0
- package/skills/statechart/scripts/test/corpus/SpacedNames/ChartRun.out +59 -0
- package/skills/statechart/scripts/test/corpus/SpacedNames/SpacedNames.cfg +2 -0
- package/skills/statechart/scripts/test/corpus/SpacedNames/SpacedNames.tla +9 -0
- package/skills/statechart/scripts/test/corpus/SpacedNames/chart.mmd +23 -0
- package/skills/statechart/scripts/test/corpus/SpacedNames/graph.dot +17 -0
- package/skills/statechart/scripts/test/corpus/SpacedNames/report.md +15 -0
- package/skills/statechart/scripts/test/corpus/SpacedNames/sany.out +1 -0
- package/skills/statechart/scripts/test/corpus/SpacedNames/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/SpacedNames/verdict-1.out +65 -0
- package/skills/statechart/scripts/test/corpus/SpacedNames/verdict-2.exit +1 -0
- package/skills/statechart/scripts/test/corpus/SpacedNames/verdict-2.out +75 -0
- package/skills/statechart/scripts/test/corpus/TwoPhase/ChartRun.out +69 -0
- package/skills/statechart/scripts/test/corpus/TwoPhase/TwoPhase.cfg +3 -0
- package/skills/statechart/scripts/test/corpus/TwoPhase/TwoPhase.tla +75 -0
- package/skills/statechart/scripts/test/corpus/TwoPhase/chart.mmd +60 -0
- package/skills/statechart/scripts/test/corpus/TwoPhase/graph.dot +297 -0
- package/skills/statechart/scripts/test/corpus/TwoPhase/report.md +20 -0
- package/skills/statechart/scripts/test/corpus/TwoPhase/sany.out +1 -0
- package/skills/statechart/scripts/test/corpus/TwoPhase/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/TwoPhase/verdict-1.out +95 -0
- package/skills/statechart/scripts/test/corpus/TwoPhase/verdict-2.out +57 -0
- package/skills/statechart/scripts/test/corpus/TwoPhase/view.mjs +1 -0
- package/skills/statechart/scripts/test/corpus/TwoPhaseFixed/ChartRun.out +57 -0
- package/skills/statechart/scripts/test/corpus/TwoPhaseFixed/TwoPhaseFixed.cfg +3 -0
- package/skills/statechart/scripts/test/corpus/TwoPhaseFixed/TwoPhaseFixed.tla +75 -0
- package/skills/statechart/scripts/test/corpus/TwoPhaseFixed/chart.mmd +37 -0
- package/skills/statechart/scripts/test/corpus/TwoPhaseFixed/graph.dot +223 -0
- package/skills/statechart/scripts/test/corpus/TwoPhaseFixed/report.md +11 -0
- package/skills/statechart/scripts/test/corpus/TwoPhaseFixed/sany.out +1 -0
- package/skills/statechart/scripts/test/corpus/TwoPhaseFixed/verdict-1.out +47 -0
- package/skills/statechart/scripts/test/corpus/TwoPhaseFixed/view.mjs +1 -0
- package/skills/statechart/scripts/test/corpus/TypeGuard/ChartRun.exit +1 -0
- package/skills/statechart/scripts/test/corpus/TypeGuard/ChartRun.out +75 -0
- package/skills/statechart/scripts/test/corpus/TypeGuard/TypeGuard.cfg +5 -0
- package/skills/statechart/scripts/test/corpus/TypeGuard/TypeGuard.tla +11 -0
- package/skills/statechart/scripts/test/corpus/TypeGuard/chart.mmd +18 -0
- package/skills/statechart/scripts/test/corpus/TypeGuard/graph.dot +14 -0
- package/skills/statechart/scripts/test/corpus/TypeGuard/report.md +29 -0
- package/skills/statechart/scripts/test/corpus/TypeGuard/sany.out +2 -0
- package/skills/statechart/scripts/test/corpus/TypeGuard/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/TypeGuard/verdict-1.out +64 -0
- package/skills/statechart/scripts/test/corpus/TypeGuard/verdict-2.exit +1 -0
- package/skills/statechart/scripts/test/corpus/TypeGuard/verdict-2.out +75 -0
- package/skills/statechart/scripts/test/corpus/UserNoDeadlock/ChartRun.out +58 -0
- package/skills/statechart/scripts/test/corpus/UserNoDeadlock/UserNoDeadlock.cfg +2 -0
- package/skills/statechart/scripts/test/corpus/UserNoDeadlock/UserNoDeadlock.tla +8 -0
- package/skills/statechart/scripts/test/corpus/UserNoDeadlock/chart.mmd +22 -0
- package/skills/statechart/scripts/test/corpus/UserNoDeadlock/graph.dot +16 -0
- package/skills/statechart/scripts/test/corpus/UserNoDeadlock/report.md +15 -0
- package/skills/statechart/scripts/test/corpus/UserNoDeadlock/sany.out +2 -0
- package/skills/statechart/scripts/test/corpus/UserNoDeadlock/verdict-1.exit +1 -0
- package/skills/statechart/scripts/test/corpus/UserNoDeadlock/verdict-1.out +56 -0
- package/skills/statechart/scripts/test/corpus/UserNoDeadlock/verdict-2.exit +1 -0
- package/skills/statechart/scripts/test/corpus/UserNoDeadlock/verdict-2.out +77 -0
- package/skills/statechart/scripts/test/corpus.test.ts +403 -0
- package/skills/statechart/scripts/test/differential.test.ts +565 -0
- package/skills/statechart/scripts/test/grid/Grid.cfg +2 -0
- package/skills/statechart/scripts/test/grid/Grid.tla +9 -0
- package/skills/statechart/scripts/test/grid/sany.out +2 -0
- package/skills/statechart/scripts/test/java.ts +63 -0
- package/skills/statechart/scripts/test/labels.ts +643 -0
- package/skills/statechart/scripts/test/messages.test.ts +212 -0
- package/skills/statechart/scripts/test/pipeline.test.ts +270 -0
- package/skills/statechart/scripts/test/plain-tlc.test.ts +255 -0
- package/skills/statechart/scripts/test/render.test.ts +81 -0
- package/skills/statechart/scripts/test/tlc-out.test.ts +90 -0
- package/skills/statechart/scripts/test/tlc.ts +158 -0
- package/skills/statechart/scripts/tsconfig.json +11 -0
- package/skills/statechart/scripts/worker.ts +35 -0
- package/skills/swarm/SKILL.md +41 -0
- package/skills/tdd/SKILL.md +42 -0
- package/skills/teach/SKILL.md +21 -0
- package/skills/technical-writing/SKILL.md +106 -0
- package/skills/typescript-best-practices/SKILL.md +32 -0
- package/skills/typescript-best-practices/references/patterns.md +328 -0
- package/skills/unslop/SKILL.md +62 -0
- package/skills/why/SKILL.md +98 -0
- package/skills/why/references/epistemics.md +71 -0
- package/skills/why/references/investigator-prompt.md +84 -0
- package/skills/why/references/source-playbook.md +17 -0
- package/skills/why/references/sources/code-archaeology.md +51 -0
- package/skills/why/references/sources/databricks.md +70 -0
- package/skills/why/references/sources/datadog.md +97 -0
- package/skills/why/references/sources/incident-postmortem.md +5 -0
- package/skills/why/references/sources/linear.md +48 -0
- package/skills/why/references/sources/notion.md +55 -0
- package/skills/why/references/sources/sentry.md +66 -0
- package/skills/why/references/sources/slack.md +17 -0
- package/skills/why/references/synthesizer-prompt.md +129 -0
- package/src/aliases.ts +64 -0
- package/src/ask.ts +131 -0
- package/src/autocomplete.ts +21 -0
- package/src/child-env.ts +44 -0
- package/src/index.ts +123 -0
- package/src/loop.ts +189 -0
- package/src/mode.ts +156 -0
- package/src/modes.ts +8 -0
- package/src/paths.ts +72 -0
- package/src/plan.ts +141 -0
- package/src/readonly-shell.ts +107 -0
- package/src/rule.ts +56 -0
- package/src/sections.ts +117 -0
- package/src/subscriptions/cron.ts +75 -0
- package/src/subscriptions/github.ts +149 -0
- package/src/subscriptions/index.ts +547 -0
- package/src/subscriptions/webhook.ts +209 -0
- package/src/task/agents.ts +55 -0
- package/src/task/card.ts +32 -0
- package/src/task/format.ts +88 -0
- package/src/task/models.ts +140 -0
- package/src/task/records.ts +232 -0
- package/src/task/registry.ts +77 -0
- package/src/task/runner.ts +370 -0
- package/src/task/status.ts +170 -0
- package/src/task/stop.ts +100 -0
- package/src/task/tool.ts +491 -0
- package/src/task/worktree.ts +67 -0
- package/src/todo.ts +122 -0
- package/src/visualize.ts +244 -0
- package/test/aliases.test.ts +57 -0
- package/test/ask.test.ts +85 -0
- package/test/autocomplete.test.ts +39 -0
- package/test/e2e/aliases.e2e.test.ts +81 -0
- package/test/e2e/ask.e2e.test.ts +135 -0
- package/test/e2e/harness.ts +179 -0
- package/test/e2e/host.e2e.test.ts +193 -0
- package/test/e2e/lifecycle.e2e.test.ts +99 -0
- package/test/e2e/mode.e2e.test.ts +160 -0
- package/test/e2e/package.e2e.test.ts +67 -0
- package/test/e2e/task.e2e.test.ts +734 -0
- package/test/e2e/todo.e2e.test.ts +59 -0
- package/test/fixtures/cursor-askquestion-calls.json +806 -0
- package/test/fixtures/fake-pi.mjs +96 -0
- package/test/fixtures/image-model.ts +16 -0
- package/test/fixtures/list-models.ts +15 -0
- package/test/fixtures/readonly-shell-exit.ts +17 -0
- package/test/fixtures/scripted-model.ts +113 -0
- package/test/loop.test.ts +209 -0
- package/test/mode.test.ts +143 -0
- package/test/plan.test.ts +126 -0
- package/test/readonly-shell-security.test.ts +84 -0
- package/test/readonly-shell.test.ts +44 -0
- package/test/records.test.ts +143 -0
- package/test/registry.test.ts +18 -0
- package/test/resources.test.ts +162 -0
- package/test/rule.test.ts +92 -0
- package/test/scripts.test.ts +182 -0
- package/test/sections.test.ts +56 -0
- package/test/subscriptions-cron.test.ts +60 -0
- package/test/subscriptions.test.ts +598 -0
- package/test/task-agents.test.ts +49 -0
- package/test/task-card.test.ts +34 -0
- package/test/task-fork.test.ts +233 -0
- package/test/task-format.test.ts +65 -0
- package/test/task-model.test.ts +141 -0
- package/test/task-runner.test.ts +259 -0
- package/test/task-schema.test.ts +50 -0
- package/test/task-status.test.ts +155 -0
- package/test/task-worktree.test.ts +106 -0
- package/test/todo.test.ts +74 -0
- package/test/visualize.test.ts +152 -0
- package/tsconfig.json +5 -0
- package/vitest.config.ts +16 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { SessionManager } from '@earendil-works/pi-coding-agent';
|
|
5
|
+
import type { ModelSpec } from './models.ts';
|
|
6
|
+
import type { Worktree } from './worktree.ts';
|
|
7
|
+
|
|
8
|
+
/** What resume needs to restart an agent the way it first ran. */
|
|
9
|
+
export interface AgentRecord {
|
|
10
|
+
agentId: string;
|
|
11
|
+
type: string;
|
|
12
|
+
model?: ModelSpec;
|
|
13
|
+
readonly: boolean;
|
|
14
|
+
/** The parent workspace the agent belongs to; a cloud agent runs in `worktree` instead. */
|
|
15
|
+
cwd: string;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
/** 1 for a subagent of the main chat, 2 for a subagent of a subagent; absent in records written before it existed. */
|
|
18
|
+
depth?: number;
|
|
19
|
+
worktree?: Worktree;
|
|
20
|
+
/** The agent whose transcript this one started as a copy of (Task `fork`). */
|
|
21
|
+
forkedFrom?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isWorktree(value: unknown): value is Worktree {
|
|
25
|
+
const worktree = value as Partial<Worktree> | null;
|
|
26
|
+
return typeof worktree === 'object' && worktree !== null && (['path', 'cwd', 'branch', 'base', 'commit'] as const).every((key) => typeof worktree[key] === 'string');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isModel(value: unknown): value is ModelSpec {
|
|
30
|
+
const model = value as Partial<ModelSpec> | null;
|
|
31
|
+
return typeof model === 'object' && model !== null && typeof model.provider === 'string' && typeof model.id === 'string' && (model.thinking === undefined || typeof model.thinking === 'string');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isRecord(value: unknown, agentId: string): value is AgentRecord {
|
|
35
|
+
const record = value as Partial<AgentRecord> | null;
|
|
36
|
+
return (
|
|
37
|
+
typeof record === 'object' &&
|
|
38
|
+
record !== null &&
|
|
39
|
+
!Array.isArray(record) &&
|
|
40
|
+
record.agentId === agentId &&
|
|
41
|
+
typeof record.type === 'string' &&
|
|
42
|
+
typeof record.readonly === 'boolean' &&
|
|
43
|
+
typeof record.cwd === 'string' &&
|
|
44
|
+
typeof record.createdAt === 'string' &&
|
|
45
|
+
(record.model === undefined || isModel(record.model)) &&
|
|
46
|
+
(record.depth === undefined || Number.isInteger(record.depth)) &&
|
|
47
|
+
(record.worktree === undefined || isWorktree(record.worktree)) &&
|
|
48
|
+
(record.forkedFrom === undefined || typeof record.forkedFrom === 'string')
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function writeRecord(dir: string, record: AgentRecord): void {
|
|
53
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
54
|
+
const path = join(dir, `${record.agentId}.json`);
|
|
55
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
56
|
+
writeFileSync(temporary, `${JSON.stringify(record, null, 2)}\n`, { mode: 0o600 });
|
|
57
|
+
renameSync(temporary, path);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function readRecord(dir: string, agentId: string): AgentRecord | undefined {
|
|
61
|
+
const path = join(dir, `${agentId}.json`);
|
|
62
|
+
if (!existsSync(path)) return undefined;
|
|
63
|
+
const unreadable = new Error(`Agent record ${path} is unreadable; delete it and start a new agent.`);
|
|
64
|
+
let value: unknown;
|
|
65
|
+
try {
|
|
66
|
+
value = JSON.parse(readFileSync(path, 'utf8'));
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (error instanceof SyntaxError) throw unreadable;
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
if (!isRecord(value, agentId)) throw unreadable;
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Pi names a session file `<timestamp>_<session id>.jsonl`; the agent ID is the session ID. */
|
|
76
|
+
export function transcriptFor(dir: string, agentId: string): string | undefined {
|
|
77
|
+
if (!existsSync(dir)) return undefined;
|
|
78
|
+
const file = readdirSync(dir).find((name) => name.endsWith(`_${agentId}.jsonl`));
|
|
79
|
+
return file === undefined ? undefined : join(dir, file);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The working directory a Pi session file's header names, or undefined without a readable header. */
|
|
83
|
+
function sessionCwd(file: string): string | undefined {
|
|
84
|
+
try {
|
|
85
|
+
const header = JSON.parse(readFileSync(file, 'utf8').split('\n', 1)[0] ?? '') as { type?: unknown; cwd?: unknown } | null;
|
|
86
|
+
return header?.type === 'session' && typeof header.cwd === 'string' ? header.cwd : undefined;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (error instanceof SyntaxError) return undefined;
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Starts `agentId`'s transcript as a copy of `sourceId`'s, through Pi's own session fork: a new session ID in the
|
|
95
|
+
* header, the source as its parent session, and the source's working directory, so `--session-id` finds it where the
|
|
96
|
+
* source ran. Returns the new transcript path.
|
|
97
|
+
*/
|
|
98
|
+
export function forkTranscript(dir: string, sourceId: string, agentId: string, turns?: number): string {
|
|
99
|
+
const source = transcriptFor(dir, sourceId);
|
|
100
|
+
if (source === undefined) throw new Error(`Agent ${sourceId} has no transcript to fork.`);
|
|
101
|
+
const cwd = sessionCwd(source);
|
|
102
|
+
if (cwd === undefined) throw new Error(`Agent ${sourceId}'s transcript ${source} has no session header, so it cannot be forked.`);
|
|
103
|
+
const fork = SessionManager.forkFrom(source, cwd, dir, { id: agentId });
|
|
104
|
+
const file = fork.getSessionFile();
|
|
105
|
+
if (file === undefined) throw new Error(`Pi did not write a transcript for the fork of agent ${sourceId}.`);
|
|
106
|
+
if (turns !== undefined) keepFirstTurns(fork, file, turns);
|
|
107
|
+
return file;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Cuts the copy's active branch just before its (turns + 1)th user message, so the fork opens at the end of turn `turns`.
|
|
112
|
+
* `SessionManager.createBranchedSession(leafId)` mints its own session ID and file name, so it cannot truncate the file
|
|
113
|
+
* `forkFrom(..., { id: agentId })` wrote under the agent's ID (docs/native-gaps.md).
|
|
114
|
+
*/
|
|
115
|
+
function keepFirstTurns(fork: SessionManager, file: string, turns: number): void {
|
|
116
|
+
const branch = fork.getBranch();
|
|
117
|
+
const cut = branch.filter((entry) => entry.type === 'message' && entry.message.role === 'user')[turns];
|
|
118
|
+
if (cut === undefined) return;
|
|
119
|
+
const kept = [fork.getHeader(), ...branch.slice(0, branch.indexOf(cut))];
|
|
120
|
+
writeFileSync(file, kept.map((entry) => `${JSON.stringify(entry)}\n`).join(''), { mode: 0o600 });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Tools a writable launch gives back: those a launch-only readonly run (plan mode) took from the agent's last writable
|
|
125
|
+
* loadout. Pi restores the tool loadout a session last recorded, so without them the agent would stay readonly.
|
|
126
|
+
*/
|
|
127
|
+
export function readonlyLaunchRestores(transcript: string): string[] {
|
|
128
|
+
const active = new Set<string>();
|
|
129
|
+
let writable: string[] = [];
|
|
130
|
+
for (const entry of SessionManager.open(transcript).getBranch()) {
|
|
131
|
+
if (entry.type !== 'message' || entry.message.role !== 'system') continue;
|
|
132
|
+
for (const tool of entry.message.toolsRemoved ?? []) active.delete(tool.name);
|
|
133
|
+
for (const tool of entry.message.toolsAdded ?? []) active.add(tool.name);
|
|
134
|
+
if (active.has('write') || active.has('edit')) writable = [...active];
|
|
135
|
+
}
|
|
136
|
+
return writable.filter((name) => !active.has(name));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** IDs of every agent with a record in `dir`. */
|
|
140
|
+
export function recordIds(dir: string): string[] {
|
|
141
|
+
if (!existsSync(dir)) return [];
|
|
142
|
+
return readdirSync(dir)
|
|
143
|
+
.filter((name) => name.endsWith('.json'))
|
|
144
|
+
.map((name) => name.slice(0, -'.json'.length));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function alive(pid: number): boolean {
|
|
148
|
+
try {
|
|
149
|
+
process.kill(pid, 0);
|
|
150
|
+
return true;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
return (error as NodeJS.ErrnoException).code === 'EPERM';
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const isPid = (value: number) => Number.isInteger(value) && value > 0;
|
|
157
|
+
|
|
158
|
+
/** The live pid the agent's run lock names, or undefined when no live process holds it. */
|
|
159
|
+
export function runningPid(dir: string, agentId: string): number | undefined {
|
|
160
|
+
const holder = holderOf(join(dir, `${agentId}.lock`));
|
|
161
|
+
return holder !== undefined && isPid(holder) && alive(holder) ? holder : undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** The pid a lock names: undefined when the file is gone, NaN when it holds no readable pid. */
|
|
165
|
+
function holderOf(path: string): number | undefined {
|
|
166
|
+
try {
|
|
167
|
+
return Number.parseInt(readFileSync(path, 'utf8'), 10);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined;
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function createExclusive(path: string): boolean {
|
|
175
|
+
try {
|
|
176
|
+
writeFileSync(path, String(process.pid), { flag: 'wx', mode: 0o600 });
|
|
177
|
+
return true;
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if ((error as NodeJS.ErrnoException).code === 'EEXIST') return false;
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function writeAtomically(path: string, text: string): void {
|
|
185
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
186
|
+
writeFileSync(temporary, text, { mode: 0o600 });
|
|
187
|
+
renameSync(temporary, path);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const stillRunning = (agentId: string, holder: number | undefined, path: string) =>
|
|
191
|
+
new Error(`Agent ${agentId} is still running (pid ${holder !== undefined && isPid(holder) ? holder : 'unknown'}; lock ${path}). Wait for its completion before resuming it.`);
|
|
192
|
+
|
|
193
|
+
export interface RunLock {
|
|
194
|
+
/** Names the process that writes the transcript, so the lock stays held exactly as long as that process lives. */
|
|
195
|
+
holdFor: (pid: number) => void;
|
|
196
|
+
/** Removes the lock unless another holder has taken it over since. */
|
|
197
|
+
release: () => void;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Pi appends to a session file without a lock, so two processes on one agent ID would interleave its transcript.
|
|
202
|
+
* The lock file holds the pid of the process writing the transcript. A lock without a readable pid counts as held.
|
|
203
|
+
* A dead holder's lock is removed only if it still names that holder, then created again exclusively; two
|
|
204
|
+
* processes taking over the same stale lock in the narrow window between that re-read and the removal can both succeed.
|
|
205
|
+
*/
|
|
206
|
+
export function acquireRunLock(dir: string, agentId: string): RunLock {
|
|
207
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
208
|
+
const path = join(dir, `${agentId}.lock`);
|
|
209
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
210
|
+
if (createExclusive(path)) {
|
|
211
|
+
let owner = process.pid;
|
|
212
|
+
return {
|
|
213
|
+
holdFor: (pid) => {
|
|
214
|
+
writeAtomically(path, String(pid));
|
|
215
|
+
owner = pid;
|
|
216
|
+
},
|
|
217
|
+
// Best effort: a lock left behind names a pid that will be dead, so the next acquire takes it over.
|
|
218
|
+
release: () => {
|
|
219
|
+
try {
|
|
220
|
+
if (holderOf(path) === owner) rmSync(path, { force: true });
|
|
221
|
+
} catch (error) {
|
|
222
|
+
if (!['EACCES', 'EPERM', 'EROFS', 'EBUSY', 'EIO'].includes((error as NodeJS.ErrnoException).code ?? '')) throw error;
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const holder = holderOf(path);
|
|
228
|
+
if (holder !== undefined && (!isPid(holder) || alive(holder))) throw stillRunning(agentId, holder, path);
|
|
229
|
+
if (holder !== undefined && holderOf(path) === holder) rmSync(path, { force: true });
|
|
230
|
+
}
|
|
231
|
+
throw stillRunning(agentId, holderOf(path), path);
|
|
232
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { AgentActivityOutcome, BoundaryResult, ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import { isOneShot } from '../modes.ts';
|
|
3
|
+
import { type NotificationFields, notificationText } from './format.ts';
|
|
4
|
+
import type { ChildHandle, ChildOutcome } from './runner.ts';
|
|
5
|
+
|
|
6
|
+
export const NOTIFICATION_TYPE = 'pstack/task';
|
|
7
|
+
|
|
8
|
+
interface Running {
|
|
9
|
+
settled: Promise<void>;
|
|
10
|
+
handle: ChildHandle;
|
|
11
|
+
/** How the child runs now, which can be stricter than its record (plan mode). */
|
|
12
|
+
readonly: boolean;
|
|
13
|
+
background: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface BackgroundNotice {
|
|
17
|
+
title: string;
|
|
18
|
+
toolCallId: string;
|
|
19
|
+
mode: ExtensionContext['mode'];
|
|
20
|
+
detailFor: (outcome: ChildOutcome) => Pick<NotificationFields, 'status' | 'detail' | 'usage'>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Every running child of this runtime, foreground and background, so one agent ID never has two writers here.
|
|
25
|
+
* Background completions become notices: a follow-up message in long-lived sessions, and a settle-time entry in
|
|
26
|
+
* one-shot print and JSON runs, which wait for them before exiting.
|
|
27
|
+
*/
|
|
28
|
+
export function createSubagentRegistry(pi: ExtensionAPI) {
|
|
29
|
+
const running = new Map<string, Running>();
|
|
30
|
+
const pending: NotificationFields[] = [];
|
|
31
|
+
let retired = false;
|
|
32
|
+
|
|
33
|
+
const deliver = (fields: NotificationFields, mode: ExtensionContext['mode']) => {
|
|
34
|
+
if (isOneShot(mode)) pending.push(fields);
|
|
35
|
+
else pi.sendMessage({ customType: NOTIFICATION_TYPE, content: notificationText(fields), display: true, details: fields }, { triggerTurn: true, deliverAs: 'followUp' });
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** Registers synchronously, before any await in the caller, so a sibling call in the same message sees it. */
|
|
39
|
+
const track = (agentId: string, handle: ChildHandle, release: () => void, readonly: boolean, notice?: BackgroundNotice) => {
|
|
40
|
+
const settled = handle.done.then((outcome) => {
|
|
41
|
+
running.delete(agentId);
|
|
42
|
+
release();
|
|
43
|
+
// Shutdown killed this child; the retired runtime's pi must not be touched.
|
|
44
|
+
if (retired || notice === undefined) return;
|
|
45
|
+
deliver({ ...notice.detailFor(outcome), agentId, title: notice.title, toolCallId: notice.toolCallId }, notice.mode);
|
|
46
|
+
});
|
|
47
|
+
running.set(agentId, { settled, handle, readonly, background: notice !== undefined });
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const backgroundRunning = () => [...running.values()].filter((entry) => entry.background);
|
|
51
|
+
|
|
52
|
+
/** An aborted run ends now: a one-shot run does not hold its exit for background subagents after Escape or a watchdog abort. */
|
|
53
|
+
const beforeSettle = async (ctx: ExtensionContext, outcome: AgentActivityOutcome): Promise<BoundaryResult | undefined> => {
|
|
54
|
+
if (!isOneShot(ctx.mode) || outcome === 'aborted' || (pending.length === 0 && backgroundRunning().length === 0)) return undefined;
|
|
55
|
+
if (pending.length === 0) await Promise.race(backgroundRunning().map((entry) => entry.settled));
|
|
56
|
+
const ready = pending.splice(0);
|
|
57
|
+
return { entries: ready.map((fields) => ({ type: 'custom_message' as const, customType: NOTIFICATION_TYPE, content: notificationText(fields), display: true, details: fields })), continue: true };
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const shutdown = (): number => {
|
|
61
|
+
retired = true;
|
|
62
|
+
const background = backgroundRunning().length;
|
|
63
|
+
for (const entry of running.values()) entry.handle.kill();
|
|
64
|
+
running.clear();
|
|
65
|
+
pending.splice(0);
|
|
66
|
+
return background;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
isRunning: (agentId: string) => running.has(agentId),
|
|
71
|
+
get: (agentId: string): Pick<Running, 'handle' | 'readonly'> | undefined => running.get(agentId),
|
|
72
|
+
hasBackgroundWork: () => backgroundRunning().length > 0,
|
|
73
|
+
track,
|
|
74
|
+
beforeSettle,
|
|
75
|
+
shutdown,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
5
|
+
|
|
6
|
+
import type { ImageContent, Usage } from '@earendil-works/pi-ai';
|
|
7
|
+
import { PACKAGE_ROOT } from '../paths.ts';
|
|
8
|
+
import type { ModelSpec } from './models.ts';
|
|
9
|
+
|
|
10
|
+
// TodoWrite only changes the child's own todo list, so a readonly reviewer can still keep one. The child replaces
|
|
11
|
+
// bash with a sandboxed read-only shell (readonly-shell.ts).
|
|
12
|
+
const READONLY_TOOLS = 'read,grep,find,ls,bash,TodoWrite';
|
|
13
|
+
const AGENT_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,126}[A-Za-z0-9]$/;
|
|
14
|
+
export const KILL_GRACE_MS = 3_000;
|
|
15
|
+
// Dialogs block a run until answered. A subagent has nobody to ask, so its owner dismisses them.
|
|
16
|
+
const DIALOG_METHODS = new Set(['select', 'confirm', 'input', 'editor']);
|
|
17
|
+
|
|
18
|
+
export interface ChildArgsOptions {
|
|
19
|
+
sessionDir: string;
|
|
20
|
+
agentId: string;
|
|
21
|
+
model?: ModelSpec;
|
|
22
|
+
readonly?: boolean;
|
|
23
|
+
/** Tools to add back to the loadout Pi restores from the transcript (records.ts `readonlyLaunchRestores`). */
|
|
24
|
+
restoreTools?: readonly string[];
|
|
25
|
+
/** The parent's project trust, so the child loads the same project resources (`ctx.isProjectTrusted()`). */
|
|
26
|
+
trusted?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Argv for one `pi --mode rpc` subagent process. Its prompts travel as RPC commands on stdin, never in argv. */
|
|
30
|
+
export function childArgs(options: ChildArgsOptions): string[] {
|
|
31
|
+
const model = options.model === undefined ? [] : ['--model', `${options.model.provider}/${options.model.id}`, ...(options.model.thinking === undefined ? [] : ['--thinking', options.model.thinking])];
|
|
32
|
+
return [
|
|
33
|
+
'--mode',
|
|
34
|
+
'rpc',
|
|
35
|
+
'--session-dir',
|
|
36
|
+
options.sessionDir,
|
|
37
|
+
'--session-id',
|
|
38
|
+
options.agentId,
|
|
39
|
+
'-e',
|
|
40
|
+
PACKAGE_ROOT,
|
|
41
|
+
...(options.trusted === undefined ? [] : [options.trusted ? '--approve' : '--no-approve']),
|
|
42
|
+
...model,
|
|
43
|
+
...(options.readonly === true ? ['--tools', READONLY_TOOLS, '--no-mcp'] : []),
|
|
44
|
+
...(options.readonly !== true && (options.restoreTools ?? []).length > 0 ? ['--tools', (options.restoreTools ?? []).map((name) => `+${name}`).join(',')] : []),
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function validateAgentId(id: string): string {
|
|
49
|
+
if (!AGENT_ID.test(id)) throw new Error(`"${id.slice(0, 80)}" is not a valid agent ID.`);
|
|
50
|
+
return id;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Absolute paths of files to attach: `~` expands as in Pi's read paths, and each must be an existing file. */
|
|
54
|
+
export function resolveAttachments(paths: readonly string[], cwd: string, home: string = homedir()): string[] {
|
|
55
|
+
return paths.map((path) => {
|
|
56
|
+
const expanded = path === '~' || path.startsWith('~/') ? join(home, path.slice(1)) : path;
|
|
57
|
+
const absolute = isAbsolute(expanded) ? expanded : resolve(cwd, expanded);
|
|
58
|
+
if (!existsSync(absolute)) throw new Error(`file_attachments: ${absolute} does not exist`);
|
|
59
|
+
if (!statSync(absolute).isFile()) throw new Error(`file_attachments: ${absolute} is not a file`);
|
|
60
|
+
return absolute;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const isBmp = (bytes: Buffer) => bytes.length >= 30 && bytes.toString('latin1', 0, 2) === 'BM' && [12, 40, 52, 56, 108, 124].includes(bytes.readUInt32LE(14));
|
|
65
|
+
const IMAGE_TYPES: [string, (bytes: Buffer) => boolean][] = [
|
|
66
|
+
['image/png', (bytes) => bytes.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))],
|
|
67
|
+
['image/jpeg', (bytes) => bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff && bytes[3] !== 0xf7],
|
|
68
|
+
['image/gif', (bytes) => /^GIF8[79]a/.test(bytes.toString('latin1', 0, 6))],
|
|
69
|
+
['image/webp', (bytes) => bytes.toString('latin1', 0, 4) === 'RIFF' && bytes.toString('latin1', 8, 12) === 'WEBP'],
|
|
70
|
+
['image/bmp', isBmp],
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
export interface ChildInput {
|
|
74
|
+
text: string;
|
|
75
|
+
images: ImageContent[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The first message Pi builds from piped input and `@file` arguments, which RPC mode does not accept: the trimmed
|
|
80
|
+
* prompt, then each non-empty text file in a <file> block, and each image as an attachment with an empty <file> block.
|
|
81
|
+
*/
|
|
82
|
+
export function withAttachments(prompt: string, attachments: readonly string[]): ChildInput {
|
|
83
|
+
let text = prompt.trim();
|
|
84
|
+
const images: ImageContent[] = [];
|
|
85
|
+
for (const path of attachments) {
|
|
86
|
+
const bytes = readFileSync(path);
|
|
87
|
+
if (bytes.length === 0) continue;
|
|
88
|
+
const mimeType = IMAGE_TYPES.find(([, matches]) => matches(bytes))?.[0];
|
|
89
|
+
if (mimeType === undefined) {
|
|
90
|
+
text += `<file name="${path}">\n${bytes.toString('utf8').replace(/^\uFEFF/, '')}\n</file>\n`;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
images.push({ type: 'image', mimeType, data: bytes.toString('base64') });
|
|
94
|
+
text += `<file name="${path}"></file>\n`;
|
|
95
|
+
}
|
|
96
|
+
return { text, images };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface ChildOptions {
|
|
100
|
+
command: readonly string[];
|
|
101
|
+
args: readonly string[];
|
|
102
|
+
prompt: string;
|
|
103
|
+
images?: readonly ImageContent[];
|
|
104
|
+
cwd: string;
|
|
105
|
+
env: NodeJS.ProcessEnv;
|
|
106
|
+
signal?: AbortSignal;
|
|
107
|
+
onEvent?: (event: { type: string } & Record<string, unknown>) => void;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface ChildOutcome {
|
|
111
|
+
finalText: string;
|
|
112
|
+
usage: Usage;
|
|
113
|
+
failure?: string;
|
|
114
|
+
stderr: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface ChildHandle {
|
|
118
|
+
done: Promise<ChildOutcome>;
|
|
119
|
+
/** Ends the child and its process group now: session shutdown or an aborted foreground call. */
|
|
120
|
+
kill: () => void;
|
|
121
|
+
/** stop_agent: aborts the run over RPC so the transcript records it, then kills the group if it outlives the grace period. */
|
|
122
|
+
stop: () => void;
|
|
123
|
+
/** Queues a message into the running turn (a new turn if the run just ended); rejects once the child is closing. */
|
|
124
|
+
steer: (text: string, images: readonly ImageContent[]) => Promise<void>;
|
|
125
|
+
pid: number | undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export const zeroUsage = (): Usage => ({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } });
|
|
129
|
+
|
|
130
|
+
export function addUsage(left: Usage, right: Usage): Usage {
|
|
131
|
+
return {
|
|
132
|
+
input: left.input + right.input,
|
|
133
|
+
output: left.output + right.output,
|
|
134
|
+
cacheRead: left.cacheRead + right.cacheRead,
|
|
135
|
+
cacheWrite: left.cacheWrite + right.cacheWrite,
|
|
136
|
+
totalTokens: left.totalTokens + right.totalTokens,
|
|
137
|
+
cost: {
|
|
138
|
+
input: left.cost.input + right.cost.input,
|
|
139
|
+
output: left.cost.output + right.cost.output,
|
|
140
|
+
cacheRead: left.cost.cacheRead + right.cost.cacheRead,
|
|
141
|
+
cacheWrite: left.cost.cacheWrite + right.cost.cacheWrite,
|
|
142
|
+
total: left.cost.total + right.cost.total,
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
interface AssistantRecord {
|
|
148
|
+
text: string;
|
|
149
|
+
stopReason?: string;
|
|
150
|
+
errorMessage?: string;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Usage a JSON-mode event adds to the child's session totals, counted the way Pi's own session stats count it:
|
|
155
|
+
* assistant replies, tool results that carry nested usage (such as a grandchild subagent), and compactions.
|
|
156
|
+
*/
|
|
157
|
+
function usageOf(event: Record<string, unknown>): Usage | undefined {
|
|
158
|
+
if (event['type'] === 'compaction_end') return (event['result'] as { usage?: Usage } | undefined)?.usage;
|
|
159
|
+
const message = event['message'] as { role?: string; usage?: Usage } | undefined;
|
|
160
|
+
if (event['type'] !== 'message_end' || (message?.role !== 'assistant' && message?.role !== 'toolResult')) return undefined;
|
|
161
|
+
return message.usage;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function readAssistant(event: Record<string, unknown>): AssistantRecord | undefined {
|
|
165
|
+
const message = event['message'] as { role?: string; content?: { type: string; text?: string }[]; stopReason?: string; errorMessage?: string } | undefined;
|
|
166
|
+
if (event['type'] !== 'message_end' || message?.role !== 'assistant') return undefined;
|
|
167
|
+
const text = (message.content ?? [])
|
|
168
|
+
.filter((part) => part.type === 'text')
|
|
169
|
+
.map((part) => part.text ?? '')
|
|
170
|
+
.join('');
|
|
171
|
+
return {
|
|
172
|
+
text,
|
|
173
|
+
...(message.stopReason === undefined ? {} : { stopReason: message.stopReason }),
|
|
174
|
+
...(message.errorMessage === undefined ? {} : { errorMessage: message.errorMessage }),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function lineReader(onLine: (line: string) => void): (chunk: Buffer) => void {
|
|
179
|
+
let buffer = '';
|
|
180
|
+
return (chunk) => {
|
|
181
|
+
buffer += chunk.toString('utf8');
|
|
182
|
+
for (let index = buffer.indexOf('\n'); index !== -1; index = buffer.indexOf('\n')) {
|
|
183
|
+
const line = buffer.slice(0, index).trim();
|
|
184
|
+
buffer = buffer.slice(index + 1);
|
|
185
|
+
if (line !== '') onLine(line);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** RPC mode reserves stdout for protocol records, but a child extension can still print there; such a line must not crash the parent. */
|
|
191
|
+
function parseEvent(line: string): ({ type: string } & Record<string, unknown>) | undefined {
|
|
192
|
+
try {
|
|
193
|
+
const value: unknown = JSON.parse(line);
|
|
194
|
+
return typeof value === 'object' && value !== null && typeof (value as { type?: unknown }).type === 'string' ? (value as { type: string } & Record<string, unknown>) : undefined;
|
|
195
|
+
} catch (error) {
|
|
196
|
+
if (error instanceof SyntaxError) return undefined;
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Signals the child's whole process group. A group that is already gone (ESRCH) or not ours (EPERM) is left alone. */
|
|
202
|
+
export function signalGroup(pid: number, signal: NodeJS.Signals): void {
|
|
203
|
+
try {
|
|
204
|
+
process.kill(-pid, signal);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
207
|
+
if (code !== 'ESRCH' && code !== 'EPERM') throw error;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function killGroup(pid: number | undefined): void {
|
|
212
|
+
if (pid === undefined) return;
|
|
213
|
+
signalGroup(pid, 'SIGTERM');
|
|
214
|
+
setTimeout(() => signalGroup(pid, 'SIGKILL'), KILL_GRACE_MS).unref();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
interface ChildState {
|
|
218
|
+
usage: Usage;
|
|
219
|
+
stderr: string;
|
|
220
|
+
aborted: boolean;
|
|
221
|
+
stopped: boolean;
|
|
222
|
+
/** Why Pi refused the first prompt. */
|
|
223
|
+
refused?: string;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function outcomeOf(last: AssistantRecord | undefined, state: ChildState, code: number | null): ChildOutcome {
|
|
227
|
+
const base = { finalText: last?.text ?? '', usage: state.usage, stderr: state.stderr };
|
|
228
|
+
if (state.aborted) return { ...base, failure: 'Subagent was aborted' };
|
|
229
|
+
if (state.stopped) return { ...base, failure: 'Subagent was stopped with stop_agent' };
|
|
230
|
+
if (state.refused !== undefined) return { ...base, failure: state.refused };
|
|
231
|
+
if (last?.stopReason === 'error' || last?.stopReason === 'aborted') return { ...base, failure: last.errorMessage ?? `subagent stopped: ${last.stopReason}` };
|
|
232
|
+
if (code !== 0) return { ...base, failure: `pi exited with code ${code}: ${state.stderr.trim().split('\n').at(-1) ?? ''}`.trim() };
|
|
233
|
+
return base;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
interface RpcResponse {
|
|
237
|
+
id?: string;
|
|
238
|
+
success: boolean;
|
|
239
|
+
error?: string;
|
|
240
|
+
data?: { disposition?: string; steering?: string[]; followUp?: string[] };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Runs one `pi --mode rpc` child in its own process group, so abort and shutdown can stop it and everything it started.
|
|
245
|
+
* The child stays open while its run is active or a command awaits its response, so a steer always lands in a run;
|
|
246
|
+
* once the run settles with nothing outstanding, closing stdin makes Pi save the session and exit.
|
|
247
|
+
* Pi's `RpcClient` cannot start or end this child (no detached spawn, stdin close, `extension_ui_response`, or exit
|
|
248
|
+
* code); docs/native-gaps.md records each missing mechanism.
|
|
249
|
+
*/
|
|
250
|
+
export function startChild(options: ChildOptions): ChildHandle {
|
|
251
|
+
const [command, ...prefix] = options.command;
|
|
252
|
+
if (command === undefined) throw new Error('startChild needs a command');
|
|
253
|
+
const child = spawn(command, [...prefix, ...options.args], { cwd: options.cwd, env: options.env, stdio: ['pipe', 'pipe', 'pipe'], detached: true });
|
|
254
|
+
const state: ChildState = { usage: zeroUsage(), stderr: '', aborted: false, stopped: false };
|
|
255
|
+
let last: AssistantRecord | undefined;
|
|
256
|
+
let open = true;
|
|
257
|
+
let idle = true;
|
|
258
|
+
let steered = false;
|
|
259
|
+
let nextId = 0;
|
|
260
|
+
let grace: ReturnType<typeof setTimeout> | undefined;
|
|
261
|
+
const waiting = new Map<string, (response: RpcResponse) => void>();
|
|
262
|
+
|
|
263
|
+
const write = (record: Record<string, unknown>) => {
|
|
264
|
+
if (open) child.stdin.write(`${JSON.stringify(record)}\n`);
|
|
265
|
+
};
|
|
266
|
+
const close = () => {
|
|
267
|
+
if (!open) return;
|
|
268
|
+
open = false;
|
|
269
|
+
child.stdin.end();
|
|
270
|
+
};
|
|
271
|
+
const request = (record: Record<string, unknown>, onResponse: (response: RpcResponse) => void) => {
|
|
272
|
+
const id = `pstack-${nextId++}`;
|
|
273
|
+
waiting.set(id, onResponse);
|
|
274
|
+
write({ ...record, id });
|
|
275
|
+
};
|
|
276
|
+
const prompt = (text: string, images: readonly ImageContent[], onResponse: (response: RpcResponse) => void, streamingBehavior?: 'steer') =>
|
|
277
|
+
request({ type: 'prompt', message: text, ...(images.length === 0 ? {} : { images }), ...(streamingBehavior === undefined ? {} : { streamingBehavior }) }, (response) => {
|
|
278
|
+
if (response.data?.disposition === 'started') idle = false;
|
|
279
|
+
onResponse(response);
|
|
280
|
+
});
|
|
281
|
+
// A steer can reach Pi just as its run ends and stay queued with no run to take it, so it goes out as a new prompt.
|
|
282
|
+
const closeWhenSettled = () => {
|
|
283
|
+
if (!open || !idle || waiting.size > 0) return;
|
|
284
|
+
if (!steered || state.stopped) return close();
|
|
285
|
+
steered = false;
|
|
286
|
+
request({ type: 'clear_queue' }, (response) => {
|
|
287
|
+
const left = [...(response.data?.steering ?? []), ...(response.data?.followUp ?? [])];
|
|
288
|
+
if (left.length > 0) prompt(left.join('\n\n'), [], () => undefined);
|
|
289
|
+
});
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const kill = () => {
|
|
293
|
+
state.aborted = true;
|
|
294
|
+
killGroup(child.pid);
|
|
295
|
+
};
|
|
296
|
+
const stop = () => {
|
|
297
|
+
if (state.stopped || state.aborted) return;
|
|
298
|
+
state.stopped = true;
|
|
299
|
+
request({ type: 'clear_queue' }, () => undefined);
|
|
300
|
+
request({ type: 'abort' }, () => undefined);
|
|
301
|
+
grace = setTimeout(() => killGroup(child.pid), KILL_GRACE_MS);
|
|
302
|
+
grace.unref();
|
|
303
|
+
};
|
|
304
|
+
const steer = (text: string, images: readonly ImageContent[]) =>
|
|
305
|
+
new Promise<void>((resolve, reject) => {
|
|
306
|
+
if (!open || state.stopped || state.aborted) return reject(new Error('The subagent has finished its run.'));
|
|
307
|
+
steered = true;
|
|
308
|
+
prompt(text, images, (response) => (response.success ? resolve() : reject(new Error(response.error ?? 'Pi refused the message.'))), 'steer');
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const onRecord = (record: { type: string } & Record<string, unknown>) => {
|
|
312
|
+
if (record.type === 'response') {
|
|
313
|
+
const response = record as unknown as RpcResponse;
|
|
314
|
+
const onResponse = response.id === undefined ? undefined : waiting.get(response.id);
|
|
315
|
+
if (onResponse === undefined) return;
|
|
316
|
+
waiting.delete(response.id as string);
|
|
317
|
+
onResponse(response);
|
|
318
|
+
return closeWhenSettled();
|
|
319
|
+
}
|
|
320
|
+
if (record.type === 'extension_ui_request') {
|
|
321
|
+
if (DIALOG_METHODS.has(String(record['method']))) write({ type: 'extension_ui_response', id: record['id'], cancelled: true });
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
options.onEvent?.(record);
|
|
325
|
+
if (record.type === 'agent_start') idle = false;
|
|
326
|
+
const usage = usageOf(record);
|
|
327
|
+
if (usage !== undefined) state.usage = addUsage(state.usage, usage);
|
|
328
|
+
last = readAssistant(record) ?? last;
|
|
329
|
+
if (record.type === 'agent_settled') {
|
|
330
|
+
idle = true;
|
|
331
|
+
closeWhenSettled();
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
child.stdout.on(
|
|
336
|
+
'data',
|
|
337
|
+
lineReader((line) => {
|
|
338
|
+
const record = parseEvent(line);
|
|
339
|
+
if (record === undefined) state.stderr = `${state.stderr}${line}\n`.slice(-8_192);
|
|
340
|
+
else onRecord(record);
|
|
341
|
+
}),
|
|
342
|
+
);
|
|
343
|
+
child.stderr.on('data', (chunk: Buffer) => {
|
|
344
|
+
state.stderr = `${state.stderr}${chunk.toString('utf8')}`.slice(-8_192);
|
|
345
|
+
});
|
|
346
|
+
// A child that exits or is killed before it reads a large prompt closes the pipe. Unhandled, that EPIPE would be an
|
|
347
|
+
// uncaught exception in the parent, and interactive Pi exits on one; the child's exit already reports the failure.
|
|
348
|
+
child.stdin.on('error', (error: NodeJS.ErrnoException) => {
|
|
349
|
+
if (error.code !== 'EPIPE' && error.code !== 'ECONNRESET') state.stderr = `${state.stderr}stdin: ${error.message}\n`.slice(-8_192);
|
|
350
|
+
});
|
|
351
|
+
if (options.signal?.aborted) kill();
|
|
352
|
+
else options.signal?.addEventListener('abort', kill, { once: true });
|
|
353
|
+
if (state.aborted || (options.prompt.trim() === '' && (options.images ?? []).length === 0)) close();
|
|
354
|
+
else
|
|
355
|
+
prompt(options.prompt, options.images ?? [], (response) => {
|
|
356
|
+
if (!response.success) state.refused = response.error ?? 'Pi refused the prompt.';
|
|
357
|
+
});
|
|
358
|
+
const done = new Promise<ChildOutcome>((resolveDone) => {
|
|
359
|
+
child.on('error', (error) => resolveDone({ finalText: '', usage: state.usage, stderr: state.stderr, failure: `could not start pi: ${error.message}` }));
|
|
360
|
+
child.on('close', (code) => {
|
|
361
|
+
open = false;
|
|
362
|
+
clearTimeout(grace);
|
|
363
|
+
options.signal?.removeEventListener('abort', kill);
|
|
364
|
+
resolveDone(outcomeOf(last, state, code));
|
|
365
|
+
for (const onResponse of waiting.values()) onResponse({ success: false, error: 'The subagent exited.' });
|
|
366
|
+
waiting.clear();
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
return { done, kill, stop, steer, pid: child.pid };
|
|
370
|
+
}
|