dsh-pstack 0.2.0-beta.1 → 0.2.0-beta.3
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 +2 -3
- package/README.md +135 -84
- package/README.zh.md +185 -0
- package/cordis.patch.yml +97 -27
- package/dist/host-contract-CUPSpeNk.d.mts +22 -0
- package/dist/index.d.mts +49 -238
- package/dist/index.mjs +250 -123
- package/dist/models.d.mts +44 -0
- package/dist/models.mjs +201 -0
- package/dist/paths-BT77khwm.mjs +48 -0
- package/dist/pstack/LICENSE +21 -0
- package/dist/pstack/README.md +291 -0
- package/dist/pstack/agents/poteto-agent.md +9 -0
- package/{assets → dist/pstack}/docs/guide/01-setup.md +13 -11
- package/{assets → dist/pstack}/docs/guide/02-poteto-mode.md +4 -4
- package/{assets → dist/pstack}/docs/guide/04-design.md +4 -4
- package/{assets → dist/pstack}/docs/guide/05-build-and-clean.md +2 -2
- package/{assets → dist/pstack}/docs/guide/06-verify-and-ship.md +13 -3
- package/{assets → dist/pstack}/docs/guide/07-overnight.md +15 -13
- package/{assets → dist/pstack}/docs/guide/09-make-it-yours.md +6 -2
- package/{assets → dist/pstack}/docs/guide/10-recipes-and-pitfalls.md +7 -5
- package/{assets → dist/pstack}/docs/guide/README.md +1 -1
- package/dist/pstack/docs/harness/dsh.md +98 -0
- package/{assets → dist/pstack}/skills/architect/SKILL.md +12 -12
- package/dist/pstack/skills/arena/SKILL.md +50 -0
- package/{assets → dist/pstack}/skills/automate-me/SKILL.md +14 -14
- package/{assets → dist/pstack}/skills/benchmark-checklist/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/blast-radius/SKILL.md +4 -6
- package/{assets → dist/pstack}/skills/correct/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/create-formal-verification-skill/SKILL.md +4 -2
- package/{assets → dist/pstack}/skills/create-verification-skill/SKILL.md +5 -5
- package/dist/pstack/skills/figure-it-out/SKILL.md +54 -0
- package/dist/pstack/skills/how/SKILL.md +38 -0
- package/{assets → dist/pstack}/skills/in-your-own-words/SKILL.md +1 -1
- package/dist/pstack/skills/interrogate/SKILL.md +33 -0
- package/{assets → dist/pstack}/skills/ketchup/SKILL.md +3 -5
- package/dist/pstack/skills/ketchup-video/SKILL.md +29 -0
- package/{assets → dist/pstack}/skills/maintain-formal-verification-skill/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/maintain-verification-skill/SKILL.md +3 -3
- package/dist/pstack/skills/make-bot-ui/SKILL.md +117 -0
- package/dist/pstack/skills/no-comments/SKILL.md +26 -0
- package/{assets → dist/pstack}/skills/poteto-help/SKILL.md +34 -31
- package/{assets → dist/pstack}/skills/poteto-help/references/prompting.md +1 -1
- package/{assets → dist/pstack}/skills/poteto-help/references/recipes.md +3 -3
- package/{assets → dist/pstack}/skills/poteto-mode/SKILL.md +20 -19
- package/dist/pstack/skills/poteto-mode/playbooks/authoring-a-skill.md +12 -0
- package/dist/pstack/skills/poteto-mode/playbooks/autonomous-run.md +13 -0
- package/dist/pstack/skills/poteto-mode/playbooks/autopilot-full.md +25 -0
- package/dist/pstack/skills/poteto-mode/playbooks/autopilot-stack.md +8 -0
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/babysit.md +4 -4
- package/dist/pstack/skills/poteto-mode/playbooks/bug-fix.md +15 -0
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/eval.md +3 -3
- package/dist/pstack/skills/poteto-mode/playbooks/feature.md +21 -0
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/hillclimb.md +3 -3
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/investigation.md +2 -2
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/multi-phase-plan.md +23 -23
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/opening-a-pr.md +6 -6
- package/dist/pstack/skills/poteto-mode/playbooks/orchestrate.md +25 -0
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/pause-safely.md +2 -2
- package/dist/pstack/skills/poteto-mode/playbooks/perf-issue.md +25 -0
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/prototype.md +4 -4
- package/dist/pstack/skills/poteto-mode/playbooks/refactoring.md +16 -0
- package/dist/pstack/skills/poteto-mode/playbooks/runtime-forensics.md +11 -0
- package/dist/pstack/skills/poteto-mode/playbooks/session-pickup.md +11 -0
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/shipping.md +2 -2
- package/{assets → dist/pstack}/skills/poteto-mode/playbooks/trace-forensics.md +2 -2
- package/dist/pstack/skills/poteto-mode/playbooks/visual-parity.md +11 -0
- package/dist/pstack/skills/poteto-mode/playbooks/worktree-cleanup.md +12 -0
- package/{assets → dist/pstack}/skills/poteto-mode/scripts/check-plan.mjs +6 -8
- package/dist/pstack/skills/poteto-mode/scripts/check-plan.test.mjs +128 -0
- package/{assets/skills/poteto-mode/scripts/check-plan.test.mjs → dist/pstack/skills/poteto-mode/scripts/expect.mjs} +12 -101
- package/dist/pstack/skills/poteto-mode/scripts/support.mjs +93 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/cli.mjs +123 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/cli.test.mjs +217 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/fakes.test-helper.mjs +119 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/github.mjs +626 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/github.test.mjs +521 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/policy.mjs +657 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/policy.test.mjs +523 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/render.mjs +106 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/types.mjs +9 -0
- package/dist/pstack/skills/poteto-mode/scripts/watch-pr/watch-pr.mjs +5 -0
- package/{assets → dist/pstack}/skills/poteto-mode/scripts/worktree-audit.mjs +15 -22
- package/dist/pstack/skills/poteto-mode/scripts/worktree-audit.test.mjs +134 -0
- package/{assets → dist/pstack}/skills/principle-attack-the-premise/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-boundary-discipline/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-encode-lessons-in-structure/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-exhaust-the-design-space/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-experience-first/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-explain-the-number/SKILL.md +3 -2
- package/{assets → dist/pstack}/skills/principle-fix-root-causes/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-laziness-protocol/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-make-operations-idempotent/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-minimize-reader-load/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-model-the-domain/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-never-block-on-the-human/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-outcome-oriented-execution/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-prove-it-works/SKILL.md +2 -2
- package/{assets → dist/pstack}/skills/principle-redesign-from-first-principles/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-separate-before-serializing-shared-state/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-sequence-verifiable-units/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-subtract-before-you-add/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-test-behavior-not-implementation/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/principle-type-system-discipline/SKILL.md +1 -1
- package/dist/pstack/skills/recall/SKILL.md +37 -0
- package/dist/pstack/skills/reflect/SKILL.md +71 -0
- package/{assets → dist/pstack}/skills/reflect/references/divergent-reviewer.md +4 -4
- package/{assets → dist/pstack}/skills/reflect/references/judgment-reviewer.md +2 -2
- package/{assets → dist/pstack}/skills/reflect/references/synthesizer.md +5 -5
- package/{assets → dist/pstack}/skills/reflect/references/tooling-reviewer.md +2 -2
- package/dist/pstack/skills/setup-pstack/SKILL.md +80 -0
- package/{assets → dist/pstack}/skills/sharpen/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/show-me-your-work/SKILL.md +8 -8
- package/{assets → dist/pstack}/skills/show-me-your-work/scripts/log.mjs +1 -3
- package/dist/pstack/skills/show-me-your-work/scripts/log.test.mjs +56 -0
- package/dist/pstack/skills/statechart/SKILL.md +82 -0
- package/dist/pstack/skills/statechart/references/render.md +93 -0
- package/dist/pstack/skills/statechart/scripts/backends/cfg.mjs +31 -0
- package/dist/pstack/skills/statechart/scripts/backends/mermaid.mjs +186 -0
- package/dist/pstack/skills/statechart/scripts/backends/plain-text.mjs +62 -0
- package/dist/pstack/skills/statechart/scripts/backends/report.mjs +84 -0
- package/dist/pstack/skills/statechart/scripts/frontends/config.mjs +268 -0
- package/dist/pstack/skills/statechart/scripts/frontends/dot.mjs +67 -0
- package/dist/pstack/skills/statechart/scripts/frontends/marks.mjs +94 -0
- package/dist/pstack/skills/statechart/scripts/frontends/messages.mjs +48 -0
- package/dist/pstack/skills/statechart/scripts/frontends/spec.mjs +101 -0
- package/dist/pstack/skills/statechart/scripts/frontends/trace.mjs +39 -0
- package/dist/pstack/skills/statechart/scripts/frontends/value.mjs +180 -0
- package/dist/pstack/skills/statechart/scripts/frontends/verdict.mjs +40 -0
- package/dist/pstack/skills/statechart/scripts/frontends/view.mjs +99 -0
- package/dist/pstack/skills/statechart/scripts/ir.mjs +277 -0
- package/dist/pstack/skills/statechart/scripts/passes/annotate.mjs +92 -0
- package/dist/pstack/skills/statechart/scripts/passes/label.mjs +210 -0
- package/dist/pstack/skills/statechart/scripts/passes/lift.mjs +84 -0
- package/dist/pstack/skills/statechart/scripts/passes/loop-check.mjs +47 -0
- package/dist/pstack/skills/statechart/scripts/passes/paths.mjs +72 -0
- package/dist/pstack/skills/statechart/scripts/passes/reach.mjs +135 -0
- package/dist/pstack/skills/statechart/scripts/passes/region-check.mjs +82 -0
- package/dist/pstack/skills/statechart/scripts/passes/regions.mjs +268 -0
- package/dist/pstack/skills/statechart/scripts/pipeline.mjs +72 -0
- package/dist/pstack/skills/statechart/scripts/run.mjs +440 -0
- package/dist/pstack/skills/statechart/scripts/statechart.mjs +155 -0
- package/dist/pstack/skills/statechart/scripts/statechart.test.mjs +198 -0
- package/dist/pstack/skills/statechart/scripts/worker.mjs +31 -0
- package/dist/pstack/skills/swarm/SKILL.md +43 -0
- package/{assets → dist/pstack}/skills/tdd/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/teach/SKILL.md +2 -2
- package/{assets → dist/pstack}/skills/technical-writing/SKILL.md +1 -1
- package/{assets → dist/pstack}/skills/typescript-best-practices/SKILL.md +3 -6
- package/{assets → dist/pstack}/skills/why/SKILL.md +8 -8
- package/dist/script.d.mts +9 -111
- package/dist/script.mjs +32 -329
- package/dist/skill.d.mts +38 -0
- package/dist/skill.mjs +183 -0
- package/dist/skills/authored/decision-audit/SKILL.md +99 -0
- package/dist/skills/authored/decision-audit/SKILL.zh.md +99 -0
- package/dist/skills/authored/decision-audit/references/recover-commit.md +17 -0
- package/dist/skills/authored/decision-audit/references/recover-commit.zh.md +17 -0
- package/dist/skills/authored/decision-write/SKILL.md +131 -0
- package/dist/skills/authored/decision-write/SKILL.zh.md +131 -0
- package/dist/skills/authored/decision-write/references/land.md +48 -0
- package/dist/skills/authored/decision-write/references/land.zh.md +48 -0
- package/dist/skills/authored/setup-project-docs/SKILL.md +58 -0
- package/dist/skills/authored/setup-project-docs/SKILL.zh.md +58 -0
- package/dist/skills/authored/setup-project-docs/references/decision-conventions.md +11 -0
- package/dist/skills/authored/setup-project-docs/references/decision-conventions.zh.md +11 -0
- package/dist/skills/vendored/retro/SKILL.md +44 -0
- package/dist/skills/vendored/retro/agents/openai.yaml +5 -0
- package/dist/skills/vendored/writing-for-agents/SKILL-MECHANICS.md +22 -0
- package/dist/skills/vendored/writing-for-agents/SKILL.md +81 -0
- package/dist/skills/vendored/writing-for-agents/agents/openai.yaml +3 -0
- package/dist/subagent.d.mts +56 -193
- package/dist/subagent.mjs +443 -741
- package/locale/en.json +3 -2
- package/locale/zh.json +3 -2
- package/package.json +63 -66
- package/assets/agents/poteto-agent.md +0 -8
- package/assets/docs/harness/dsh.md +0 -60
- package/assets/docs/pstack-models.example.md +0 -54
- package/assets/skills/arena/SKILL.md +0 -50
- package/assets/skills/figure-it-out/SKILL.md +0 -54
- package/assets/skills/how/SKILL.md +0 -38
- package/assets/skills/interrogate/SKILL.md +0 -71
- package/assets/skills/interrogate/references/code-quality-review.md +0 -47
- package/assets/skills/interrogate/references/lead-judgment.md +0 -39
- package/assets/skills/interrogate/references/reviewer-prompt.md +0 -62
- package/assets/skills/interrogate/references/rubric.md +0 -77
- package/assets/skills/no-comments/SKILL.md +0 -26
- package/assets/skills/poteto-mode/playbooks/authoring-a-skill.md +0 -12
- package/assets/skills/poteto-mode/playbooks/autonomous-run.md +0 -15
- package/assets/skills/poteto-mode/playbooks/autopilot-full.md +0 -25
- package/assets/skills/poteto-mode/playbooks/autopilot-stack.md +0 -12
- package/assets/skills/poteto-mode/playbooks/bug-fix.md +0 -15
- package/assets/skills/poteto-mode/playbooks/feature.md +0 -21
- package/assets/skills/poteto-mode/playbooks/orchestrate.md +0 -112
- package/assets/skills/poteto-mode/playbooks/perf-issue.md +0 -25
- package/assets/skills/poteto-mode/playbooks/refactoring.md +0 -16
- package/assets/skills/poteto-mode/playbooks/runtime-forensics.md +0 -11
- package/assets/skills/poteto-mode/playbooks/session-pickup.md +0 -11
- package/assets/skills/poteto-mode/playbooks/visual-parity.md +0 -11
- package/assets/skills/poteto-mode/playbooks/worktree-cleanup.md +0 -20
- package/assets/skills/poteto-mode/scripts/orch/orch.mjs +0 -4282
- package/assets/skills/poteto-mode/scripts/orch/orch.test.mjs +0 -2042
- package/assets/skills/poteto-mode/scripts/watch-pr/cli.test.mjs +0 -5101
- package/assets/skills/poteto-mode/scripts/watch-pr/github.test.mjs +0 -1177
- package/assets/skills/poteto-mode/scripts/watch-pr/policy.test.mjs +0 -1441
- package/assets/skills/poteto-mode/scripts/watch-pr/watch-pr.mjs +0 -4601
- package/assets/skills/recall/SKILL.md +0 -37
- package/assets/skills/reflect/SKILL.md +0 -69
- package/assets/skills/setup-pstack/SKILL.md +0 -67
- package/assets/skills/swarm/SKILL.md +0 -43
- package/dist/frontmatter-BQX3ekwP.mjs +0 -229
- package/dist/frontmatter-BQX3ekwP.mjs.map +0 -1
- package/dist/history.d.mts +0 -429
- package/dist/history.mjs +0 -1129
- package/dist/history.mjs.map +0 -1
- package/dist/host-contract-o47JH_r8.d.mts +0 -527
- package/dist/index.mjs.map +0 -1
- package/dist/paths-D_JlVmBk.mjs +0 -42
- package/dist/paths-D_JlVmBk.mjs.map +0 -1
- package/dist/script.mjs.map +0 -1
- package/dist/subagent.mjs.map +0 -1
- /package/{static/icon.png → assets/logo.png} +0 -0
- /package/{assets → dist/pstack}/agents/comment-sicko.md +0 -0
- /package/{assets → dist/pstack}/docs/guide/03-understand.md +0 -0
- /package/{assets → dist/pstack}/docs/guide/08-principles.md +0 -0
- /package/{assets → dist/pstack}/docs/guide/images/design.jpg +0 -0
- /package/{assets → dist/pstack}/docs/guide/images/overnight.jpg +0 -0
- /package/{assets → dist/pstack}/docs/guide/images/recipes.jpg +0 -0
- /package/{assets → dist/pstack}/docs/guide/images/router.jpg +0 -0
- /package/{assets → dist/pstack}/docs/guide/images/understanding.jpg +0 -0
- /package/{assets → dist/pstack}/docs/guide/images/verification.jpg +0 -0
- /package/{assets → dist/pstack}/skills/architect/references/design-red-flags.md +0 -0
- /package/{assets → dist/pstack}/skills/architect/references/rationale-template.md +0 -0
- /package/{assets → dist/pstack}/skills/architect/references/runner-prompt.md +0 -0
- /package/{assets → dist/pstack}/skills/bro/SKILL.md +0 -0
- /package/{assets → dist/pstack}/skills/create-verification-skill/references/feature-map-example/README.md +0 -0
- /package/{assets → dist/pstack}/skills/create-verification-skill/references/feature-map-example/create-note.md +0 -0
- /package/{assets → dist/pstack}/skills/create-verification-skill/references/feature-map-example/search.md +0 -0
- /package/{assets → dist/pstack}/skills/how/references/explainer-prompt.md +0 -0
- /package/{assets → dist/pstack}/skills/how/references/explorer-prompt.md +0 -0
- /package/{assets → dist/pstack}/skills/poteto-mode/references/bugbot-triage.md +0 -0
- /package/{assets → dist/pstack}/skills/principle-build-the-lever/SKILL.md +0 -0
- /package/{assets → dist/pstack}/skills/principle-foundational-thinking/SKILL.md +0 -0
- /package/{assets → dist/pstack}/skills/principle-guard-the-context-window/SKILL.md +0 -0
- /package/{assets → dist/pstack}/skills/show-me-your-work/references/decision-log-template.tsv +0 -0
- /package/{assets → dist/pstack}/skills/typescript-best-practices/references/patterns.md +0 -0
- /package/{assets → dist/pstack}/skills/unslop/SKILL.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/epistemics.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/investigator-prompt.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/source-playbook.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/code-archaeology.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/databricks.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/datadog.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/incident-postmortem.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/linear.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/notion.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/sentry.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/sources/slack.md +0 -0
- /package/{assets → dist/pstack}/skills/why/references/synthesizer-prompt.md +0 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
import { LEVEL, PART, hasSection, must, ruleKey, stateRules } from "./ir.mjs";
|
|
2
|
+
import { plainText } from "./backends/plain-text.mjs";
|
|
3
|
+
import { boundRun, chartRun, configText } from "./backends/cfg.mjs";
|
|
4
|
+
import { readConfig } from "./frontends/config.mjs";
|
|
5
|
+
import { parseDot } from "./frontends/dot.mjs";
|
|
6
|
+
import { readMessages } from "./frontends/messages.mjs";
|
|
7
|
+
import { readCounterexample, readMarks } from "./frontends/marks.mjs";
|
|
8
|
+
import { initialOnlyProperties, readChecks, readSany, readSpec } from "./frontends/spec.mjs";
|
|
9
|
+
import { readTrace } from "./frontends/trace.mjs";
|
|
10
|
+
import { readVerdict } from "./frontends/verdict.mjs";
|
|
11
|
+
import { addPast, deadEnds, finder, lasso, mark } from "./passes/annotate.mjs";
|
|
12
|
+
import { pathGraph } from "./passes/paths.mjs";
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import os from "node:os";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { execFile, spawn } from "node:child_process";
|
|
17
|
+
import { inspect } from "node:util";
|
|
18
|
+
import { Buffer } from "node:buffer";
|
|
19
|
+
import { randomBytes } from "node:crypto";
|
|
20
|
+
// runtime/skills/statechart/scripts/run.ts
|
|
21
|
+
function* fileLines(file) {
|
|
22
|
+
const fd = fs.openSync(file, "r");
|
|
23
|
+
try {
|
|
24
|
+
let buf = Buffer.allocUnsafe(PART);
|
|
25
|
+
let kept = 0;
|
|
26
|
+
for (;;) {
|
|
27
|
+
if (kept === buf.length) buf = Buffer.concat([buf], buf.length * 2);
|
|
28
|
+
const n = fs.readSync(fd, buf, kept, buf.length - kept, null);
|
|
29
|
+
if (!n) break;
|
|
30
|
+
const end = kept + n;
|
|
31
|
+
const cut = buf.lastIndexOf(10, end - 1);
|
|
32
|
+
if (cut === -1) {
|
|
33
|
+
kept = end;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
yield* buf.toString("utf8", 0, cut).split("\n");
|
|
37
|
+
kept = buf.copy(buf, 0, cut + 1, end);
|
|
38
|
+
}
|
|
39
|
+
yield buf.toString("utf8", 0, kept);
|
|
40
|
+
} finally {
|
|
41
|
+
fs.closeSync(fd);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const TLC_TOOL = "http:tla2tools[url=https://github.com/tlaplus/tlaplus/releases/download/v1.7.4/tla2tools.jar,checksum=sha256:936a262061c914694dfd669a543be24573c45d5aa0ff20a8b96b23d01e050e88]@1.7.4";
|
|
45
|
+
const JAVA_TOOL = "java@temurin-25";
|
|
46
|
+
const PINNED_FLAGS = [
|
|
47
|
+
"-workers",
|
|
48
|
+
"1",
|
|
49
|
+
"-fp",
|
|
50
|
+
"0",
|
|
51
|
+
"-lncheck",
|
|
52
|
+
"final"
|
|
53
|
+
];
|
|
54
|
+
const runChild = (file, args, options) => new Promise((resolve) => {
|
|
55
|
+
execFile(file, args, {
|
|
56
|
+
encoding: "utf8",
|
|
57
|
+
maxBuffer: 1 << 30,
|
|
58
|
+
...options
|
|
59
|
+
}, (error, stdout, stderr) => resolve({
|
|
60
|
+
status: error ? typeof error.code === "number" ? error.code : null : 0,
|
|
61
|
+
stdout,
|
|
62
|
+
stderr,
|
|
63
|
+
error: typeof error?.code === "string" ? error : void 0
|
|
64
|
+
}));
|
|
65
|
+
});
|
|
66
|
+
const killGroup = (pid) => new Promise((resolve) => spawn("/bin/sh", [
|
|
67
|
+
"-c",
|
|
68
|
+
"kill -s KILL -- \"-$1\"",
|
|
69
|
+
"sh",
|
|
70
|
+
String(pid)
|
|
71
|
+
], { stdio: "ignore" }).on("exit", resolve));
|
|
72
|
+
const messageOf = (error) => typeof error === "object" && error !== null && "message" in error && typeof error.message === "string" ? error.message : inspect(error);
|
|
73
|
+
const joined = (files) => {
|
|
74
|
+
const fds = [];
|
|
75
|
+
const sizes = [];
|
|
76
|
+
for (const f of files) {
|
|
77
|
+
const fd = fs.openSync(f, "r");
|
|
78
|
+
fds.push(fd);
|
|
79
|
+
sizes.push(fs.fstatSync(fd).size);
|
|
80
|
+
}
|
|
81
|
+
return { *[Symbol.iterator]() {
|
|
82
|
+
for (let f = 0; f < fds.length; f++) for (let pos = 0; pos < sizes[f];) {
|
|
83
|
+
const buf = Buffer.allocUnsafe(Math.min(PART, sizes[f] - pos));
|
|
84
|
+
const n = fs.readSync(fds[f], buf, 0, buf.length, pos);
|
|
85
|
+
if (!n) break;
|
|
86
|
+
pos += n;
|
|
87
|
+
yield buf.subarray(0, n);
|
|
88
|
+
}
|
|
89
|
+
} };
|
|
90
|
+
};
|
|
91
|
+
const utf8 = new TextDecoder("utf-8", { ignoreBOM: true });
|
|
92
|
+
function* outputLines(output) {
|
|
93
|
+
let rest = /* @__PURE__ */ new Uint8Array(0);
|
|
94
|
+
for (const part of output) {
|
|
95
|
+
const buf = rest.length ? Buffer.concat([rest, part]) : part;
|
|
96
|
+
const cut = buf.lastIndexOf(10);
|
|
97
|
+
if (cut === -1) {
|
|
98
|
+
rest = buf;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
yield* utf8.decode(buf.subarray(0, cut)).split("\n");
|
|
102
|
+
rest = buf.subarray(cut + 1);
|
|
103
|
+
}
|
|
104
|
+
yield utf8.decode(rest);
|
|
105
|
+
}
|
|
106
|
+
const messagesOf = (output) => ({ [Symbol.iterator]: () => readMessages(outputLines(output)) });
|
|
107
|
+
const runToFiles = (file, args, { out, ...options }) => new Promise((resolve) => {
|
|
108
|
+
const files = [`${out}.stdout`, `${out}.stderr`];
|
|
109
|
+
const fds = [];
|
|
110
|
+
for (const f of files) fds.push(fs.openSync(f, "w"));
|
|
111
|
+
let child;
|
|
112
|
+
try {
|
|
113
|
+
child = spawn(file, args, {
|
|
114
|
+
...options,
|
|
115
|
+
stdio: ["ignore", ...fds]
|
|
116
|
+
});
|
|
117
|
+
} finally {
|
|
118
|
+
for (const fd of fds) fs.closeSync(fd);
|
|
119
|
+
}
|
|
120
|
+
let error;
|
|
121
|
+
const settle = (code) => resolve({
|
|
122
|
+
status: error ? null : code,
|
|
123
|
+
stdout: files[0],
|
|
124
|
+
output: joined(files),
|
|
125
|
+
error
|
|
126
|
+
});
|
|
127
|
+
child.on("error", (e) => {
|
|
128
|
+
error = e;
|
|
129
|
+
if (child.pid === void 0) settle(null);
|
|
130
|
+
});
|
|
131
|
+
child.on("close", (code) => {
|
|
132
|
+
if (child.pid !== void 0) settle(code);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
async function findTlc(cwd) {
|
|
136
|
+
const { TLA2TOOLS, JAVA } = process.env;
|
|
137
|
+
if (TLA2TOOLS) return {
|
|
138
|
+
jar: TLA2TOOLS,
|
|
139
|
+
java: JAVA || "java"
|
|
140
|
+
};
|
|
141
|
+
const mise = (...args) => runChild("mise", args, {
|
|
142
|
+
cwd,
|
|
143
|
+
env: {
|
|
144
|
+
...process.env,
|
|
145
|
+
MISE_CEILING_PATHS: cwd
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
const where = () => Promise.all([mise("where", TLC_TOOL), mise("which", "java", "--tool", JAVA_TOOL)]);
|
|
149
|
+
let found = await where();
|
|
150
|
+
if (found.some((r) => r.status !== 0)) {
|
|
151
|
+
const r = await mise("install", JAVA_TOOL, TLC_TOOL);
|
|
152
|
+
if (r.status !== 0) throw new Error(`Cannot find TLC: mise install failed: ${r.error?.message ?? r.stderr.trim()}\nOr set TLA2TOOLS to the path of tla2tools.jar, and JAVA to a java binary (Java 11 or later).`);
|
|
153
|
+
found = await where();
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
jar: path.join(found[0].stdout.trim(), "tla2tools.jar"),
|
|
157
|
+
java: JAVA || found[1].stdout.trim()
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const exitClass = (status) => status === 0 ? 0 : status === 11 || status === 12 || status === 13 ? 1 : 2;
|
|
161
|
+
const tail = (r) => {
|
|
162
|
+
if (r.error) return r.error.message;
|
|
163
|
+
const kept = [];
|
|
164
|
+
let size = 0;
|
|
165
|
+
for (const chunk of plainText(messagesOf(r.output))) {
|
|
166
|
+
kept.push(chunk);
|
|
167
|
+
size += chunk.length;
|
|
168
|
+
while (size - kept[0].length >= 1 << 28) size -= kept.shift().length;
|
|
169
|
+
}
|
|
170
|
+
return kept.join("").trim().split("\n").slice(-30).join("\n");
|
|
171
|
+
};
|
|
172
|
+
const stopOf = (r) => {
|
|
173
|
+
const errors = [];
|
|
174
|
+
for (const m of r.error ? [] : messagesOf(r.output)) if (typeof m !== "string" && (m.level === LEVEL.ERROR || m.level === LEVEL.TLCBUG || errors.length && m.level === LEVEL.STATE)) errors.push(m);
|
|
175
|
+
if (!errors.length) return {
|
|
176
|
+
status: r.status,
|
|
177
|
+
message: tail(r)
|
|
178
|
+
};
|
|
179
|
+
const lines = [...plainText(errors)].join("").trim().split("\n");
|
|
180
|
+
if (lines.length > 40) lines.splice(40, Infinity, `... ${lines.length - 40} more lines`);
|
|
181
|
+
return {
|
|
182
|
+
status: r.status,
|
|
183
|
+
message: lines.join("\n")
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
const names = (rules, kind) => {
|
|
187
|
+
const found = [];
|
|
188
|
+
for (const rule of rules) if (rule.kind === kind) found.push(rule.name);
|
|
189
|
+
return found;
|
|
190
|
+
};
|
|
191
|
+
const brokenRule = (rules, broke) => {
|
|
192
|
+
if (broke.kind === "temporal") return void 0;
|
|
193
|
+
for (const rule of rules) if (rule.kind === broke.kind && (broke.kind === "deadlock" || rule.name === broke.name)) return rule;
|
|
194
|
+
if (broke.kind === "invariant") {
|
|
195
|
+
for (const rule of rules) if (rule.kind === "property" && rule.name === broke.name) return rule;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
function* only(marks, rules) {
|
|
199
|
+
for (const { values, broken } of marks) {
|
|
200
|
+
const kept = [];
|
|
201
|
+
for (const name of broken) if (rules.has(name)) kept.push(name);
|
|
202
|
+
if (kept.length) yield {
|
|
203
|
+
values,
|
|
204
|
+
broken: kept
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
async function check(specFile, configFile) {
|
|
209
|
+
const cfg = readConfig({
|
|
210
|
+
file: configFile,
|
|
211
|
+
text: fs.readFileSync(configFile, "utf8")
|
|
212
|
+
});
|
|
213
|
+
const checks = readChecks({
|
|
214
|
+
configFile,
|
|
215
|
+
config: cfg
|
|
216
|
+
});
|
|
217
|
+
const folder = path.dirname(path.resolve(specFile));
|
|
218
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "statechart-"));
|
|
219
|
+
try {
|
|
220
|
+
const tlc = await findTlc(dir);
|
|
221
|
+
const config = (name, sections, extra) => {
|
|
222
|
+
fs.writeFileSync(path.join(dir, `${name}.cfg`), configText(sections, extra));
|
|
223
|
+
return [
|
|
224
|
+
`-metadir`,
|
|
225
|
+
path.join(dir, `meta-${name}`),
|
|
226
|
+
"-config",
|
|
227
|
+
path.join(dir, `${name}.cfg`)
|
|
228
|
+
];
|
|
229
|
+
};
|
|
230
|
+
const runJava = (name, main, args, jvm = []) => {
|
|
231
|
+
const tmp = path.join(dir, `tmp-${name}`);
|
|
232
|
+
fs.mkdirSync(tmp);
|
|
233
|
+
return runToFiles(tlc.java, [
|
|
234
|
+
`-Djava.io.tmpdir=${tmp}`,
|
|
235
|
+
`-DTLA-Library=${folder}`,
|
|
236
|
+
...jvm,
|
|
237
|
+
"-cp",
|
|
238
|
+
tlc.jar,
|
|
239
|
+
main,
|
|
240
|
+
...args
|
|
241
|
+
], {
|
|
242
|
+
cwd: dir,
|
|
243
|
+
out: path.join(dir, name),
|
|
244
|
+
env: process.platform === "darwin" ? process.env : {
|
|
245
|
+
...process.env,
|
|
246
|
+
LC_ALL: "C.UTF-8"
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
};
|
|
250
|
+
const xml = await runJava("sany", "tla2sany.xml.XMLExporter", [
|
|
251
|
+
"-o",
|
|
252
|
+
"-I",
|
|
253
|
+
folder,
|
|
254
|
+
path.resolve(specFile)
|
|
255
|
+
], ["-XX:TieredStopAtLevel=1"]);
|
|
256
|
+
if (xml.status !== 0) throw new Error(`SANY could not read ${specFile} (exit ${xml.status}):\n${tail(xml)}`);
|
|
257
|
+
const sany = readSany(fs.readFileSync(xml.stdout, "utf8"));
|
|
258
|
+
const module = path.basename(specFile, ".tla");
|
|
259
|
+
const spec = {
|
|
260
|
+
...readSpec({
|
|
261
|
+
module,
|
|
262
|
+
sany,
|
|
263
|
+
checks
|
|
264
|
+
}),
|
|
265
|
+
folder
|
|
266
|
+
};
|
|
267
|
+
const rules = stateRules(spec);
|
|
268
|
+
const all = [...rules];
|
|
269
|
+
for (const name of spec.properties) all.push({
|
|
270
|
+
kind: "property",
|
|
271
|
+
name
|
|
272
|
+
});
|
|
273
|
+
const verdicts = /* @__PURE__ */ new Map();
|
|
274
|
+
const decide = (rule, outcome, r) => verdicts.set(ruleKey(rule), {
|
|
275
|
+
rule,
|
|
276
|
+
outcome,
|
|
277
|
+
messages: r && messagesOf(r.output)
|
|
278
|
+
});
|
|
279
|
+
const judge = async () => {
|
|
280
|
+
const groups = [all];
|
|
281
|
+
let first;
|
|
282
|
+
let stop;
|
|
283
|
+
const states = {
|
|
284
|
+
distinct: 0,
|
|
285
|
+
initial: 0,
|
|
286
|
+
complete: false,
|
|
287
|
+
cut: false
|
|
288
|
+
};
|
|
289
|
+
for (let k = 1; groups.length;) {
|
|
290
|
+
const group = [];
|
|
291
|
+
for (const rule of groups[0]) if (!verdicts.has(ruleKey(rule))) group.push(rule);
|
|
292
|
+
if (first && !group.length) {
|
|
293
|
+
groups.shift();
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const name = `verdict-${k++}`;
|
|
297
|
+
const extra = [];
|
|
298
|
+
const invariants = names(group, "invariant");
|
|
299
|
+
const properties = names(group, "property");
|
|
300
|
+
if (invariants.length) extra.push(`INVARIANT ${invariants.join(" ")}`);
|
|
301
|
+
if (properties.length) extra.push(`PROPERTY ${properties.join(" ")}`);
|
|
302
|
+
const bounded = first !== void 0 && !states.complete;
|
|
303
|
+
if (bounded) extra.push("CONSTRAINT ChartBound");
|
|
304
|
+
const r = await runJava(name, "tlc2.TLC", [
|
|
305
|
+
"-tool",
|
|
306
|
+
...PINNED_FLAGS,
|
|
307
|
+
...names(group, "deadlock").length ? [] : ["-deadlock"],
|
|
308
|
+
...config(name, spec.written, extra),
|
|
309
|
+
bounded ? "ChartBound" : spec.module
|
|
310
|
+
]);
|
|
311
|
+
first ??= r;
|
|
312
|
+
const { broke, distinct, initial } = readVerdict(messagesOf(r.output));
|
|
313
|
+
states.distinct = Math.max(states.distinct, distinct);
|
|
314
|
+
states.initial = Math.max(states.initial, initial);
|
|
315
|
+
const rule = broke && brokenRule(group, broke);
|
|
316
|
+
const cut = bounded && distinct >= 1e5;
|
|
317
|
+
if (exitClass(r.status) === 2) {
|
|
318
|
+
stop = stopOf(r);
|
|
319
|
+
break;
|
|
320
|
+
} else if (rule) decide(rule, "broken", r);
|
|
321
|
+
else if (cut && (r.status === 0 || broke?.kind === "temporal")) {
|
|
322
|
+
states.cut = true;
|
|
323
|
+
for (const rule of group) decide(rule, "not checked");
|
|
324
|
+
} else if (r.status === 0) {
|
|
325
|
+
states.complete = true;
|
|
326
|
+
for (const rule of group) decide(rule, "holds");
|
|
327
|
+
} else if (broke && properties.length) {
|
|
328
|
+
if (broke.kind === "temporal") {
|
|
329
|
+
states.complete = true;
|
|
330
|
+
for (const rule of group) if (rule.kind !== "property") decide(rule, "holds");
|
|
331
|
+
}
|
|
332
|
+
const pending = [];
|
|
333
|
+
const rest = [];
|
|
334
|
+
for (const rule of group) if (rule.kind === "property") pending.push([rule]);
|
|
335
|
+
else if (!verdicts.has(ruleKey(rule))) rest.push(rule);
|
|
336
|
+
if (pending.length === 1) decide(pending[0][0], "broken", r);
|
|
337
|
+
else groups.splice(0, 1, rest, ...pending);
|
|
338
|
+
} else throw new Error(`TLC exited ${r.status} on ${specFile}, but its output names no rule that broke:\n${tail(r)}`);
|
|
339
|
+
}
|
|
340
|
+
for (const rule of all) if (!verdicts.has(ruleKey(rule))) decide(rule, "not checked");
|
|
341
|
+
return {
|
|
342
|
+
first,
|
|
343
|
+
stop,
|
|
344
|
+
states
|
|
345
|
+
};
|
|
346
|
+
};
|
|
347
|
+
fs.writeFileSync(path.join(dir, "ChartBound.tla"), boundRun(spec.module));
|
|
348
|
+
const judged = await judge();
|
|
349
|
+
const exit = exitClass(judged.first.status);
|
|
350
|
+
const failed = (r) => /* @__PURE__ */ new Error(`TLC could not check ${specFile} (exit ${r.status}):\n${stopOf(r).message}`);
|
|
351
|
+
const initialOnly = initialOnlyProperties(sany, spec);
|
|
352
|
+
const propertiesWith = (bugOf) => {
|
|
353
|
+
const properties = [];
|
|
354
|
+
for (const rule of all) {
|
|
355
|
+
if (rule.kind !== "property") continue;
|
|
356
|
+
const v = must(verdicts, ruleKey(rule));
|
|
357
|
+
properties.push({
|
|
358
|
+
...v,
|
|
359
|
+
bug: v.outcome === "broken" && v.messages ? bugOf(readTrace(v.messages)) : void 0,
|
|
360
|
+
initialOnly: initialOnly.has(rule.name)
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
return properties;
|
|
364
|
+
};
|
|
365
|
+
if (exit === 2) throw failed(judged.first);
|
|
366
|
+
const checked = [];
|
|
367
|
+
for (const rule of rules) checked.push(must(verdicts, ruleKey(rule)));
|
|
368
|
+
const common = {
|
|
369
|
+
spec,
|
|
370
|
+
exit,
|
|
371
|
+
stop: judged.stop,
|
|
372
|
+
states: judged.states,
|
|
373
|
+
messages: messagesOf(judged.first.output),
|
|
374
|
+
rules: checked
|
|
375
|
+
};
|
|
376
|
+
if (judged.states.distinct <= 500) {
|
|
377
|
+
const tag = `chart-${randomBytes(16).toString("hex")}`;
|
|
378
|
+
fs.writeFileSync(path.join(dir, "ChartRun.tla"), chartRun(spec, tag));
|
|
379
|
+
const drawn = await runJava("ChartRun", "tlc2.TLC", [
|
|
380
|
+
"-tool",
|
|
381
|
+
...PINNED_FLAGS,
|
|
382
|
+
"-deadlock",
|
|
383
|
+
...config("ChartRun", spec.written, ["INVARIANT ChartMark", "CONSTRAINT ChartBudget"]),
|
|
384
|
+
"-dump",
|
|
385
|
+
"dot,actionlabels",
|
|
386
|
+
path.join(dir, "graph"),
|
|
387
|
+
"ChartRun"
|
|
388
|
+
]);
|
|
389
|
+
const file = path.join(dir, "graph.dot");
|
|
390
|
+
if (!fs.existsSync(file)) throw failed(drawn);
|
|
391
|
+
if (readVerdict(messagesOf(drawn.output)).distinct <= 500) {
|
|
392
|
+
const graph = parseDot(fileLines(file));
|
|
393
|
+
const find = finder(graph, spec.variables);
|
|
394
|
+
const drawing = drawn.status === 0 ? void 0 : { status: drawn.status };
|
|
395
|
+
const broken = /* @__PURE__ */ new Set();
|
|
396
|
+
for (const { rule, outcome } of verdicts.values()) if (rule.kind === "invariant" && outcome === "broken") broken.add(rule.name);
|
|
397
|
+
const marks = only(readMarks(readMessages(fileLines(drawn.stdout)), tag), broken);
|
|
398
|
+
const past = mark(graph, marks, spec.variables, rules, find);
|
|
399
|
+
const excludes = hasSection(spec, "ACTION_CONSTRAINT");
|
|
400
|
+
for (const rule of rules) {
|
|
401
|
+
const { outcome, messages } = must(verdicts, ruleKey(rule));
|
|
402
|
+
if (rule.kind === "deadlock" && outcome === "broken" && !drawing && !hasSection(spec, "CONSTRAINT", "ACTION_CONSTRAINT")) deadEnds(graph, rule);
|
|
403
|
+
if (outcome !== "broken" || !messages) continue;
|
|
404
|
+
const counterexample = readCounterexample(messages);
|
|
405
|
+
if (!drawing || lasso(graph, {
|
|
406
|
+
seq: counterexample,
|
|
407
|
+
loop: void 0
|
|
408
|
+
}, spec.variables, find)) addPast(graph, counterexample, spec.variables, rule, past, excludes, find);
|
|
409
|
+
}
|
|
410
|
+
const properties = propertiesWith((trace) => lasso(graph, trace, spec.variables, find));
|
|
411
|
+
return {
|
|
412
|
+
...common,
|
|
413
|
+
graph,
|
|
414
|
+
paths: void 0,
|
|
415
|
+
properties,
|
|
416
|
+
drawing
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
const drawn = pathGraph(spec.variables);
|
|
421
|
+
const paths = [];
|
|
422
|
+
for (const rule of rules) {
|
|
423
|
+
const { outcome, messages } = must(verdicts, ruleKey(rule));
|
|
424
|
+
if (outcome === "broken" && messages) paths.push(drawn.bug(rule, readCounterexample(messages)));
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
...common,
|
|
428
|
+
graph: drawn.graph,
|
|
429
|
+
paths,
|
|
430
|
+
properties: propertiesWith(drawn.lasso),
|
|
431
|
+
drawing: void 0
|
|
432
|
+
};
|
|
433
|
+
} finally {
|
|
434
|
+
fs.rmSync(dir, {
|
|
435
|
+
recursive: true,
|
|
436
|
+
force: true
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
export { PINNED_FLAGS, check, fileLines, killGroup, messageOf };
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { killGroup, messageOf } from "./run.mjs";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
import { parseArgs } from "node:util";
|
|
8
|
+
// runtime/skills/statechart/scripts/statechart.ts
|
|
9
|
+
const USAGE = `Usage: statechart SPEC.tla [--config FILE] [--view FILE] [--out DIR]
|
|
10
|
+
|
|
11
|
+
Checks SPEC.tla with TLC and draws its state graph as Mermaid. Above 500 states, the chart
|
|
12
|
+
draws only the paths to the rules that break, and with no rule broken there is no chart.mmd.
|
|
13
|
+
--config FILE TLC config. Default: SPEC.cfg beside the spec.
|
|
14
|
+
--view FILE A module that exports state(vars) and, if you want, step(action, from, to),
|
|
15
|
+
group(vars), colors, and title. state() returns a string for a flat chart, an
|
|
16
|
+
array for parent states, or an object for regions. Default: each state's values.
|
|
17
|
+
colors gives each group blue, orange, purple, yellow, teal, tan, or pink.
|
|
18
|
+
--out DIR Where to write report.md, chart.mmd, tlc.out, and tlc-RULE.out for each
|
|
19
|
+
property that breaks and each rule that breaks off the chart. The folder must
|
|
20
|
+
be new or empty. Default: SPEC-chart.
|
|
21
|
+
Exit code: TLC's own on the config as written: 0 when every rule holds, 1 when a rule breaks,
|
|
22
|
+
and 2 when TLC stops with an error, a signal kills the check, or the command cannot run. 130 or
|
|
23
|
+
143 when SIGINT or SIGTERM stops the check. Only exits 0 and 1 write output.`;
|
|
24
|
+
let stop;
|
|
25
|
+
const stopped = new Promise((resolve) => {
|
|
26
|
+
for (const name of ["SIGINT", "SIGTERM"]) process.on(name, () => {
|
|
27
|
+
stop ??= name;
|
|
28
|
+
resolve();
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
let flags;
|
|
32
|
+
let specFile;
|
|
33
|
+
try {
|
|
34
|
+
const args = parseArgs({
|
|
35
|
+
allowPositionals: true,
|
|
36
|
+
options: {
|
|
37
|
+
config: { type: "string" },
|
|
38
|
+
view: { type: "string" },
|
|
39
|
+
out: { type: "string" }
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
flags = args.values;
|
|
43
|
+
[specFile] = args.positionals;
|
|
44
|
+
if (args.positionals.length !== 1) throw new Error("Give one SPEC.tla.");
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(`${messageOf(error)}\n\n${USAGE}`);
|
|
47
|
+
process.exit(2);
|
|
48
|
+
}
|
|
49
|
+
const codeOf = (error) => error instanceof Error && "code" in error ? error.code : void 0;
|
|
50
|
+
try {
|
|
51
|
+
const configFile = flags.config ?? specFile.replace(/\.tla$/, ".cfg");
|
|
52
|
+
if (!fs.existsSync(configFile)) throw new Error(`Cannot find the TLC config ${configFile}. Pass --config FILE.`);
|
|
53
|
+
const out = flags.out ?? specFile.replace(/\.tla$/, "-chart");
|
|
54
|
+
let entries = [];
|
|
55
|
+
let dest;
|
|
56
|
+
try {
|
|
57
|
+
entries = fs.readdirSync(out);
|
|
58
|
+
dest = fs.realpathSync(out);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
if (codeOf(error) !== "ENOENT") throw new Error(`${out} cannot be the output folder. ${messageOf(error)}. Pass --out with a new folder.`, { cause: error });
|
|
61
|
+
if (fs.lstatSync(out, { throwIfNoEntry: false })) throw new Error(`${out} cannot be the output folder. It is a broken link. Pass --out with a new folder.`, { cause: error });
|
|
62
|
+
dest = path.resolve(out);
|
|
63
|
+
}
|
|
64
|
+
if (entries.length) throw new Error(`${out} is not empty. It has ${entries.sort()[0]}. The command writes only to a new or empty folder. Pass --out with a new folder.`);
|
|
65
|
+
let base = path.dirname(dest);
|
|
66
|
+
while (!fs.existsSync(base)) base = path.dirname(base);
|
|
67
|
+
const stage = fs.mkdtempSync(path.join(base, `.${path.basename(dest)}.statechart-`));
|
|
68
|
+
const done = path.join(stage, "done");
|
|
69
|
+
let tmp;
|
|
70
|
+
let janitor;
|
|
71
|
+
let exitCode;
|
|
72
|
+
try {
|
|
73
|
+
tmp = fs.mkdtempSync(path.join(os.tmpdir(), "statechart-"));
|
|
74
|
+
const runtime = process.versions.deno ? [
|
|
75
|
+
"run",
|
|
76
|
+
"--quiet",
|
|
77
|
+
"--no-prompt",
|
|
78
|
+
"--allow-read",
|
|
79
|
+
"--allow-write",
|
|
80
|
+
"--allow-run",
|
|
81
|
+
"--allow-env"
|
|
82
|
+
] : process.execArgv;
|
|
83
|
+
const worker = spawn(process.execPath, [
|
|
84
|
+
...runtime,
|
|
85
|
+
path.join(import.meta.dirname, import.meta.url.endsWith(".ts") ? "worker.ts" : "worker.mjs"),
|
|
86
|
+
stage,
|
|
87
|
+
specFile,
|
|
88
|
+
configFile,
|
|
89
|
+
...flags.view ? [flags.view] : []
|
|
90
|
+
], {
|
|
91
|
+
detached: true,
|
|
92
|
+
stdio: [
|
|
93
|
+
"ignore",
|
|
94
|
+
"inherit",
|
|
95
|
+
"inherit"
|
|
96
|
+
],
|
|
97
|
+
env: {
|
|
98
|
+
...process.env,
|
|
99
|
+
TMPDIR: tmp
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
janitor = spawn("/bin/sh", [
|
|
103
|
+
"-c",
|
|
104
|
+
"while read -r line; do :; done; kill -s KILL -- \"-$1\"; rm -rf -- \"$2\" \"$3\"",
|
|
105
|
+
"sh",
|
|
106
|
+
String(worker.pid),
|
|
107
|
+
stage,
|
|
108
|
+
tmp
|
|
109
|
+
], {
|
|
110
|
+
detached: true,
|
|
111
|
+
stdio: [
|
|
112
|
+
"pipe",
|
|
113
|
+
"ignore",
|
|
114
|
+
"ignore"
|
|
115
|
+
]
|
|
116
|
+
});
|
|
117
|
+
const exited = new Promise((resolve) => worker.on("exit", (code) => resolve(code ?? 2)));
|
|
118
|
+
await Promise.race([stopped, exited]);
|
|
119
|
+
await killGroup(worker.pid);
|
|
120
|
+
const verdict = await exited;
|
|
121
|
+
if (stop) {
|
|
122
|
+
console.error(`${stop} stopped the check, so the command wrote nothing.`);
|
|
123
|
+
exitCode = 128 + os.constants.signals[stop];
|
|
124
|
+
} else if (worker.signalCode) {
|
|
125
|
+
console.error(`${worker.signalCode} killed the check, so the command wrote nothing.`);
|
|
126
|
+
exitCode = 2;
|
|
127
|
+
} else if ((verdict === 0 || verdict === 1) && fs.existsSync(done)) {
|
|
128
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
129
|
+
try {
|
|
130
|
+
fs.renameSync(done, dest);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
if (codeOf(error) !== "ENOTEMPTY" && codeOf(error) !== "EEXIST") throw error;
|
|
133
|
+
throw new Error(`${path.join(out, fs.readdirSync(dest).sort()[0])} appeared while the check ran, so the command wrote nothing. Pass --out with a new folder.`, { cause: error });
|
|
134
|
+
}
|
|
135
|
+
process.stdout.write(fs.readFileSync(path.join(dest, "report.md")));
|
|
136
|
+
console.log(`\nWrote ${out}`);
|
|
137
|
+
exitCode = verdict;
|
|
138
|
+
} else exitCode = 2;
|
|
139
|
+
} finally {
|
|
140
|
+
fs.rmSync(stage, {
|
|
141
|
+
recursive: true,
|
|
142
|
+
force: true
|
|
143
|
+
});
|
|
144
|
+
if (tmp) fs.rmSync(tmp, {
|
|
145
|
+
recursive: true,
|
|
146
|
+
force: true
|
|
147
|
+
});
|
|
148
|
+
janitor?.kill("SIGKILL");
|
|
149
|
+
}
|
|
150
|
+
process.exit(exitCode);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error(messageOf(error));
|
|
153
|
+
process.exit(2);
|
|
154
|
+
}
|
|
155
|
+
export {};
|