Skip to content

fix(pstack): bring the statechart skill up to date with six fixes - #776

Merged
poteto merged 6 commits into
mainfrom
lauren/statechart-fixes-port-bf8f
Oct 22, 2026
Merged

poteto merged 6 commits into
mainfrom
lauren/statechart-fixes-port-bf8f

Conversation

@poteto

@poteto poteto commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Why

The statechart skill has six fixes since the version in pstack. Before them, a run can give a wrong chart or a wrong exit code and not say so: a second model with different variables, a crash in TLC, or a rule that the config lists as both INVARIANT and PROPERTY. A failed publish can lose the run's work. A PROPERTY line stays in every TLC run, so a broken PROPERTY is reported as a break of an INVARIANT that holds, and --expect-broken does not accept a PROPERTY. This change brings the skill up to its current version.

What changed

Each commit is one fix, in the order in which the fixes landed:

  1. test(pstack): accept Naturals.tla anywhere under the run's temp folder. TLC 1.8.0 writes the standard modules one folder deeper inside the run's temp folder. The test accepts any path under that folder. This is the one failure that feat(pstack): add the statechart skill to draw TLA+ models as charts #741 found with a TLC nightly build.
  2. fix(pstack): stop three silent wrong outputs in statechart
    • The command stops when the label map reads a variable or field that the --fixed or --against model does not have. Before, it gave that variable the current model's start value.
    • New options --config, --fixed-config, and --against-config. The agreed spec is never checked with the code model's config. When a config does not exist, the error lists the configs beside the spec.
    • When the agreed config does not check a rule, the stories mark the rule "not checked". Before, they said that the agreed spec keeps it.
  3. fix(pstack): keep statechart work when TLC or publishing fails
    • When TLC finds a broken rule and then throws a Java exception while it reports it, the command keeps the paths that TLC printed, marks the rule broken, and warns that the charts can miss states.
    • When the file system cannot rename into the output folder (ENOSYS, EXDEV, or EISDIR, as on FUSE mounts), the command copies the files into place.
    • The command writes the label map scaffold before TLC runs, and checks the inputs again before --png renders.
  4. feat(pstack): check every rule with all CPUs, and check PROPERTY rules in statechart
    • TLC checks each rule with one worker per CPU. When a rule is broken, TLC checks it again with one worker, which gives the shortest bug paths, the same on every run. The command prints how many times TLC ran and how long that took.
    • Each PROPERTY gets its own TLC run, and --expect-broken accepts it. The charts draw only INVARIANT rules, so a broken PROPERTY names TLC's counterexample file.
  5. feat(pstack): say whether the agreed spec lists each bug, and flag uncited conditions in statechart
    • With --against, each story says whether a config beside the agreed spec expects TLC to break the bug's rule, from the config's \* expect: and \* module: lines. summary.json records this.
    • With --fixed or --against, the command lists each condition of an action that has no \* file:line comment, when a rule holds. A rule can hold only because the model waits for a condition that the code does not check.
    • SKILL.md asks for those citations, defines a valid replay, and asks the hand-off to say whether the spec already lists each bug.
  6. fix(pstack): report the bug when a config lists one rule as both INVARIANT and PROPERTY
    • That rule gets one TLC run, as an INVARIANT.
    • The command reads each form of a broken PROPERTY that TLC prints: invariant, action property, property of the initial state, and temporal property.
    • The test that kills a run no longer fails when the process has already stopped.

Adapted or left out

Blast radius

Only pstack/skills/statechart/ and its generated copy in harness/pstack/claude-code/skills/statechart/ change. Nothing outside the skill imports its scripts. The pstack version stays at 0.15.30. A separate PR bumps it.

