@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
package/src/loop.ts
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import { Type } from 'typebox';
|
|
3
|
+
import { isOneShot } from './modes.ts';
|
|
4
|
+
|
|
5
|
+
const DAY_MS = 86_400_000;
|
|
6
|
+
const UNIT_MS: Record<string, number> = { s: 1_000, m: 60_000, h: 3_600_000, d: DAY_MS };
|
|
7
|
+
const DURATION = /^(?:\d+[smhd])+$/;
|
|
8
|
+
// setTimeout fires at once for delays past 2^31-1 ms (about 24.8 days), which would turn a long loop into a runaway.
|
|
9
|
+
const MAX_DELAY_MS = 24 * DAY_MS;
|
|
10
|
+
const TOO_LONG = 'Loop intervals can be at most 24d.';
|
|
11
|
+
|
|
12
|
+
/** Milliseconds for a duration such as 30s, 5m, 1h30m; undefined when the text is not a duration. */
|
|
13
|
+
export function parseDuration(text: string): number | undefined {
|
|
14
|
+
if (!DURATION.test(text)) return undefined;
|
|
15
|
+
const total = [...text.matchAll(/(\d+)([smhd])/g)].reduce((sum, [, count, unit]) => sum + Number(count) * (UNIT_MS[unit ?? 's'] ?? 0), 0);
|
|
16
|
+
return total > 0 ? total : undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function delayOf(text: string): number {
|
|
20
|
+
const ms = parseDuration(text);
|
|
21
|
+
if (ms === undefined) throw new Error(`"${text}" is not a duration such as 30s, 10m, or 1h.`);
|
|
22
|
+
if (ms > MAX_DELAY_MS) throw new Error(TOO_LONG);
|
|
23
|
+
return ms;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type LoopCommand = { kind: 'status' } | { kind: 'stop' } | { kind: 'start'; prompt: string; intervalMs?: number } | { kind: 'error'; message: string };
|
|
27
|
+
|
|
28
|
+
export function parseLoopArgs(args: string): LoopCommand {
|
|
29
|
+
const trimmed = args.trim();
|
|
30
|
+
if (trimmed === '') return { kind: 'status' };
|
|
31
|
+
if (trimmed === 'stop' || trimmed === 'off') return { kind: 'stop' };
|
|
32
|
+
const [, first = '', rest = ''] = /^(\S+)\s*([\s\S]*)$/.exec(trimmed) ?? [];
|
|
33
|
+
const intervalMs = parseDuration(first);
|
|
34
|
+
if (intervalMs === undefined) return { kind: 'start', prompt: trimmed };
|
|
35
|
+
if (intervalMs > MAX_DELAY_MS) return { kind: 'error', message: TOO_LONG };
|
|
36
|
+
return rest.trim() === '' ? { kind: 'error', message: '/loop needs a prompt: /loop [interval] <prompt>' } : { kind: 'start', prompt: rest.trim(), intervalMs };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface Loop {
|
|
40
|
+
prompt: string;
|
|
41
|
+
intervalMs?: number;
|
|
42
|
+
iteration: number;
|
|
43
|
+
nextWakeMs?: number;
|
|
44
|
+
timer?: ReturnType<typeof setTimeout>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const formatMs = (ms: number) => (ms % 86_400_000 === 0 ? `${ms / 86_400_000}d` : ms % 3_600_000 === 0 ? `${ms / 3_600_000}h` : ms % 60_000 === 0 ? `${ms / 60_000}m` : `${Math.round(ms / 1_000)}s`);
|
|
48
|
+
|
|
49
|
+
/** Loop state rides in the system prompt, so the prompt itself stays verbatim and a template prompt keeps it too. */
|
|
50
|
+
function loopSection(loop: Loop): string {
|
|
51
|
+
if (loop.intervalMs !== undefined) {
|
|
52
|
+
return `A /loop is running: iteration ${loop.iteration}, every ${formatMs(loop.intervalMs)}. The user ends it with /loop stop; call the Loop tool with stop: true once the goal is met.`;
|
|
53
|
+
}
|
|
54
|
+
return `A /loop is running: iteration ${loop.iteration}, agent-paced. Call the Loop tool with wake_in (like 10m) to schedule the next run, or stop: true to end it. Without a wake_in call the loop ends after this turn.`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface LoopOptions {
|
|
58
|
+
/** True while this session waits on work that will wake the agent, such as background subagents. */
|
|
59
|
+
hasPendingWork: () => boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function createLoopController(pi: ExtensionAPI, options: LoopOptions) {
|
|
63
|
+
let loop: Loop | undefined;
|
|
64
|
+
let lastCtx: ExtensionContext | undefined;
|
|
65
|
+
|
|
66
|
+
const notify = (ctx: ExtensionContext | undefined, message: string) => {
|
|
67
|
+
if (ctx?.hasUI) ctx.ui.notify(message, 'info');
|
|
68
|
+
};
|
|
69
|
+
const clear = () => {
|
|
70
|
+
if (loop?.timer !== undefined) clearTimeout(loop.timer);
|
|
71
|
+
loop = undefined;
|
|
72
|
+
};
|
|
73
|
+
const stop = (ctx: ExtensionContext | undefined, reason: string) => {
|
|
74
|
+
clear();
|
|
75
|
+
notify(ctx, `Loop stopped: ${reason}`);
|
|
76
|
+
};
|
|
77
|
+
const fire = () => {
|
|
78
|
+
if (loop === undefined) return;
|
|
79
|
+
const { timer: _timer, nextWakeMs: _wake, ...rest } = loop;
|
|
80
|
+
loop = { ...rest, iteration: rest.iteration + 1 };
|
|
81
|
+
// Expansion lets a loop run a skill or template each time.
|
|
82
|
+
pi.sendUserMessage(loop.prompt, { expandPromptTemplates: true, ...(lastCtx === undefined || lastCtx.isIdle() ? {} : { deliverAs: 'followUp' as const }) });
|
|
83
|
+
};
|
|
84
|
+
const settled = (ctx: ExtensionContext, aborted: boolean) => {
|
|
85
|
+
lastCtx = ctx;
|
|
86
|
+
if (loop === undefined || loop.timer !== undefined) return;
|
|
87
|
+
if (aborted) return stop(ctx, 'the run was aborted.');
|
|
88
|
+
const delay = loop.intervalMs ?? loop.nextWakeMs;
|
|
89
|
+
if (delay !== undefined) loop = { ...loop, timer: setTimeout(fire, delay) };
|
|
90
|
+
else if (!options.hasPendingWork()) stop(ctx, 'the agent scheduled no next wake.');
|
|
91
|
+
};
|
|
92
|
+
const start = (ctx: ExtensionContext, prompt: string, intervalMs: number | undefined) => {
|
|
93
|
+
if (loop !== undefined) stop(ctx, 'replaced by a new /loop.');
|
|
94
|
+
lastCtx = ctx;
|
|
95
|
+
loop = { prompt, iteration: 0, ...(intervalMs === undefined ? {} : { intervalMs }) };
|
|
96
|
+
fire();
|
|
97
|
+
};
|
|
98
|
+
/** An agent arms a loop mid-turn, so its first run waits for this turn to settle and then for the first delay. */
|
|
99
|
+
const arm = (ctx: ExtensionContext, prompt: string, schedule: { intervalMs: number } | { firstWakeMs: number }) => {
|
|
100
|
+
if (loop !== undefined) stop(ctx, 'replaced by a new loop.');
|
|
101
|
+
loop = 'intervalMs' in schedule ? { prompt, iteration: 0, intervalMs: schedule.intervalMs } : { prompt, iteration: 0, nextWakeMs: schedule.firstWakeMs };
|
|
102
|
+
};
|
|
103
|
+
const status = () => (loop === undefined ? 'No loop is running.' : `Loop: "${loop.prompt}" (iteration ${loop.iteration}${loop.intervalMs === undefined ? ', agent-paced' : `, every ${formatMs(loop.intervalMs)}`})`);
|
|
104
|
+
const wake = (delayMs: number) => {
|
|
105
|
+
if (loop === undefined) throw new Error('No loop is running. Start one with prompt plus interval or wake_in, or ask the user to run /loop.');
|
|
106
|
+
if (loop.intervalMs !== undefined) throw new Error(`This loop runs every ${formatMs(loop.intervalMs)}; only the user can change its interval. Pass stop: true to end it.`);
|
|
107
|
+
// A wake already scheduled is replaced, so the new delay counts from the end of this turn.
|
|
108
|
+
if (loop.timer !== undefined) clearTimeout(loop.timer);
|
|
109
|
+
const { timer: _timer, ...rest } = loop;
|
|
110
|
+
loop = { ...rest, nextWakeMs: delayMs };
|
|
111
|
+
};
|
|
112
|
+
const section = () => (loop === undefined ? undefined : loopSection(loop));
|
|
113
|
+
return { notify, stop, settled, start, arm, status, wake, clear, section, active: () => loop !== undefined };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
type LoopController = ReturnType<typeof createLoopController>;
|
|
117
|
+
|
|
118
|
+
const ONE_SHOT = '/loop needs an interactive or RPC session; print and JSON runs exit when the agent settles.';
|
|
119
|
+
|
|
120
|
+
function handleLoopCommand(controller: LoopController, args: string, ctx: ExtensionCommandContext): void {
|
|
121
|
+
const command = parseLoopArgs(args);
|
|
122
|
+
if (command.kind === 'status') controller.notify(ctx, controller.status());
|
|
123
|
+
else if (command.kind === 'stop') controller.stop(ctx, 'stopped by /loop stop.');
|
|
124
|
+
else if (command.kind === 'error') controller.notify(ctx, command.message);
|
|
125
|
+
else if (isOneShot(ctx.mode)) process.stderr.write(`${ONE_SHOT}\n`);
|
|
126
|
+
else controller.start(ctx, command.prompt, command.intervalMs);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const LoopParams = Type.Object({
|
|
130
|
+
prompt: Type.Optional(Type.String({ description: 'Start (or replace) a loop that sends this prompt each run. It may be a skill or template command such as /poteto-mode <task>.' })),
|
|
131
|
+
interval: Type.Optional(Type.String({ description: 'With prompt: a fixed cadence such as 30m or 1h; the first run starts that long after this turn ends.' })),
|
|
132
|
+
wake_in: Type.Optional(Type.String({ description: 'With prompt: the first run of an agent-paced loop. Alone: the next run of the running agent-paced loop. A duration such as 10m, at most 24d.' })),
|
|
133
|
+
stop: Type.Optional(Type.Boolean({ description: 'End the loop once its goal is met.' })),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
function loopToolText(controller: LoopController, params: { prompt?: string; interval?: string; wake_in?: string; stop?: boolean }, ctx: ExtensionContext): string {
|
|
137
|
+
if (isOneShot(ctx.mode)) throw new Error(`${ONE_SHOT} Do the work now instead of scheduling it.`);
|
|
138
|
+
if (params.stop === true) {
|
|
139
|
+
if (!controller.active()) throw new Error('No loop is running.');
|
|
140
|
+
controller.stop(ctx, 'the agent ended it.');
|
|
141
|
+
return 'Loop stopped.';
|
|
142
|
+
}
|
|
143
|
+
if (params.prompt !== undefined) {
|
|
144
|
+
if (params.interval !== undefined) {
|
|
145
|
+
const intervalMs = delayOf(params.interval);
|
|
146
|
+
controller.arm(ctx, params.prompt, { intervalMs });
|
|
147
|
+
return `Loop armed: "${params.prompt}" every ${formatMs(intervalMs)}; the first run starts ${formatMs(intervalMs)} after this turn ends.`;
|
|
148
|
+
}
|
|
149
|
+
if (params.wake_in === undefined) throw new Error('To start a loop, pass interval for a fixed cadence or wake_in for the first run of an agent-paced loop.');
|
|
150
|
+
const firstWakeMs = delayOf(params.wake_in);
|
|
151
|
+
controller.arm(ctx, params.prompt, { firstWakeMs });
|
|
152
|
+
return `Loop armed: "${params.prompt}", agent-paced; the first run starts ${formatMs(firstWakeMs)} after this turn ends.`;
|
|
153
|
+
}
|
|
154
|
+
if (params.interval !== undefined) throw new Error('interval applies only when starting a loop with prompt.');
|
|
155
|
+
if (params.wake_in === undefined) throw new Error('Pass prompt with interval or wake_in to start a loop, wake_in to schedule the next run, or stop: true.');
|
|
156
|
+
const delay = delayOf(params.wake_in);
|
|
157
|
+
controller.wake(delay);
|
|
158
|
+
return `Next loop run in ${formatMs(delay)}, after this turn ends.`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function registerLoopTool(pi: ExtensionAPI, controller: LoopController): void {
|
|
162
|
+
pi.registerTool({
|
|
163
|
+
name: 'Loop',
|
|
164
|
+
label: 'Loop',
|
|
165
|
+
description: '/loop for you: start a loop that re-sends a prompt at a fixed interval or at wakes you pick, schedule the next wake of an agent-paced loop, or stop the loop once its goal is met. Use it when a playbook says to arm /loop.',
|
|
166
|
+
promptSnippet: 'Start a /loop, schedule its next wake, or stop it',
|
|
167
|
+
parameters: LoopParams,
|
|
168
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
169
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
170
|
+
return { content: [{ type: 'text', text: loopToolText(controller, params, ctx) }], details: undefined };
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** /loop: run a prompt at a fixed interval, or at wakes the agent picks, until stopped. */
|
|
176
|
+
export function registerLoop(pi: ExtensionAPI, options: LoopOptions): { section: () => string | undefined } {
|
|
177
|
+
const controller = createLoopController(pi, options);
|
|
178
|
+
pi.on('session_start', (_event, ctx) => {
|
|
179
|
+
if (isOneShot(ctx.mode)) pi.setActiveTools(pi.getActiveTools().filter((name) => name !== 'Loop'));
|
|
180
|
+
});
|
|
181
|
+
pi.on('agent_settled', (event, ctx) => controller.settled(ctx, event.aborted));
|
|
182
|
+
pi.on('session_shutdown', () => controller.clear());
|
|
183
|
+
pi.registerCommand('loop', {
|
|
184
|
+
description: 'Run a prompt repeatedly: /loop [interval] <prompt> (interval like 30s, 5m, 1h, at most 24d); /loop stop',
|
|
185
|
+
handler: async (args, ctx) => handleLoopCommand(controller, args, ctx),
|
|
186
|
+
});
|
|
187
|
+
registerLoopTool(pi, controller);
|
|
188
|
+
return { section: controller.section };
|
|
189
|
+
}
|
package/src/mode.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext, ThemeColor } from '@earendil-works/pi-coding-agent';
|
|
5
|
+
import { parseFrontmatter } from '@earendil-works/pi-coding-agent';
|
|
6
|
+
|
|
7
|
+
export const MODE_ENTRY = 'pstack/mode';
|
|
8
|
+
const STATUS_KEY = 'pstack-mode';
|
|
9
|
+
|
|
10
|
+
/** The mode on the branch. `path` is the loaded skill's file; absent when no loaded skill has the name. */
|
|
11
|
+
export interface ActiveMode {
|
|
12
|
+
skill: string;
|
|
13
|
+
path?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type ModeCommand = { kind: 'status' } | { kind: 'off' } | { kind: 'set'; skill: string; task: string };
|
|
17
|
+
|
|
18
|
+
export function parseModeArgs(args: string): ModeCommand {
|
|
19
|
+
const trimmed = args.trim();
|
|
20
|
+
if (trimmed === '') return { kind: 'status' };
|
|
21
|
+
if (trimmed === 'off') return { kind: 'off' };
|
|
22
|
+
const match = /^\/?(\S+)(?:\s([\s\S]*))?$/.exec(trimmed);
|
|
23
|
+
return { kind: 'set', skill: match?.[1] ?? trimmed, task: (match?.[2] ?? '').trim() };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface BranchEntry {
|
|
27
|
+
type: string;
|
|
28
|
+
customType?: string;
|
|
29
|
+
data?: unknown;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const SKILL_NAME = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The last mode entry on the branch, as a skill name only: a session file can come from elsewhere, so a stored path
|
|
36
|
+
* is ignored and a value that is not a plausible skill name (it would reach the system prompt) counts as no mode.
|
|
37
|
+
*/
|
|
38
|
+
export function modeFromBranch(entries: readonly BranchEntry[]): ActiveMode | undefined {
|
|
39
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
40
|
+
const entry = entries[index];
|
|
41
|
+
if (entry?.type !== 'custom' || entry.customType !== MODE_ENTRY) continue;
|
|
42
|
+
const skill = (entry.data as { skill?: unknown } | null)?.skill;
|
|
43
|
+
return typeof skill === 'string' && SKILL_NAME.test(skill) ? { skill } : undefined;
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The mode text for every request: the skill block in the same shape as Pi's own
|
|
50
|
+
* `/skill:name` expansion (agent-session.js `_expandSkillCommand`), which Pi does not export as a function.
|
|
51
|
+
*/
|
|
52
|
+
export function modeSection(mode: ActiveMode): string {
|
|
53
|
+
if (mode.path === undefined) return `Mode ${mode.skill} is on, but no loaded skill has that name. Tell the user to run /mode off or /mode <skill>.`;
|
|
54
|
+
if (!existsSync(mode.path)) return `Mode ${mode.skill} is on, but its skill file ${mode.path} no longer exists. Tell the user to run /mode off or /mode <skill>.`;
|
|
55
|
+
// The frontmatter, including `reminder`, stays out of the model's context in a mode (MODE-004).
|
|
56
|
+
const { body } = parseFrontmatter(readFileSync(mode.path, 'utf8'));
|
|
57
|
+
return [`Mode ${mode.skill} is on for every turn until the user runs /mode off.`, `<skill name="${mode.skill}" location="${mode.path}">`, `References are relative to ${dirname(mode.path)}.`, '', body.trim(), '</skill>'].join('\n');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// A mode's badge shows the skill's `icon` (default: lightning) and `color`. Icon names map to terminal glyphs and
|
|
61
|
+
// color names to the nearest theme color.
|
|
62
|
+
const BADGE_ICONS: Record<string, string> = {
|
|
63
|
+
crown: '♛',
|
|
64
|
+
lightning: '⚡',
|
|
65
|
+
code: '</>',
|
|
66
|
+
terminal: '>_',
|
|
67
|
+
bug: '✱',
|
|
68
|
+
'git-branch': '⎇',
|
|
69
|
+
'book-open': '❐',
|
|
70
|
+
beaker: '⚗',
|
|
71
|
+
shield: '⛨',
|
|
72
|
+
rocket: '➶',
|
|
73
|
+
};
|
|
74
|
+
const BADGE_COLORS: Record<string, ThemeColor> = { green: 'success', red: 'error', yellow: 'warning', orange: 'warning', cyan: 'accent', blue: 'accent', purple: 'accent', magenta: 'accent', brand: 'accent' };
|
|
75
|
+
|
|
76
|
+
/** A skill carries `icon` and `color` under `metadata`, or at the top level of its frontmatter. */
|
|
77
|
+
function skillMetadata(mode: ActiveMode): Record<string, unknown> {
|
|
78
|
+
if (mode.path === undefined || !existsSync(mode.path)) return {};
|
|
79
|
+
const { frontmatter } = parseFrontmatter(readFileSync(mode.path, 'utf8'));
|
|
80
|
+
const metadata = typeof frontmatter['metadata'] === 'object' && frontmatter['metadata'] !== null ? (frontmatter['metadata'] as Record<string, unknown>) : {};
|
|
81
|
+
return { icon: frontmatter['icon'], color: frontmatter['color'], ...metadata };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function modeBadge(mode: ActiveMode, metadata: Record<string, unknown> = skillMetadata(mode)): { text: string; color?: ThemeColor } {
|
|
85
|
+
const iconName = String(metadata['icon']);
|
|
86
|
+
const colorName = String(metadata['color']);
|
|
87
|
+
const icon = Object.hasOwn(BADGE_ICONS, iconName) ? BADGE_ICONS[iconName] : BADGE_ICONS['lightning'];
|
|
88
|
+
const color = Object.hasOwn(BADGE_COLORS, colorName) ? BADGE_COLORS[colorName] : undefined;
|
|
89
|
+
return { text: `${icon} mode: ${mode.skill}`, ...(color === undefined ? {} : { color }) };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function loadedSkills(pi: ExtensionAPI): Map<string, string> {
|
|
93
|
+
const skills = new Map<string, string>();
|
|
94
|
+
for (const command of pi.getCommands()) {
|
|
95
|
+
if (command.source === 'skill' && command.name.startsWith('skill:')) skills.set(command.name.slice('skill:'.length), command.sourceInfo.path);
|
|
96
|
+
}
|
|
97
|
+
return skills;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function registerMode(pi: ExtensionAPI): { section: () => string | undefined } {
|
|
101
|
+
let active: ActiveMode | undefined;
|
|
102
|
+
const show = (ctx: ExtensionContext) => {
|
|
103
|
+
if (!ctx.hasUI) return;
|
|
104
|
+
const badge = active === undefined ? undefined : modeBadge(active);
|
|
105
|
+
ctx.ui.setStatus(STATUS_KEY, badge === undefined ? undefined : badge.color === undefined ? badge.text : ctx.ui.theme.fg(badge.color, badge.text));
|
|
106
|
+
};
|
|
107
|
+
// The skill is looked up by name among loaded skills; a stored path is never read, since it can be stale or crafted.
|
|
108
|
+
const restore = (ctx: ExtensionContext) => {
|
|
109
|
+
const stored = modeFromBranch(ctx.sessionManager.getBranch());
|
|
110
|
+
active = stored === undefined ? undefined : (tryResolve(stored.skill) ?? { skill: stored.skill });
|
|
111
|
+
show(ctx);
|
|
112
|
+
};
|
|
113
|
+
pi.on('session_start', (_event, ctx) => restore(ctx));
|
|
114
|
+
pi.on('session_tree', (_event, ctx) => restore(ctx));
|
|
115
|
+
|
|
116
|
+
const resolveSkill = (name: string): ActiveMode => {
|
|
117
|
+
const skills = loadedSkills(pi);
|
|
118
|
+
const path = skills.get(name);
|
|
119
|
+
if (path === undefined) throw new Error(`No skill named "${name}". Skills that can back a mode: ${[...skills.keys()].toSorted().join(', ')}.`);
|
|
120
|
+
return { skill: name, path };
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
function tryResolve(name: string): ActiveMode | undefined {
|
|
124
|
+
try {
|
|
125
|
+
return resolveSkill(name);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (error instanceof Error && error.message.startsWith('No skill named')) return undefined;
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const handle = (args: string, ctx: ExtensionCommandContext) => {
|
|
133
|
+
const command = parseModeArgs(args);
|
|
134
|
+
if (command.kind === 'status') return ctx.ui.notify(active === undefined ? 'No mode is on.' : `Mode on: ${active.skill} (${active.path ?? 'no loaded skill has that name'}).`, 'info');
|
|
135
|
+
active = command.kind === 'off' ? undefined : resolveSkill(command.skill);
|
|
136
|
+
pi.appendEntry(MODE_ENTRY, active === undefined ? null : { skill: active.skill });
|
|
137
|
+
show(ctx);
|
|
138
|
+
ctx.ui.notify(active === undefined ? 'Mode off.' : `Mode on: ${active.skill}. /mode off exits.`, 'info');
|
|
139
|
+
if (command.kind === 'set' && command.task !== '') pi.sendUserMessage(command.task, { expandPromptTemplates: true, ...(ctx.isIdle() ? {} : { deliverAs: 'followUp' as const }) });
|
|
140
|
+
return undefined;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
pi.registerCommand('mode', {
|
|
144
|
+
description: 'Keep a skill on every turn: /mode <skill> [task], /mode off, /mode',
|
|
145
|
+
getArgumentCompletions: (prefix) => ['off', ...[...loadedSkills(pi).keys()].toSorted()].filter((name) => name.startsWith(prefix.trim())).map((name) => ({ value: name, label: name })),
|
|
146
|
+
handler: async (args, ctx) => {
|
|
147
|
+
try {
|
|
148
|
+
handle(args, ctx);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
ctx.ui.notify((error as Error).message, 'error');
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return { section: () => (active === undefined ? undefined : modeSection(active)) };
|
|
156
|
+
}
|
package/src/modes.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ExtensionContext } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import { childIdentity } from './child-env.ts';
|
|
3
|
+
|
|
4
|
+
/** Print and JSON runs exit once the agent settles, and so does every subagent, an RPC child that its owner closes then. */
|
|
5
|
+
export const isOneShot = (mode: ExtensionContext['mode']): boolean => mode === 'print' || mode === 'json' || childIdentity.depth > 0;
|
|
6
|
+
|
|
7
|
+
/** Whether a user can answer a dialog. A subagent's owner dismisses every dialog, so a subagent has nobody to ask. */
|
|
8
|
+
export const canAsk = (ctx: Pick<ExtensionContext, 'hasUI'>): boolean => ctx.hasUI && childIdentity.depth === 0;
|
package/src/paths.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { dirname, join, resolve } from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
|
|
4
|
+
import { getAgentDir } from '@earendil-works/pi-coding-agent';
|
|
5
|
+
import { childIdentity } from './child-env.ts';
|
|
6
|
+
|
|
7
|
+
export const PACKAGE_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
8
|
+
export const SKILLS_DIR = join(PACKAGE_ROOT, 'skills');
|
|
9
|
+
export const AGENTS_DIR = join(PACKAGE_ROOT, 'agents');
|
|
10
|
+
|
|
11
|
+
/** Pi-owned state for this package. Follows PI_CODING_AGENT_DIR, so tests and users can relocate it. */
|
|
12
|
+
export function stateDir(): string {
|
|
13
|
+
return join(getAgentDir(), 'pstack');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** The model rule: pstack's per-role model choices, added to every request's system prompt. */
|
|
17
|
+
export function rulePath(): string {
|
|
18
|
+
return join(stateDir(), 'pstack-models.md');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Webhook routines' sender keys, one `<slug>/credentials.json` each. */
|
|
22
|
+
export function routinesDir(): string {
|
|
23
|
+
return join(stateDir(), 'routines');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Pi's rule for a session folder name, so per-workspace state lines up with `<agentDir>/sessions/<slug>`. The live
|
|
28
|
+
* `sessionManager.getSessionDir()` needs a session and names the current one, which is `<workspace>/subagents` in a
|
|
29
|
+
* subagent, so the rule stays here for a working directory (docs/native-gaps.md).
|
|
30
|
+
*/
|
|
31
|
+
export function workspaceSlug(cwd: string): string {
|
|
32
|
+
return `--${resolve(cwd)
|
|
33
|
+
.replace(/^[/\\]/, '')
|
|
34
|
+
.replace(/[/\\:]/g, '-')}--`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* State for one working directory, so transcripts and stores never cross workspaces (pstack's own rule). A subagent
|
|
39
|
+
* uses its main chat's, so an isolated worker in its own worktree shares the tree's Projects, records, and transcripts.
|
|
40
|
+
*/
|
|
41
|
+
export function workspaceStateDir(cwd: string, inherited: string | undefined = childIdentity.workspaceState): string {
|
|
42
|
+
return inherited ?? join(stateDir(), 'workspaces', workspaceSlug(cwd));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Subagent transcripts, records, and run locks for this workspace. */
|
|
46
|
+
export function subagentDir(cwd: string): string {
|
|
47
|
+
return join(workspaceStateDir(cwd), 'subagents');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Projects of this workspace: `<slug>/notes.md` is a project's tracker and `<slug>/store/` its store. */
|
|
51
|
+
export function projectsDir(cwd: string): string {
|
|
52
|
+
return join(workspaceStateDir(cwd), 'projects');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Files a session's tools produce for the user, such as visualize's SVG charts. */
|
|
56
|
+
export function artifactsDir(cwd: string, sessionId: string): string {
|
|
57
|
+
return join(workspaceStateDir(cwd), 'artifacts', sessionId);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function worktreesDir(cwd: string): string {
|
|
61
|
+
return join(workspaceStateDir(cwd), 'worktrees');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The store of the main chat that started this subagent tree, which autopilot owners write their trails into. */
|
|
65
|
+
export function rootStoreDir(cwd: string, sessionId: string, inherited: string | undefined): string {
|
|
66
|
+
return inherited ?? storeDir(cwd, sessionId);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** "The current agent's store" that orchestrate's store layout writes into: one directory per session. */
|
|
70
|
+
export function storeDir(cwd: string, sessionId: string): string {
|
|
71
|
+
return join(workspaceStateDir(cwd), 'store', sessionId);
|
|
72
|
+
}
|
package/src/plan.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import { createReadonlyBashTool, releaseReadonlySandbox } from './readonly-shell.ts';
|
|
3
|
+
|
|
4
|
+
export const PLAN_ENTRY = 'pstack/plan';
|
|
5
|
+
const STATUS_KEY = 'pstack-plan';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Pi's `bash` cannot be swapped for the sandboxed shell by name without replacing it for the whole session, so plan
|
|
9
|
+
* mode offers the sandboxed shell under its own name and makes it the only shell while plan mode is on.
|
|
10
|
+
*/
|
|
11
|
+
export const PLAN_BASH = 'bash_readonly';
|
|
12
|
+
|
|
13
|
+
/** The tools plan mode keeps active. Names that are not registered are ignored by `setActiveTools`. */
|
|
14
|
+
export const PLAN_TOOLS = ['read', 'grep', 'find', 'ls', PLAN_BASH, 'AskQuestion', 'TodoWrite', 'agent_status', 'Task'] as const;
|
|
15
|
+
const ALLOWED = new Set<string>(PLAN_TOOLS);
|
|
16
|
+
|
|
17
|
+
export type PlanCommand = { kind: 'status' } | { kind: 'off' } | { kind: 'on'; task: string };
|
|
18
|
+
|
|
19
|
+
export function parsePlanArgs(args: string): PlanCommand {
|
|
20
|
+
const trimmed = args.trim();
|
|
21
|
+
if (trimmed === '') return { kind: 'status' };
|
|
22
|
+
if (trimmed === 'off') return { kind: 'off' };
|
|
23
|
+
if (trimmed === 'on') return { kind: 'on', task: '' };
|
|
24
|
+
return { kind: 'on', task: trimmed };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** What a session entry stores: whether plan mode is on, and the tools to restore when it ends. */
|
|
28
|
+
export interface PlanState {
|
|
29
|
+
enabled: boolean;
|
|
30
|
+
toolsBefore?: string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface BranchEntry {
|
|
34
|
+
type: string;
|
|
35
|
+
customType?: string;
|
|
36
|
+
data?: unknown;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The last plan entry on the branch; anything malformed counts as off. */
|
|
40
|
+
export function planFromBranch(entries: readonly BranchEntry[]): PlanState {
|
|
41
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
42
|
+
const entry = entries[index];
|
|
43
|
+
if (entry?.type !== 'custom' || entry.customType !== PLAN_ENTRY) continue;
|
|
44
|
+
const data = entry.data as { enabled?: unknown; toolsBefore?: unknown } | null;
|
|
45
|
+
if (data?.enabled !== true) return { enabled: false };
|
|
46
|
+
const tools = data.toolsBefore;
|
|
47
|
+
return Array.isArray(tools) && tools.every((name) => typeof name === 'string') ? { enabled: true, toolsBefore: tools as string[] } : { enabled: true };
|
|
48
|
+
}
|
|
49
|
+
return { enabled: false };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Why a call is refused in plan mode, or undefined when it may run. */
|
|
53
|
+
export function planBlockReason(toolName: string): string | undefined {
|
|
54
|
+
if (ALLOWED.has(toolName)) return undefined;
|
|
55
|
+
if (toolName === 'write' || toolName === 'edit') return `Plan mode is on: ${toolName} is blocked, since plan mode changes no files. Put the change in the plan; the user runs /plan off to execute it.`;
|
|
56
|
+
if (toolName === 'bash') return `Plan mode is on: use ${PLAN_BASH}, the read-only sandboxed shell, instead of bash.`;
|
|
57
|
+
return `Plan mode is on: ${toolName} is not available. Plan mode allows only ${PLAN_TOOLS.join(', ')}.`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function planSection(): string {
|
|
61
|
+
return [
|
|
62
|
+
'Plan mode is on until the user runs /plan off. Do not change files or state.',
|
|
63
|
+
`Your tools are read-only: read, grep, find, ls, ${PLAN_BASH} (a shell in an OS sandbox that denies writes and network access), AskQuestion, TodoWrite, agent_status, and Task, whose subagents always run readonly: a fresh one runs in this checkout, never in a worktree, and a resume or fork stays where its agent ran. write and edit are blocked.`,
|
|
64
|
+
'Investigate the code and context the task needs. When a real decision belongs to the user (a product choice or a tradeoff you cannot settle from evidence), ask with AskQuestion; settle facts yourself.',
|
|
65
|
+
'Finish with a plan: the goal; numbered steps, each with how to verify it; and the risks and open questions.',
|
|
66
|
+
'The user leaves plan mode with /plan off to execute the plan.',
|
|
67
|
+
].join('\n');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Pi's read-only built-ins and the sandboxed shell are always on in plan mode. The other plan tools stay on only
|
|
71
|
+
// if they were on before: pstack turns Task off at the depth limit, and plan mode must not hand it back.
|
|
72
|
+
const ALWAYS = ['read', 'grep', 'find', 'ls', PLAN_BASH];
|
|
73
|
+
|
|
74
|
+
/** The tools plan mode activates, given the tools active before it. */
|
|
75
|
+
export function planTools(before: readonly string[]): string[] {
|
|
76
|
+
return [...new Set([...ALWAYS, ...before.filter((name) => ALLOWED.has(name))])];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function registerPlanMode(pi: ExtensionAPI): { section: () => string | undefined; enabled: () => boolean } {
|
|
80
|
+
let state: PlanState = { enabled: false };
|
|
81
|
+
|
|
82
|
+
// Inactive until plan mode turns it on.
|
|
83
|
+
pi.registerTool({ ...createReadonlyBashTool(PLAN_BASH), defaultActive: false });
|
|
84
|
+
pi.on('session_shutdown', releaseReadonlySandbox);
|
|
85
|
+
|
|
86
|
+
const show = (ctx: ExtensionContext) => {
|
|
87
|
+
if (ctx.hasUI) ctx.ui.setStatus(STATUS_KEY, state.enabled ? ctx.ui.theme.fg('warning', 'plan') : undefined);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const enable = (toolsBefore: string[]) => {
|
|
91
|
+
state = { enabled: true, toolsBefore };
|
|
92
|
+
pi.setActiveTools(planTools(toolsBefore));
|
|
93
|
+
};
|
|
94
|
+
const disable = () => {
|
|
95
|
+
const before = state.toolsBefore ?? pi.getActiveTools();
|
|
96
|
+
state = { enabled: false };
|
|
97
|
+
pi.setActiveTools(before.filter((name) => name !== PLAN_BASH));
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const restore = (ctx: ExtensionContext) => {
|
|
101
|
+
const stored = planFromBranch(ctx.sessionManager.getBranch());
|
|
102
|
+
if (stored.enabled) enable(stored.toolsBefore ?? state.toolsBefore ?? pi.getActiveTools());
|
|
103
|
+
else if (state.enabled) disable();
|
|
104
|
+
else if (pi.getActiveTools().includes(PLAN_BASH)) pi.setActiveTools(pi.getActiveTools().filter((name) => name !== PLAN_BASH));
|
|
105
|
+
show(ctx);
|
|
106
|
+
};
|
|
107
|
+
pi.on('session_start', (_event, ctx) => restore(ctx));
|
|
108
|
+
pi.on('session_tree', (_event, ctx) => restore(ctx));
|
|
109
|
+
|
|
110
|
+
// Active tools decide what the model is offered; this decides what may run, including names the model
|
|
111
|
+
// invents. Task runs: it reads `enabled` and makes each launch readonly and local without changing its input,
|
|
112
|
+
// so the restriction never reaches an agent's record.
|
|
113
|
+
pi.on('tool_call', (event) => {
|
|
114
|
+
if (!state.enabled) return undefined;
|
|
115
|
+
const reason = planBlockReason(event.toolName);
|
|
116
|
+
return reason === undefined ? undefined : { block: true, reason };
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const handle = (args: string, ctx: ExtensionCommandContext) => {
|
|
120
|
+
const command = parsePlanArgs(args);
|
|
121
|
+
if (command.kind === 'status') return ctx.ui.notify(state.enabled ? 'Plan mode is on. /plan off exits.' : 'Plan mode is off. /plan [task] turns it on.', 'info');
|
|
122
|
+
if (command.kind === 'off') {
|
|
123
|
+
if (state.enabled) disable();
|
|
124
|
+
} else if (!state.enabled) enable(pi.getActiveTools());
|
|
125
|
+
pi.appendEntry(PLAN_ENTRY, state);
|
|
126
|
+
show(ctx);
|
|
127
|
+
ctx.ui.notify(state.enabled ? 'Plan mode on: read-only tools only. /plan off exits.' : 'Plan mode off: tools restored.', 'info');
|
|
128
|
+
if (command.kind === 'on' && command.task !== '') pi.sendUserMessage(command.task, { expandPromptTemplates: true, ...(ctx.isIdle() ? {} : { deliverAs: 'followUp' as const }) });
|
|
129
|
+
return undefined;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
pi.registerCommand('plan', {
|
|
133
|
+
description: 'Plan mode: read-only tools, investigate and produce a plan. /plan [task], /plan off, /plan',
|
|
134
|
+
getArgumentCompletions: (prefix) => ['off'].filter((name) => name.startsWith(prefix.trim())).map((name) => ({ value: name, label: name })),
|
|
135
|
+
handler: async (args, ctx) => {
|
|
136
|
+
handle(args, ctx);
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return { section: () => (state.enabled ? planSection() : undefined), enabled: () => state.enabled };
|
|
141
|
+
}
|