From c38566c1e1e4fc41843cc7a71ca1be0b2be41465 Mon Sep 17 00:00:00 2001 From: mdsmithaustin <15272014+mdsmithaustin@users.noreply.github.com> Date: Sun, 25 Oct 2026 14:03:31 -0600 Subject: [PATCH 1/6] test(statechart): resolve the temp root and load the rename shim on macOS On a macOS host with TLC, 17 of the 18 failing statechart tests failed for two reasons in the test file, not in the scripts. The test folders came from an unresolved TMPDIR under /var, while a run's cwd resolves to /private/var. The scripts then printed the kept work folder as an absolute path where the tests expect one relative to the cwd, and TLC printed module paths under /private/var. The test file now resolves TMPDIR once, before any folder is made. macOS ignores LD_PRELOAD, so the rename shim never loaded and four tests saw renames succeed. A fifth passed without exercising anything. On darwin the shim now loads through DYLD_INSERT_LIBRARIES, which Node's signature allows. It replaces mkdir and rename through __interpose entries, and it calls libc's functions directly, because dlsym(RTLD_NEXT) returns the replacement there and recursed. Refs #308 --- .../scripts/test/statechart.test.mjs | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/tests/skills/statechart/scripts/test/statechart.test.mjs b/tests/skills/statechart/scripts/test/statechart.test.mjs index a379e8d4..1133bb88 100644 --- a/tests/skills/statechart/scripts/test/statechart.test.mjs +++ b/tests/skills/statechart/scripts/test/statechart.test.mjs @@ -8,6 +8,7 @@ import { mkdtempSync, readdirSync, readFileSync, + realpathSync, renameSync, rmSync, statSync, @@ -47,6 +48,10 @@ import { ruleBook } from "../../../../../skills/statechart/scripts/rule-book.mjs import { copyPath } from "../../../../../skills/statechart/scripts/snapshot.mjs"; import { findTlc, JAVA_TOOL, TLC_TOOL } from "../../../../../skills/statechart/scripts/tlc-tools.mjs"; +// A run's cwd is a resolved path, so the folders it names must be built from a resolved TMPDIR. +// macOS keeps TMPDIR under /var, a link to /private/var. +process.env.TMPDIR = realpathSync(tmpdir()); + const here = dirname(fileURLToPath(import.meta.url)); const scripts = join(here, "../../../../../skills/statechart/scripts"); const fixture = (name) => readFileSync(join(here, name), "utf8"); @@ -6073,6 +6078,14 @@ const failRenames = `#define _GNU_SOURCE #include #include #include +#ifdef __APPLE__ +/* dyld replaces a function only through an __interpose entry, and there dlsym(RTLD_NEXT) + returns the replacement, so original() hands out libc's functions instead. */ +#define mkdir preloaded_mkdir +#define rename preloaded_rename +#define dlsym(handle, name) original(name) +static void *original(const char *name); +#endif static size_t hits; static int raced; static int removeOne(const char *path, const struct stat *s, int type, struct FTW *at) { @@ -6158,13 +6171,26 @@ int rename(const char *from, const char *to) { if (renamed == 0 && named(to, "shared") && racing("when-made")) takeOver(to); return renamed; } +#ifdef __APPLE__ +#undef mkdir +#undef rename +static void *original(const char *name) { + return strcmp(name, "mkdir") == 0 ? (void *)mkdir : (void *)rename; +} +__attribute__((used, section("__DATA,__interpose"))) static const void *interposed[][2] = { + {(const void *)preloaded_mkdir, (const void *)mkdir}, + {(const void *)preloaded_rename, (const void *)rename}, +}; +#endif `; +// macOS ignores LD_PRELOAD. Node's signature lets DYLD_INSERT_LIBRARIES load the library. const failingRenames = (() => { const dir = mkdtempSync(join(tmpdir(), "statechart-preload-")); writeFileSync(join(dir, "rename.c"), failRenames); const so = join(dir, "rename.so"); const cc = spawnSync("cc", ["-shared", "-fPIC", "-o", so, join(dir, "rename.c"), "-ldl"]); - return cc.status === 0 ? so : null; + if (cc.status !== 0) return null; + return process.platform === "darwin" ? { DYLD_INSERT_LIBRARIES: so } : { LD_PRELOAD: so }; })(); test( "a run whose rename fails after it moves the output folder aside puts the folder back, or says where it is", @@ -6178,7 +6204,7 @@ test( assert.equal(first.status, 0, first.stderr); const chart = readFileSync(join(shared, "chart.mmd"), "utf8"); const run = (fails) => - node("statechart.mjs", args, dir, { LD_PRELOAD: failingRenames, RENAME_FAILS: fails }); + node("statechart.mjs", args, dir, { ...failingRenames, RENAME_FAILS: fails }); const back = run("NI"); assert.equal(back.status, 2, back.stderr); @@ -6219,7 +6245,7 @@ test( `export const state = (v) => (v.on ? "On" : "Off");\nexport const step = () => "${label}";\n`, ); const r = node("statechart.mjs", args, dir, { - LD_PRELOAD: failingRenames, + ...failingRenames, RENAME_CANNOT: cannot, }); assert.equal(r.status, 0, r.stderr); @@ -6238,7 +6264,7 @@ test( const shared = join(dir, "shared", "chart.mmd"); const run = (anotherRun) => node("statechart.mjs", ["from-spec", "Lamp.tla", "--out", "shared"], dir, { - LD_PRELOAD: failingRenames, + ...failingRenames, RENAME_CANNOT: "ENOSYS", ANOTHER_RUN: anotherRun, }); @@ -6268,7 +6294,7 @@ test( const dir = mkdtempSync(join(tmpdir(), "statechart-")); writeLamp(dir); const r = node("statechart.mjs", ["from-spec", "Lamp.tla", "--out", "shared"], dir, { - LD_PRELOAD: failingRenames, + ...failingRenames, RENAME_CANNOT: "ENOSYS", ANOTHER_RUN: "removed-during-copy", }); @@ -6289,7 +6315,7 @@ test( const dir = mkdtempSync(join(tmpdir(), "statechart-")); writeLamp(dir); const r = node("statechart.mjs", ["from-spec", "Lamp.tla", "--out", "shared"], dir, { - LD_PRELOAD: failingRenames, + ...failingRenames, RENAME_CANNOT: "ENOSYS", ANOTHER_RUN: "when-made", }); From 00930464a9d57dc7069a947e963cebd1cdc5b296 Mon Sep 17 00:00:00 2001 From: mdsmithaustin <15272014+mdsmithaustin@users.noreply.github.com> Date: Sun, 25 Oct 2026 14:03:58 -0600 Subject: [PATCH 2/6] test(statechart): reach TMPDIR through a symlink in the real-mise ceiling test findTlc runs mise in an empty temp folder and names that folder in MISE_CEILING_PATHS. mise compares the ceiling with its resolved cwd, so a temp folder named through a symlink does not stop the search, and mise reads the untrusted mise.toml above it. The test now reaches TMPDIR through a symlink, so it fails on any platform until the ceiling is a resolved path. Before the test file resolved TMPDIR, the /var link on macOS hit the same failure. Refs #308 --- tests/skills/statechart/scripts/test/statechart.test.mjs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/skills/statechart/scripts/test/statechart.test.mjs b/tests/skills/statechart/scripts/test/statechart.test.mjs index 1133bb88..8ee09a33 100644 --- a/tests/skills/statechart/scripts/test/statechart.test.mjs +++ b/tests/skills/statechart/scripts/test/statechart.test.mjs @@ -2005,7 +2005,7 @@ test( ); test( - "statechart.mjs finds TLC with the real mise when an untrusted mise.toml is next to the spec and above TMPDIR", + "statechart.mjs finds TLC with the real mise when an untrusted mise.toml is next to the spec and above a TMPDIR reached through a symlink", { skip: !tools || process.env.TLA2TOOLS @@ -2023,7 +2023,8 @@ test( mkdirSync(tmp, { recursive: true }); writeFileSync(join(dir, "mise.toml"), '[tools]\njava = "17"\n\n[env]\nSTATECHART = "1"\n'); writeFileSync(join(dir, "upload.mmd"), fixture("upload.mmd")); - const env = { JAVA: "", TMPDIR: tmp }; + symlinkSync(dir, join(root, "link")); + const env = { JAVA: "", TMPDIR: join(root, "link", "tmp") }; const r = node("statechart.mjs", ["from-chart", "upload.mmd"], dir, env, at); assert.equal(r.status, 0, r.stderr); assert.equal(r.stderr, ""); From 4d40dae805d63035b249ec462ae3a4bb518f9fb6 Mon Sep 17 00:00:00 2001 From: mdsmithaustin <15272014+mdsmithaustin@users.noreply.github.com> Date: Sun, 25 Oct 2026 14:10:33 -0600 Subject: [PATCH 3/6] fix(statechart): give mise the resolved temp folder as its ceiling findTlc named its empty temp folder in MISE_CEILING_PATHS as os.tmpdir() spelled it. mise compares the ceiling with its resolved cwd, so when TMPDIR runs through a symlink, as /var does on macOS, mise climbed past the folder and failed on an untrusted mise.toml above it. findTlc now resolves the folder first. The install command that the error prints had the same fault, because the shell's $PWD keeps the symlink. It now uses $(pwd -P). PORTING.md records these and the test-file edits as port edits, so the next upstream sync keeps them. Closes #308 --- PORTING.md | 2 +- skills/statechart/scripts/tlc-tools.mjs | 5 +++-- tests/skills/statechart/scripts/test/statechart.test.mjs | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/PORTING.md b/PORTING.md index 154d704f..bd9555d9 100644 --- a/PORTING.md +++ b/PORTING.md @@ -47,7 +47,7 @@ Upstream is the source of truth for the workflows themselves. This file records - **documentation-impact** (`skills/documentation-impact/`) is a port addition for authoring and independently reviewing documentation affected by a change. Preserve its auto-invocable metadata, `tools/skill-trigger-cases.json` entry, README and guide entries, central Poteto completion trigger, and Babysit and Shipping readiness hooks during sync. Preserve its canonical author result rules. The pass covers local work without a PR and binds any required independent review verdict to the examined artifact and documentation context. Visual tools remain optional. Keep model selection and lifecycle orchestration in the calling workflow. - **pr-recap** (`skills/pr-recap/`) is a port addition that posts one reviewer recap comment per pull request: a two-sentence story, one hand-drawn sketch rendered by the bundled Bun tool in `scripts/sketch` and attached with `gh`, a "Not covered" line, and a collapsed details block. `skills/pr-recap/CREDITS.md` carries the MIT notices for the six skills its rules adapt and the OFL notice for the vendored Excalifont subset. On sync, keep the skill, its trigger row, the **Recap** paragraph in Opening a PR, the sketch tests in `tests/skills/pr-recap/`, and their `lint.yml`, `tools/gate-check.sh`, and Dependabot entries. Its refresh trigger in `skills/poteto-mode/references/task-triggers.md` and the `threads-only` sentence in step 1 of `skills/poteto-mode/playbooks/babysit.md` are also port additions, so keep that line and that sentence when a sync rewrites them. -- **statechart** (`skills/statechart/`) keeps upstream's scripts. Its tests live in `tests/skills/statechart/scripts/test/`, per the tests-out-of-`skills/` rule. Upstream runs them in a `validate-plugins.yml` step. Here they run in the `statechart script tests` step of the `lint.yml` skills job and in the `statechart node tests` line of the `gate-check.sh` test tier, both as `node --test` over `*.test.mjs`. CI uses the runner's preinstalled Node, because `tools/test_repository_hardening.py` bans `setup-node`. On sync, carry upstream changes to that step into both places. +- **statechart** (`skills/statechart/`) keeps upstream's scripts. Its tests live in `tests/skills/statechart/scripts/test/`, per the tests-out-of-`skills/` rule. Upstream runs them in a `validate-plugins.yml` step. Here they run in the `statechart script tests` step of the `lint.yml` skills job and in the `statechart node tests` line of the `gate-check.sh` test tier, both as `node --test` over `*.test.mjs`. CI uses the runner's preinstalled Node, because `tools/test_repository_hardening.py` bans `setup-node`. On sync, carry upstream changes to that step into both places. The macOS fixes from #308 are port edits (2026-10) that upstream lacks. In `tlc-tools.mjs`, `findTlc` resolves its empty folder before it names that folder in `MISE_CEILING_PATHS`, and the printed install command uses `$(pwd -P)`. mise compares the ceiling with its resolved cwd, so a TMPDIR under a symlink let it read an untrusted `mise.toml` above. In the test file, three edits apply. TMPDIR is resolved before any folder is made, because a run's cwd is resolved and the `/var` link on macOS made printed folders absolute. The real-mise ceiling test reaches TMPDIR through a symlink. The rename shim has `#ifdef __APPLE__` blocks that replace `mkdir` and `rename` through `__interpose` entries, and `failingRenames` loads it with `DYLD_INSERT_LIBRARIES` on darwin, because macOS ignores `LD_PRELOAD`. On sync, keep these edits where upstream rewrites those lines, and drop each one that upstream adopts. - **deslop** (`skills/deslop/`) is a port addition that replaces cursor-team-kit's `/deslop` with a pstack-owned code cleanup pass: nineteen numbered rules over the branch's changed hunks, a test run that proves behavior unchanged, and a report. `skills/deslop/CREDITS.md` ships inside the skill directory so the MIT notices travel with every `npx skills` copy. On sync, keep the skill, its trigger row in `tools/skill-trigger-cases.json`, the README and guide entries, poteto-mode's Before every commit or amend trigger, the deslop lines in Opening a PR, both autopilot playbooks, and the multi-phase checklist, step 5's hand-off to the no-comments skill and its Report line, and the ledger promises `deslop-available-for-code`, `opening-a-pr-deslops-diff`, `deslop-cleans-code-slop`, and `deslop-hands-unsettled-comments-to-no-comments`. Upstream never writes `skills/deslop/`; if it ever does, keep whichever version has the rule table. - **Gated publish** (port addition, 2026-10). In a repository that gates pushes through no-mistakes, Opening a PR's **Gated publish** section routes every publish, PR creation, restack, and retarget through the no-mistakes CLI, sends work-in-progress snapshots to `refs/pstack/wip/` or the repository's own agent ref convention, and hands sandbox work to the host. On sync, keep that section, the gated pointers in Babysit, Shipping, Autopilot-full, Autopilot-stack, Orchestrate, and the multi-phase checklist, the guide paragraph in `docs/guide/06-verify-and-ship.md`, the `no-mistakes` line in `tools/cross-suite-foreign.txt`, and the `opening-a-pr-gated-publish-*` ledger promises with the `open-pr-gated-run` case. - **Gate checks** (port addition, 2026-10). [README contributor checks](./README.md#contributor-checks) owns the tier commands, interpreter selection, and CI boundary. On sync, keep `.no-mistakes.yaml`, `tools/gate-check.sh`, the `gate-lint` and `gate-test` mise tasks, and the concurrency blocks in `lint.yml` and `skill-checks.yml`. Assign each new upstream check to one script tier or keep it CI-only, and update the README boundary when that changes. diff --git a/skills/statechart/scripts/tlc-tools.mjs b/skills/statechart/scripts/tlc-tools.mjs index 09d46de4..44c0b7e8 100644 --- a/skills/statechart/scripts/tlc-tools.mjs +++ b/skills/statechart/scripts/tlc-tools.mjs @@ -20,8 +20,9 @@ export function findTlc() { if (file && !fs.existsSync(file)) throw new Error(`${name} is ${file}, which does not exist.`); if (TLA2TOOLS) return { jar: TLA2TOOLS, java: JAVA || "java" }; const command = (tools) => - `cd "$(mktemp -d)" && MISE_CEILING_PATHS="$PWD" mise install ${tools.map((t) => `'${t}'`).join(" ")}`; - const empty = fs.mkdtempSync(path.join(os.tmpdir(), "statechart-mise-")); + `cd "$(mktemp -d)" && MISE_CEILING_PATHS="$(pwd -P)" mise install ${tools.map((t) => `'${t}'`).join(" ")}`; + // mise compares the ceiling with its resolved cwd, so a TMPDIR under a symlink must be resolved. + const empty = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "statechart-mise-"))); const env = { ...process.env, MISE_CEILING_PATHS: empty }; const run = (...args) => spawnSync("mise", args, { cwd: empty, env, encoding: "utf8" }); const mise = (...args) => { diff --git a/tests/skills/statechart/scripts/test/statechart.test.mjs b/tests/skills/statechart/scripts/test/statechart.test.mjs index 8ee09a33..70c96a1d 100644 --- a/tests/skills/statechart/scripts/test/statechart.test.mjs +++ b/tests/skills/statechart/scripts/test/statechart.test.mjs @@ -1457,7 +1457,7 @@ test("statechart.mjs writes the spec next to the chart, keeps it on the next run }); const installCommand = (...tools) => - `cd "$(mktemp -d)" && MISE_CEILING_PATHS="$PWD" mise install ${tools.map((t) => `'${t}'`).join(" ")}`; + `cd "$(mktemp -d)" && MISE_CEILING_PATHS="$(pwd -P)" mise install ${tools.map((t) => `'${t}'`).join(" ")}`; test("statechart.mjs shows mise's own error when mise fails before it can look for TLC", () => { const dir = mkdtempSync(join(tmpdir(), "statechart-")); From c869761641712957f5942d25a652e5060d675e6e Mon Sep 17 00:00:00 2001 From: mdsmithaustin <15272014+mdsmithaustin@users.noreply.github.com> Date: Sun, 25 Oct 2026 14:43:56 -0600 Subject: [PATCH 4/6] docs(porting): name every statechart test-file edit from #308 The entry left out the installCommand helper and the spread call sites of failingRenames, so a sync that took upstream's test file whole would break tests against the ported script. --- PORTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PORTING.md b/PORTING.md index bd9555d9..49076e49 100644 --- a/PORTING.md +++ b/PORTING.md @@ -47,7 +47,7 @@ Upstream is the source of truth for the workflows themselves. This file records - **documentation-impact** (`skills/documentation-impact/`) is a port addition for authoring and independently reviewing documentation affected by a change. Preserve its auto-invocable metadata, `tools/skill-trigger-cases.json` entry, README and guide entries, central Poteto completion trigger, and Babysit and Shipping readiness hooks during sync. Preserve its canonical author result rules. The pass covers local work without a PR and binds any required independent review verdict to the examined artifact and documentation context. Visual tools remain optional. Keep model selection and lifecycle orchestration in the calling workflow. - **pr-recap** (`skills/pr-recap/`) is a port addition that posts one reviewer recap comment per pull request: a two-sentence story, one hand-drawn sketch rendered by the bundled Bun tool in `scripts/sketch` and attached with `gh`, a "Not covered" line, and a collapsed details block. `skills/pr-recap/CREDITS.md` carries the MIT notices for the six skills its rules adapt and the OFL notice for the vendored Excalifont subset. On sync, keep the skill, its trigger row, the **Recap** paragraph in Opening a PR, the sketch tests in `tests/skills/pr-recap/`, and their `lint.yml`, `tools/gate-check.sh`, and Dependabot entries. Its refresh trigger in `skills/poteto-mode/references/task-triggers.md` and the `threads-only` sentence in step 1 of `skills/poteto-mode/playbooks/babysit.md` are also port additions, so keep that line and that sentence when a sync rewrites them. -- **statechart** (`skills/statechart/`) keeps upstream's scripts. Its tests live in `tests/skills/statechart/scripts/test/`, per the tests-out-of-`skills/` rule. Upstream runs them in a `validate-plugins.yml` step. Here they run in the `statechart script tests` step of the `lint.yml` skills job and in the `statechart node tests` line of the `gate-check.sh` test tier, both as `node --test` over `*.test.mjs`. CI uses the runner's preinstalled Node, because `tools/test_repository_hardening.py` bans `setup-node`. On sync, carry upstream changes to that step into both places. The macOS fixes from #308 are port edits (2026-10) that upstream lacks. In `tlc-tools.mjs`, `findTlc` resolves its empty folder before it names that folder in `MISE_CEILING_PATHS`, and the printed install command uses `$(pwd -P)`. mise compares the ceiling with its resolved cwd, so a TMPDIR under a symlink let it read an untrusted `mise.toml` above. In the test file, three edits apply. TMPDIR is resolved before any folder is made, because a run's cwd is resolved and the `/var` link on macOS made printed folders absolute. The real-mise ceiling test reaches TMPDIR through a symlink. The rename shim has `#ifdef __APPLE__` blocks that replace `mkdir` and `rename` through `__interpose` entries, and `failingRenames` loads it with `DYLD_INSERT_LIBRARIES` on darwin, because macOS ignores `LD_PRELOAD`. On sync, keep these edits where upstream rewrites those lines, and drop each one that upstream adopts. +- **statechart** (`skills/statechart/`) keeps upstream's scripts, except for the `tlc-tools.mjs` edit below. Its tests live in `tests/skills/statechart/scripts/test/`, per the tests-out-of-`skills/` rule. Upstream runs them in a `validate-plugins.yml` step. Here they run in the `statechart script tests` step of the `lint.yml` skills job and in the `statechart node tests` line of the `gate-check.sh` test tier, both as `node --test` over `*.test.mjs`. CI uses the runner's preinstalled Node, because `tools/test_repository_hardening.py` bans `setup-node`. On sync, carry upstream changes to that step into both places. The macOS fixes from #308 are port edits (2026-10) that upstream lacks. In `tlc-tools.mjs`, `findTlc` resolves its empty folder before it names that folder in `MISE_CEILING_PATHS`, and the printed install command uses `$(pwd -P)`. mise compares the ceiling with its resolved cwd, so a TMPDIR under a symlink let it read an untrusted `mise.toml` above. `statechart.test.mjs` has four edits. Its `installCommand` helper uses `$(pwd -P)` to match the script, and three tests compare that text with the script's output. TMPDIR is resolved before any folder is made, because a run's cwd is resolved and the `/var` link on macOS made printed folders absolute. The real-mise ceiling test reaches TMPDIR through a symlink. The rename shim has `#ifdef __APPLE__` blocks that replace `mkdir` and `rename` through `__interpose` entries, because macOS ignores `LD_PRELOAD`. `failingRenames` is an environment object, `LD_PRELOAD` on Linux and `DYLD_INSERT_LIBRARIES` on darwin, and the rename tests spread it (`...failingRenames`). The readlink shim for the PID-namespace test stays Linux-only, because macOS has no `unshare` and the test skips there. On sync, keep these edits where upstream rewrites those lines, and drop each one that upstream adopts. - **deslop** (`skills/deslop/`) is a port addition that replaces cursor-team-kit's `/deslop` with a pstack-owned code cleanup pass: nineteen numbered rules over the branch's changed hunks, a test run that proves behavior unchanged, and a report. `skills/deslop/CREDITS.md` ships inside the skill directory so the MIT notices travel with every `npx skills` copy. On sync, keep the skill, its trigger row in `tools/skill-trigger-cases.json`, the README and guide entries, poteto-mode's Before every commit or amend trigger, the deslop lines in Opening a PR, both autopilot playbooks, and the multi-phase checklist, step 5's hand-off to the no-comments skill and its Report line, and the ledger promises `deslop-available-for-code`, `opening-a-pr-deslops-diff`, `deslop-cleans-code-slop`, and `deslop-hands-unsettled-comments-to-no-comments`. Upstream never writes `skills/deslop/`; if it ever does, keep whichever version has the rule table. - **Gated publish** (port addition, 2026-10). In a repository that gates pushes through no-mistakes, Opening a PR's **Gated publish** section routes every publish, PR creation, restack, and retarget through the no-mistakes CLI, sends work-in-progress snapshots to `refs/pstack/wip/` or the repository's own agent ref convention, and hands sandbox work to the host. On sync, keep that section, the gated pointers in Babysit, Shipping, Autopilot-full, Autopilot-stack, Orchestrate, and the multi-phase checklist, the guide paragraph in `docs/guide/06-verify-and-ship.md`, the `no-mistakes` line in `tools/cross-suite-foreign.txt`, and the `opening-a-pr-gated-publish-*` ledger promises with the `open-pr-gated-run` case. - **Gate checks** (port addition, 2026-10). [README contributor checks](./README.md#contributor-checks) owns the tier commands, interpreter selection, and CI boundary. On sync, keep `.no-mistakes.yaml`, `tools/gate-check.sh`, the `gate-lint` and `gate-test` mise tasks, and the concurrency blocks in `lint.yml` and `skill-checks.yml`. Assign each new upstream check to one script tier or keep it CI-only, and update the README boundary when that changes. From 7e96ba7cd11ce71da2ac7d4aded8b2e312b07835 Mon Sep 17 00:00:00 2001 From: mdsmithaustin <15272014+mdsmithaustin@users.noreply.github.com> Date: Sun, 25 Oct 2026 16:38:02 -0600 Subject: [PATCH 5/6] test(statechart): check the findTlc ceiling with the fake mise so CI runs it The real-mise ceiling test skips in CI, which has no mise, so a regression in the resolved MISE_CEILING_PATHS would pass there. This test runs findTlc with the fake mise under a mise.toml that sets locked = true, above a TMPDIR reached through a symlink. With the unresolved ceiling, mise reads that file and install fails in --locked mode. --- .../scripts/test/statechart.test.mjs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/skills/statechart/scripts/test/statechart.test.mjs b/tests/skills/statechart/scripts/test/statechart.test.mjs index 70c96a1d..48e17584 100644 --- a/tests/skills/statechart/scripts/test/statechart.test.mjs +++ b/tests/skills/statechart/scripts/test/statechart.test.mjs @@ -2004,6 +2004,36 @@ test( }, ); +test("findTlc installs TLC with mise under a mise.toml that sets locked = true above a TMPDIR reached through a symlink", () => { + const { at, dir, env } = outsideRepoWithoutTlc(); + const root = dirname(dir); + writeFileSync(join(dir, "mise.toml"), "[settings]\nlocked = true\n"); + mkdirSync(join(dir, "tmp")); + symlinkSync(dir, join(root, "link")); + const home = join(root, "tlaplus"); + mkdirSync(home); + writeFileSync(join(home, "tla2tools.jar"), ""); + const findTlcHere = `import { findTlc } from ${JSON.stringify(pathToFileURL(join(at, "tlc-tools.mjs")).href)}; +console.log(JSON.stringify(findTlc()));`; + const r = spawnSync(process.execPath, ["--input-type=module", "-e", findTlcHere], { + cwd: dir, + encoding: "utf8", + env: { + ...process.env, + ...env, + TLAPLUS_HOME: home, + TLC_JAVA: process.execPath, + TMPDIR: join(root, "link", "tmp"), + }, + }); + assert.equal(r.status, 0, r.stderr); + assert.equal(r.stderr, INSTALLING); + assert.deepEqual(JSON.parse(r.stdout), { + jar: join(home, "tla2tools.jar"), + java: process.execPath, + }); +}); + test( "statechart.mjs finds TLC with the real mise when an untrusted mise.toml is next to the spec and above a TMPDIR reached through a symlink", { From 86280d0fc315bef92790d83e008938b3c2a88448 Mon Sep 17 00:00:00 2001 From: mdsmithaustin <15272014+mdsmithaustin@users.noreply.github.com> Date: Sun, 25 Oct 2026 16:38:31 -0600 Subject: [PATCH 6/6] docs(porting): list all seven statechart test-file edits from upstream A sync keeps only the edits the entry lists. It missed two older ones: the imports and scripts constant that point back into skills/ since #297 moved the file to tests/, and the /srv/u fixture path that check-personal-paths.py needs since #304. It also cited a test-location rule by a name PORTING.md never defines, so it now names the entry that states the rule. The count covers the new fake-mise findTlc test. --- PORTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PORTING.md b/PORTING.md index 49076e49..b78b5c79 100644 --- a/PORTING.md +++ b/PORTING.md @@ -47,7 +47,7 @@ Upstream is the source of truth for the workflows themselves. This file records - **documentation-impact** (`skills/documentation-impact/`) is a port addition for authoring and independently reviewing documentation affected by a change. Preserve its auto-invocable metadata, `tools/skill-trigger-cases.json` entry, README and guide entries, central Poteto completion trigger, and Babysit and Shipping readiness hooks during sync. Preserve its canonical author result rules. The pass covers local work without a PR and binds any required independent review verdict to the examined artifact and documentation context. Visual tools remain optional. Keep model selection and lifecycle orchestration in the calling workflow. - **pr-recap** (`skills/pr-recap/`) is a port addition that posts one reviewer recap comment per pull request: a two-sentence story, one hand-drawn sketch rendered by the bundled Bun tool in `scripts/sketch` and attached with `gh`, a "Not covered" line, and a collapsed details block. `skills/pr-recap/CREDITS.md` carries the MIT notices for the six skills its rules adapt and the OFL notice for the vendored Excalifont subset. On sync, keep the skill, its trigger row, the **Recap** paragraph in Opening a PR, the sketch tests in `tests/skills/pr-recap/`, and their `lint.yml`, `tools/gate-check.sh`, and Dependabot entries. Its refresh trigger in `skills/poteto-mode/references/task-triggers.md` and the `threads-only` sentence in step 1 of `skills/poteto-mode/playbooks/babysit.md` are also port additions, so keep that line and that sentence when a sync rewrites them. -- **statechart** (`skills/statechart/`) keeps upstream's scripts, except for the `tlc-tools.mjs` edit below. Its tests live in `tests/skills/statechart/scripts/test/`, per the tests-out-of-`skills/` rule. Upstream runs them in a `validate-plugins.yml` step. Here they run in the `statechart script tests` step of the `lint.yml` skills job and in the `statechart node tests` line of the `gate-check.sh` test tier, both as `node --test` over `*.test.mjs`. CI uses the runner's preinstalled Node, because `tools/test_repository_hardening.py` bans `setup-node`. On sync, carry upstream changes to that step into both places. The macOS fixes from #308 are port edits (2026-10) that upstream lacks. In `tlc-tools.mjs`, `findTlc` resolves its empty folder before it names that folder in `MISE_CEILING_PATHS`, and the printed install command uses `$(pwd -P)`. mise compares the ceiling with its resolved cwd, so a TMPDIR under a symlink let it read an untrusted `mise.toml` above. `statechart.test.mjs` has four edits. Its `installCommand` helper uses `$(pwd -P)` to match the script, and three tests compare that text with the script's output. TMPDIR is resolved before any folder is made, because a run's cwd is resolved and the `/var` link on macOS made printed folders absolute. The real-mise ceiling test reaches TMPDIR through a symlink. The rename shim has `#ifdef __APPLE__` blocks that replace `mkdir` and `rename` through `__interpose` entries, because macOS ignores `LD_PRELOAD`. `failingRenames` is an environment object, `LD_PRELOAD` on Linux and `DYLD_INSERT_LIBRARIES` on darwin, and the rename tests spread it (`...failingRenames`). The readlink shim for the PID-namespace test stays Linux-only, because macOS has no `unshare` and the test skips there. On sync, keep these edits where upstream rewrites those lines, and drop each one that upstream adopts. +- **statechart** (`skills/statechart/`) keeps upstream's scripts, except for the `tlc-tools.mjs` edit below. Its tests live in `tests/skills/statechart/scripts/test/`, because test files sit under the root `tests/` tree at their `skills/` path, a rule the fork-only CI and tooling entry below states. Upstream runs them in a `validate-plugins.yml` step. Here they run in the `statechart script tests` step of the `lint.yml` skills job and in the `statechart node tests` line of the `gate-check.sh` test tier, both as `node --test` over `*.test.mjs`. CI uses the runner's preinstalled Node, because `tools/test_repository_hardening.py` bans `setup-node`. On sync, carry upstream changes to that step into both places. `statechart.test.mjs` has seven edits that upstream lacks, and its `.mmd` fixtures match upstream's. Two are older. The #297 port moved the file to `tests/`, so its imports and its `scripts` constant point at `../../../../../skills/statechart/scripts/`. Since the #304 sync, the `copyPath` test uses `/srv/u/Count.tla` where upstream puts the same file under `/home/`, because `tools/check-personal-paths.py` reports any `/home//` path and so fails the lint tier and CI. The other five come from #308, port edits (2026-10) that also change `tlc-tools.mjs`. There, `findTlc` resolves its empty folder before it names that folder in `MISE_CEILING_PATHS`, and the printed install command uses `$(pwd -P)`. mise compares the ceiling with its resolved cwd, so a TMPDIR under a symlink let it read an untrusted `mise.toml` above. In the test file, the `installCommand` helper uses `$(pwd -P)` to match the script, and three tests compare that text with the script's output. TMPDIR is resolved before any folder is made, because a run's cwd is resolved and the `/var` link on macOS made printed folders absolute. The real-mise ceiling test reaches TMPDIR through a symlink. A `findTlc` test runs the fake mise under a `locked = true` `mise.toml` above a TMPDIR reached through a symlink, so CI, which has no mise and skips the real-mise test, still checks the ceiling. The rename shim has `#ifdef __APPLE__` blocks that replace `mkdir` and `rename` through `__interpose` entries, because macOS ignores `LD_PRELOAD`. In the same edit, `failingRenames` became an environment object that the rename tests spread (`...failingRenames`), `LD_PRELOAD` on Linux and `DYLD_INSERT_LIBRARIES` on darwin. The readlink shim for the PID-namespace test stays Linux-only, because macOS has no `unshare` and the test skips there. On sync, keep these seven edits where upstream rewrites those lines, and drop each one that upstream adopts. - **deslop** (`skills/deslop/`) is a port addition that replaces cursor-team-kit's `/deslop` with a pstack-owned code cleanup pass: nineteen numbered rules over the branch's changed hunks, a test run that proves behavior unchanged, and a report. `skills/deslop/CREDITS.md` ships inside the skill directory so the MIT notices travel with every `npx skills` copy. On sync, keep the skill, its trigger row in `tools/skill-trigger-cases.json`, the README and guide entries, poteto-mode's Before every commit or amend trigger, the deslop lines in Opening a PR, both autopilot playbooks, and the multi-phase checklist, step 5's hand-off to the no-comments skill and its Report line, and the ledger promises `deslop-available-for-code`, `opening-a-pr-deslops-diff`, `deslop-cleans-code-slop`, and `deslop-hands-unsettled-comments-to-no-comments`. Upstream never writes `skills/deslop/`; if it ever does, keep whichever version has the rule table. - **Gated publish** (port addition, 2026-10). In a repository that gates pushes through no-mistakes, Opening a PR's **Gated publish** section routes every publish, PR creation, restack, and retarget through the no-mistakes CLI, sends work-in-progress snapshots to `refs/pstack/wip/` or the repository's own agent ref convention, and hands sandbox work to the host. On sync, keep that section, the gated pointers in Babysit, Shipping, Autopilot-full, Autopilot-stack, Orchestrate, and the multi-phase checklist, the guide paragraph in `docs/guide/06-verify-and-ship.md`, the `no-mistakes` line in `tools/cross-suite-foreign.txt`, and the `opening-a-pr-gated-publish-*` ledger promises with the `open-pr-gated-run` case. - **Gate checks** (port addition, 2026-10). [README contributor checks](./README.md#contributor-checks) owns the tier commands, interpreter selection, and CI boundary. On sync, keep `.no-mistakes.yaml`, `tools/gate-check.sh`, the `gate-lint` and `gate-test` mise tasks, and the concurrency blocks in `lint.yml` and `skill-checks.yml`. Assign each new upstream check to one script tier or keep it CI-only, and update the README boundary when that changes.