Verification

  • With TLC 1.7.4, all 67 skill tests pass and none skip. Without TLC, as in CI, 29 pass and 38 skip.
  • Each commit passes the skill tests with TLC 1.7.4 and without TLC, and passes generate-pstack-harness-trees.mjs --check.
  • With the TLC 1.8.0 prerelease, 66 of 67 pass. TLC 1.8.0 words a broken temporal property as "Temporal property X was violated." in place of "Temporal properties were violated.", and the scripts know only the 1.7.4 words. The current source of the skill has the same gap, so this change keeps it. The latest stable TLA+ release, which the "no TLC" message points to, is 1.7.4.
  • With INVARIANT PaidMeansCharged and PROPERTY ChargedAtMostOnce on the payment model below, the current skill says "PaidMeansCharged is broken". With this change, it says that PaidMeansCharged holds and ChargedAtMostOnce is broken, and it names the counterexample file.
  • validate-plugins.mjs, check-pstack-docs.mjs, and generate-pstack-harness-trees.mjs --check pass. A scan for internal paths, names, and model ids finds 0 hits in the patch.
  • An end-to-end run on a small payment model, with the output folder on a FUSE mount: the code model retries a charge, and the agreed spec forbids that. from-spec PayCode.tla --against Pay.tla --against-config PayChecked.cfg --expect-broken NoDoubleCharge,ChargedAtMostOnce first writes the mapping scaffold and exits 2. After the labels are filled in, it exits 0. It finds the double charge, checks NoDoubleCharge once although the config lists it as both INVARIANT and PROPERTY, and says that PayRetry.cfg beside the agreed spec expects this bug. It also flags the one condition that has no code citation, and publishes the folder by copy.
Open in Web Open in Cursor 

cursoragent and others added 6 commits October 22, 2026 16:07
TLC 1.8.0 extracts the standard modules into its own folder inside
java.io.tmpdir, so the statechart test that checks each TLC run's temp
folder now accepts any path under it.

Co-authored-by: lauren <poteto@users.noreply.github.com>
- Stop when the label map reads a variable or field that the --fixed or
  --against model does not have, in place of giving it the current
  model's start value.
- Add --config, --fixed-config, and --against-config. The agreed spec is
  never checked with the config of the code model, and a missing config
  error lists the configs beside the spec.
- Mark a rule that the agreed config does not check as "not checked" in
  the stories, in place of saying the agreed spec keeps it.

Co-authored-by: lauren <poteto@users.noreply.github.com>
- When TLC finds a broken rule and then throws a Java exception while it
  reports it, keep the paths TLC printed, mark the rule broken, and warn
  that the charts can miss states.
- On a filesystem that cannot rename into the output folder (ENOSYS,
  EXDEV, EISDIR, such as FUSE mounts), copy the files into place.
- Write the label map scaffold before TLC runs, and check the inputs
  again before --png renders.

Co-authored-by: lauren <poteto@users.noreply.github.com>
…s in statechart

- TLC checks each rule with one worker per CPU. When a rule is broken,
  TLC checks it again with one worker, which finds the shortest bug
  paths, the same on every run. The command prints how many times TLC
  ran and how long that took.
- Each PROPERTY in the config gets its own TLC run, and --expect-broken
  accepts it. The charts draw only INVARIANT rules, so a broken PROPERTY
  names TLC's counterexample file.

pstack finds TLC through TLA2TOOLS and JAVA only, so the repo-pinned
TLC install and its tests are not part of this change.

Co-authored-by: lauren <poteto@users.noreply.github.com>
…cited conditions in statechart

- With --against, each story says whether a config beside the agreed
  spec expects TLC to break the bug's rule, from its "\* expect:" and
  "\* module:" lines, and summary.json records it. The command warns
  when no config has an expect line for that module.
- With --fixed or --against, the command lists each condition of an
  action that no comment ties to a file and line, as in \* pay.ts:120,
  when a rule holds, and summary.json records them.
- SKILL.md asks for those citations, defines a valid replay, and asks
  the hand-off to say whether the spec already lists each bug.

Co-authored-by: lauren <poteto@users.noreply.github.com>
…RIANT and PROPERTY

- A rule that the config lists as both INVARIANT and PROPERTY gets one
  TLC run, as the INVARIANT, so its bug is charted once.
- Report a broken PROPERTY in each form TLC uses: an invariant for
  [](P), an action property for [][A]_v, a property of the initial
  state for a bare predicate, and a temporal property otherwise.
- The test that kills a run no longer fails when the process has
  already exited (ESRCH).

Co-authored-by: lauren <poteto@users.noreply.github.com>
@cursor
cursor Bot force-pushed the lauren/statechart-fixes-port-bf8f branch from d2c7bf7 to 52faf12 Compare October 22, 2026 16:50
@poteto
poteto merged commit 7698375 into main Oct 22, 2026
2 checks passed
@poteto
poteto deleted the lauren/statechart-fixes-port-bf8f branch October 22, 2026 16:57

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 52faf12. Configure here.

from.push(`--${other.role}-dot`, other.dot, "--spec", other.spec);
from.push(...other.traces.flatMap((t) => [`--${other.role}-traces`, t.file]));
if (other.traces.length)
from.push(`--${other.role}-rules`, other.traces.map((t) => t.rule).join(","));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PROPERTY-only rules marked unchecked

