raga-cli 0.0.0-stage → 0.9.0
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/CHANGELOG.md +118 -0
- package/LICENSE +21 -0
- package/README.md +162 -2
- package/bundled-skills/AGENTS.md +5 -0
- package/bundled-skills/README.md +25 -0
- package/bundled-skills/agents/comment-sicko.json +9 -0
- package/bundled-skills/agents/comment-sicko.md +28 -0
- package/bundled-skills/agents/model-roles.json +41 -0
- package/bundled-skills/agents/poteto-agent.json +9 -0
- package/bundled-skills/agents/poteto-agent.md +4 -0
- package/bundled-skills/agents/worker.json +8 -0
- package/bundled-skills/agents/worker.md +1 -0
- package/bundled-skills/bundle.json +1514 -0
- package/bundled-skills/dependencies.json +40 -0
- package/bundled-skills/docs/guide/01-setup.md +3 -0
- package/bundled-skills/docs/guide/02-peak.md +135 -0
- package/bundled-skills/docs/guide/03-understand.md +79 -0
- package/bundled-skills/docs/guide/04-design.md +133 -0
- package/bundled-skills/docs/guide/05-build-and-clean.md +83 -0
- package/bundled-skills/docs/guide/06-verify-and-ship.md +166 -0
- package/bundled-skills/docs/guide/07-overnight.md +118 -0
- package/bundled-skills/docs/guide/08-principles.md +72 -0
- package/bundled-skills/docs/guide/09-make-it-yours.md +96 -0
- package/bundled-skills/docs/guide/10-recipes-and-pitfalls.md +180 -0
- package/bundled-skills/docs/guide/README.md +38 -0
- package/bundled-skills/docs/guide/images/design.jpg +0 -0
- package/bundled-skills/docs/guide/images/overnight.jpg +0 -0
- package/bundled-skills/docs/guide/images/recipes.jpg +0 -0
- package/bundled-skills/docs/guide/images/router.jpg +0 -0
- package/bundled-skills/docs/guide/images/understanding.jpg +0 -0
- package/bundled-skills/docs/guide/images/verification.jpg +0 -0
- package/bundled-skills/licenses/commander +22 -0
- package/bundled-skills/licenses/cursor-team-kit +21 -0
- package/bundled-skills/licenses/no-mistakes +21 -0
- package/bundled-skills/licenses/pstack +21 -0
- package/bundled-skills/skills/bend/SKILL.md +58 -0
- package/bundled-skills/skills/bend/bend_tool.py +144 -0
- package/bundled-skills/skills/peak/SKILL.md +184 -0
- package/bundled-skills/skills/peak/playbooks/authoring-a-skill.md +12 -0
- package/bundled-skills/skills/peak/playbooks/autonomous-run.md +13 -0
- package/bundled-skills/skills/peak/playbooks/autopilot-full.md +25 -0
- package/bundled-skills/skills/peak/playbooks/autopilot-stack.md +12 -0
- package/bundled-skills/skills/peak/playbooks/babysit.md +18 -0
- package/bundled-skills/skills/peak/playbooks/bug-fix.md +15 -0
- package/bundled-skills/skills/peak/playbooks/eval.md +25 -0
- package/bundled-skills/skills/peak/playbooks/feature.md +21 -0
- package/bundled-skills/skills/peak/playbooks/hillclimb.md +21 -0
- package/bundled-skills/skills/peak/playbooks/investigation.md +14 -0
- package/bundled-skills/skills/peak/playbooks/multi-phase-plan.md +155 -0
- package/bundled-skills/skills/peak/playbooks/opening-a-pr.md +34 -0
- package/bundled-skills/skills/peak/playbooks/orchestrate.md +112 -0
- package/bundled-skills/skills/peak/playbooks/pause-safely.md +10 -0
- package/bundled-skills/skills/peak/playbooks/perf-issue.md +25 -0
- package/bundled-skills/skills/peak/playbooks/prototype.md +14 -0
- package/bundled-skills/skills/peak/playbooks/refactoring.md +16 -0
- package/bundled-skills/skills/peak/playbooks/runtime-forensics.md +11 -0
- package/bundled-skills/skills/peak/playbooks/session-pickup.md +11 -0
- package/bundled-skills/skills/peak/playbooks/shipping.md +12 -0
- package/bundled-skills/skills/peak/playbooks/trace-forensics.md +14 -0
- package/bundled-skills/skills/peak/playbooks/visual-parity.md +11 -0
- package/bundled-skills/skills/peak/playbooks/worktree-cleanup.md +6 -0
- package/bundled-skills/skills/peak/references/architect/SKILL.md +81 -0
- package/bundled-skills/skills/peak/references/architect/references/design-red-flags.md +57 -0
- package/bundled-skills/skills/peak/references/architect/references/rationale-template.md +35 -0
- package/bundled-skills/skills/peak/references/architect/references/runner-prompt.md +20 -0
- package/bundled-skills/skills/peak/references/arena/SKILL.md +50 -0
- package/bundled-skills/skills/peak/references/automate-me/SKILL.md +106 -0
- package/bundled-skills/skills/peak/references/benchmark-checklist/SKILL.md +41 -0
- package/bundled-skills/skills/peak/references/blast-radius/SKILL.md +52 -0
- package/bundled-skills/skills/peak/references/bro/SKILL.md +9 -0
- package/bundled-skills/skills/peak/references/control-cli/SKILL.md +111 -0
- package/bundled-skills/skills/peak/references/control-ui/SKILL.md +111 -0
- package/bundled-skills/skills/peak/references/correct/SKILL.md +34 -0
- package/bundled-skills/skills/peak/references/create-formal-verification-skill/SKILL.md +43 -0
- package/bundled-skills/skills/peak/references/create-skill/SKILL.md +14 -0
- package/bundled-skills/skills/peak/references/create-verification-skill/SKILL.md +46 -0
- package/bundled-skills/skills/peak/references/create-verification-skill/references/feature-map-example/README.md +47 -0
- package/bundled-skills/skills/peak/references/create-verification-skill/references/feature-map-example/create-note.md +39 -0
- package/bundled-skills/skills/peak/references/create-verification-skill/references/feature-map-example/search.md +45 -0
- package/bundled-skills/skills/peak/references/deslop/SKILL.md +24 -0
- package/bundled-skills/skills/peak/references/figure-it-out/SKILL.md +54 -0
- package/bundled-skills/skills/peak/references/how/SKILL.md +38 -0
- package/bundled-skills/skills/peak/references/how/references/explainer-prompt.md +55 -0
- package/bundled-skills/skills/peak/references/how/references/explorer-prompt.md +35 -0
- package/bundled-skills/skills/peak/references/in-your-own-words/SKILL.md +9 -0
- package/bundled-skills/skills/peak/references/interrogate/SKILL.md +76 -0
- package/bundled-skills/skills/peak/references/interrogate/references/code-quality-review.md +47 -0
- package/bundled-skills/skills/peak/references/interrogate/references/lead-judgment.md +39 -0
- package/bundled-skills/skills/peak/references/interrogate/references/reviewer-prompt.md +62 -0
- package/bundled-skills/skills/peak/references/interrogate/references/rubric.md +77 -0
- package/bundled-skills/skills/peak/references/ketchup/SKILL.md +45 -0
- package/bundled-skills/skills/peak/references/maintain-formal-verification-skill/SKILL.md +60 -0
- package/bundled-skills/skills/peak/references/maintain-verification-skill/SKILL.md +41 -0
- package/bundled-skills/skills/peak/references/no-comments/SKILL.md +26 -0
- package/bundled-skills/skills/peak/references/no-mistakes/SKILL.md +20 -0
- package/bundled-skills/skills/peak/references/no-mistakes/upstream/SKILL.md +394 -0
- package/bundled-skills/skills/peak/references/peak-setup/SKILL.md +18 -0
- package/bundled-skills/skills/peak/references/poteto-help/SKILL.md +16 -0
- package/bundled-skills/skills/peak/references/poteto-help/references/prompting.md +51 -0
- package/bundled-skills/skills/peak/references/poteto-help/references/recipes.md +51 -0
- package/bundled-skills/skills/peak/references/principle-attack-the-premise/SKILL.md +23 -0
- package/bundled-skills/skills/peak/references/principle-boundary-discipline/SKILL.md +36 -0
- package/bundled-skills/skills/peak/references/principle-build-the-lever/SKILL.md +25 -0
- package/bundled-skills/skills/peak/references/principle-encode-lessons-in-structure/SKILL.md +13 -0
- package/bundled-skills/skills/peak/references/principle-exhaust-the-design-space/SKILL.md +11 -0
- package/bundled-skills/skills/peak/references/principle-experience-first/SKILL.md +21 -0
- package/bundled-skills/skills/peak/references/principle-explain-the-number/SKILL.md +19 -0
- package/bundled-skills/skills/peak/references/principle-fix-root-causes/SKILL.md +23 -0
- package/bundled-skills/skills/peak/references/principle-foundational-thinking/SKILL.md +23 -0
- package/bundled-skills/skills/peak/references/principle-guard-the-context-window/SKILL.md +14 -0
- package/bundled-skills/skills/peak/references/principle-laziness-protocol/SKILL.md +12 -0
- package/bundled-skills/skills/peak/references/principle-make-operations-idempotent/SKILL.md +24 -0
- package/bundled-skills/skills/peak/references/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +16 -0
- package/bundled-skills/skills/peak/references/principle-minimize-reader-load/SKILL.md +20 -0
- package/bundled-skills/skills/peak/references/principle-model-the-domain/SKILL.md +26 -0
- package/bundled-skills/skills/peak/references/principle-never-block-on-the-human/SKILL.md +17 -0
- package/bundled-skills/skills/peak/references/principle-outcome-oriented-execution/SKILL.md +23 -0
- package/bundled-skills/skills/peak/references/principle-prove-it-works/SKILL.md +24 -0
- package/bundled-skills/skills/peak/references/principle-redesign-from-first-principles/SKILL.md +18 -0
- package/bundled-skills/skills/peak/references/principle-separate-before-serializing-shared-state/SKILL.md +18 -0
- package/bundled-skills/skills/peak/references/principle-sequence-verifiable-units/SKILL.md +19 -0
- package/bundled-skills/skills/peak/references/principle-subtract-before-you-add/SKILL.md +20 -0
- package/bundled-skills/skills/peak/references/principle-test-behavior-not-implementation/SKILL.md +22 -0
- package/bundled-skills/skills/peak/references/principle-type-system-discipline/SKILL.md +15 -0
- package/bundled-skills/skills/peak/references/raga-runtime/SKILL.md +51 -0
- package/bundled-skills/skills/peak/references/raga-runtime/managed-tools.mjs +34 -0
- package/bundled-skills/skills/peak/references/raga-runtime/raga_runtime.py +55 -0
- package/bundled-skills/skills/peak/references/recall/SKILL.md +37 -0
- package/bundled-skills/skills/peak/references/reflect/SKILL.md +63 -0
- package/bundled-skills/skills/peak/references/reflect/references/divergent-reviewer.md +38 -0
- package/bundled-skills/skills/peak/references/reflect/references/judgment-reviewer.md +37 -0
- package/bundled-skills/skills/peak/references/reflect/references/synthesizer.md +56 -0
- package/bundled-skills/skills/peak/references/reflect/references/tooling-reviewer.md +48 -0
- package/bundled-skills/skills/peak/references/reviewer-triage.md +142 -0
- package/bundled-skills/skills/peak/references/sharpen/SKILL.md +18 -0
- package/bundled-skills/skills/peak/references/show-me-your-work/SKILL.md +84 -0
- package/bundled-skills/skills/peak/references/show-me-your-work/references/decision-log-template.tsv +1 -0
- package/bundled-skills/skills/peak/references/show-me-your-work/scripts/log.sh +42 -0
- package/bundled-skills/skills/peak/references/statechart/SKILL.md +38 -0
- package/bundled-skills/skills/peak/references/statechart/scripts/chart-to-tla.mjs +236 -0
- package/bundled-skills/skills/peak/references/statechart/scripts/compare-charts.mjs +104 -0
- package/bundled-skills/skills/peak/references/statechart/scripts/lib.mjs +725 -0
- package/bundled-skills/skills/peak/references/statechart/scripts/model/OutputFolder.cfg +6 -0
- package/bundled-skills/skills/peak/references/statechart/scripts/model/OutputFolder.map.mjs +106 -0
- package/bundled-skills/skills/peak/references/statechart/scripts/model/OutputFolder.tla +176 -0
- package/bundled-skills/skills/peak/references/statechart/scripts/statechart.mjs +749 -0
- package/bundled-skills/skills/peak/references/statechart/scripts/test/statechart.test.mjs +2790 -0
- package/bundled-skills/skills/peak/references/statechart/scripts/test/sync.mmd +19 -0
- package/bundled-skills/skills/peak/references/statechart/scripts/test/upload.mmd +16 -0
- package/bundled-skills/skills/peak/references/statechart/scripts/tlc-to-chart.mjs +1347 -0
- package/bundled-skills/skills/peak/references/swarm/SKILL.md +43 -0
- package/bundled-skills/skills/peak/references/tdd/SKILL.md +44 -0
- package/bundled-skills/skills/peak/references/teach/SKILL.md +23 -0
- package/bundled-skills/skills/peak/references/technical-writing/SKILL.md +108 -0
- package/bundled-skills/skills/peak/references/typescript-best-practices/SKILL.md +33 -0
- package/bundled-skills/skills/peak/references/typescript-best-practices/references/patterns.md +328 -0
- package/bundled-skills/skills/peak/references/unslop/SKILL.md +64 -0
- package/bundled-skills/skills/peak/references/verify-this/SKILL.md +76 -0
- package/bundled-skills/skills/peak/references/why/SKILL.md +100 -0
- package/bundled-skills/skills/peak/references/why/references/epistemics.md +71 -0
- package/bundled-skills/skills/peak/references/why/references/investigator-prompt.md +84 -0
- package/bundled-skills/skills/peak/references/why/references/source-playbook.md +17 -0
- package/bundled-skills/skills/peak/references/why/references/sources/code-archaeology.md +51 -0
- package/bundled-skills/skills/peak/references/why/references/sources/databricks.md +70 -0
- package/bundled-skills/skills/peak/references/why/references/sources/datadog.md +97 -0
- package/bundled-skills/skills/peak/references/why/references/sources/incident-postmortem.md +5 -0
- package/bundled-skills/skills/peak/references/why/references/sources/linear.md +48 -0
- package/bundled-skills/skills/peak/references/why/references/sources/notion.md +55 -0
- package/bundled-skills/skills/peak/references/why/references/sources/sentry.md +66 -0
- package/bundled-skills/skills/peak/references/why/references/sources/slack.md +17 -0
- package/bundled-skills/skills/peak/references/why/references/synthesizer-prompt.md +129 -0
- package/bundled-skills/skills/peak/scripts/check-plan.mjs +205 -0
- package/bundled-skills/skills/peak/scripts/check-plan.test.ts +96 -0
- package/bundled-skills/skills/peak/scripts/orch/orch.test.ts +761 -0
- package/bundled-skills/skills/peak/scripts/orch/orch.ts +588 -0
- package/bundled-skills/skills/peak/scripts/orch/store.ts +1676 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/LICENSE +22 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/esm.mjs +16 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/index.js +24 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/lib/argument.js +149 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/lib/command.js +2778 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/lib/error.js +39 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/lib/help.js +747 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/lib/option.js +379 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/lib/suggestSimilar.js +101 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/package.json +1 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/typings/esm.d.mts +3 -0
- package/bundled-skills/skills/peak/scripts/vendor/commander/typings/index.d.ts +1113 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/cli.test.ts +224 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/cli.ts +223 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/fakes.test-helper.ts +129 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/github.test.ts +436 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/github.ts +718 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/policy.test.ts +420 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/policy.ts +836 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/render.ts +169 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/tsconfig.json +13 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/types.compile.ts +93 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/types.ts +413 -0
- package/bundled-skills/skills/peak/scripts/watch-pr/watch-pr +4 -0
- package/bundled-skills/skills/peak/scripts/worktree-audit.sh +4 -0
- package/bundled-skills/skills/raga-artifacts/SKILL.md +117 -0
- package/bundled-skills/skills/raga-artifacts/artifact.mjs +254 -0
- package/bundled-skills/skills/raga-artifacts/artifact_tool.py +55 -0
- package/bundled-skills/skills/raga-artifacts/diagrams.md +641 -0
- package/bundled-skills/skills/raga-artifacts/templates/doc.html +124 -0
- package/bundled-skills/skills/raga-artifacts/templates/slides.html +138 -0
- package/dist/bundle/chunks/anthropic-messages-BMDYDGHW.js +6 -0
- package/dist/bundle/chunks/anthropic.js +68 -0
- package/dist/bundle/chunks/azure-openai-responses-Q7EZTEHK.js +2 -0
- package/dist/bundle/chunks/bedrock-converse-stream.js +86 -0
- package/dist/bundle/chunks/chunk-3MNXTBQ3.js +2 -0
- package/dist/bundle/chunks/chunk-3V45WARH.js +7 -0
- package/dist/bundle/chunks/chunk-5AGIVGSV.js +6 -0
- package/dist/bundle/chunks/chunk-6QGV3IWD.js +6 -0
- package/dist/bundle/chunks/chunk-7CBJINYJ.js +20 -0
- package/dist/bundle/chunks/chunk-7DDKXS2Q.js +2 -0
- package/dist/bundle/chunks/chunk-7ZC4UXSL.js +2 -0
- package/dist/bundle/chunks/chunk-CF5GWZHW.js +31 -0
- package/dist/bundle/chunks/chunk-ENUUAC3O.js +131 -0
- package/dist/bundle/chunks/chunk-F7LFZTHT.js +1591 -0
- package/dist/bundle/chunks/chunk-G54UBZTF.js +44 -0
- package/dist/bundle/chunks/chunk-H76KZMOI.js +2 -0
- package/dist/bundle/chunks/chunk-IXDQLPKS.js +11 -0
- package/dist/bundle/chunks/chunk-JLV25LHE.js +2 -0
- package/dist/bundle/chunks/chunk-JQDZ4Z7L.js +2 -0
- package/dist/bundle/chunks/chunk-JXWJOIH4.js +2 -0
- package/dist/bundle/chunks/chunk-KNY3PXQI.js +2 -0
- package/dist/bundle/chunks/chunk-L2ZPNLYD.js +2 -0
- package/dist/bundle/chunks/chunk-LPXNMA2W.js +2 -0
- package/dist/bundle/chunks/chunk-LVVZS2NO.js +12 -0
- package/dist/bundle/chunks/chunk-LX24DVQ5.js +2 -0
- package/dist/bundle/chunks/chunk-NP6TH6UT.js +64 -0
- package/dist/bundle/chunks/chunk-OPPYO2IE.js +2 -0
- package/dist/bundle/chunks/chunk-P2QCWBFM.js +9 -0
- package/dist/bundle/chunks/chunk-P7FPUEYA.js +2 -0
- package/dist/bundle/chunks/chunk-PGXDEOVV.js +2 -0
- package/dist/bundle/chunks/chunk-PJACZCIR.js +2 -0
- package/dist/bundle/chunks/chunk-PMRZO46Y.js +2 -0
- package/dist/bundle/chunks/chunk-PRRNXB7S.js +2 -0
- package/dist/bundle/chunks/chunk-Q4U25BSY.js +2 -0
- package/dist/bundle/chunks/chunk-RDZVAGPR.js +2 -0
- package/dist/bundle/chunks/chunk-RFPFZTK2.js +7 -0
- package/dist/bundle/chunks/chunk-RP2WKNT7.js +4 -0
- package/dist/bundle/chunks/chunk-S5J3UB5O.js +75 -0
- package/dist/bundle/chunks/chunk-TEPMHNKQ.js +2 -0
- package/dist/bundle/chunks/chunk-TMTOQ7H7.js +2 -0
- package/dist/bundle/chunks/chunk-UKVNB6C4.js +2 -0
- package/dist/bundle/chunks/chunk-USJZMAIR.js +2 -0
- package/dist/bundle/chunks/chunk-UTB4DTYQ.js +2 -0
- package/dist/bundle/chunks/chunk-WJGLPWUE.js +16 -0
- package/dist/bundle/chunks/cli-QLDCDYOR.js +51 -0
- package/dist/bundle/chunks/cloudflare-workers-ai-system-one-S6CT2FXS.js +2 -0
- package/dist/bundle/chunks/codemode-worker.js +478 -0
- package/dist/bundle/chunks/easter-egg-3d-NPDBBPPF.js +2 -0
- package/dist/bundle/chunks/execute-WPTMS4XG.js +505 -0
- package/dist/bundle/chunks/github-copilot.js +2 -0
- package/dist/bundle/chunks/google-generative-ai-35NZKG6S.js +2 -0
- package/dist/bundle/chunks/google-vertex-P33AHYYX.js +2 -0
- package/dist/bundle/chunks/https-proxy-agent-LVOO5TPL.js +13 -0
- package/dist/bundle/chunks/image-resize-worker.js +2 -0
- package/dist/bundle/chunks/jiti-loader-GL4HJFAD.js +21 -0
- package/dist/bundle/chunks/jiti-static-loader-2RCDI6B4.js +2 -0
- package/dist/bundle/chunks/kimi-coding.js +2 -0
- package/dist/bundle/chunks/lib-4AVRLBHM.js +9 -0
- package/dist/bundle/chunks/llama-cpp-classify-U4EH6TAM.js +22 -0
- package/dist/bundle/chunks/meta.js +2 -0
- package/dist/bundle/chunks/mistral-conversations-J62DPS2P.js +5 -0
- package/dist/bundle/chunks/multipart-parser-FMGQ7342.js +3 -0
- package/dist/bundle/chunks/node-AUOGOBCN.js +2 -0
- package/dist/bundle/chunks/node-KCMANYC2.js +16 -0
- package/dist/bundle/chunks/openai-chatgpt.js +68 -0
- package/dist/bundle/chunks/openai-codex-responses-4HXATL2M.js +10 -0
- package/dist/bundle/chunks/openai-codex.js +68 -0
- package/dist/bundle/chunks/openai-completions-75YYY35N.js +7 -0
- package/dist/bundle/chunks/openai-decisions-HEBEVGA2.js +5 -0
- package/dist/bundle/chunks/openai-responses-UWNAKTBV.js +4 -0
- package/dist/bundle/chunks/openrouter-images-HVOPXKHS.js +2 -0
- package/dist/bundle/chunks/openrouter.js +68 -0
- package/dist/bundle/chunks/pi-messages-DG7CALOV.js +8 -0
- package/dist/bundle/chunks/radius.js +68 -0
- package/dist/bundle/chunks/runtime-N5MUH2WA.js +2 -0
- package/dist/bundle/chunks/src-QKGKDFSU.js +4 -0
- package/dist/bundle/chunks/typesafe-system-one-24HEGHNN.js +2 -0
- package/dist/bundle/chunks/virtual-modules-4I6Y6DPQ.js +2 -0
- package/dist/bundle/chunks/xai.js +2 -0
- package/dist/bundle/cli-runtime.js +3 -0
- package/dist/bundle/cli.js +5 -0
- package/dist/bundle/index.js +2 -0
- package/dist/bundle/raga-runtime.js +3 -0
- package/dist/bundle/raga.js +5 -0
- package/dist/bundle/rpc-entry.js +3 -0
- package/dist/core/export-html/template.css +1085 -0
- package/dist/core/export-html/template.html +56 -0
- package/dist/core/export-html/template.js +2044 -0
- package/dist/core/export-html/vendor/highlight.min.js +1213 -0
- package/dist/core/export-html/vendor/marked-cjk-friendly.min.js +6 -0
- package/dist/core/export-html/vendor/marked.min.js +79 -0
- package/dist/modes/interactive/assets/clankolas.png +0 -0
- package/dist/modes/interactive/theme/dark.json +78 -0
- package/dist/modes/interactive/theme/light.json +78 -0
- package/docs/cli-integration.md +106 -0
- package/docs/cli.md +343 -0
- package/docs/codemode.md +230 -0
- package/docs/compaction.md +489 -0
- package/docs/configuration.md +62 -0
- package/docs/containerization.md +183 -0
- package/docs/custom-provider.md +227 -0
- package/docs/docs.json +220 -0
- package/docs/environment-variables.md +101 -0
- package/docs/extensions.md +400 -0
- package/docs/how-pi-works.md +49 -0
- package/docs/images/doom-extension.png +0 -0
- package/docs/images/exy.png +0 -0
- package/docs/images/interactive-mode.png +0 -0
- package/docs/images/tree-view.png +0 -0
- package/docs/index.md +39 -0
- package/docs/json.md +226 -0
- package/docs/keybindings.md +195 -0
- package/docs/llama-cpp.md +121 -0
- package/docs/mcp.md +272 -0
- package/docs/message-types.md +279 -0
- package/docs/models-configuration.md +196 -0
- package/docs/models.md +225 -0
- package/docs/nvidia-rtx-llama-cpp.md +96 -0
- package/docs/packages.md +129 -0
- package/docs/prompt-templates.md +59 -0
- package/docs/providers.md +247 -0
- package/docs/quickstart.md +136 -0
- package/docs/rpc-commands.md +860 -0
- package/docs/rpc-extension-ui.md +200 -0
- package/docs/rpc.md +193 -0
- package/docs/sdk.md +149 -0
- package/docs/security.md +99 -0
- package/docs/session-format.md +293 -0
- package/docs/sessions.md +66 -0
- package/docs/settings.md +185 -0
- package/docs/shell-aliases.md +93 -0
- package/docs/skills.md +93 -0
- package/docs/slash-commands.md +61 -0
- package/docs/terminal-setup.md +233 -0
- package/docs/termux.md +117 -0
- package/docs/themes.md +138 -0
- package/docs/tmux.md +55 -0
- package/docs/tui.md +195 -0
- package/docs/usage.md +94 -0
- package/docs/virtual-models.md +114 -0
- package/docs/windows.md +81 -0
- package/examples/README.md +36 -0
- package/examples/extensions/README.md +214 -0
- package/examples/extensions/auto-commit-on-exit.ts +49 -0
- package/examples/extensions/bash-spawn-hook.ts +30 -0
- package/examples/extensions/bookmark.ts +50 -0
- package/examples/extensions/border-status-editor.ts +150 -0
- package/examples/extensions/built-in-tool-renderer.ts +225 -0
- package/examples/extensions/claude-rules.ts +86 -0
- package/examples/extensions/commands.ts +72 -0
- package/examples/extensions/confirm-destructive.ts +59 -0
- package/examples/extensions/custom-compaction.ts +117 -0
- package/examples/extensions/custom-footer.ts +64 -0
- package/examples/extensions/custom-header.ts +73 -0
- package/examples/extensions/custom-provider-anthropic/index.ts +617 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
- package/examples/extensions/custom-provider-anthropic/package.json +19 -0
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +405 -0
- package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
- package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
- package/examples/extensions/debug-provider.ts +90 -0
- package/examples/extensions/dirty-repo-guard.ts +56 -0
- package/examples/extensions/doom-overlay/README.md +46 -0
- package/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
- package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
- package/examples/extensions/doom-overlay/doom/build.sh +152 -0
- package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
- package/examples/extensions/doom-overlay/doom-component.ts +132 -0
- package/examples/extensions/doom-overlay/doom-engine.ts +173 -0
- package/examples/extensions/doom-overlay/doom-keys.ts +104 -0
- package/examples/extensions/doom-overlay/index.ts +74 -0
- package/examples/extensions/doom-overlay/wad-finder.ts +55 -0
- package/examples/extensions/dynamic-resources/SKILL.md +8 -0
- package/examples/extensions/dynamic-resources/dynamic.json +81 -0
- package/examples/extensions/dynamic-resources/dynamic.md +5 -0
- package/examples/extensions/dynamic-resources/index.ts +15 -0
- package/examples/extensions/dynamic-tools.ts +74 -0
- package/examples/extensions/entry-renderer.ts +41 -0
- package/examples/extensions/event-bus.ts +43 -0
- package/examples/extensions/file-trigger.ts +41 -0
- package/examples/extensions/git-checkpoint.ts +53 -0
- package/examples/extensions/git-merge-and-resolve.ts +115 -0
- package/examples/extensions/github-issue-autocomplete.ts +185 -0
- package/examples/extensions/gondolin/index.ts +531 -0
- package/examples/extensions/gondolin/package-lock.json +175 -0
- package/examples/extensions/gondolin/package.json +19 -0
- package/examples/extensions/handoff.ts +190 -0
- package/examples/extensions/hello.ts +26 -0
- package/examples/extensions/hidden-thinking-label.ts +53 -0
- package/examples/extensions/inline-bash.ts +94 -0
- package/examples/extensions/input-transform-streaming.ts +39 -0
- package/examples/extensions/input-transform.ts +43 -0
- package/examples/extensions/interactive-shell.ts +196 -0
- package/examples/extensions/jev-router.ts +113 -0
- package/examples/extensions/mac-system-theme.ts +47 -0
- package/examples/extensions/message-renderer.ts +59 -0
- package/examples/extensions/minimal-mode.ts +353 -0
- package/examples/extensions/modal-editor.ts +85 -0
- package/examples/extensions/model-status.ts +31 -0
- package/examples/extensions/notify.ts +57 -0
- package/examples/extensions/overlay-qa-tests.ts +1450 -0
- package/examples/extensions/overlay-test.ts +153 -0
- package/examples/extensions/permission-gate.ts +34 -0
- package/examples/extensions/pirate.ts +47 -0
- package/examples/extensions/plan-mode/README.md +66 -0
- package/examples/extensions/plan-mode/index.ts +394 -0
- package/examples/extensions/plan-mode/utils.ts +168 -0
- package/examples/extensions/preset.ts +436 -0
- package/examples/extensions/project-trust.ts +64 -0
- package/examples/extensions/prompt-customizer.ts +49 -0
- package/examples/extensions/protected-paths.ts +30 -0
- package/examples/extensions/provider-payload.ts +18 -0
- package/examples/extensions/qna.ts +118 -0
- package/examples/extensions/question.ts +286 -0
- package/examples/extensions/questionnaire.ts +448 -0
- package/examples/extensions/rainbow-editor.ts +88 -0
- package/examples/extensions/reload-runtime.ts +37 -0
- package/examples/extensions/rpc-demo.ts +118 -0
- package/examples/extensions/sandbox/index.ts +326 -0
- package/examples/extensions/sandbox/package-lock.json +92 -0
- package/examples/extensions/sandbox/package.json +19 -0
- package/examples/extensions/send-user-message.ts +97 -0
- package/examples/extensions/session-name.ts +27 -0
- package/examples/extensions/shutdown-command.ts +63 -0
- package/examples/extensions/snake.ts +343 -0
- package/examples/extensions/space-invaders.ts +560 -0
- package/examples/extensions/ssh.ts +220 -0
- package/examples/extensions/status-line.ts +32 -0
- package/examples/extensions/structured-output.ts +65 -0
- package/examples/extensions/subagent/README.md +177 -0
- package/examples/extensions/subagent/agents/planner.md +37 -0
- package/examples/extensions/subagent/agents/reviewer.md +35 -0
- package/examples/extensions/subagent/agents/scout.md +50 -0
- package/examples/extensions/subagent/agents/worker.md +24 -0
- package/examples/extensions/subagent/agents.ts +157 -0
- package/examples/extensions/subagent/index.ts +1038 -0
- package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
- package/examples/extensions/subagent/prompts/implement.md +10 -0
- package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
- package/examples/extensions/summarize.ts +199 -0
- package/examples/extensions/system-prompt-header.ts +17 -0
- package/examples/extensions/tic-tac-toe.ts +1008 -0
- package/examples/extensions/timed-confirm.ts +70 -0
- package/examples/extensions/titlebar-spinner.ts +58 -0
- package/examples/extensions/todo.ts +297 -0
- package/examples/extensions/tool-override.ts +144 -0
- package/examples/extensions/tools.ts +146 -0
- package/examples/extensions/trigger-compact.ts +50 -0
- package/examples/extensions/truncated-tool.ts +195 -0
- package/examples/extensions/widget-placement.ts +9 -0
- package/examples/extensions/with-deps/index.ts +32 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +22 -0
- package/examples/extensions/working-indicator.ts +123 -0
- package/examples/extensions/working-message-test.ts +25 -0
- package/examples/rpc-client.ts +35 -0
- package/examples/rpc-extension-ui.ts +661 -0
- package/examples/sdk/01-minimal.ts +26 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +75 -0
- package/examples/sdk/04-skills.ts +55 -0
- package/examples/sdk/05-tools.ts +48 -0
- package/examples/sdk/06-extensions.ts +99 -0
- package/examples/sdk/07-context-files.ts +47 -0
- package/examples/sdk/08-prompt-templates.ts +51 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +34 -0
- package/examples/sdk/10-settings.ts +53 -0
- package/examples/sdk/11-sessions.ts +52 -0
- package/examples/sdk/12-full-control.ts +74 -0
- package/examples/sdk/13-session-runtime.ts +67 -0
- package/examples/sdk/14-codemode-mcp.ts +61 -0
- package/examples/sdk/README.md +141 -0
- package/package.json +21 -5
- package/python/raga_rlm/__init__.py +352 -0
- package/python/raga_rlm/mlx_models.py +265 -0
- package/python/raga_rlm/repl.py +920 -0
- package/schemas/keybindings.schema.json +441 -0
- package/schemas/models.schema.json +1211 -0
- package/schemas/settings.schema.json +829 -0
- package/schemas/theme.schema.json +365 -0
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import process from "node:process";
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
|
+
import { checkTlcOutput, findTlc, parseDot } from "./lib.mjs";
|
|
9
|
+
|
|
10
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
|
|
12
|
+
const USAGE = {
|
|
13
|
+
"from-spec":
|
|
14
|
+
"node statechart.mjs from-spec SPEC.tla [--fixed FIXED.tla | --against AGREED.tla] [--label TEXT] [--map FILE] [--expect-broken RULE]... [--title TEXT] [--out DIR] [--png]",
|
|
15
|
+
"from-chart":
|
|
16
|
+
"node statechart.mjs from-chart CHART.mmd [--module NAME] [--expect-broken RULE]... [--title TEXT] [--out DIR] [--png]",
|
|
17
|
+
};
|
|
18
|
+
const usage = (lines) => `Usage: ${lines.join("\n ")}`;
|
|
19
|
+
|
|
20
|
+
const FROM_SPEC = `from-spec SPEC.tla
|
|
21
|
+
Checks each INVARIANT in SPEC.cfg in a separate TLC run. TLC stops at the first rule that a state
|
|
22
|
+
breaks, and the separate runs find the rest. Each run also has the rule as a CONSTRAINT, so TLC
|
|
23
|
+
stops each path at its first state that breaks the rule, and a rule that stays broken reports
|
|
24
|
+
only the step that broke it. SPEC.cfg sits next to the spec and has SPECIFICATION and one
|
|
25
|
+
INVARIANT line per rule. One more run dumps the state graph, and then the command draws the
|
|
26
|
+
charts. TLC names each step after the innermost named action that takes it, so give each kind of
|
|
27
|
+
step its own named action. The command warns when steps carry the name of the whole next-state
|
|
28
|
+
action.
|
|
29
|
+
|
|
30
|
+
--fixed FIXED.tla The model of the open fix. It is a copy of the spec with its own file and
|
|
31
|
+
module name, such as SpecFixed.tla with MODULE SpecFixed. The command checks
|
|
32
|
+
it the same way, with FIXED.cfg, or with SPEC.cfg when there is no FIXED.cfg.
|
|
33
|
+
The charts then mark each bad state fixed or still wrong.
|
|
34
|
+
--against AGREED.tla
|
|
35
|
+
The spec that SPEC.tla must follow, when SPEC.tla models the code. It needs
|
|
36
|
+
its own file and module name. The command checks it with AGREED.cfg, or with
|
|
37
|
+
SPEC.cfg when there is no AGREED.cfg. SPEC.tla may add variables, record
|
|
38
|
+
fields, and actions that AGREED.tla does not have. The comparison leaves them
|
|
39
|
+
out. Do not use it with --fixed.
|
|
40
|
+
--label TEXT The name of the --fixed or --against model in the charts and the stories.
|
|
41
|
+
Default: "the fix", or "the spec".
|
|
42
|
+
--map FILE The labels for each model state and step. Default: SPEC.map.mjs next to the
|
|
43
|
+
spec. When the file does not exist, the command writes one to fill in and
|
|
44
|
+
exits 2. Its comments say what each function gets.`;
|
|
45
|
+
|
|
46
|
+
const FROM_CHART = `from-chart CHART.mmd
|
|
47
|
+
Writes NAME.tla, NAME.cfg, and NAME.map.mjs next to the chart. When NAME.tla already exists, it
|
|
48
|
+
writes none of them. Then it checks the spec as from-spec does, and compares the chart that the
|
|
49
|
+
spec draws with CHART.mmd.
|
|
50
|
+
|
|
51
|
+
--module NAME The TLA+ module name. Default: the chart's file name in CamelCase, such as
|
|
52
|
+
NotesApp for notes-app.mmd.
|
|
53
|
+
|
|
54
|
+
The chart is a Mermaid stateDiagram-v2. Each transition label is one action. For parallel
|
|
55
|
+
regions, split one top-level composite with --, and put each part in a named composite, such as
|
|
56
|
+
state Connection { ... }. Each region is one variable. A step moves every region whose current
|
|
57
|
+
state has a transition with the step's label, and the other regions stay where they are. Each
|
|
58
|
+
rule is one comment line:
|
|
59
|
+
%% never NAME: A & B -- the rule in one sentence
|
|
60
|
+
Each name after the colon is a state caption or a composite, and each comes from a different
|
|
61
|
+
region. A composite stands for every state in it. The command refuses final, choice, fork, and
|
|
62
|
+
history states, and regions below the top level.`;
|
|
63
|
+
|
|
64
|
+
const COMMON = `Both commands
|
|
65
|
+
--expect-broken RULE
|
|
66
|
+
A rule that is known to break. Repeat it, or list rules with commas, as in
|
|
67
|
+
A,B. The command then exits 0 when exactly these rules break, and the --fixed
|
|
68
|
+
or --against model breaks no other rule.
|
|
69
|
+
--title TEXT The chart title. Each bug chart adds ", bug N".
|
|
70
|
+
--out DIR The output folder. Default: NAME-statechart next to the input. A run writes its
|
|
71
|
+
files in a temporary folder, and then replaces the whole output folder with
|
|
72
|
+
it. When runs overlap, the output folder has the files of the run that
|
|
73
|
+
finished last. The command does not use an output folder that has files that
|
|
74
|
+
it did not write.
|
|
75
|
+
--png Also render each chart to PNG with the managed MERMAID_CLI executable.
|
|
76
|
+
-h, --help Print this help, and exit 0.
|
|
77
|
+
|
|
78
|
+
Output
|
|
79
|
+
The command prints whether each rule holds, the chart's counts, and one story per bug.
|
|
80
|
+
from-chart also prints the comparison. The command writes these files in the output folder:
|
|
81
|
+
chart.mmd The overview.
|
|
82
|
+
bug-N.mmd Only the steps of bug N, numbered. Bugs are numbered by fewest reader steps,
|
|
83
|
+
then fewest model steps, then rule name, then cause. Bugs that only the
|
|
84
|
+
--fixed or --against model has come last.
|
|
85
|
+
bug-N-developer.mmd, bug-N-layered.mmd
|
|
86
|
+
The path of bug N through the developer's states, when the mapping exports
|
|
87
|
+
detail().
|
|
88
|
+
stories.md One entry per bug: the rule, why it breaks, the shortest path in reader
|
|
89
|
+
steps, and what the --fixed or --against model does instead.
|
|
90
|
+
summary.json The counts behind the chart.
|
|
91
|
+
tlc/ The config and the TLC output of each run. When the overview has regions,
|
|
92
|
+
from-spec also writes tlc/regions-all.mmd, the regions with every arrow, and
|
|
93
|
+
tlc/regions-proof.mmd, every state and step of TLC's graph in one region.
|
|
94
|
+
compare-charts.mjs tlc/regions-all.mmd tlc/regions-proof.mmd repeats the
|
|
95
|
+
proof.
|
|
96
|
+
*.png Each chart as a PNG, with --png. A run without --png deletes them.
|
|
97
|
+
|
|
98
|
+
Reading a chart
|
|
99
|
+
from-spec draws the overview as two parallel regions when every state reads "context: progress":
|
|
100
|
+
the contexts on one side, the progress on the other, both at the same time. It does so only when
|
|
101
|
+
that has fewer states, and when the regions combine back into exactly every state and step of
|
|
102
|
+
TLC's graph. Otherwise it puts the states of one context in a box named context, and prints why.
|
|
103
|
+
Regions draw only the arrows of the shortest way to each state, of each bug path, and of the
|
|
104
|
+
nearest step with each label. The script checks that these arrows make no step that TLC's graph
|
|
105
|
+
does not have.
|
|
106
|
+
An arrow from a box's border leaves from every state in the box.
|
|
107
|
+
In regions, a step leaves every region that has it. "[when A]" or "[unless A]" says which states
|
|
108
|
+
of the other region the step needs. A loop says the region may also stay.
|
|
109
|
+
Other words in [ ] come from the mapping's guard(), and "after N s" from its timer(). TLC does
|
|
110
|
+
not check them, and has no clock, so a reviewer checks them against the code.
|
|
111
|
+
Gray states work. Red states break a rule. With --fixed, red states stay wrong with the fix, and
|
|
112
|
+
green states are fixed by it or exist only with it. With --against, red states break a rule in
|
|
113
|
+
either model, and green states exist only in the spec.
|
|
114
|
+
When the mapping exports group(), the fill is the state's group, which the legend names, and the
|
|
115
|
+
status is a thick red or green border on top. tlc-to-chart.mjs --help tells more.
|
|
116
|
+
A state is bad when it breaks a rule, or when every path from the start to it passes a state
|
|
117
|
+
that breaks one. TLC stops each path at the first state that breaks the rule, so a rule that
|
|
118
|
+
stays broken colors only the states where it first breaks and the states after them that no
|
|
119
|
+
path without a bug reaches.
|
|
120
|
+
A thick blue border and numbered labels mark the bug. Mermaid state diagrams cannot color an
|
|
121
|
+
edge.
|
|
122
|
+
A label "with the fix, STEP" or "in the spec, STEP" is a step that only the other model takes. On
|
|
123
|
+
a bug's path it leaves the state before the first step that the other model shows the reader
|
|
124
|
+
differently, and ends where that model goes instead. "with the fix, STEP cannot happen" or "in
|
|
125
|
+
the spec, STEP cannot happen" loops on the state where that model stops the step. Outside
|
|
126
|
+
regions, two states have at most one arrow between them. Its lines are the numbered steps, then
|
|
127
|
+
SPEC.tla's steps, then the other model's steps, with a blank line between each group.
|
|
128
|
+
An edge lists every step that takes it, most common first. Words that the steps share at the
|
|
129
|
+
start are written once, as in "you tap Pay or Back".
|
|
130
|
+
|
|
131
|
+
Regenerate
|
|
132
|
+
With --expect-broken, one script rebuilds every chart and stops on any change:
|
|
133
|
+
set -e
|
|
134
|
+
node statechart.mjs from-spec Code.tla --against Agreed.tla --expect-broken NoLostUpload --png
|
|
135
|
+
|
|
136
|
+
TLC
|
|
137
|
+
TLA2TOOLS is the path to tla2tools.jar, and JAVA is the java binary (default: java, Java 11 or
|
|
138
|
+
later). Download tla2tools.jar from https://github.com/tlaplus/tlaplus/releases/download/v1.8.0/tla2tools.jar. When TLA2TOOLS
|
|
139
|
+
is not set, the command says where to get TLC and exits 2.
|
|
140
|
+
|
|
141
|
+
Exit codes
|
|
142
|
+
0 Every rule holds, or exactly the --expect-broken rules break. from-chart also draws CHART.mmd
|
|
143
|
+
back unchanged.
|
|
144
|
+
1 Another rule is broken, an expected rule holds, or the chart that the spec draws is not
|
|
145
|
+
CHART.mmd. The output says which.
|
|
146
|
+
2 The command cannot finish: a bad argument, a missing file, a chart or TLC output that it
|
|
147
|
+
cannot read, no TLC, a helper script that cannot start, an output folder with files that the
|
|
148
|
+
command did not write, an input file that changed during the run, a new mapping to fill in,
|
|
149
|
+
or a chart that --png cannot render. When a chart does not render, the command keeps the
|
|
150
|
+
files it wrote and exits 2, whatever the rules give. Otherwise, exit 2 does not change the
|
|
151
|
+
output folder, unless the command moved it aside and cannot put it back. Then the message
|
|
152
|
+
names the folder's new path. When TLC stops on an error, the message names TLC's output, in a
|
|
153
|
+
temporary folder that the command keeps.`;
|
|
154
|
+
|
|
155
|
+
const HELP = {
|
|
156
|
+
"": `${usage([...Object.values(USAGE), "node statechart.mjs [from-spec|from-chart] --help"])}
|
|
157
|
+
|
|
158
|
+
Draws a Mermaid statechart of what an app's user sees, from TLC's state graph of a TLA+ spec. The
|
|
159
|
+
help calls that user the reader.
|
|
160
|
+
|
|
161
|
+
${FROM_SPEC}
|
|
162
|
+
|
|
163
|
+
${FROM_CHART}
|
|
164
|
+
|
|
165
|
+
${COMMON}`,
|
|
166
|
+
"from-spec": `${usage([USAGE["from-spec"]])}\n\n${FROM_SPEC}\n\n${COMMON}`,
|
|
167
|
+
"from-chart": `${usage([USAGE["from-chart"]])}\n\n${FROM_CHART}\n\n${COMMON}`,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const more = (cmd) =>
|
|
171
|
+
`Run node statechart.mjs ${cmd ? `${cmd} ` : ""}--help for the flags, the output, and the exit codes.`;
|
|
172
|
+
|
|
173
|
+
class CannotFinish extends Error {}
|
|
174
|
+
class TlcStopped extends CannotFinish {}
|
|
175
|
+
|
|
176
|
+
const COMMANDS = {
|
|
177
|
+
"from-spec": {
|
|
178
|
+
flags: ["--map", "--fixed", "--against", "--label", "--expect-broken", "--title", "--out"],
|
|
179
|
+
run: fromSpec,
|
|
180
|
+
},
|
|
181
|
+
"from-chart": { flags: ["--module", "--expect-broken", "--title", "--out"], run: fromChart },
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
function parseArgs([command, ...rest]) {
|
|
185
|
+
if (command === "-h" || command === "--help") return { help: HELP[""] };
|
|
186
|
+
if (!Object.hasOwn(COMMANDS, command))
|
|
187
|
+
throw new CannotFinish(`${usage(Object.values(USAGE))}\n${more()}`);
|
|
188
|
+
const flags = { "expect-broken": new Set() };
|
|
189
|
+
let input;
|
|
190
|
+
for (let i = 0; i < rest.length; i++) {
|
|
191
|
+
const a = rest[i];
|
|
192
|
+
if (a === "-h" || a === "--help") return { help: HELP[command] };
|
|
193
|
+
if (a === "--png") flags.png = true;
|
|
194
|
+
else if (COMMANDS[command].flags.includes(a)) {
|
|
195
|
+
if (i + 1 === rest.length) throw new CannotFinish(`${a} needs a value. ${more(command)}`);
|
|
196
|
+
const name = a.slice(2);
|
|
197
|
+
if (flags[name] instanceof Set) for (const v of rest[++i].split(",")) flags[name].add(v);
|
|
198
|
+
else flags[name] = rest[++i];
|
|
199
|
+
} else if (!a.startsWith("-") && input === undefined) input = a;
|
|
200
|
+
else throw new CannotFinish(`unknown argument ${a}. ${more(command)}`);
|
|
201
|
+
}
|
|
202
|
+
if (input === undefined) throw new CannotFinish(`${usage([USAGE[command]])}\n${more(command)}`);
|
|
203
|
+
return { command, input, flags };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const rel = (file) => path.relative(process.cwd(), file) || ".";
|
|
207
|
+
const cfgOf = (spec) => spec.replace(/\.tla$/, ".cfg");
|
|
208
|
+
|
|
209
|
+
function findTools() {
|
|
210
|
+
try {
|
|
211
|
+
return findTlc();
|
|
212
|
+
} catch (e) {
|
|
213
|
+
throw new CannotFinish(e.message);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function script(name, scriptArgs, about, { okExits = [0] } = {}) {
|
|
218
|
+
const r = spawnSync(process.execPath, [path.join(here, name), ...scriptArgs], {
|
|
219
|
+
encoding: "utf8",
|
|
220
|
+
});
|
|
221
|
+
if (r.error) throw new CannotFinish(`cannot run ${name}: ${r.error.message}`);
|
|
222
|
+
if (okExits.includes(r.status)) return r;
|
|
223
|
+
const thrown = r.stderr.match(/^Error: (.*)$/m)?.[1];
|
|
224
|
+
throw new CannotFinish(thrown ? `${rel(about)}: ${thrown}` : r.stderr.trim());
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const KEYWORD =
|
|
228
|
+
/^(SPECIFICATION|INIT|NEXT|CONSTANTS?|INVARIANTS?|PROPERTY|PROPERTIES|SYMMETRY|VIEW|CONSTRAINTS?|ACTION_CONSTRAINTS?|CHECK_DEADLOCK|POSTCONDITION|ALIAS)$/;
|
|
229
|
+
|
|
230
|
+
function splitRules(cfg) {
|
|
231
|
+
const words = cfg
|
|
232
|
+
.replace(/\(\*[^]*?\*\)/g, " ")
|
|
233
|
+
.replace(/\\\*.*/g, " ")
|
|
234
|
+
.split(/\s+/)
|
|
235
|
+
.filter(Boolean);
|
|
236
|
+
const kept = [];
|
|
237
|
+
const rules = [];
|
|
238
|
+
let section = "";
|
|
239
|
+
for (const w of words) {
|
|
240
|
+
if (KEYWORD.test(w)) section = w;
|
|
241
|
+
if (!/^INVARIANTS?$/.test(section)) kept.push(KEYWORD.test(w) && kept.length ? `\n${w}` : w);
|
|
242
|
+
else if (w !== section && !rules.includes(w)) rules.push(w);
|
|
243
|
+
}
|
|
244
|
+
return { cfgWithoutRules: `${kept.join(" ")}\n`, rules };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function tlc(tools, spec, cfg, base, dump) {
|
|
248
|
+
fs.writeFileSync(`${base}.cfg`, cfg);
|
|
249
|
+
const metadir = fs.mkdtempSync(path.join(os.tmpdir(), "tlc-"));
|
|
250
|
+
// TLC writes the standard modules, such as Naturals.tla, to java.io.tmpdir under fixed names,
|
|
251
|
+
// so runs that share that folder can read a module while another run rewrites it.
|
|
252
|
+
const javaTmp = path.join(metadir, "tmp");
|
|
253
|
+
fs.mkdirSync(javaTmp);
|
|
254
|
+
// -deadlock: a UI at rest has nothing left to do, which TLC would report as a deadlock.
|
|
255
|
+
const r = spawnSync(
|
|
256
|
+
tools.java,
|
|
257
|
+
[
|
|
258
|
+
`-Djava.io.tmpdir=${javaTmp}`,
|
|
259
|
+
"-cp",
|
|
260
|
+
tools.jar,
|
|
261
|
+
"tlc2.TLC",
|
|
262
|
+
"-metadir",
|
|
263
|
+
metadir,
|
|
264
|
+
"-deadlock",
|
|
265
|
+
"-continue",
|
|
266
|
+
...(dump ? ["-dump", "dot,actionlabels", path.resolve(dump)] : []),
|
|
267
|
+
"-config",
|
|
268
|
+
path.resolve(`${base}.cfg`),
|
|
269
|
+
path.basename(spec),
|
|
270
|
+
],
|
|
271
|
+
{ cwd: path.dirname(path.resolve(spec)), encoding: "utf8", maxBuffer: 1 << 30 },
|
|
272
|
+
);
|
|
273
|
+
fs.rmSync(metadir, { recursive: true, force: true });
|
|
274
|
+
if (r.error) throw new CannotFinish(`cannot run TLC: ${r.error.message}`);
|
|
275
|
+
fs.writeFileSync(`${base}.out`, r.stdout);
|
|
276
|
+
try {
|
|
277
|
+
checkTlcOutput(r.stdout, `${base}.out`);
|
|
278
|
+
} catch (e) {
|
|
279
|
+
throw new TlcStopped(e.message);
|
|
280
|
+
}
|
|
281
|
+
return r.stdout;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function warnUnnamedSteps(spec, cfg, dot) {
|
|
285
|
+
const text = fs
|
|
286
|
+
.readFileSync(spec, "utf8")
|
|
287
|
+
.replace(/\(\*[^]*?\*\)/g, " ")
|
|
288
|
+
.replace(/\\\*.*/g, " ");
|
|
289
|
+
const specName = /^SPECIFICATION\s+(\w+)/m.exec(cfg)?.[1];
|
|
290
|
+
const specBody = specName && new RegExp(`^${specName}\\s*==(.*(\\n[ \\t]+.*)*)`, "m").exec(text);
|
|
291
|
+
const next =
|
|
292
|
+
/^NEXT\s+(\w+)/m.exec(cfg)?.[1] ?? /\[\]\s*\[\s*(\w+)\s*\]_/.exec(specBody?.[1] ?? text)?.[1];
|
|
293
|
+
const graph = parseDot(fs.readFileSync(dot, "utf8"));
|
|
294
|
+
const unnamed = [...graph.edges, ...graph.loops].filter((e) => e.action === next).length;
|
|
295
|
+
if (unnamed > 0)
|
|
296
|
+
console.error(
|
|
297
|
+
`${rel(spec)}: ${unnamed} ${unnamed === 1 ? "step has" : "steps have"} the name ${next}, the whole next-state action, because TLC names a step after the innermost named action that takes it. If the reader must tell these steps apart, give each part of ${next} its own name, as in Pay == ... and ${next} == Pay \\/ Charge.`,
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function readModel(spec, cfgFile) {
|
|
302
|
+
if (!fs.existsSync(spec)) throw new CannotFinish(`${rel(spec)} does not exist`);
|
|
303
|
+
if (!fs.existsSync(cfgFile))
|
|
304
|
+
throw new CannotFinish(
|
|
305
|
+
`${rel(cfgFile)} does not exist. It needs SPECIFICATION and one INVARIANT line per rule.`,
|
|
306
|
+
);
|
|
307
|
+
return { spec, cfgFile, ...splitRules(fs.readFileSync(cfgFile, "utf8")) };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function readInput(spec, expected) {
|
|
311
|
+
const model = readModel(spec, cfgOf(spec));
|
|
312
|
+
const unknown = [...expected].filter((r) => !model.rules.includes(r));
|
|
313
|
+
if (unknown.length)
|
|
314
|
+
throw new CannotFinish(
|
|
315
|
+
`--expect-broken ${unknown.join(", ")}: not a rule in ${rel(model.cfgFile)}, whose rules are ${model.rules.join(", ") || "none"}.`,
|
|
316
|
+
);
|
|
317
|
+
return model;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function check(tools, { spec, cfgFile, cfgWithoutRules, rules }, work, expected) {
|
|
321
|
+
const name = path.basename(spec, ".tla");
|
|
322
|
+
const tlcDir = path.join(work, "tlc");
|
|
323
|
+
const dot = path.join(tlcDir, `${name}.dot`);
|
|
324
|
+
tlc(tools, spec, cfgWithoutRules, path.join(tlcDir, name), dot);
|
|
325
|
+
warnUnnamedSteps(spec, cfgWithoutRules, dot);
|
|
326
|
+
console.log(`TLC on ${path.basename(spec)}:`);
|
|
327
|
+
if (rules.length === 0)
|
|
328
|
+
console.log(` ${path.basename(cfgFile)} has no INVARIANT, so no rule is checked.`);
|
|
329
|
+
// With -continue TLC reports every bad state. The CONSTRAINT stops each path at its first bad
|
|
330
|
+
// state, so a rule that stays broken reports only the step that broke it, not every later state.
|
|
331
|
+
const traces = rules.map((rule) => {
|
|
332
|
+
const file = path.join(tlcDir, `${name}-${rule}`);
|
|
333
|
+
const cfg = `${cfgWithoutRules}INVARIANT ${rule}\nCONSTRAINT ${rule}\n`;
|
|
334
|
+
const broken = /^Error: Invariant /m.test(tlc(tools, spec, cfg, file));
|
|
335
|
+
console.log(
|
|
336
|
+
` ${rule} ${broken ? (expected.has(rule) ? "is broken (expected)" : "is broken") : "holds"}.`,
|
|
337
|
+
);
|
|
338
|
+
return { file: `${file}.out`, rule, broken };
|
|
339
|
+
});
|
|
340
|
+
return { spec, dot, traces, broken: traces.some((t) => t.broken) };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function rulesAsExpected(now, other, expected) {
|
|
344
|
+
const brokenIn = (m) => (m?.traces ?? []).filter((t) => t.broken).map((t) => t.rule);
|
|
345
|
+
if (expected.size === 0) return !now.broken && !other?.broken;
|
|
346
|
+
const unexpected = [...new Set([...brokenIn(now), ...brokenIn(other)])].filter(
|
|
347
|
+
(r) => !expected.has(r),
|
|
348
|
+
);
|
|
349
|
+
const holds = [...expected].filter((r) => !brokenIn(now).includes(r));
|
|
350
|
+
if (unexpected.length) console.log(`\nBroken but not expected: ${unexpected.join(", ")}.`);
|
|
351
|
+
if (holds.length) console.log(`\nExpected to break but holds: ${holds.join(", ")}.`);
|
|
352
|
+
if (!unexpected.length && !holds.length)
|
|
353
|
+
console.log(`\nBroken as expected: ${[...expected].join(", ")}.`);
|
|
354
|
+
return !unexpected.length && !holds.length;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const SCAFFOLD = (name, graph) => {
|
|
358
|
+
const vars = Object.keys([...graph.nodes.values()][0]?.vars ?? {});
|
|
359
|
+
const actions = [...new Set([...graph.edges, ...graph.loops].map((e) => e.action))].sort();
|
|
360
|
+
return `// Chart labels for ${name}, for statechart.mjs. Write labels in plain words, not variable or action names.
|
|
361
|
+
// v, before, and after map each variable to its value. Strings, numbers, and booleans keep their kind.
|
|
362
|
+
// Sequences, tuples, and sets become arrays, indexed from 0. Records and functions become objects.
|
|
363
|
+
// Variables: ${vars.join(", ")}.
|
|
364
|
+
|
|
365
|
+
// The actions the reader takes. facts.settled is true when only these are enabled, and the overview
|
|
366
|
+
// keeps the shortest path to each settled state, plus every bug path.
|
|
367
|
+
export const readerActions = [];
|
|
368
|
+
|
|
369
|
+
// The label for one model state. facts.enabled is the set of actions the state enables,
|
|
370
|
+
// including steps that change no variable. Write it as "context: progress", such as
|
|
371
|
+
// "Offline: upload queued": the chart puts the states of one context in one box.
|
|
372
|
+
// Do not claim a result that a pending step can still change, such as "upload failed" while a
|
|
373
|
+
// retry is still queued.
|
|
374
|
+
export function state(v, facts) {
|
|
375
|
+
return JSON.stringify(v);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// The names of the two parallel regions, when the overview draws them: contexts, then progress.
|
|
379
|
+
// export const regionNames = ["Network", "What happened to the file"];
|
|
380
|
+
|
|
381
|
+
// The group of one model state, in words the reader sees, such as "Upload done". The chart fills
|
|
382
|
+
// the states of one group with one color, and the legend names the groups. TLC still decides
|
|
383
|
+
// which states break a rule, and draws them with a thick red border on top of the group color.
|
|
384
|
+
// groupColors gives a group a light color. Groups without one take a palette color.
|
|
385
|
+
// export function group(v, facts) {}
|
|
386
|
+
// export const groupColors = { "Upload done": "#d1f2eb" };
|
|
387
|
+
|
|
388
|
+
// The label for one step. A step that leaves the reader in the same state is not drawn,
|
|
389
|
+
// unless the reader takes it on a bug path. "" draws no label, and a numbered bug step shows the action.
|
|
390
|
+
const STEPS = {
|
|
391
|
+
${actions.map((a) => `\t${a}: ${JSON.stringify(a)},`).join("\n")}
|
|
392
|
+
};
|
|
393
|
+
export function step(action, before, after) {
|
|
394
|
+
return STEPS[action] ?? action;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// When the step can happen, in plain words, such as "offline or disk full". The chart shows
|
|
398
|
+
// "step [words]". "" adds nothing.
|
|
399
|
+
// export function guard(action, before, after) {}
|
|
400
|
+
|
|
401
|
+
// For a timer step, how many seconds the code waits, from the constant in the code. The chart
|
|
402
|
+
// shows "after N s, step". Return nothing for other steps.
|
|
403
|
+
// export function timer(action, before, after) {}
|
|
404
|
+
|
|
405
|
+
// Why a bad state is bad, in words a product manager reads, such as "the laptop went offline, and
|
|
406
|
+
// the app drops an upload it cannot send". No variable, function, or event names.
|
|
407
|
+
// Counterexamples that break one rule with one cause are one bug, so two bugs that end on the
|
|
408
|
+
// same screen stay apart only if their causes differ. Return null to group by the end state.
|
|
409
|
+
// export function cause(v, facts, invariant) {}
|
|
410
|
+
|
|
411
|
+
// The developer's words for one model state. With it, each bug is also drawn through these states.
|
|
412
|
+
// export function detail(v, facts) {}
|
|
413
|
+
|
|
414
|
+
// The legend's words for each status, in place of the defaults. Group entries keep their names.
|
|
415
|
+
// export const legend = { works: "Works on main", wrong: "Wrong on main", fixed: "Fixed in the open PR" };
|
|
416
|
+
`;
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
async function draw({ now, other, map, work, out, title, comparing }) {
|
|
420
|
+
const shown = published(work, out);
|
|
421
|
+
const mapping = await import(pathToFileURL(path.resolve(map)).href);
|
|
422
|
+
const from = ["--dot", now.dot, "--map", map, "--spec", now.spec];
|
|
423
|
+
from.push(...now.traces.flatMap((t) => ["--traces", t.file]));
|
|
424
|
+
if (other) {
|
|
425
|
+
from.push(`--${other.role}-dot`, other.dot, "--spec", other.spec);
|
|
426
|
+
from.push(...other.traces.flatMap((t) => [`--${other.role}-traces`, t.file]));
|
|
427
|
+
if (other.label !== undefined) from.push("--label", other.label);
|
|
428
|
+
}
|
|
429
|
+
if (comparing) from.push("--full-labels", "--flat");
|
|
430
|
+
else if (mapping.readerActions?.length) from.push("--cover");
|
|
431
|
+
const titled = (suffix) => (title ? ["--title", `${title}${suffix}`] : []);
|
|
432
|
+
|
|
433
|
+
const files = {
|
|
434
|
+
overview: path.join(work, "chart.mmd"),
|
|
435
|
+
stories: path.join(work, "stories.md"),
|
|
436
|
+
summary: path.join(work, "summary.json"),
|
|
437
|
+
};
|
|
438
|
+
const proof = path.join(work, "tlc", "regions-proof.mmd");
|
|
439
|
+
const everyArrow = path.join(work, "tlc", "regions-all.mmd");
|
|
440
|
+
script(
|
|
441
|
+
"tlc-to-chart.mjs",
|
|
442
|
+
[
|
|
443
|
+
...from,
|
|
444
|
+
...(comparing ? [] : ["--regions", "--proof", proof, "--regions-all", everyArrow]),
|
|
445
|
+
...titled(""),
|
|
446
|
+
"--highlight",
|
|
447
|
+
"0",
|
|
448
|
+
"--out",
|
|
449
|
+
files.overview,
|
|
450
|
+
"--story",
|
|
451
|
+
files.stories,
|
|
452
|
+
"--summary",
|
|
453
|
+
files.summary,
|
|
454
|
+
],
|
|
455
|
+
map,
|
|
456
|
+
);
|
|
457
|
+
const summary = JSON.parse(fs.readFileSync(files.summary, "utf8"));
|
|
458
|
+
const charts = [files.overview];
|
|
459
|
+
for (const c of summary.classes) {
|
|
460
|
+
const views = typeof mapping.detail === "function" ? ["", "-developer", "-layered"] : [""];
|
|
461
|
+
for (const view of views) {
|
|
462
|
+
const file = path.join(work, `bug-${c.n}${view}.mmd`);
|
|
463
|
+
const shape = [...(view ? ["--view", view.slice(1)] : []), "--path-only"];
|
|
464
|
+
script(
|
|
465
|
+
"tlc-to-chart.mjs",
|
|
466
|
+
[...from, ...shape, ...titled(`, bug ${c.n}`), "--highlight", String(c.n), "--out", file],
|
|
467
|
+
map,
|
|
468
|
+
);
|
|
469
|
+
charts.push(file);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const { works, wrong, fixed: fixedCount } = summary.chartStatus;
|
|
474
|
+
const L = summary.second?.label;
|
|
475
|
+
console.log(
|
|
476
|
+
`\nChart: ${summary.chartStates} states and ${summary.chartTransitions} transitions from ${summary.modelStates} model states.`,
|
|
477
|
+
);
|
|
478
|
+
const r = summary.regions;
|
|
479
|
+
if (r?.drawn)
|
|
480
|
+
console.log(
|
|
481
|
+
`It has 2 parallel regions, of ${r.states[0]} and ${r.states[1]} states. Proof: their ${r.all} arrows together make exactly the ${r.flat.states} states and ${r.flat.transitions} labeled steps of ${shown(proof)}, and the ${r.transitions} drawn arrows make only steps among them. ${shown(everyArrow)} has all ${r.all} arrows.`,
|
|
482
|
+
);
|
|
483
|
+
else if (r) console.log(`It has no parallel regions, because ${r.why}.`);
|
|
484
|
+
const breaks = `${wrong} ${wrong === 1 ? "breaks" : "break"} a rule`;
|
|
485
|
+
console.log(
|
|
486
|
+
other?.role === "fixed"
|
|
487
|
+
? `${works} work today, ${wrong} still wrong with ${L}, ${fixedCount} fixed or new with ${L}.\n`
|
|
488
|
+
: other?.role === "against"
|
|
489
|
+
? `${works} work, ${breaks}, ${fixedCount} only in ${L}.\n`
|
|
490
|
+
: `${works} work, ${breaks}.\n`,
|
|
491
|
+
);
|
|
492
|
+
if (summary.codeLike.length)
|
|
493
|
+
console.log(
|
|
494
|
+
`These labels or causes read like code. Write them in the reader's words in ${rel(map)}:\n${summary.codeLike.map((t) => ` ${t}`).join("\n")}\n`,
|
|
495
|
+
);
|
|
496
|
+
console.log(
|
|
497
|
+
summary.classes.length
|
|
498
|
+
? fs.readFileSync(files.stories, "utf8").trimEnd()
|
|
499
|
+
: "No rule is broken.",
|
|
500
|
+
);
|
|
501
|
+
return { files, charts };
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function render(charts, shown) {
|
|
505
|
+
const pngs = [];
|
|
506
|
+
for (const chart of charts) {
|
|
507
|
+
const png = chart.replace(/\.mmd$/, ".png");
|
|
508
|
+
const r = spawnSync(
|
|
509
|
+
process.env.MERMAID_CLI || "mmdc",
|
|
510
|
+
[ "-i", chart, "-o", png, "-s", "2"],
|
|
511
|
+
{
|
|
512
|
+
encoding: "utf8",
|
|
513
|
+
},
|
|
514
|
+
);
|
|
515
|
+
if (r.status === 0) pngs.push(png);
|
|
516
|
+
else
|
|
517
|
+
console.error(
|
|
518
|
+
`could not render ${shown(chart)}: ${(r.stderr || r.error?.message || "").trim()}`,
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
return pngs;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function finish({ work, out, inputs }, { files, charts }, ok, png) {
|
|
525
|
+
const pngs = png ? render(charts, published(work, out)) : [];
|
|
526
|
+
publish(work, out, inputs);
|
|
527
|
+
const written = [...charts, ...pngs, files.stories, files.summary]
|
|
528
|
+
.map((f) => path.basename(f))
|
|
529
|
+
.join(", ");
|
|
530
|
+
console.log(`\nWrote ${written} in ${rel(out)}.`);
|
|
531
|
+
if (png && pngs.length < charts.length) return 2;
|
|
532
|
+
return ok ? 0 : 1;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const published = (work, out) => (file) => rel(path.join(out, path.relative(work, file)));
|
|
536
|
+
|
|
537
|
+
const OWN =
|
|
538
|
+
/^((chart|bug-\d+(-developer|-layered)?)\.(mmd|png)|stories\.md|summary\.json|tlc|\.DS_Store)$/;
|
|
539
|
+
|
|
540
|
+
function foreign(dir) {
|
|
541
|
+
try {
|
|
542
|
+
return fs
|
|
543
|
+
.readdirSync(dir)
|
|
544
|
+
.filter((f) => !OWN.test(f))
|
|
545
|
+
.sort();
|
|
546
|
+
} catch (e) {
|
|
547
|
+
if (e.code === "ENOENT") return [];
|
|
548
|
+
throw new CannotFinish(`cannot use ${rel(dir)} as the output folder: ${e.message}`);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function refuseForeign(out, files = foreign(out)) {
|
|
553
|
+
const [first, ...others] = files;
|
|
554
|
+
if (first)
|
|
555
|
+
throw new CannotFinish(
|
|
556
|
+
`${rel(out)} has ${others.length ? `${others.length + 1} files that this command did not write, such as ${first}` : `a file that this command did not write: ${first}`}. A run puts its files in place of the whole output folder, so pass --out with a new folder.`,
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function outDir(flagged, input, name) {
|
|
561
|
+
const out = flagged ?? path.join(path.dirname(input), `${name}-statechart`);
|
|
562
|
+
refuseForeign(out);
|
|
563
|
+
return out;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const fingerprint = (file) => {
|
|
567
|
+
try {
|
|
568
|
+
const { ino, ctimeNs } = fs.statSync(file, { bigint: true });
|
|
569
|
+
return `${ino} ${ctimeNs} ${fs.readFileSync(file, "base64")}`;
|
|
570
|
+
} catch {
|
|
571
|
+
return null;
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
function snapshot(files, inputs = new Map()) {
|
|
576
|
+
for (const f of files) if (f && !inputs.has(f)) inputs.set(f, fingerprint(f));
|
|
577
|
+
return inputs;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
async function inWorkFolder(body) {
|
|
581
|
+
const work = fs.mkdtempSync(path.join(os.tmpdir(), "statechart-"));
|
|
582
|
+
fs.mkdirSync(path.join(work, "tlc"));
|
|
583
|
+
let keep = false;
|
|
584
|
+
try {
|
|
585
|
+
return await body(work);
|
|
586
|
+
} catch (e) {
|
|
587
|
+
keep = e instanceof TlcStopped;
|
|
588
|
+
throw e;
|
|
589
|
+
} finally {
|
|
590
|
+
if (!keep) fs.rmSync(work, { recursive: true, force: true });
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const moved = (from, to, ...missed) => {
|
|
595
|
+
try {
|
|
596
|
+
fs.renameSync(from, to);
|
|
597
|
+
return true;
|
|
598
|
+
} catch (e) {
|
|
599
|
+
if (missed.includes(e.code)) return false;
|
|
600
|
+
throw e;
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
function publish(work, out, inputs) {
|
|
605
|
+
const parent = path.dirname(path.resolve(out));
|
|
606
|
+
fs.mkdirSync(parent, { recursive: true });
|
|
607
|
+
const next = fs.mkdtempSync(path.join(parent, `.${path.basename(out)}-`));
|
|
608
|
+
const old = `${next}-old`;
|
|
609
|
+
let aside = false;
|
|
610
|
+
try {
|
|
611
|
+
fs.cpSync(work, next, { recursive: true });
|
|
612
|
+
const changed = [...inputs].find(([f, was]) => fingerprint(f) !== was)?.[0];
|
|
613
|
+
if (changed)
|
|
614
|
+
throw new CannotFinish(
|
|
615
|
+
`${rel(changed)} changed while this run read it, so the run left ${rel(out)} as it was. Run the command again.`,
|
|
616
|
+
);
|
|
617
|
+
refuseForeign(out);
|
|
618
|
+
while (!moved(next, out, "ENOTEMPTY", "EEXIST")) {
|
|
619
|
+
if (aside) {
|
|
620
|
+
refuseForeign(out);
|
|
621
|
+
fs.rmSync(old, { recursive: true, force: true });
|
|
622
|
+
aside = false;
|
|
623
|
+
}
|
|
624
|
+
aside = moved(out, old, "ENOENT");
|
|
625
|
+
if (aside) refuseForeign(out, foreign(old));
|
|
626
|
+
}
|
|
627
|
+
} catch (e) {
|
|
628
|
+
const back = !aside || putBack(old, out);
|
|
629
|
+
fs.rmSync(next, { recursive: true, force: true });
|
|
630
|
+
const why = e instanceof CannotFinish ? e.message : `cannot replace ${rel(out)}: ${e.message}.`;
|
|
631
|
+
if (!back)
|
|
632
|
+
throw new CannotFinish(
|
|
633
|
+
`${why} This run moved the previous ${rel(out)} to ${rel(old)} and could not put it back.`,
|
|
634
|
+
);
|
|
635
|
+
throw e instanceof CannotFinish
|
|
636
|
+
? e
|
|
637
|
+
: new CannotFinish(`${why} The run left ${rel(out)} as it was.`);
|
|
638
|
+
}
|
|
639
|
+
if (aside) fs.rmSync(old, { recursive: true, force: true });
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function putBack(old, out) {
|
|
643
|
+
try {
|
|
644
|
+
return moved(old, out, "ENOTEMPTY", "EEXIST");
|
|
645
|
+
} catch {
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function secondModel(input, { fixed, against, label }) {
|
|
651
|
+
if (fixed && against)
|
|
652
|
+
throw new CannotFinish(`use --fixed or --against, not both. ${more("from-spec")}`);
|
|
653
|
+
if (!fixed && !against) {
|
|
654
|
+
if (label === undefined) return null;
|
|
655
|
+
throw new CannotFinish(
|
|
656
|
+
`--label names the --fixed or --against model; it needs one of them. ${more("from-spec")}`,
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
const [role, spec] = fixed ? ["fixed", fixed] : ["against", against];
|
|
660
|
+
const name = path.basename(input, ".tla");
|
|
661
|
+
// TLC's output files are named after the spec, so a second model with the same name overwrites them.
|
|
662
|
+
if (path.basename(spec, ".tla") === name)
|
|
663
|
+
throw new CannotFinish(
|
|
664
|
+
`${rel(spec)} needs its own file and module name, such as ${name}${fixed ? "Fixed" : "Agreed"}.tla.`,
|
|
665
|
+
);
|
|
666
|
+
const cfg = fs.existsSync(cfgOf(spec)) ? cfgOf(spec) : cfgOf(input);
|
|
667
|
+
return { role, label, model: readModel(spec, cfg) };
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
async function fromSpec(input, { "expect-broken": expected, ...flags }) {
|
|
671
|
+
const name = path.basename(input, ".tla");
|
|
672
|
+
const map = flags.map ?? path.join(path.dirname(input), `${name}.map.mjs`);
|
|
673
|
+
const secondSpec = flags.fixed ?? flags.against;
|
|
674
|
+
const inputs = snapshot([input, cfgOf(input), map, secondSpec, secondSpec && cfgOf(secondSpec)]);
|
|
675
|
+
const second = secondModel(input, flags);
|
|
676
|
+
const tools = findTools();
|
|
677
|
+
const model = readInput(input, expected);
|
|
678
|
+
const out = outDir(flags.out, input, name);
|
|
679
|
+
return inWorkFolder(async (work) => {
|
|
680
|
+
const now = check(tools, model, work, expected);
|
|
681
|
+
if (!fs.existsSync(map)) {
|
|
682
|
+
fs.writeFileSync(map, SCAFFOLD(name, parseDot(fs.readFileSync(now.dot, "utf8"))));
|
|
683
|
+
throw new CannotFinish(
|
|
684
|
+
`\nWrote ${rel(map)}. Fill in its labels, then run this command again.`,
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
const other = second && {
|
|
688
|
+
role: second.role,
|
|
689
|
+
label: second.label,
|
|
690
|
+
...check(tools, second.model, work, expected),
|
|
691
|
+
};
|
|
692
|
+
const drawn = await draw({ now, other, map, work, out, title: flags.title });
|
|
693
|
+
return finish({ work, out, inputs }, drawn, rulesAsExpected(now, other, expected), flags.png);
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
async function fromChart(input, { "expect-broken": expected, ...flags }) {
|
|
698
|
+
const name =
|
|
699
|
+
flags.module ??
|
|
700
|
+
path
|
|
701
|
+
.basename(input, path.extname(input))
|
|
702
|
+
.replace(/(^|[^A-Za-z0-9]+)([A-Za-z0-9])/g, (_, __, c) => c.toUpperCase());
|
|
703
|
+
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(name))
|
|
704
|
+
throw new CannotFinish(`cannot make a TLA+ module name from ${input}; add --module NAME`);
|
|
705
|
+
const spec = path.join(path.dirname(input), `${name}.tla`);
|
|
706
|
+
const map = spec.replace(/\.tla$/, ".map.mjs");
|
|
707
|
+
const inputs = snapshot([input]);
|
|
708
|
+
if (fs.existsSync(spec)) {
|
|
709
|
+
console.log(`Kept ${rel(spec)}. Delete it to write the spec again from ${rel(input)}.`);
|
|
710
|
+
} else {
|
|
711
|
+
script("chart-to-tla.mjs", [input, "--module", name], input);
|
|
712
|
+
console.log(`Wrote ${name}.tla, ${name}.cfg, and ${name}.map.mjs from ${rel(input)}.`);
|
|
713
|
+
}
|
|
714
|
+
snapshot([spec, cfgOf(spec), map], inputs);
|
|
715
|
+
const tools = findTools();
|
|
716
|
+
const model = readInput(spec, expected);
|
|
717
|
+
const out = outDir(flags.out, input, name);
|
|
718
|
+
return inWorkFolder(async (work) => {
|
|
719
|
+
const now = check(tools, model, work, expected);
|
|
720
|
+
const drawn = await draw({ now, map, work, out, title: flags.title, comparing: true });
|
|
721
|
+
const compared = script("compare-charts.mjs", [input, drawn.files.overview], input, {
|
|
722
|
+
okExits: [0, 1],
|
|
723
|
+
});
|
|
724
|
+
if (compared.status === 0) console.log(`\nThe spec draws ${rel(input)} back unchanged.`);
|
|
725
|
+
else
|
|
726
|
+
console.log(
|
|
727
|
+
`\nThe spec does not draw ${rel(input)}:\n${compared.stdout.trimEnd().replace(/^/gm, " ")}`,
|
|
728
|
+
);
|
|
729
|
+
return finish(
|
|
730
|
+
{ work, out, inputs },
|
|
731
|
+
drawn,
|
|
732
|
+
rulesAsExpected(now, null, expected) && compared.status === 0,
|
|
733
|
+
flags.png,
|
|
734
|
+
);
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
try {
|
|
739
|
+
const { help, command, input, flags } = parseArgs(process.argv.slice(2));
|
|
740
|
+
if (help) {
|
|
741
|
+
console.log(help);
|
|
742
|
+
process.exit(0);
|
|
743
|
+
}
|
|
744
|
+
process.exit(await COMMANDS[command].run(input, flags));
|
|
745
|
+
} catch (e) {
|
|
746
|
+
if (!(e instanceof CannotFinish)) throw e;
|
|
747
|
+
console.error(e.message);
|
|
748
|
+
process.exit(2);
|
|
749
|
+
}
|