From 79b7209e262ee61264abb43fae90809e78a4c0e2 Mon Sep 17 00:00:00 2001 From: Negrescu Alexandru Date: Sat, 31 Oct 2026 20:53:44 +0200 Subject: [PATCH 1/3] fix: unshadow rule 156 and drop duplicate rule 170 Rule 156 sat below the `readonly: false` token rule, which rewrote the inside of its match first. A reflect sentence that missed its sentence rule kept "agent mode (full tools per spawn)". The rule now runs first and writes "full tools per spawn". Rule 170 wrote the same `agent`: `poteto-agent` text as the generic `subagent_type: "..."` rule above it. Both rows leave shadow-exempt.tsv. The rebuilt tree at c742dd6 is unchanged, and `rule shadowing` passes with no recorded rows. --- omp-port/rules.sed | 3 +-- omp-port/shadow-exempt.tsv | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/omp-port/rules.sed b/omp-port/rules.sed index 8ce0986..36029e4 100644 --- a/omp-port/rules.sed +++ b/omp-port/rules.sed @@ -152,8 +152,8 @@ s#`subagent_type`#`agent`#g s#subagent_type#agent#g s#`run_in_background: true`#one `task` call with all items in `tasks[]`#g s#`readonly: true`#read-only posture carried in the brief#g -s#`readonly: false`#full tools per spawn#g s#agent mode \(`readonly: false`\)#full tools per spawn#g +s#`readonly: false`#full tools per spawn#g s#`readonly`: `true`#read-only posture carried in the brief#g s#readonly strips MCPs\.#nothing strips MCPs here.#g # A judge is spawned with read posture in its brief, never as a field. Matched on the field name @@ -167,7 +167,6 @@ s#`model` from the `([^`]+)` line#`agent` from the `task.agentModelOverrides` en s#Spawn one investigator per category that has a matching MCP, all in a single message so they run concurrently#Assign one investigator to each category that has a matching MCP, in one `task` call with all items in `tasks[]` and the required shared `context` so they run concurrently# s#Spawn one synthesizer\.#Start one synthesizer in one `task` call with one item in `tasks[]` and the required shared `context`.# # Inline selectors that survive into patch context are exact values, not a Markdown catch-all. -s#`subagent_type: "poteto-agent"`#`agent`: `poteto-agent`#g s#Shape: one or two questions with 4-6 options each, `allow_multiple: true` for category questions\.#Shape: one or two questions with 4-6 options each. Put `multi: true` on each category question object.# # Plan lanes and the checker name an exact discovered worker agent, never a model-map role label. s#per the \*\*swarm\*\* skill, on the `swarm workers` model \(default `grok-[^`]+`\)#per the **swarm** skill, using exact discovered worker agents. Inspect each agent's frontmatter or `task.agentModelOverrides[]` for its model# diff --git a/omp-port/shadow-exempt.tsv b/omp-port/shadow-exempt.tsv index 50f1168..48af647 100644 --- a/omp-port/shadow-exempt.tsv +++ b/omp-port/shadow-exempt.tsv @@ -6,6 +6,3 @@ # fragment is matched as a literal substring of the rule's own line, as in stale-exempt.tsv. # # Re-checked at: c742dd6 (pin). - -s#agent mode \(`readonly: false`\)# the `readonly: false` token rule directly above rewrites the inside of its match first, so a sentence that falls through the reflect sentence rules would keep "agent mode (full tools per spawn)". No effect at c742dd6: those sentence rules consume both upstream lines. Moving it above that token rule makes it fire. -s#`subagent_type: "poteto-agent"`#`agent`: `poteto-agent`#g duplicates the generic `subagent_type: "..."` rule above it, which writes the same `agent`: `poteto-agent` text. No effect on the tree; deleting it changes nothing. From 0f3f7926b8dfef87b095aadf6fac4afba132869b Mon Sep 17 00:00:00 2001 From: Negrescu Alexandru Date: Sat, 31 Oct 2026 21:03:27 +0200 Subject: [PATCH 2/3] feat(port): drop setup-pstack and exclude it from the build pstack on omp needs no model configuration. An agent with no override runs on its frontmatter model, then on the parent session's model. setup-pstack only wrote `modelRoles` aliases and `task.agentModelOverrides` entries, which an operator can write by hand. omp-port/excluded.txt lists upstream paths the port does not ship. extract_upstream deletes them right after the export, so the build, the rule probes, and the slug report never see that text. Without the list, install_tree would reinstall upstream's Cursor setup-pstack. The new `excluded paths` check fails when an excluded path is in the tree or a tree file names it. Two mutations prove both halves. The omp-mechanics "Choose models" section replaces the setup reference. rules.sed points poteto-help at it, and three nets catch a reworded `/setup-pstack` line. Rules that only setup-pstack text matched go. Removed gates: `chat-model lever` and `setup config contract` checked setup-pstack and its reference only. `omp model levers` now checks poteto-mode alone. The owned-drift mutation records an old poteto-mode object, because no owned path has an upstream counterpart now. --- .github/workflows/gate.yml | 2 - .omp-plugin/marketplace.json | 2 +- PORTING.md | 66 +++--- README.md | 2 +- omp-port/check-port.sh | 70 ++----- omp-port/excluded.txt | 7 + omp-port/lib.sh | 17 +- omp-port/owned.txt | 1 - omp-port/rules.sed | 34 ++-- omp-port/stale-exempt.tsv | 1 - omp-port/swallow.tsv | 6 +- omp-port/test-gate-mutations.sh | 51 ++--- plugins/pstack/README.md | 12 +- plugins/pstack/skills/omp-mechanics/SKILL.md | 8 +- .../references/setup-pstack-config.md | 21 -- plugins/pstack/skills/poteto-help/SKILL.md | 16 +- plugins/pstack/skills/setup-pstack/SKILL.md | 73 ------- .../setup-pstack/scripts/apply-models.mjs | 150 -------------- .../scripts/apply-models.test.mjs | 189 ------------------ 19 files changed, 138 insertions(+), 590 deletions(-) create mode 100644 omp-port/excluded.txt delete mode 100644 plugins/pstack/skills/omp-mechanics/references/setup-pstack-config.md delete mode 100644 plugins/pstack/skills/setup-pstack/SKILL.md delete mode 100755 plugins/pstack/skills/setup-pstack/scripts/apply-models.mjs delete mode 100644 plugins/pstack/skills/setup-pstack/scripts/apply-models.test.mjs diff --git a/.github/workflows/gate.yml b/.github/workflows/gate.yml index 415ade6..c9fba33 100644 --- a/.github/workflows/gate.yml +++ b/.github/workflows/gate.yml @@ -36,8 +36,6 @@ jobs: - name: Script tests working-directory: plugins/pstack/skills/poteto-mode/scripts run: bun run test - - name: Setup script tests - run: bun test plugins/pstack/skills/setup-pstack/scripts - name: Script typecheck working-directory: plugins/pstack/skills/poteto-mode/scripts run: bun run typecheck diff --git a/.omp-plugin/marketplace.json b/.omp-plugin/marketplace.json index 44ba91a..cce58eb 100644 --- a/.omp-plugin/marketplace.json +++ b/.omp-plugin/marketplace.json @@ -10,7 +10,7 @@ "plugins": [ { "name": "pstack", - "description": "60 skills, 23 playbooks, 24 principles, the poteto-mode pin, and two agents. Upstream cursor/plugins pstack at c742dd6.", + "description": "59 skills, 23 playbooks, 24 principles, the poteto-mode pin, and two agents. Upstream cursor/plugins pstack at c742dd6.", "version": "0.15.36", "category": "development", "license": "MIT", diff --git a/PORTING.md b/PORTING.md index e258d15..34283e0 100644 --- a/PORTING.md +++ b/PORTING.md @@ -34,10 +34,11 @@ the head a second time and getting a different answer. ### How to re-sync The port is a build, not a maintained tree. `plugins/pstack/skills` and `plugins/pstack/agents` are -upstream text at the pinned sha with `omp-port/rules.sed` applied, then `omp-port/patches/*.patch`, -then the paths in `omp-port/owned.txt` carried across. A hand edit under either directory fails the -`reproducible` check, which rebuilds the pin and diffs it against the tree, and the next build -deletes it. Put the change in `rules.sed`, in a patch, or in an owned path instead. +upstream text at the pinned sha minus the paths in `omp-port/excluded.txt`, with +`omp-port/rules.sed` applied, then `omp-port/patches/*.patch`, then the paths in +`omp-port/owned.txt` carried across. A hand edit under either directory fails the `reproducible` +check, which rebuilds the pin and diffs it against the tree, and the next build deletes it. Put the +change in `rules.sed`, in a patch, or in an owned path instead. The `upstream-sync` workflow does this daily. It runs `bash omp-port/check-port.sh` on `main` first, so a red trunk stops the run rather than being merged over, then builds at the newest upstream @@ -48,8 +49,9 @@ while any open PR still carries a `sync/upstream-` head. A maintainer runs `bash omp-port/sync-upstream.sh []` for the same build locally, target defaulting to that newest commit, or `bash omp-port/sync-upstream.sh rebuild` to rebuild at the -current pin after editing `rules.sed`, `patches/`, or `owned.txt`. A target that never touched -`pstack/` is refused, because the next run has to find that sha again in the path-filtered history. +current pin after editing `rules.sed`, `patches/`, `owned.txt`, or `excluded.txt`. A target that +never touched `pstack/` is refused, because the next run has to find that sha again in the +path-filtered history. Then run `bash omp-port/check-port.sh` and commit. There is nothing to resolve. The build never edits an upstream file in place, so upstream text and @@ -196,8 +198,9 @@ The marketplace install is the user shape. That caches the plugin at `~/.omp/plugins/cache/plugins/pstack-omp___pstack___` and links it at the same `~/.omp/plugins/node_modules/pstack` path. Verified on omp 18.1.13, 2026-09-07. The -skills load, `setup-pstack` is among them and the rest hide, and both extensions register from -`package.json` `omp.extensions`, so `--poteto` injects the reminder in `-p` mode. +skills load, the ones without `disable-model-invocation` visible and the rest hidden, and both +extensions register from `package.json` `omp.extensions`, so `--poteto` injects the reminder in +`-p` mode. The agents do not load from a marketplace root. omp scans a marketplace plugin's `agents/` directory only through the `claude-plugins` discovery provider, which is off by default @@ -235,9 +238,22 @@ installed. the model slugs upstream names first and a catch-all under them whose hits the sync and the gate report as `untiered` so they earn a tiered rule. `omp-port/patches/` carries the port's own code as unified diffs applied after the rules, which is where a change no substitution can express belongs. -`omp-port/owned.txt` names three port-owned skills. `pstack-omp` is the sole live task, vibe, eval, -and runtime contract. `omp-mechanics` keeps only pstack-specific OMP deltas, and `setup-pstack` -owns model selection and config writes. Upstream syncs preserve these paths without rewriting them. +`omp-port/owned.txt` names two port-owned skills. `pstack-omp` is the sole live task, vibe, eval, +and runtime contract. `omp-mechanics` keeps only pstack-specific OMP deltas. Upstream syncs preserve +these paths without rewriting them. + +`omp-port/excluded.txt` names upstream paths the port does not ship, one per line. `extract_upstream` +deletes each one right after the export, so no rule, patch, or probe sees that text, and a rule that +only an excluded file matched reads as stale. The `excluded paths` check fails when an excluded path +is in the tree or a tree file names it. Without that check, a reworded upstream line would ship a +`/skill:` for a skill that is not installed. + +The one entry is `skills/setup-pstack`. Upstream's version writes a Cursor rules file. pstack on omp +needs no model configuration, because an agent with no override runs on its frontmatter model and +then on the parent session's model. The Choose models section of `skills/omp-mechanics/SKILL.md` +says how to set `task.agentModelOverrides` and `modelRoles` by hand, and `rules.sed` points +poteto-help there. An upstream commit that touches only an excluded path still appears in the drift +list. Syncing it moves the pin and changes nothing in the tree. ### A patch rewrites code, never prose @@ -282,25 +298,19 @@ checks are gone. What remains is residue detection: Cursor wire fields and tool transcript layout, model slugs, retired role keys, install paths, a claimed `hub` tool, an unscoped `omp cleanse --all` recommended as a diagnostic, and build integrity. -`skills/setup-pstack` is port-owned, so the build restores it from `HEAD` and never rebuilds it from -upstream. Upstream's version writes a Cursor rules file with no omp meaning, so the fork is -deliberate, but the drift list alone does not mark it: an upstream edit to that skill shows up as an -ordinary pstack commit with no patch conflict and no reminder to re-derive it. +The build never rebuilds an owned path from upstream. It restores the path from the worktree or +from `HEAD`. When upstream also carries that path, the fork is deliberate, but the drift list alone +does not mark it. An upstream edit shows up as an ordinary pstack commit with no patch conflict and +no reminder to re-derive the owned copy. `bash omp-port/sync-upstream.sh check` therefore writes a `shadowed :` line per owned path to stderr, counting the upstream commits sitting ahead of the pin on that path. That count resets the -moment the pin moves past the commit, which is how ab1f702's edit to setup-pstack step 3(c) went -unread through the c742dd6 sync. So `omp-port/owned.txt` also records, per owned path, the upstream -object a maintainer last read against the owned copy, and `owned drift` fails when upstream's -object at the target differs. The failure prints both objects and the `git diff` that shows the -change. Carry what the owned copy needs, then record the new object. The two other owned paths have -no upstream counterpart and record `-`, so upstream adding one fails the same way. - -`skills/setup-pstack/scripts/apply-models.mjs` makes the config write. `omp config set` stores a -selector the catalog does not have and an alias no role defines without complaint, so a bad value -used to surface only at spawn. The script checks every alias, effort, and agent value against -`omp models --json` and the merged `modelRoles` before its first write, preserves every entry pstack -does not own, and converges on a rerun. Its tests run in the gate against a stub `omp` on `PATH`, -and the `setup config contract` check asserts the skill still routes through it. +moment the pin moves past the commit. That is how ab1f702's edit to setup-pstack step 3(c) went +unread through the c742dd6 sync, while the port still owned setup-pstack. So `omp-port/owned.txt` +also records, per owned path, the upstream object a maintainer last read against the owned copy, and +`owned drift` fails when upstream's object at the target differs. The failure prints both objects +and the `git diff` that shows the change. Carry what the owned copy needs, then record the new +object. Neither owned path has an upstream counterpart, so both record `-`, and upstream adding one +fails the same way. Known deltas the port mirrors faithfully and will not diverge on. The guide says the verification feature map lives at `references/features` while both trees write `features/`. The guide recommends a daily `/maintain-verification-skill` run while both trees state no cadence. diff --git a/README.md b/README.md index 5809845..ffd614e 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ An omp marketplace carrying one plugin. `pstack` is Lauren Tan's engineering methodology for Cursor, ported to oh-my-pi and made -model and provider agnostic, 60 skills with 23 playbooks and 24 principle leaves. +model and provider agnostic, 59 skills with 23 playbooks and 24 principle leaves. Wide parallel runs are a `swarm` dispatch, not a separate plugin. ``` diff --git a/omp-port/check-port.sh b/omp-port/check-port.sh index ab3b614..fb50783 100755 --- a/omp-port/check-port.sh +++ b/omp-port/check-port.sh @@ -98,21 +98,11 @@ scan() { scan "$PAT_SLUG" "model-agnostic" slug -model_levers="" -for f in skills/setup-pstack/SKILL.md skills/poteto-mode/SKILL.md; do - grep -Fq 'task.agentModelOverrides' "$f" || model_levers="$model_levers $f" -done -if [ -n "$model_levers" ]; then - report "omp model levers" "FAIL" - for f in $model_levers; do violate "$f must name task.agentModelOverrides"; done -else - report "omp model levers" "PASS setup-pstack and poteto-mode name task.agentModelOverrides" -fi -if grep -rIlq '/model' skills/setup-pstack/SKILL.md 2>/dev/null; then - report "chat-model lever" "PASS /model named in setup-pstack" +if grep -Fq 'task.agentModelOverrides' skills/poteto-mode/SKILL.md; then + report "omp model levers" "PASS poteto-mode names task.agentModelOverrides" else - report "chat-model lever" "FAIL" - violate "setup-pstack must tell the user to pick the chat model with /model" + report "omp model levers" "FAIL" + violate "skills/poteto-mode/SKILL.md must name task.agentModelOverrides" fi # Review diversity is no longer asserted here. These three checks pinned exact sentences inside @@ -289,41 +279,6 @@ runtime_contract() { else report "one-item role routing" "PASS every explicit single-spawn line has one-item context" fi - - bad="" - setup_skill=skills/setup-pstack/SKILL.md - setup_ref=skills/omp-mechanics/references/setup-pstack-config.md - expected_aliases=$'pstack_fast_code\npstack_instruction\npstack_judgment' - actual_aliases=$(grep -hoE '^[[:space:]]{2,}pstack_[a-z0-9_]+:' "$setup_ref" 2>/dev/null | sed 's/^[[:space:]]*//; s/:$//' | sort -u || true) - [ "$actual_aliases" = "$expected_aliases" ] || bad="unexpected pstack-owned modelRoles alias set"$'\n' - expected_alias_shapes=$' pstack_fast_code: ":"\n pstack_instruction: ":"\n pstack_judgment: ":"' - actual_alias_shapes=$(awk '/^modelRoles:/{roles=1; next} roles && /^task:/{exit} roles && /^ pstack_/{print}' "$setup_ref" | sort || true) - [ "$actual_alias_shapes" = "$expected_alias_shapes" ] || bad="$bad"'unexpected pstack alias shape'$'\n' - grep -q '^task:$' "$setup_ref" && grep -q '^ agentModelOverrides:$' "$setup_ref" && grep -qF ' : "@pstack_fast_code"' "$setup_ref" || bad="$bad"'task.agentModelOverrides YAML shape is missing'$'\n' - for alias in pstack_fast_code pstack_judgment pstack_instruction; do grep -qF "$alias" "$setup_skill" || bad="$bad$alias is missing from setup-pstack"$'\n'; done - setup_keys=$(grep -nE '^[[:space:]]{4,}pstack_[a-z0-9_]+:' "$setup_skill" "$setup_ref" 2>/dev/null || true) - [ -z "$setup_keys" ] || bad="$bad$setup_keys"$'\n' - old_setup=$(grep -nE '^([[:space:]]{4,})?(feature, refactoring|arena runners|arena cross-judge pool|architect runners|swarm workers|interrogate reviewers|reflect judgment, divergent, synthesizer):|For panel roles .* value is a list' "$setup_skill" 2>/dev/null || true) - [ -z "$old_setup" ] || bad="$bad$old_setup"$'\n' - grep -Fxq 'A `task.agentModelOverrides` entry is pstack-owned only when its exact key is a discovered agent and its value is one of `@pstack_fast_code`, `@pstack_judgment`, or `@pstack_instruction`.' "$setup_skill" || bad="$bad"'per-agent setup ownership is missing'$'\n' - grep -Fxq 'Preserve every unrelated entry in both maps.' "$setup_skill" || bad="$bad"'unrelated setup ownership preservation is missing'$'\n' - grep -Fq 'capability, exact discovered agent name' "$setup_skill" || bad="$bad"'setup capability mapping is missing'$'\n' - grep -Fq 'The effort ladder is `max > xhigh > high > medium > low > minimal`.' "$setup_skill" || bad="$bad"'setup effort ladder is missing minimal or ordering'$'\n' - grep -Fq 'APPLY=$(realpath skill://setup-pstack)/scripts/apply-models.mjs' "$setup_skill" && grep -Fq -- '--dry-run' "$setup_skill" && [ -f skills/setup-pstack/scripts/apply-models.mjs ] || bad="$bad"'setup does not plan and write through scripts/apply-models.mjs'$'\n' - grep -Fq 'selected model' "$setup_skill" && grep -Fq '`thinking.efforts`' "$setup_skill" || bad="$bad"'setup does not validate supported model efforts'$'\n' - grep -Fq 'An omitted override uses the discovered agent' "$setup_skill" && grep -Fq "frontmatter model first, then the parent session's active or default model" "$setup_skill" || bad="$bad"'setup omission precedence is missing'$'\n' - grep -Fq 'Family diversity is optional' "$setup_skill" || bad="$bad"'setup family fallback is missing'$'\n' - bad_alias_choices=$(grep -nE 'offer(s|ing).*`(inherit-parent|auto)`| `(inherit-parent|auto)` always pass|omission for parent inheritance|Omit an override to inherit the parent model' "$setup_skill" 2>/dev/null || true) - [ -z "$bad_alias_choices" ] || bad="$bad$bad_alias_choices"$'\n' - grep -Fq 'exactly one supported effort suffix from `max`, `xhigh`, `high`, `medium`, `low`, and `minimal`' "$setup_ref" || bad="$bad"'setup reference suffix list is missing minimal'$'\n' - grep -Fq 'remaining base to equal a selector from `omp models --json`' "$setup_ref" || bad="$bad"'setup reference base-selector validation is missing'$'\n' - grep -Fq 'selected model' "$setup_ref" && grep -Fq '`thinking.efforts`' "$setup_ref" || bad="$bad"'setup reference does not validate model efforts'$'\n' - if [ -n "$bad" ]; then - report "setup config contract" "FAIL" - while read -r line; do [ -n "$line" ] && violate "$line"; done <<<"$bad" - else - report "setup config contract" "PASS ownership, aliases, model efforts, suffix, base selector, and the apply-models lever" - fi } runtime_contract @@ -352,6 +307,23 @@ else report "guide claims" "PASS ${#guide_skills[@]} skills, prototype and multi-phase-plan playbooks, check-plan.mjs, feature map, /poteto-mode command" fi +# The build deletes every path in omp-port/excluded.txt, so one in the tree came back by hand or +# through owned.txt. A line that names one sends an agent to a skill that is not installed. +excl="" +while IFS= read -r p; do + [ ! -e "$p" ] || excl="$excl$p is in the tree"$'\n' + hits=$(grep -rInF -- "$(basename "$p")" "${SCOPE[@]}" 2>/dev/null | + awk -v p="$p" 'index($0, p "/") != 1 && index($0, p ":") != 1' || true) + [ -z "$hits" ] || excl="$excl$hits"$'\n' +done < <(excluded_paths) +if [ -n "$excl" ]; then + report "excluded paths" "FAIL" + while read -r e; do [ -n "$e" ] && violate "$e"; done <<<"$excl" + violate "omp-port/excluded.txt keeps these out of the build; delete the path, or add a rules.sed rule for the line that names it" +else + report "excluded paths" "PASS $(excluded_paths | grep -c .) path(s) absent from the tree and named nowhere in it" +fi + # rules.sed read as data rather than run, so these need no clone and run before the contracts-only # exit, where the fixture mutations reach them. Each is a shape that once shipped with every other # check green. diff --git a/omp-port/excluded.txt b/omp-port/excluded.txt new file mode 100644 index 0000000..00a1ce5 --- /dev/null +++ b/omp-port/excluded.txt @@ -0,0 +1,7 @@ +# Upstream paths under pstack/ the port does not ship, one per line, relative to plugins/pstack. +# extract_upstream deletes each one right after the export, so no rule, patch, or probe sees its +# text. `excluded paths` in check-port.sh fails when one is in the tree or a tree file names it. +# +# Upstream's setup-pstack writes a Cursor rules file. pstack on omp needs no model configuration, +# and the Choose models section of skills/omp-mechanics/SKILL.md says how to pick models by hand. +skills/setup-pstack diff --git a/omp-port/lib.sh b/omp-port/lib.sh index 5608f6c..a369013 100644 --- a/omp-port/lib.sh +++ b/omp-port/lib.sh @@ -52,11 +52,24 @@ ensure_canon() { done } -# Step 1, upstream text as it is, with pstack/ stripped so skills and agents land at the root. +# excluded.txt is one upstream path per line, relative to plugins/pstack, that the port does not +# ship. Comments and blanks are the file's, as in owned.txt. +EXCLUDED_FILE="${EXCLUDED:-$PORT_DIR/excluded.txt}" +excluded_paths() { + [ -f "$EXCLUDED_FILE" ] || return 0 + awk '{ sub(/#.*/, ""); gsub(/^[[:space:]]+|[[:space:]]+$/, "") } $0 != ""' "$EXCLUDED_FILE" +} + +# Step 1, upstream text as it is, with pstack/ stripped so skills and agents land at the root and +# the excluded paths deleted. Deleting them here rather than after the build keeps them out of every +# probe as well, so a rule that only an excluded file matched reads as stale. extract_upstream() { - local sha="$1" work="$2" + local sha="$1" work="$2" p git -C "$CANON" archive "$sha" pstack/skills pstack/agents | tar -x --strip-components=1 -C "$work" || return 1 + while IFS= read -r p; do + rm -rf "${work:?}/$p" || return 1 + done < <(excluded_paths) [ -d "$work/skills" ] && [ -d "$work/agents" ] } diff --git a/omp-port/owned.txt b/omp-port/owned.txt index 7a816f2..f923dfb 100644 --- a/omp-port/owned.txt +++ b/omp-port/owned.txt @@ -9,4 +9,3 @@ # owned path is read before a sync lands rather than discarded by the build in silence. skills/omp-mechanics - skills/pstack-omp - -skills/setup-pstack 800811ddcb1d1d1831bfd853cfad14429a3d6842 diff --git a/omp-port/rules.sed b/omp-port/rules.sed index 36029e4..e13574a 100644 --- a/omp-port/rules.sed +++ b/omp-port/rules.sed @@ -5,8 +5,9 @@ # mechanic it maps to. This file replaces PORTING.md's substitution table: the table is here now. # Order is load-bearing in three places, each marked below. # -# Owned prose lives in the three skill paths named by omp-port/owned.txt. Script changes live in -# omp-port/patches. Neither belongs here. +# Owned prose lives in the two skill paths named by omp-port/owned.txt. Script changes live in +# omp-port/patches. Upstream paths the port does not ship are in omp-port/excluded.txt. None of +# these belongs here. ## 1. Whole-sentence rewrites, which must read raw upstream text before any token rule edits it. @@ -38,12 +39,13 @@ s#^\*\*Match the model to the job\.\*\*.*$#**Match the role to the job.** Resolv # `/poteto-mode on|off|status`, `alt+shift+t`, and `--poteto` flag. These run before the # `/skill:` rule, which would otherwise rewrite the `/poteto-mode` their left sides quote. s#^1\. Install with `/add-plugin pstack` in chat, or from Customize in the sidebar\.$#1. Install with `/marketplace add negoro26/pstack-omp`, then `/marketplace install pstack@pstack-omp`.# -s#^2\. Run \[`/setup-pstack`\].*$#2. Run [`/setup-pstack`](../setup-pstack/SKILL.md). It selects models and a reasoning budget for exact discovered agents, writing three pstack-owned `modelRoles` aliases and `task.agentModelOverrides` entries in `~/.omp/agent/config.yml`. It does not create missing agents or select the chat model. Read the skill for activation and optional verification-skill setup.# -s#^- No `~/\.cursor/rules/pstack-models\.mdc` means.*$#- No exact override for a role means its loaded agent frontmatter or the parent chat model selects its model. Absence of an override does not prove setup was never run.# +s#^2\. Run \[`/setup-pstack`\].*$#2. Models need no configuration. Each agent runs on its frontmatter model, then on the chat model. To pick models per agent, follow [Choose models](../omp-mechanics/SKILL.md\#choose-models). Offer once each to generate a verification skill with `/skill:create-verification-skill` and a formal verification skill with `/skill:create-formal-verification-skill`, unless the project already has one.# +s#^- No `~/\.cursor/rules/pstack-models\.mdc` means.*$#- No exact override for a role means its loaded agent frontmatter or the parent chat model selects its model.# s#When the model rule is missing and it matters,#When required role overrides are missing and model selection matters,# +s#^- Now: give them `/setup-pstack` to type, and answer their question too\.$#- Now: point them to [Choose models](../omp-mechanics/SKILL.md\#choose-models), and answer their question too.# s#^- Later: answer their question, and add one line saying every role keeps its default model until they run `/setup-pstack`\.$#- Later: answer their question, and say agent frontmatter or parent inheritance remains in effect until they configure exact role overrides.# -s#^If cost is the worry, say where the tokens go and how to spend fewer\..*$#If cost is the worry, explain that extra workers and review panels spend more tokens. Rerun `/setup-pstack` to choose a smaller reasoning budget or cheaper available models. An agent without a model override uses its frontmatter or inherits the chat model. Fewer configured reviewer slots mean fewer subagents. Never write `auto` or `inherit-parent` as an OMP model selector. Save `/poteto-mode` for work that needs rigor.# -s#Installing changes nothing until the user invokes a skill\. Only `/setup-pstack` loads from the user's words\.#Skills run when invoked or routed. The installed extension also provides the session pin commands. `setup-pstack` is available for model-configuration requests.# +s#^If cost is the worry, say where the tokens go and how to spend fewer\..*$#If cost is the worry, explain that extra workers and review panels spend more tokens. To spend fewer, pick a lower effort or cheaper available models per [Choose models](../omp-mechanics/SKILL.md\#choose-models). An agent without a model override uses its frontmatter or inherits the chat model. Never write `auto` or `inherit-parent` as an OMP model selector. Save `/poteto-mode` for work that needs rigor.# +s#Installing changes nothing until the user invokes a skill\. Only `/setup-pstack` loads from the user's words\.#Skills run when invoked or routed. The installed extension also provides the session pin commands.# s#^Whether `/poteto-mode` stays on depends on how the user starts it:$#Choose whether to invoke the router once or pin it for the session:# s#^- Enter on `/poteto-mode` attaches.*$#- `/skill:poteto-mode` invokes the router for a task; it does not enable the persistent pin.# s#^- Option\+Enter on Mac or Alt\+Enter on Windows,.*$#- `/poteto-mode on` pins the router for every turn, `/poteto-mode off` unpins it, and `/poteto-mode status` reports the current pin. These are the installed extension's OMP commands, not Cursor's Custom Mode UI.# @@ -51,7 +53,8 @@ s#^- Cursor's docs list Custom Modes.*$#- The extension registers Alt+Shift+T to s#^Link \[Cursor's skills docs\].*$#Link the installed [OMP README](../../README.md) for pin commands and [runtime mapping](../pstack-omp/SKILL.md) for delegation. Mid-chat, "new task" makes the router match a fresh playbook. Resolve any helper against the live agent roster; use `poteto-agent` only when loaded, otherwise follow the runtime mapping's role and default-worker routing.# s#a Custom Mode keeps it on#`/poteto-mode on` keeps it pinned#g s#^\| The mode stopped applying after a few turns \|.*$#| The mode stopped applying after a few turns | Check `/poteto-mode status`. Use `/poteto-mode on` to pin it, or invoke `/skill:poteto-mode` for each task. |# -s#^\| A new model choice had no effect \|.*$#| A new model choice had no effect | Confirm the loaded agent name and its exact `task.agentModelOverrides` entry. Follow `/skill:setup-pstack` activation guidance; an override cannot create an agent. |# +s#^\| A new model choice had no effect \|.*$#| A new model choice had no effect | Confirm the loaded agent name and its exact `task.agentModelOverrides` entry, then start a new session. An override cannot create an agent. |# +s#^\| Pick a model for each role and a reasoning budget \| \[`/setup-pstack`\]\(\.\./setup-pstack/SKILL\.md\) \|$#| Pick a model for each role and a reasoning budget | [Choose models](../omp-mechanics/SKILL.md\#choose-models) |# s#^\| A skill didn't load on its own \|.*$#| A skill didn't load on its own | Invoke `/skill:` explicitly or let the router load it. Only skills enabled for model invocation appear in the per-turn index. |# s#which each get their own machine#which get private worktrees, not separate machines#g s#^- `/loop` and `/create-skill` are Cursor built-ins\.$#- `/loop` is an OMP runtime command when exposed. Skill authoring uses the [authoring playbook](../poteto-mode/playbooks/authoring-a-skill.md); there is no assumed Cursor `/create-skill` command.# @@ -65,7 +68,7 @@ s#\]\(../../docs/guide/([0-9][0-9]-[a-z-]+\.md)\)#](https://github.com/cursor/pl s#The links here point into the installed plugin, which the user may not be able to open, so give the user the file's public copy: `https://github.com/cursor/plugins/blob/main/pstack/` followed by its path\.#Link installed skill and runtime files directly. For a public copy of ported behavior, use `https://github.com/negoro26/pstack-omp/blob/main/plugins/pstack/` followed by the installed plugin-relative path. Guide URLs point to upstream for workflow background only; use this port's README and runtime skills for OMP commands.# # Poteto help's companion list. `omp cleanse` detects AND fixes repo-wide, so it is no diagnostic. s#^- `/deslop`, `control-cli`, and `control-ui` ship in the `cursor-team-kit` plugin\.$#- Prose cleanup uses `skill://unslop`. Diagnostics use the project's documented read-only checks, with LSP diagnostics only when a live server is configured. OMP exposes browser interaction through its eval prelude and CLI interaction through named bash services and `proc://`. Do not run `omp cleanse` as a diagnostic check.# -s#^Other tools can read the skills, because they use the Agent Skills format.*$#The engineering workflows come from upstream. `skill://pstack-omp` maps worker execution and lifecycle to OMP; `skill://setup-pstack` configures OMP models, and the mode extension preserves the session pin.# +s#^Other tools can read the skills, because they use the Agent Skills format.*$#The engineering workflows come from upstream. `skill://pstack-omp` maps worker execution and lifecycle to OMP, `skill://omp-mechanics` says how to choose models, and the mode extension preserves the session pin.# # Reflect's lens table loses Cursor's default-model column below, so its separator loses one too. /^## 2\. Spawn three reviewers in parallel$/,/^## 3\. Synthesize$/s#^\|---\|---\|---\|---\|$#|---|---|---|# @@ -191,19 +194,12 @@ s#`gpt-[0-9.]+-sol-[a-z]+`#your strongest instruction-following model#g s#`grok-[0-9.]+-([a-z]+-)?fast(-[a-z]+)?`#your fast code model#g # The same family under Cursor's provider prefix, effort token in the middle. s#`cursor-grok-[0-9.]+-([a-z]+-)?fast`#your fast code model#g -# The same tiers where the rule-file example writes a role value with no backticks. Anchored to -# the end of a `: ` line, which is the only place upstream writes a bare slug. -s#: claude-(fable|opus)-[0-9.-]+(-(thinking-)?[a-z]+)?$#: your strongest judgment model# -s#: gpt-[0-9.]+-sol-[a-z]+$#: your strongest instruction-following model# -s#: grok-[0-9.]+-([a-z]+-)?fast(-[a-z]+)?$#: your fast code model# # Catch-all for a slug no tier above knows. Two hyphen groups required, so an illustrative # `gpt-4` rename example is not a prescription and survives. omp-port reports what this rewrote. s#`(claude|gpt|grok|gemini|opus)-[a-z0-9.]+-[a-z0-9.-]+`#your configured model for this role#g ## 3. Remaining exact task text and generated frontmatter. -# Cursor's Task-spawn slug enumeration and hypothetical models API -> `omp models`. -s#Enumerate the model slugs you can pass to a `Task` subagent in this session\. That is the dependable source\. If Cursor also exposes a models API or CLI that lists the user's entitled models, prefer it for completeness\.#Run `omp models` to list the models configured on this machine. That is the dependable source.# # Cursor's blocking watch mode has the same liveness hazard without naming another runtime primitive. s#Reaching for `drive` inside a phase agent stops that agent finishing its turn\.#Blocking inside a phase agent stops that agent finishing its turn.# # These exact Cursor tool tokens survive only in otherwise rewritten sentences. @@ -443,6 +439,12 @@ s#Reviewers return findings in the `Task` response body\.#Reviewers return findi s#Use Glob to find directories and files, Grep to find key symbols, Read to understand#Use `glob` to find directories and files, `grep` to find key symbols, `read` to understand# s#Use Read, Grep, and Glob as needed\.#Use `read`, `grep`, and `glob` as needed.# s#Subagents inherit it\. Multiple `Task` calls on the same branch each get their own worktree#A worker shares the parent's checkout unless its spawn sets `isolated: true` where the live schema exposes it, so give every concurrent writer its own worktree explicitly# +# setup-pstack is in omp-port/excluded.txt, so the build ships no such skill. The poteto-help +# sentence rules above point at the omp-mechanics model note; these nets catch a line upstream +# rewords, ahead of the /skill: rule below. +s#\[`/setup-pstack`\]#[Choose models]#g +s#\.\./setup-pstack/SKILL\.md#../omp-mechanics/SKILL.md\#choose-models#g +s#`/setup-pstack`#the Choose models section of `skill://omp-mechanics`#g # omp registers one slash command per skill as /skill:; a bare /how is not a command on this # harness. `/poteto-mode` is in the alternation only because the gate scans for it like any other # name; the port's own extension registers that command, so the rewrite is a no-op in practice. @@ -451,7 +453,7 @@ s#Subagents inherit it\. Multiple `Task` calls on the same branch each get their # forgot: upstream added eighteen skills after it was written and every one of them shipped a bare # /name the gate could not accept. Adding a skill upstream now means adding its name to the # alternation, once. -s#`/(teach|recall|reflect|tdd|swarm|arena|interrogate|correct|setup-pstack|automate-me|figure-it-out|show-me-your-work|benchmark-checklist|blast-radius|ketchup-video|ketchup|bro|make-bot-ui|poteto-help|no-comments|unslop|create-verification-skill|maintain-verification-skill|create-formal-verification-skill|maintain-formal-verification-skill|statechart|architect|why|how|technical-writing|sharpen|in-your-own-words|typescript-best-practices|poteto-mode|principle-[a-z-]+)`#`/skill:\1`#g +s#`/(teach|recall|reflect|tdd|swarm|arena|interrogate|correct|automate-me|figure-it-out|show-me-your-work|benchmark-checklist|blast-radius|ketchup-video|ketchup|bro|make-bot-ui|poteto-help|no-comments|unslop|create-verification-skill|maintain-verification-skill|create-formal-verification-skill|maintain-formal-verification-skill|statechart|architect|why|how|technical-writing|sharpen|in-your-own-words|typescript-best-practices|poteto-mode|principle-[a-z-]+)`#`/skill:\1`#g # One verdict vocabulary. pstack-omp and swarm both name PASS/ISSUES/BLOCKED; shipping named a # different three, and its landing rule tested for a different set, so a verifier told by one file # and checked by another could not satisfy both. diff --git a/omp-port/stale-exempt.tsv b/omp-port/stale-exempt.tsv index c005007..7f505ba 100644 --- a/omp-port/stale-exempt.tsv +++ b/omp-port/stale-exempt.tsv @@ -38,7 +38,6 @@ One subagent per PR, not batched, each a Cursor cloud agen superseded: upstream `claude-fable-[0-9.-]+(-(thinking-)?[a-z]+)?` forward-compat net: the left side is a Cursor wire token or path that upstream does not emit today. The rule earns its place by not firing, because it is the only thing standing between that text and a Cursor API in the tree. `gpt-[0-9.]+-sol-[a-z]+` forward-compat net: the left side is a Cursor wire token or path that upstream does not emit today. The rule earns its place by not firing, because it is the only thing standing between that text and a Cursor API in the tree. `cursor-grok-[0-9.]+-([a-z]+-)?fast` forward-compat net: the left side is a Cursor wire token or path that upstream does not emit today. The rule earns its place by not firing, because it is the only thing standing between that text and a Cursor API in the tree. -: gpt-[0-9.]+-sol-[a-z]+$ forward-compat net: the left side is a Cursor wire token or path that upstream does not emit today. The rule earns its place by not firing, because it is the only thing standing between that text and a Cursor API in the tree. One Cursor cloud agent superseded: upstream reworded this sentence and a different rule now covers the current phrasing. Kept because the reword is cheap for upstream to undo. Cloud-agent PR tools default to draft, so set `draft: fals superseded: upstream reworded this sentence and a different rule now covers the current phrasing. Kept because the reword is cheap for upstream to undo. Fan out N parallel cloud workers\. upstream removed Swarm's standalone overview at eb4db2b; retain the cloud-worker translation if that sentence returns. diff --git a/omp-port/swallow.tsv b/omp-port/swallow.tsv index 2088f93..b64b08a 100644 --- a/omp-port/swallow.tsv +++ b/omp-port/swallow.tsv @@ -13,15 +13,15 @@ s#^Every spawn is a Task subagent with `subagent_type: generalPurpose` e4ddbfe1a s#^Every spawn is `generalPurpose` with `readonly: false` 3701c9bd247551ee595db114a1f24002d3ba5fb2ecf2174c2bc44c3fdbde3b99 why's spawn contract: Cursor's agent mode and why readonly strips MCP. On omp MCP tools are inherited and posture lives in the brief, which the replacement says, keeping "investigators write nothing". s#^Each reviewer gets `subagent_type: generalPurpose`, `readonly: true`, and `model` set to its configured entry, e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 matches nothing since ab1f702 removed the sentence; stale-exempt.tsv keeps the rule as a net. The empty hash makes its return a review, not a silent drop. s#^\*\*Match the model to the job\.\*\* 801f64a121262c1e05986d6ad25efbd4a2b414b22531bfa5469fd273359065c9 the router's per-spawn slug tuning. c742dd6 added six sentences on rejected slugs and fallbacks. omp's task item takes no model, so there is no slug to reject; the replacement keeps the one omp lever, a per-spawn `effort` where the schema has it. -s#^2\. Run \[`/setup-pstack`\] 4b08daf74512955b83a4bdcfe0a1cfcf8b8430fae65a2669bd61acf2ececcde2 poteto-help's setup step describes Cursor's rule file and new-chat activation. The replacement describes omp's config write and keeps the verification-skill offer as a pointer to the skill. +s#^2\. Run \[`/setup-pstack`\] 4b08daf74512955b83a4bdcfe0a1cfcf8b8430fae65a2669bd61acf2ececcde2 poteto-help's setup step describes Cursor's rule file and new-chat activation. setup-pstack is excluded, so the replacement says models need no configuration, links the omp-mechanics model note, and keeps both verification-skill offers. s#^- No `~/\.cursor/rules/pstack-models\.mdc` means 01bb794d2319efc4873978ecbbd68ec87f8680e523c82858af06d9eceb7d3649 one bullet whose only claim is about Cursor's rule file. The replacement says what a missing override means on omp. -s#^If cost is the worry, say where the tokens go and how to spend fewer\. 48ba7e30ee8918b4cfd4a03e24bd26f16007ccfa68b77d496baf2b1a71c059e8 poteto-help's cost answer. Each sentence reappears in omp terms: panels cost tokens, rerun setup, unset overrides inherit, fewer slots, save the router for rigor. +s#^If cost is the worry, say where the tokens go and how to spend fewer\. 48ba7e30ee8918b4cfd4a03e24bd26f16007ccfa68b77d496baf2b1a71c059e8 poteto-help's cost answer. The replacement keeps panels cost tokens, cheaper models through the omp-mechanics model note, unset overrides inherit, and save the router for rigor. It drops the shorter panel list, which was a setup-pstack setting. s#^- Enter on `/poteto-mode` attaches 95a8446c11422ce1fa2e5e8dbbea802b039f40586ff53b140cc2cbdeebb3d517 Cursor's Enter-attaches-once composer behaviour. omp has /skill: invocation instead, which the replacement names. s#^- Option\+Enter on Mac or Alt\+Enter on Windows, 8c4489b30922aba4c9de57659ddf7188acbe9373d22a8bbe0d752f76357cb273 Cursor's Custom Mode shortcut. omp pins through the potetomode extension's commands, which the replacement names. s#^- Cursor's docs list Custom Modes b25fe6d53b47be6b3950c8886f9f41b3ad7a4501f1a8219352a3cfb121a9f08b where Cursor documents Custom Modes. The replacement names the extension's own toggle and print-mode flag. s#^Link \[Cursor's skills docs\] 7c905f629bb7a9b502196503e8757caf4848d8fcca10bc0f0a4beed1fefa4e4c Cursor's docs link and `subagent_type: "poteto-agent"`. The replacement links the omp README and runtime mapping and keeps the "new task" sentence. s#^\| The mode stopped applying after a few turns \| 3d0b3d44d05cce8834189811e4165a0f05da7dc1781c4c75c1fe10dd58f348da troubleshooting row whose cause and fix are Cursor's Enter versus Custom Mode. The replacement gives omp's pin status and commands. -s#^\| A new model choice had no effect \| d55bbd9e357ccf440a0e3eb4148e99f170bffa05a496621c3bda632ef76f9cee troubleshooting row about Cursor's rule applying to new chats. The replacement checks the exact override and the loaded agent. +s#^\| A new model choice had no effect \| d55bbd9e357ccf440a0e3eb4148e99f170bffa05a496621c3bda632ef76f9cee troubleshooting row about Cursor's rule applying to new chats. The replacement checks the exact override and the loaded agent, and keeps the new-session step. s#^\| A skill didn't load on its own \| 9f05aa9e3a0b89320e71962276e9fc0c43f05e9395627e4024100ad5cdb57c83 troubleshooting row about Cursor's word-triggered loading. The replacement describes /skill: invocation and the per-turn index. s#^Without `/poteto-mode`, a phrase such as 8a16ef1b3f73471269fe965c53193679ada5931ce9172f24adf5af4168ccd208 names Cursor's own babysit skill as the competing route. The replacement keeps the Playbooks pointer and the guide link, marked as upstream background. s#^Other tools can read the skills, because they use the Agent Skills format 1d2685690b6f21fcaf444a6ebd0d6423458bcc00ee1fdbcb936f0e62b85a8b0e portability caveat about Cursor subagents, Custom Modes and /loop. On omp those parts run, and the replacement says which skills map them. diff --git a/omp-port/test-gate-mutations.sh b/omp-port/test-gate-mutations.sh index e097ec2..fd926f2 100755 --- a/omp-port/test-gate-mutations.sh +++ b/omp-port/test-gate-mutations.sh @@ -27,6 +27,7 @@ run_gate() { [ -d "$dir/patches" ] && patches="$dir/patches" CHECK_PORT_ROOT="$root" RULES="$dir/port/rules.sed" STALE_EXEMPT="$dir/port/stale-exempt.tsv" \ SHADOW_EXEMPT="$dir/port/shadow-exempt.tsv" SWALLOW="$dir/port/swallow.tsv" OWNED="$dir/port/owned.txt" \ + EXCLUDED="$dir/port/excluded.txt" \ PATCH_DIR="$patches" CHECK_PORT_CONTRACTS_ONLY="$only" CHECK_PORT_NESTED=1 \ bash "$PORT_DIR/check-port.sh" "$@" } @@ -49,7 +50,7 @@ start_case() { dir="$WORK/$((++ncase))" mkdir -p "$dir/port" [ "$mode" != contracts ] || cp -a "$PORT_DIR/../plugins/pstack" "$dir/plugin" - cp "$PORT_DIR"/{rules.sed,stale-exempt.tsv,shadow-exempt.tsv,swallow.tsv,owned.txt} "$dir/port/" + cp "$PORT_DIR"/{rules.sed,stale-exempt.tsv,shadow-exempt.tsv,swallow.tsv,owned.txt,excluded.txt} "$dir/port/" FIXTURE="$dir/plugin" PORT_FIXTURE="$dir/port" PATCH_FIXTURE="$dir/patches" "$mutation" printf '%s\037%s\037%s\037%s\037%s\n' "$name" "$mode" "$label" "$needle" "$dir" >>"$WORK/cases" @@ -94,32 +95,15 @@ append() { printf '%s\n' "$2" >>"$1" } -mutate_preserve_ownership() { - sed -i 's/^Preserve every unrelated entry in both maps\.$/Discard every unrelated entry in both maps./' "$FIXTURE/skills/setup-pstack/SKILL.md" +# The build deletes an excluded path, so it reaches the tree only by hand or through owned.txt. +mutate_excluded_path() { + mkdir -p "$FIXTURE/skills/setup-pstack" + printf -- '---\nname: setup-pstack\n---\n' >"$FIXTURE/skills/setup-pstack/SKILL.md" } -mutate_override_ownership() { - sed -i 's/^A `task\.agentModelOverrides` entry is pstack-owned only when its exact key is a discovered agent and its value is one of `@pstack_fast_code`, `@pstack_judgment`, or `@pstack_instruction`\.$/A `task.agentModelOverrides` entry is pstack-owned when its key starts with `pstack_`./' "$FIXTURE/skills/setup-pstack/SKILL.md" -} - -mutate_alias_shape() { - sed -i '/^modelRoles:/a\ pstack_extra: ":"' "$FIXTURE/skills/omp-mechanics/references/setup-pstack-config.md" -} - -mutate_supported_suffixes() { - sed -i 's/, and `minimal`//' "$FIXTURE/skills/omp-mechanics/references/setup-pstack-config.md" -} - -mutate_base_selector() { - sed -i 's/require the remaining base to equal a selector from `omp models --json`/accept any remaining base/' "$FIXTURE/skills/omp-mechanics/references/setup-pstack-config.md" -} - -mutate_setup_apply_script() { - sed -i '/^APPLY=/d' "$FIXTURE/skills/setup-pstack/SKILL.md" -} - -mutate_setup_lever() { - sed -i 's/task\.agentModelOverrides/task.model/g' "$FIXTURE/skills/setup-pstack/SKILL.md" +# A poteto-help line this port shipped before it excluded setup-pstack. +mutate_excluded_reference() { + printf '\n- Now: give them `/skill:setup-pstack` to type, and answer their question too.\n' >>"$FIXTURE/skills/poteto-help/SKILL.md" } mutate_router_lever() { @@ -251,10 +235,10 @@ mutate_swallow_row_missing() { sed -i '/^s#^- Enter on `\/poteto-mode` attaches/d' "$PORT_FIXTURE/swallow.tsv" } -# The upstream setup-pstack tree before ab1f702, which is what owned.txt would still say had nobody -# read that commit. +# poteto-mode's upstream tree at the old target, recorded as the object last reviewed. Upstream has +# changed it since, which is what owned.txt says when nobody read an upstream edit to an owned path. mutate_owned_drift() { - sed -i 's#^skills/setup-pstack\t.*#skills/setup-pstack\td533da1671e44234a9f72d1d4db8043cedd25eb2#' "$PORT_FIXTURE/owned.txt" + append "$PORT_FIXTURE/owned.txt" "skills/poteto-mode"$'\t'"$(git -C "$CANON" rev-parse "$OLD_TARGET:pstack/skills/poteto-mode")" } # A real patch applied a second time. Its hunks are already in, so plain patch asks on the terminal @@ -286,13 +270,8 @@ mutate_target_only_patch() { } start_case 'baseline' '' '' contracts nothing -start_case 'unrelated ownership' 'setup config contract' '' contracts mutate_preserve_ownership -start_case 'per-agent ownership' 'setup config contract' '' contracts mutate_override_ownership -start_case 'alias shape' 'setup config contract' '' contracts mutate_alias_shape -start_case 'supported suffix list' 'setup config contract' '' contracts mutate_supported_suffixes -start_case 'base selector validation' 'setup config contract' '' contracts mutate_base_selector -start_case 'setup apply script' 'setup config contract' '' contracts mutate_setup_apply_script -start_case 'setup model lever' 'omp model levers' '' contracts mutate_setup_lever +start_case 'excluded path in the tree' 'excluded paths' 'skills/setup-pstack is in the tree' contracts mutate_excluded_path +start_case 'line naming an excluded path' 'excluded paths' '/skill:setup-pstack' contracts mutate_excluded_reference start_case 'router model lever' 'omp model levers' '' contracts mutate_router_lever start_case 'agent hub api' 'agent hub api' '' contracts mutate_hub_api start_case 'install root' 'install root' '' contracts mutate_install_root @@ -325,7 +304,7 @@ if ensure_canon "$PIN" "$OLD_TARGET" >/dev/null 2>&1; then start_case 'rule behind its own duplicate' 'rule shadowing' 'which each get their own machine' full mutate_shadowed_rule start_case 'swallowed text changed' 'swallow ledger' 'now: **Match the model to the job.**' full mutate_swallow_hash start_case 'wildcard rule with no row' 'swallow ledger' 'has no swallow.tsv row' full mutate_swallow_row_missing - start_case 'owned path moved upstream' 'owned drift' 'skills/setup-pstack: upstream moved' full mutate_owned_drift + start_case 'owned path moved upstream' 'owned drift' 'skills/poteto-mode: upstream moved' full mutate_owned_drift start_case 'patch that no longer applies' 'reproducible' 'failed, rules or a patch' full mutate_patch_fails if command -v script >/dev/null; then start_case 'patch prompt on a terminal' '' 'hunk FAILED' terminal mutate_patch_fails diff --git a/plugins/pstack/README.md b/plugins/pstack/README.md index cce4d92..43ea683 100644 --- a/plugins/pstack/README.md +++ b/plugins/pstack/README.md @@ -1,8 +1,8 @@ # pstack for oh-my-pi -Lauren Tan's pstack methodology, ported to omp. 60 skills, 23 playbooks, 24 principle +Lauren Tan's pstack methodology, ported to omp. 59 skills, 23 playbooks, 24 principle leaves, the `poteto-mode` pin, and the `poteto-agent` and `comment-sicko` agents. -57 skills are built from upstream. `omp-mechanics`, `pstack-omp`, and `setup-pstack` are port-owned. +57 skills are built from upstream. `omp-mechanics` and `pstack-omp` are port-owned. ## Install @@ -11,7 +11,7 @@ leaves, the `poteto-mode` pin, and the `poteto-agent` and `comment-sicko` agents /marketplace install pstack@pstack-omp ``` -That loads the 60 skills and both extensions, `potetomode` and `pstackpolicy`. The two agents are optional. +That loads the 59 skills and both extensions, `potetomode` and `pstackpolicy`. The two agents are optional. On omp 18.1.13 a marketplace plugin's `agents/` directory is scanned only through the `claude-plugins` discovery provider, and enabling that provider also loads every Claude Code plugin cached under `~/.claude/plugins`. Link the agents into omp's native root instead: @@ -84,7 +84,5 @@ does, which is collapsing conversation context. alias fails the spawn, so bind before you add. Bind it yourself with `/model`, or with `omp config set modelRoles ''`, because a dotted `modelRoles.poteto` key is rejected and `inherit-parent` resolves to `No model selected`, which also fails the spawn. -`/skill:setup-pstack` will not make this binding for you: its ownership contract limits it to the -three `pstack_*` aliases and says never to treat `poteto`, `default`, or another operator role as -pstack-owned. It is still the right command for the pstack-owned aliases, and omp registers one -slash command per skill as `/skill:`, so a bare `/setup-pstack` is not a command. +pstack needs no other model configuration. To choose models for other agents, follow +[Choose models](skills/omp-mechanics/SKILL.md#choose-models) in `omp-mechanics`. diff --git a/plugins/pstack/skills/omp-mechanics/SKILL.md b/plugins/pstack/skills/omp-mechanics/SKILL.md index d055c28..cbc98cd 100644 --- a/plugins/pstack/skills/omp-mechanics/SKILL.md +++ b/plugins/pstack/skills/omp-mechanics/SKILL.md @@ -36,9 +36,13 @@ Read `references/make-bot-ui-on-omp.md` before starting. The upstream Grok Bot w OMP does not auto-attach a skill from `globs`. Read `skill://typescript-best-practices` before editing a `.ts` or `.tsx` file. -## Setup pstack +## Choose models -Read `references/setup-pstack-config.md` for the file shape. The `setup-pstack` skill owns discovery and selection. Use `omp models --json` for each selected model's effort list, and preserve unrelated config entries. +pstack needs no model configuration. An agent with no override runs on its frontmatter model, then on the parent session's model, which `/model` selects. + +To choose a model for an agent, add an entry under `task.agentModelOverrides` in `~/.omp/agent/config.yml`. The key is the exact agent name from the live roster. The value is a selector from `omp models --json`, or `@` for an alias you define under `modelRoles`. An alias value can carry an effort suffix, such as `:high`, when the model lists that effort in `thinking.efforts`. `auto` and `inherit-parent` are not values. omp stores an entry without checking it, so a bad value fails only at spawn. Entries apply to new sessions, and an override cannot create an agent. + +A review panel in `arena` or `interrogate`, including the `arena` cross-judge, gives a second opinion only when its agents resolve to different model families. With no overrides, every panel agent without a frontmatter model runs on the parent session's model. Check the model each panel agent resolves to before you trust a panel verdict. ## Session store diff --git a/plugins/pstack/skills/omp-mechanics/references/setup-pstack-config.md b/plugins/pstack/skills/omp-mechanics/references/setup-pstack-config.md deleted file mode 100644 index 01c6314..0000000 --- a/plugins/pstack/skills/omp-mechanics/references/setup-pstack-config.md +++ /dev/null @@ -1,21 +0,0 @@ -# The OMP shape of pstack model configuration - -This reference is the file shape for **setup-pstack**. It does not define a second routing contract. - -```yaml -modelRoles: - pstack_fast_code: ":" - pstack_judgment: ":" - pstack_instruction: ":" -task: - agentModelOverrides: - : "@pstack_fast_code" -``` - -Replace the agent placeholder with a name from the live roster. The three `modelRoles` keys are the complete pstack-owned alias set. A `task.agentModelOverrides` entry is pstack-owned only when its value is one of those three `@pstack_*` aliases and its key is an exact discovered agent. Update or remove only those entries. Preserve unrelated model roles, agent overrides, and operator entries. Never use a `pstack_*` key as an agent name. - -Validate a `modelRoles` value by accepting either an exact detected selector or exactly one supported effort suffix from `max`, `xhigh`, `high`, `medium`, `low`, and `minimal`. Strip only that supported suffix and require the remaining base to equal a selector from `omp models --json`. Require the suffix to appear in the selected model's `thinking.efforts` list. The CLI can serialize that list directly as `thinking`; follow the live JSON shape. A task override value is either an exact detected selector or an `@role` alias already present in `modelRoles`; `inherit-parent` and `auto` are not values. - -An omitted task override uses the discovered agent's frontmatter model first, then the parent session's active or default model. - -The capability aliases may resolve to the same selector and the same family. Family diversity is optional. Do not fabricate a second family when the operator's model policy or roster provides one. Setup reports weaker diversity, and the workflows report it too. diff --git a/plugins/pstack/skills/poteto-help/SKILL.md b/plugins/pstack/skills/poteto-help/SKILL.md index cf45156..9bfbfc6 100644 --- a/plugins/pstack/skills/poteto-help/SKILL.md +++ b/plugins/pstack/skills/poteto-help/SKILL.md @@ -24,28 +24,28 @@ Infer the need from the message and the conversation. A named situation, such as Check the state that changes the answer, and mention it only when it does: -- No exact override for a role means its loaded agent frontmatter or the parent chat model selects its model. Absence of an override does not prove setup was never run. +- No exact override for a role means its loaded agent frontmatter or the parent chat model selects its model. - No `verify-*` skill or other app harness in the project means agents have no scripted way to drive the app. Mention `/skill:create-verification-skill` when the question is about proving a change works. - No formal verification skill in the project means no property is checked past what tests reach. Mention `/skill:create-formal-verification-skill` when the question is about a race, an invariant, or another case tests rarely hit. When one exists and the code it covers changed after the commit it records as last in sync, mention `/skill:maintain-formal-verification-skill` if the question touches that code. When required role overrides are missing and model selection matters, ask whether the user wants to pick a model for each role and a reasoning budget now. It matters when the user is new, the question is about setup or cost, or the answer depends on which models run. Ask at most once per chat. If the need is also unclear, ask both questions together. Offer two choices: -- Now: give them `/skill:setup-pstack` to type, and answer their question too. +- Now: point them to [Choose models](../omp-mechanics/SKILL.md#choose-models), and answer their question too. - Later: answer their question, and say agent frontmatter or parent inheritance remains in effect until they configure exact role overrides. ## Get set up 1. Install with `/marketplace add negoro26/pstack-omp`, then `/marketplace install pstack@pstack-omp`. -2. Run [`/skill:setup-pstack`](../setup-pstack/SKILL.md). It selects models and a reasoning budget for exact discovered agents, writing three pstack-owned `modelRoles` aliases and `task.agentModelOverrides` entries in `~/.omp/agent/config.yml`. It does not create missing agents or select the chat model. Read the skill for activation and optional verification-skill setup. +2. Models need no configuration. Each agent runs on its frontmatter model, then on the chat model. To pick models per agent, follow [Choose models](../omp-mechanics/SKILL.md#choose-models). Offer once each to generate a verification skill with `/skill:create-verification-skill` and a formal verification skill with `/skill:create-formal-verification-skill`, unless the project already has one. 3. Start a real task with `/skill:poteto-mode`, a goal, and a check that can pass or fail. -Skills run when invoked or routed. The installed extension also provides the session pin commands. `setup-pstack` is available for model-configuration requests. The [README](../../README.md) and [guide page 1](https://github.com/cursor/plugins/blob/main/pstack/docs/guide/01-setup.md) have the details. Offer to word their first prompt with them, per [`references/prompting.md`](references/prompting.md). +Skills run when invoked or routed. The installed extension also provides the session pin commands. The [README](../../README.md) and [guide page 1](https://github.com/cursor/plugins/blob/main/pstack/docs/guide/01-setup.md) have the details. Offer to word their first prompt with them, per [`references/prompting.md`](references/prompting.md). -If cost is the worry, explain that extra workers and review panels spend more tokens. Rerun `/skill:setup-pstack` to choose a smaller reasoning budget or cheaper available models. An agent without a model override uses its frontmatter or inherits the chat model. Fewer configured reviewer slots mean fewer subagents. Never write `auto` or `inherit-parent` as an OMP model selector. Save `/skill:poteto-mode` for work that needs rigor. +If cost is the worry, explain that extra workers and review panels spend more tokens. To spend fewer, pick a lower effort or cheaper available models per [Choose models](../omp-mechanics/SKILL.md#choose-models). An agent without a model override uses its frontmatter or inherits the chat model. Never write `auto` or `inherit-parent` as an OMP model selector. Save `/skill:poteto-mode` for work that needs rigor. pstack installs in omp with `/marketplace add negoro26/pstack-omp`, then `/marketplace install pstack@pstack-omp`. Invoke skills as `/skill:`, such as `/skill:poteto-mode`. Read `skill://pstack-omp` for the live task and runtime contract and `skill://omp-mechanics` for pstack-specific OMP differences. -The engineering workflows come from upstream. `skill://pstack-omp` maps worker execution and lifecycle to OMP; `skill://setup-pstack` configures OMP models, and the mode extension preserves the session pin. +The engineering workflows come from upstream. `skill://pstack-omp` maps worker execution and lifecycle to OMP, `skill://omp-mechanics` says how to choose models, and the mode extension preserves the session pin. ## Start a task with `/skill:poteto-mode` @@ -93,7 +93,7 @@ The default answer is `/skill:poteto-mode`, which runs most of the others when i | Vet a performance number before reporting or acting on it | [`/skill:benchmark-checklist`](../benchmark-checklist/SKILL.md) | | Run a large or cross-cutting change, or one to review after stepping away | [`/skill:figure-it-out`](../figure-it-out/SKILL.md) | | Keep a decision log during a run, and review it afterward | [`/skill:show-me-your-work`](../show-me-your-work/SKILL.md) | -| Pick a model for each role and a reasoning budget | [`/skill:setup-pstack`](../setup-pstack/SKILL.md) | +| Pick a model for each role and a reasoning budget | [Choose models](../omp-mechanics/SKILL.md#choose-models) | | Turn their own working habits into a personal mode skill | [`/skill:automate-me`](../automate-me/SKILL.md) | | Turn what a finished task taught into skill edits | [`/skill:reflect`](../reflect/SKILL.md) | | Stop agents from repeating the same mistakes in this repo | [`/skill:correct`](../correct/SKILL.md) | @@ -140,7 +140,7 @@ Principles are one-rule skills that `/skill:poteto-mode` reads and cites in its |---|---| | The mode stopped applying after a few turns | Check `/poteto-mode status`. Use `/poteto-mode on` to pin it, or invoke `/skill:poteto-mode` for each task. | | A question got treated as the next step of the last task | Say "new task", or say the turn doesn't need the mode. | -| A new model choice had no effect | Confirm the loaded agent name and its exact `task.agentModelOverrides` entry. Follow `/skill:setup-pstack` activation guidance; an override cannot create an agent. | +| A new model choice had no effect | Confirm the loaded agent name and its exact `task.agentModelOverrides` entry, then start a new session. An override cannot create an agent. | | Runs cost more than expected | See the cost paragraph under Get set up. | | A skill didn't load on its own | Invoke `/skill:` explicitly or let the router load it. Only skills enabled for model invocation appear in the per-turn index. | | Parallel agents overwrote each other | Give each agent its own worktree, or run them as isolated subagents, which get private worktrees, not separate machines. | diff --git a/plugins/pstack/skills/setup-pstack/SKILL.md b/plugins/pstack/skills/setup-pstack/SKILL.md deleted file mode 100644 index 71c9184..0000000 --- a/plugins/pstack/skills/setup-pstack/SKILL.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -name: setup-pstack -description: Configure pstack model selectors and reasoning budget. Detects available models and writes `modelRoles` plus exact-agent overrides. Use for /setup-pstack, "configure pstack models", "pstack budget", or changing pstack's model choices. ---- - -# Setup pstack - -Read `skill://omp-mechanics/references/setup-pstack-config.md` for the file shape. This skill owns the selection procedure. It writes `task.agentModelOverrides` and three pstack-owned `modelRoles` aliases in `~/.omp/agent/config.yml`. It never creates agents, selects the chat model, or replaces `/model`. - -## Ownership contract - -The only pstack-owned `modelRoles` keys are `pstack_fast_code`, `pstack_judgment`, and `pstack_instruction`. -A `task.agentModelOverrides` entry is pstack-owned only when its exact key is a discovered agent and its value is one of `@pstack_fast_code`, `@pstack_judgment`, or `@pstack_instruction`. -Never infer ownership from a key spelling. Never treat `default`, `poteto`, `judge`, or another operator role as pstack-owned. -Preserve every unrelated entry in both maps. - -## Steps - -### 1. Detect models, aliases, and agents - -Run `omp models --json`, read `modelRoles` and `task.agentModelOverrides` with `omp config get ... --json`, and inspect the live task roster. Inspect each discovered agent's exact name, frontmatter, declared tools, and description. For every candidate selector, read the selected model's `thinking.efforts` list from the model metadata. The CLI can serialize the same list directly as `thinking`; follow the live JSON shape. Respect any operator model policy, including a single-family policy. If no selectors are detected, ask for selectors the operator can use. `inherit-parent` and `auto` are not override values. - -An omitted override uses the discovered agent's frontmatter model first, then the parent session's active or default model. Offer omission only with that exact result. - -### 2. Build the exact mapping - -Before asking for the budget, make a table with one row per capability and these columns: capability, exact discovered agent name, current effective model, and planned alias. Use only names present in the live roster. - -- `pstack_fast_code`: discovered agents whose declared tools and description support implementation writes. -- `pstack_judgment`: discovered review-capable agents. If none exists, use a discovered full-access worker with a reviewer brief. -- `pstack_instruction`: discovered full-access agents that can follow an exact tool sequence. Do not assign a strict read-only specialist. - -For each capability, prefer an exact agent already bound to that pstack alias, then the operator's confirmed choice, then roster order. One exact agent has one override value. If the roster has fewer distinct agents than capabilities, leave an alias defined but do not invent an agent; point the aliases at the same selected selector when that is the only policy-compliant choice. All three aliases may resolve to one family. Family diversity is optional, so report weaker diversity when the policy or roster supplies one family. - -### 3. Ask for and apply the budget - -Prefer `ask`. Offer these exact labels and name the current budget when recorded: - -- `unlimited — keep max` -- `large — xhigh reasoning` -- `medium — high reasoning` -- `small — medium reasoning` -- `tiny — minimal reasoning` - -The effort ladder is `max > xhigh > high > medium > low > minimal`. Keep the detected selector as the base. Choose the requested effort only when it appears in that model's `thinking.efforts` list. Otherwise choose the highest listed effort below it. If none exists, mark that alias unresolved. `unlimited` uses the highest listed effort. Never write a requested suffix that the selected model does not support. - -### 4. Plan with the script and confirm - -`skill://` paths are not executable. Resolve the script once in the bash tool, which expands `skill://` inside `realpath`: - -```bash -APPLY=$(realpath skill://setup-pstack)/scripts/apply-models.mjs -``` - -Turn the table into arguments: `--alias =[:]` per alias, `--agent =@` per mapped agent, and `--unset-agent ` for an agent still bound to a pstack alias but no longer selected. Run `node "$APPLY" --dry-run `; `bun` runs it too. It prints the planned changes, every pstack-owned agent with the model it resolves to, and the family count. Show that output with the table, mark unavailable agents and unresolved selectors, and ask whether to accept it. Offer only detected selectors, configured aliases, or omission with the exact agent-then-parent resolution above. - -The script refuses and writes nothing when an alias is not one of the three, a base is not a selector from `omp models --json`, an effort is off the ladder or missing from the selected model's `thinking.efforts` list, or an agent value is not a `@pstack_*` alias defined in `modelRoles`. It never strips an arbitrary colon fragment. It also refuses to change or remove an agent entry pstack does not own, and any `pstack_*` agent key. Fix the row and rerun the dry run. Never edit `config.yml` by hand to get past a refusal. - -### 5. Write the config - -On acceptance, run the same command without `--dry-run`. It merges onto the current records, writes `modelRoles` before `task.agentModelOverrides`, and preserves every entry pstack does not own. A rerun with the same arguments writes nothing and says so. If it reports that `modelRoles` was written and `task.agentModelOverrides` was not, rerun the same command to finish. - -### 6. Confirm - -Report the exact aliases and agent keys written, the selectors and effort suffixes used, any unavailable capability, and the family count from the script's output. One family prints `weaker diversity: one family`; report it as information. State that the entries apply to new sessions and that rerunning updates them. `node "$APPLY" --show` prints the current aliases, agent mappings, and family count. - -### 7. Offer a verification skill (optional) - -Check whether the project has a way to drive the real app for proof. If not, offer once: "want a project-local verification skill, so agents can drive the app the way a user does and prove changes work? I can generate one." On yes, read `skill://create-verification-skill` in full and follow it. On no, move on without pushing. - -### 8. Offer a formal verification skill (optional) - -Check whether the project already has a formal verification skill (a project skill that runs a model checker, a proof, or another verifier against the code). If not, offer once: "want a formal verification skill, so agents can prove a key property of your code holds in every case in scope, not only the cases tests reach? I can generate one." On yes, read `skill://create-formal-verification-skill` in full and follow it. On no, move on without pushing. diff --git a/plugins/pstack/skills/setup-pstack/scripts/apply-models.mjs b/plugins/pstack/skills/setup-pstack/scripts/apply-models.mjs deleted file mode 100755 index 10c11a0..0000000 --- a/plugins/pstack/skills/setup-pstack/scripts/apply-models.mjs +++ /dev/null @@ -1,150 +0,0 @@ -#!/usr/bin/env node -// Applies setup-pstack's model choices to omp config, checked against `omp models --json` first. -// `omp config set` stores any string, so an unknown selector or alias otherwise fails only at spawn. -// -// Data shape. -// OWNED_ALIASES are the only modelRoles keys this script writes. -// A Selection is { alias, base, effort }: base is a `selector` from the catalog, effort is null or -// an EFFORTS member that the model's `thinking` list names. It is written as base or base:effort. -// A task.agentModelOverrides entry is pstack-owned only when its key is an exact agent name and -// its value is one of OWNED_VALUES. Every other entry in both records keeps its key, value, and order. -import { spawnSync } from "node:child_process"; - -const OWNED_ALIASES = ["pstack_fast_code", "pstack_judgment", "pstack_instruction"]; -const OWNED_VALUES = OWNED_ALIASES.map((alias) => `@${alias}`); -const EFFORTS = ["max", "xhigh", "high", "medium", "low", "minimal"]; -const RECORDS = { roles: "modelRoles", overrides: "task.agentModelOverrides" }; -const USAGE = `usage: apply-models.mjs --show - apply-models.mjs [--dry-run] [--alias =[:effort]]... - [--agent =@]... [--unset-agent ]...`; - -const fail = (message) => { - throw new Error(message); -}; - -function omp(...args) { - const run = spawnSync("omp", args, { encoding: "utf8" }); - if (run.error) fail(`cannot run omp: ${run.error.message}`); - if (run.status !== 0) fail(`omp ${args.slice(0, 3).join(" ")} exited ${run.status}: ${(run.stderr || run.stdout).trim()}`); - return run.stdout; -} - -function parseArgs(argv) { - const request = { show: false, dryRun: false, aliases: new Map(), agents: new Map() }; - const add = (map, key, value) => (map.has(key) ? fail(`${key} is given twice`) : map.set(key, value)); - for (let i = 0; i < argv.length; i++) { - const flag = argv[i]; - const value = () => { - const next = argv[++i]; - return next && !next.startsWith("--") ? next : fail(`${flag} needs a value\n${USAGE}`); - }; - const pair = () => { - const next = value(); - const eq = next.indexOf("="); - return eq > 0 && eq < next.length - 1 ? [next.slice(0, eq), next.slice(eq + 1)] : fail(`${flag} expects =, got "${next}"`); - }; - if (flag === "--show") request.show = true; - else if (flag === "--dry-run") request.dryRun = true; - else if (flag === "--alias") add(request.aliases, ...pair()); - else if (flag === "--agent") add(request.agents, ...pair()); - else if (flag === "--unset-agent") add(request.agents, value(), null); - else fail(`unknown argument "${flag}"\n${USAGE}`); - } - const edits = request.aliases.size + request.agents.size; - if (request.show && (edits || request.dryRun)) fail("--show takes no other argument"); - if (!request.show && !edits) fail(`nothing to do\n${USAGE}`); - return request; -} - -function readCatalog() { - const { models } = JSON.parse(omp("models", "--json")); - return new Map(models.map((model) => [model.selector, Array.isArray(model.thinking) ? model.thinking : []])); -} - -// A value that is itself a selector wins, so a colon inside a selector is never read as an effort. -// Otherwise only a known effort suffix is split off; any other colon fragment stays part of the base. -function parseSelection(alias, value, catalog) { - if (!OWNED_ALIASES.includes(alias)) fail(`--alias ${alias}: not a pstack alias, use one of ${OWNED_ALIASES.join(", ")}`); - if (catalog.has(value)) return { alias, base: value, effort: null }; - const cut = value.lastIndexOf(":"); - const base = value.slice(0, cut); - const effort = value.slice(cut + 1); - const suffixed = cut > 0 && EFFORTS.includes(effort); - if (!suffixed || !catalog.has(base)) fail(`--alias ${alias}=${value}: ${suffixed ? base : value} is not a selector in \`omp models --json\``); - const supported = catalog.get(base); - if (!supported.includes(effort)) fail(`--alias ${alias}=${value}: ${base} supports ${supported.join(", ") || "no effort suffix"}, not ${effort}`); - return { alias, base, effort }; -} - -function plan(current, request, catalog) { - const roles = { ...current.roles }; - for (const [alias, value] of request.aliases) { - const { base, effort } = parseSelection(alias, value, catalog); - roles[alias] = effort ? `${base}:${effort}` : base; - } - const overrides = { ...current.overrides }; - for (const [agent, value] of request.agents) { - const held = overrides[agent]; - if (held !== undefined && !OWNED_VALUES.includes(held)) fail(`${agent} maps to ${JSON.stringify(held)}, which pstack does not own; change it with \`omp config\` yourself`); - if (value === null) { - delete overrides[agent]; - continue; - } - if (agent.startsWith("pstack_")) fail(`--agent ${agent}: a pstack alias is not an agent name`); - if (!OWNED_VALUES.includes(value)) fail(`--agent ${agent}=${value}: use one of ${OWNED_VALUES.join(", ")}`); - const alias = value.slice(1); - if (!Object.hasOwn(roles, alias)) fail(`--agent ${agent}=${value}: modelRoles has no ${alias}, add --alias ${alias}=`); - overrides[agent] = value; - } - return { roles, overrides }; -} - -function describe({ roles, overrides }) { - const lines = OWNED_ALIASES.map((alias) => `${alias} = ${roles[alias] ?? "(unset)"}`); - for (const [agent, value] of Object.entries(overrides)) { - if (OWNED_VALUES.includes(value)) lines.push(`${agent} -> ${value} -> ${roles[value.slice(1)] ?? "(unset)"}`); - } - const families = new Set(OWNED_ALIASES.filter((alias) => roles[alias]).map((alias) => String(roles[alias]).split("/")[0])); - lines.push(`model families: ${families.size}${families.size === 1 ? ", weaker diversity: one family" : ""}`); - return lines.join("\n"); -} - -function changes(before, after, record) { - return [...new Set([...Object.keys(before), ...Object.keys(after)])] - .filter((key) => before[key] !== after[key]) - .map((key) => ` ${record}.${key}: ${before[key] ?? "(unset)"} -> ${after[key] ?? "(removed)"}`); -} - -function main(argv) { - const request = parseArgs(argv); - const current = {}; - for (const [field, record] of Object.entries(RECORDS)) current[field] = JSON.parse(omp("config", "get", record, "--json")).value; - if (request.show) return console.log(describe(current)); - const next = plan(current, request, request.aliases.size ? readCatalog() : new Map()); - const stale = Object.keys(RECORDS).filter((field) => JSON.stringify(current[field]) !== JSON.stringify(next[field])); - if (!stale.length) return console.log(`no change: config already matches\n${describe(next)}`); - const diff = stale.flatMap((field) => changes(current[field], next[field], RECORDS[field])); - console.log([request.dryRun ? "planned changes:" : "changes:", ...diff, describe(next)].join("\n")); - if (request.dryRun) return console.log("dry run: nothing written"); - // modelRoles goes first, so an override never lands pointing at an alias that is not written yet. - for (const [i, field] of stale.entries()) { - try { - omp("config", "set", RECORDS[field], JSON.stringify(next[field])); - } catch (error) { - const landed = stale.slice(0, i).map((done) => RECORDS[done]); - const recovery = landed.length - ? `${landed.join(", ")} was written and ${RECORDS[field]} was not. Rerun the same command to finish; it writes only what still differs.` - : "nothing was written"; - console.error(`apply-models: ${error.message}\n${recovery}`); - process.exit(1); - } - } - console.log(`written: ${stale.map((field) => RECORDS[field]).join(", ")}`); -} - -try { - main(process.argv.slice(2)); -} catch (error) { - console.error(`apply-models: refused: ${error.message}\nnothing was written`); - process.exit(1); -} diff --git a/plugins/pstack/skills/setup-pstack/scripts/apply-models.test.mjs b/plugins/pstack/skills/setup-pstack/scripts/apply-models.test.mjs deleted file mode 100644 index ae5f7cd..0000000 --- a/plugins/pstack/skills/setup-pstack/scripts/apply-models.test.mjs +++ /dev/null @@ -1,189 +0,0 @@ -import { afterAll, expect, test } from "bun:test"; -import { spawnSync } from "node:child_process"; -import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; - -const SCRIPT = join(import.meta.dir, "apply-models.mjs"); -const BIN = mkdtempSync(join(tmpdir(), "apply-models-")); -const STATE = join(BIN, "state.json"); -const ROLES = "modelRoles"; -const OVERRIDES = "task.agentModelOverrides"; -const MODELS = [ - { selector: "acme/model-a", thinking: ["low", "medium", "high", "xhigh", "max"] }, - { selector: "acme/model-b", thinking: ["low", "medium", "high"] }, - { selector: "acme/model-plain", thinking: null }, - { selector: "relay/acme/model-a:batch", thinking: ["minimal", "low", "high"] }, - { selector: "other/model-c", thinking: ["high"] }, - { selector: "acme/model-odd", thinking: ["high", "turbo"] }, -]; - -// A stand-in for omp's CLI surface the script uses, backed by one JSON file the tests read back. -writeFileSync( - join(BIN, "omp-stub.mjs"), - `import { readFileSync, writeFileSync } from "node:fs"; -const state = JSON.parse(readFileSync(process.env.OMP_STUB_STATE, "utf8")); -const [command, action, key, value] = process.argv.slice(2); -if (command === "models") process.stdout.write(JSON.stringify({ models: state.models })); -else if (command === "config" && action === "get") process.stdout.write(JSON.stringify({ key, value: state.config[key], type: "record" })); -else if (command === "config" && action === "set" && state.failSet !== key) { - state.config[key] = JSON.parse(value); - state.sets += 1; - writeFileSync(process.env.OMP_STUB_STATE, JSON.stringify(state)); -} else { - process.stderr.write("Error: simulated failure"); - process.exit(1); -} -`, -); -writeFileSync(join(BIN, "omp"), `#!/bin/sh\nexec "${process.execPath}" "${join(BIN, "omp-stub.mjs")}" "$@"\n`); -chmodSync(join(BIN, "omp"), 0o755); -afterAll(() => rmSync(BIN, { recursive: true, force: true })); - -function setup(config = {}, extra = {}) { - writeFileSync(STATE, JSON.stringify({ models: MODELS, config: { [ROLES]: {}, [OVERRIDES]: {}, ...config }, sets: 0, ...extra })); -} -const state = () => JSON.parse(readFileSync(STATE, "utf8")); -const ordered = (record) => JSON.stringify(state().config[record]); -function run(...args) { - const env = { ...process.env, PATH: `${BIN}:${process.env.PATH}`, OMP_STUB_STATE: STATE }; - const result = spawnSync(process.execPath, [SCRIPT, ...args], { encoding: "utf8", env }); - return { code: result.status, out: result.stdout, err: result.stderr }; -} -const OPERATOR = { - [ROLES]: { default: "acme/model-b", pstack_judgment: "acme/model-b:low", smol: "@default" }, - [OVERRIDES]: { explore: "acme/model-b", reviewer: "@pstack_judgment", helper: "@smol" }, -}; - -test("writes owned entries and keeps every unrelated key, value, and order", () => { - setup(OPERATOR); - const result = run( - "--alias", "pstack_judgment=acme/model-a:max", - "--alias", "pstack_fast_code=relay/acme/model-a:batch:minimal", - "--alias", "pstack_instruction=relay/acme/model-a:batch", - "--agent", "task=@pstack_fast_code", - "--unset-agent", "reviewer", - ); - expect(result.code).toBe(0); - expect(ordered(ROLES)).toBe( - JSON.stringify({ - default: "acme/model-b", - pstack_judgment: "acme/model-a:max", - smol: "@default", - pstack_fast_code: "relay/acme/model-a:batch:minimal", - pstack_instruction: "relay/acme/model-a:batch", - }), - ); - expect(ordered(OVERRIDES)).toBe(JSON.stringify({ explore: "acme/model-b", helper: "@smol", task: "@pstack_fast_code" })); -}); - -test("refuses a base that is not a catalog selector and writes neither record", () => { - for (const bad of ["acme/model-z:high", "acme/model-z", "acme/model-a:batch", "acme/model-a:"]) { - setup(OPERATOR); - const result = run("--alias", "pstack_fast_code=acme/model-a:high", "--alias", `pstack_judgment=${bad}`, "--agent", "task=@pstack_fast_code"); - expect(result.code).not.toBe(0); - expect(state().config).toEqual(OPERATOR); - } -}); - -test("refuses an effort off the ladder or missing from the selected model's list", () => { - for (const bad of ["acme/model-b:max", "acme/model-plain:low", "other/model-c:minimal", "acme/model-odd:turbo"]) { - setup(OPERATOR); - expect(run("--alias", `pstack_judgment=${bad}`).code).not.toBe(0); - expect(state().config).toEqual(OPERATOR); - } - expect(run("--alias", "pstack_judgment=acme/model-b:high").code).toBe(0); - expect(state().config[ROLES].pstack_judgment).toBe("acme/model-b:high"); -}); - -test("refuses an agent value that is not a defined pstack alias, before any write", () => { - for (const bad of ["@pstack_bogus", "@default", "acme/model-a", "@pstack_instruction"]) { - setup(OPERATOR); - const result = run("--alias", "pstack_fast_code=acme/model-a", "--agent", `task=${bad}`); - expect(result.code).not.toBe(0); - expect(state().config).toEqual(OPERATOR); - } -}); - -test("refuses to change or remove an agent entry pstack does not own", () => { - for (const args of [["--unset-agent", "explore"], ["--agent", "explore=@pstack_judgment"], ["--agent", "helper=@pstack_judgment"]]) { - setup(OPERATOR); - expect(run(...args).code).not.toBe(0); - expect(state().config).toEqual(OPERATOR); - } -}); - -test("a second identical run writes nothing and says so", () => { - setup(OPERATOR); - const args = ["--alias", "pstack_judgment=acme/model-a:high", "--agent", "task=@pstack_judgment", "--unset-agent", "reviewer"]; - expect(run(...args).code).toBe(0); - const after = state(); - expect(after.sets).toBeGreaterThan(0); - const again = run(...args); - expect(again.code).toBe(0); - expect(again.out).toContain("no change"); - expect(state()).toEqual(after); -}); - -test("a failed second write is reported with the rerun that finishes it", () => { - setup(OPERATOR, { failSet: OVERRIDES }); - const args = ["--alias", "pstack_judgment=acme/model-a:high", "--agent", "task=@pstack_judgment"]; - const failed = run(...args); - expect(failed.code).not.toBe(0); - expect(state().config[ROLES].pstack_judgment).toBe("acme/model-a:high"); - expect(state().config[OVERRIDES]).toEqual(OPERATOR[OVERRIDES]); - expect(failed.err).toContain(`${ROLES} was written and ${OVERRIDES} was not`); - expect(failed.err).toContain("Rerun the same command"); - writeFileSync(STATE, JSON.stringify({ ...state(), failSet: null })); - expect(run(...args).code).toBe(0); - expect(state().config[OVERRIDES]).toEqual({ ...OPERATOR[OVERRIDES], task: "@pstack_judgment" }); -}); - -test("one model family is reported as information, never refused", () => { - setup(); - const one = run("--alias", "pstack_fast_code=acme/model-b:low", "--alias", "pstack_judgment=acme/model-a:max", "--alias", "pstack_instruction=acme/model-a"); - expect(one.code).toBe(0); - expect(state().config[ROLES]).toEqual({ pstack_fast_code: "acme/model-b:low", pstack_judgment: "acme/model-a:max", pstack_instruction: "acme/model-a" }); - expect(one.out).toContain("model families: 1, weaker diversity: one family"); - const two = run("--alias", "pstack_judgment=other/model-c:high"); - expect(two.code).toBe(0); - expect(two.out).toContain("model families: 2"); - expect(two.out).not.toContain("weaker diversity"); -}); - -test("--show lists each pstack-owned agent with the model it resolves to", () => { - setup(OPERATOR); - const shown = run("--show"); - expect(shown.code).toBe(0); - expect(shown.out).toMatch(/^reviewer -> @pstack_judgment -> acme\/model-b:low$/m); - expect(shown.out).not.toContain("explore"); - expect(shown.out).not.toContain("helper"); - expect(state().sets).toBe(0); -}); - -test("--dry-run prints the planned change and writes nothing", () => { - setup(OPERATOR); - const planned = run("--dry-run", "--alias", "pstack_judgment=acme/model-a:high", "--agent", "task=@pstack_judgment"); - expect(planned.code).toBe(0); - expect(planned.out).toContain(`${ROLES}.pstack_judgment: acme/model-b:low -> acme/model-a:high`); - expect(planned.out).toContain(`${OVERRIDES}.task: (unset) -> @pstack_judgment`); - expect(state().config).toEqual(OPERATOR); - expect(state().sets).toBe(0); -}); - -test("refuses unknown or malformed arguments", () => { - setup(OPERATOR); - const cases = [ - [], - ["--model", "acme/model-a", "--alias", "pstack_judgment=acme/model-a"], - ["--alias", "pstack_judgment"], - ["--alias", "pstack_other=acme/model-a"], - ["--alias", "pstack_judgment=acme/model-a", "--alias", "pstack_judgment=acme/model-b"], - ["--agent", "pstack_judgment=@pstack_judgment", "--alias", "pstack_judgment=acme/model-a"], - ["--unset-agent", "--dry-run"], - ["--show", "--alias", "pstack_judgment=acme/model-a"], - ]; - for (const args of cases) expect(run(...args).code).not.toBe(0); - expect(state().config).toEqual(OPERATOR); - expect(state().sets).toBe(0); -}); From cf075142285cfb3dce0f764db10bbde732988bb8 Mon Sep 17 00:00:00 2001 From: Negrescu Alexandru Date: Sat, 31 Oct 2026 21:04:10 +0200 Subject: [PATCH 3/3] docs: rewrite README for a zero-config install The README now says who the port is for, installs in two commands, and starts a task with `/poteto-mode` and no configuration. "Why this port" makes only claims a file in this repository backs, and the comparison with dsebban/skills keeps its credit. --- README.md | 69 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index ffd614e..78ce70f 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,67 @@ # pstack-omp -An omp marketplace carrying one plugin. +pstack-omp ports Lauren Tan's [pstack](https://github.com/cursor/plugins/tree/main/pstack) from +Cursor to [oh-my-pi](https://github.com/can1357/oh-my-pi) (`omp`). It is for omp users who want +pstack's `poteto-mode` router, its playbooks for bug fixes, features, reviews, and shipping, and its +review panels. The plugin carries 59 skills, with 23 playbooks and 24 principle leaves, and two +extensions. -`pstack` is Lauren Tan's engineering methodology for Cursor, ported to oh-my-pi and made -model and provider agnostic, 59 skills with 23 playbooks and 24 principle leaves. -Wide parallel runs are a `swarm` dispatch, not a separate plugin. +## Install ``` /marketplace add negoro26/pstack-omp /marketplace install pstack@pstack-omp ``` -The marketplace install loads the skills and both extensions, `potetomode` and `pstackpolicy`. It -does not load the two agents; see the agent step in `plugins/pstack/README.md`. +Then start a task: -`PORTING.md` is the full porting record. `omp-port/check-port.sh` is the gate the port -passes before any release. Upstream is `cursor/plugins` at the commit named in the catalog. -Licensed MIT, same as upstream, with Lauren Tan's copyright retained. +``` +/poteto-mode fix the flaky login test and prove it with a passing run +``` + +`/poteto-mode` pins the router for the session and sends the rest of the line as your first message. +It needs no configuration. Each agent runs on its frontmatter model, then on your chat model. +`/poteto-mode off` unpins the router. + +The install loads the skills and both extensions, `potetomode` and `pstackpolicy`. It does not load +the two optional agents. The agent step is in [`plugins/pstack/README.md`](plugins/pstack/README.md). + +## Choose models + +To give an agent its own model, add an entry under `task.agentModelOverrides` in +`~/.omp/agent/config.yml`. [Choose models](plugins/pstack/skills/omp-mechanics/SKILL.md#choose-models) +in `omp-mechanics` gives the key, the value, and the optional `modelRoles` aliases. A review panel in +`arena` or `interrogate` gives a second opinion only when its agents resolve to different model +families. With no overrides, a panel agent without a frontmatter model runs on your chat model, so +check the model each panel agent resolves to before you trust a panel verdict. + +## Why this port + +- The plugin tree is a build, not a hand-kept copy. `omp-port/sync-upstream.sh` exports upstream + `cursor/plugins` at the commit in `omp-port/UPSTREAM`, then applies `omp-port/rules.sed` and + `omp-port/patches/`. `omp-port/check-port.sh` rebuilds that commit and fails when the tree differs. + It also fails on a rule that drops upstream text nobody reviewed and on a rule that splices its + match back into the line. +- `.github/workflows/upstream-sync.yml` syncs once a day. It runs `check-port.sh` on `main` and + against the new upstream commit, builds the sync, runs the gate again on the result, and merges the + sync pull request only when the build and the gate pass. +- The `pstackpolicy` extension refuses the forge mutations the playbooks otherwise only ask for in + prose, such as `gh pr merge`, closing or retargeting a pull request, posting a review, and a push to + `main`. It allows them only when omp starts with `PSTACK_LANDING_GRANT=1`. The full list is in + [`plugins/pstack/README.md`](plugins/pstack/README.md). +- The `potetomode` extension pins `poteto-mode` per session. `/poteto-mode on|off|status`, + `alt+shift+t`, and `omp --poteto` set the pin, and a resumed session keeps its saved state. + +## Other OMP ports + +[dsebban/skills](https://github.com/dsebban/skills) is Daniel Sebban's OMP port of pstack. You clone +it and list its `skills/` directory under `skills.customDirectories`. It ships skills this port does +not, such as `setup-pstack` for guided model configuration and `orchestrate-omp`. The `pstack-omp` +runtime adapter here incorporates his OMP routing work, which resolves the live task or vibe surface +without a fixed agent roster. [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md) carries his license +notice. + +## More -The `pstack-omp` runtime adapter incorporates Daniel Sebban's OMP routing work from -[dsebban/skills](https://github.com/dsebban/skills). It resolves the live task or vibe -surface without requiring a fixed agent roster. The original scripts remain in place. -The pin respects saved on/off state before the `--poteto` default on session resume. +`PORTING.md` records every substitution and the re-sync procedure. Licensed MIT, same as upstream, +with Lauren Tan's copyright retained.