Medium Severity · Logic Bug

--against-rules and warnUnchecked look only at INVARIANT results. A rule the agreed spec actually checks as a PROPERTY is still treated as unchecked, so stories say the spec does not check it even though this run already printed that it holds or is broken.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 52faf12. Configure here.

kpeez added a commit to kpeez/agent-toolbox that referenced this pull request Oct 22, 2026
…pt rules (#198)

## Summary

Sync swe with pstack `d457567..7698375` (cursor/plugins#729,
cursor/plugins#741, cursor/plugins#776). swe 1.21.19.

- Add `/swe:statechart`, imported verbatim with its Node scripts. All 12
files are class `copied`, so later syncs 3-way merge them.
- `typescript-best-practices` now loads on its own: `paths: ["**/*.ts",
"**/*.tsx"]` in Claude Code, description match in Codex. It was
user-invoked only.
- Add `agents/openai.yaml` to both formal-verification skills, so Codex
treats them as user-invoked like every other such skill.
- Move the pin and the README Credits link to `7698375`.

## Step 4 review

- `sharpen/SKILL.md`: skipped. Upstream now returns only the rewritten
prompt. `go-deeper` keeps the four questions, runs the rewritten task,
and already has a prompt-only mode.
- guide 02, guide 10, poteto-help recipes: skipped. Sharpen wording
only.
- guide 06, poteto-help, README: ported the statechart section and rows.

## Step 5

- `skills/statechart/`: imported (Kyle approved).

## Verification

- `pstack_sync.py check`: 184 pinned files covered.
- `node --test scripts/test/statechart.test.mjs` with TLC 1.8.0: 66 of
67 pass. One upstream failure: TLC 1.8.0 prints `Temporal property X was
violated.`, and `lib.mjs` only matches the older `Temporal properties
were violated.` Not patched, because the files stay verbatim.
- `claude plugin validate --strict` on the root and `plugins/swe`: pass.
- `skill_eval.py validate`: pass. Relative links in changed Markdown
resolve. Manifests and `openai.yaml` files parse.
caelaxie added a commit to pstack-ports/devin-pstack that referenced this pull request Oct 23, 2026
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
synced to
[`4ec7ffa`](cursor/plugins@4ec7ffa).
compared `88d4751..4ec7ffa` for `pstack/`. later commits on upstream
`main` through
[`9b93e81`](cursor/plugins@9b93e81)
do not touch `pstack/`. version is `0.15.30` in `plugin.json`,
`.devin-plugin/plugin.json`, and the readme marker.

ported:
-
[`eb4db2b`](cursor/plugins@eb4db2b)
([#741](cursor/plugins#741)) add the statechart
skill. guide page 6, poteto-help, and the readme skill table match.
slash commands keep the `/pstack:` prefix. `disable-model-invocation:
true` stays `triggers: [user]`.
-
[`7698375`](cursor/plugins@7698375)
([#776](cursor/plugins#776)) six fixes to the
statechart skill. scripts copied verbatim.
-
[`4ec7ffa`](cursor/plugins@4ec7ffa)
([#779](cursor/plugins#779)) delete statechart
comments that repeat the code, and rename helpers. the version bumps to
`0.15.30`. the same bump is in `.devin-plugin/plugin.json` and the
readme marker, which is where this repo records the version.

skipped:
- `harness/pstack/claude-code/` copies in
[#741](cursor/plugins#741),
[#776](cursor/plugins#776), and
[#779](cursor/plugins#779). this port does not
carry the generated claude code tree.
- [#741](cursor/plugins#741)
`pstack/.cursor-plugin/plugin.json`. cursor packaging. the version bump
lands in `.devin-plugin/plugin.json` instead.
- [#741](cursor/plugins#741)
`.github/workflows/validate-plugins.yml` test step. that workflow
belongs to the cursor/plugins monorepo and also checks generated harness
trees. this repo's ci is separate, so the step was not copied. `node
--test skills/statechart/scripts/test/*.test.mjs` passed locally (67
tests, with tlc).

harness proof (install, `devin plugins list`, exercising
`/pstack:statechart` and `/pstack:poteto-mode`) will follow as a
comment. the devin cli can't log in from this cloud environment.

<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a
href="https://cursor.com/agents/bc-00f3aebc-53ad-54ad-9327-63073f2102cb?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/background-agent?bcId=bc-00f3aebc-53ad-54ad-9327-63073f2102cb&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"
src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Kaitian Xie <caelaxie@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants