Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ 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
Expand Down
6 changes: 3 additions & 3 deletions .omp-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
},
"metadata": {
"description": "pstack, poteto's engineering methodology for Cursor, ported to oh-my-pi. Model and provider agnostic.",
"version": "0.15.30"
"version": "0.15.32"
},
"plugins": [
{
"name": "pstack",
"description": "59 skills, 23 playbooks, 24 principles, the poteto-mode pin, and two agents. Upstream cursor/plugins pstack at 4ec7ffa.",
"version": "0.15.30",
"description": "60 skills, 23 playbooks, 24 principles, the poteto-mode pin, and two agents. Upstream cursor/plugins pstack at e73df47.",
"version": "0.15.32",
"category": "development",
"license": "MIT",
"source": "./plugins/pstack"
Expand Down
21 changes: 16 additions & 5 deletions PORTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,11 +216,15 @@ The split is by kind, not by size.
line, a patch that stops matching costs a merge.
- Port prose goes in the owned skills, which `install_tree` restores from `HEAD` and which
therefore cannot conflict with upstream at all.
- A patch is for code: control flow, a data shape, a file upstream does not have. The patches
touch scripts only. `worktree-audit-omp.patch` is the port's own change. The three `zzz*`
patches carry open upstream PRs (#746 and #769, the check-plan half of #745, and #733 with an
age-limited marker). When upstream merges one, its patch stops applying; delete the patch rather
than resolving it.
- A patch is for code: control flow, a data shape, a file upstream does not have.
`worktree-audit-omp.patch` and `zzz-script-typecheck.patch` are the port's own changes; the
second typechecks `orch/` and `bootstrap.ts` as well as `watch-pr/`. `zzz-make-bot-ui-omp.patch`
is the one markdown patch, a notice that the skill's webhook mechanic does not exist on omp. Two
`zzz*` patches carry open upstream PRs. `zzz-watch-pr-fixes.patch` carries #769, and
`zzzz-orch-lock-takeover.patch` carries #733 with an age-limited marker. When upstream merges an
equivalent, delete the hunks it covers rather than resolving them, and do not wait for the patch
to fail first. A hunk that only adds lines can still find its context at an offset, which at
`e73df47` would have added #804's tests to `orch.test.ts` a second time.

Author a patch against **post-rules** text. `rules.sed` runs first, so a hunk quoting the raw
upstream line fails whenever an earlier rule rewrote it. `worktree-audit-omp.patch` deletes a
Expand Down Expand Up @@ -251,6 +255,13 @@ you how much upstream moved underneath you. A non-zero count on `skills/setup-ps
the selection procedure by hand before trusting the skill; the two other owned paths have no
upstream counterpart and always report as port-only.

`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.

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.

## The one thing that needed new code
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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, 59 skills with 23 playbooks and 24 principle leaves.
model and provider agnostic, 60 skills with 23 playbooks and 24 principle leaves.
Wide parallel runs are a `swarm` dispatch, not a separate plugin.

```
Expand Down
2 changes: 1 addition & 1 deletion omp-port/UPSTREAM
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4ec7ffa173eaa70d3d9467a627baf0954645a353
e73df471cd4bd918367e99f3dd9c51bd53ddf5f0
6 changes: 2 additions & 4 deletions omp-port/check-port.sh
Original file line number Diff line number Diff line change
Expand Up @@ -316,9 +316,7 @@ runtime_contract() {
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 'Strip only that supported suffix and require the remaining base to equal a selector from `omp models --json`.' "$setup_skill" || bad="$bad"'setup suffix or base-selector validation is missing'$'\n'
grep -Fq 'Never strip an arbitrary colon fragment.' "$setup_skill" || bad="$bad"'setup allows arbitrary suffix stripping'$'\n'
grep -Fq '`:low`, or `:minimal` suffix' "$setup_skill" || bad="$bad"'setup supported suffix list is missing minimal'$'\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'
Expand All @@ -331,7 +329,7 @@ runtime_contract() {
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, and base selector"
report "setup config contract" "PASS ownership, aliases, model efforts, suffix, base selector, and the apply-models lever"
fi
}
runtime_contract
Expand Down
48 changes: 32 additions & 16 deletions omp-port/patches/worktree-audit-omp.patch
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
# ripgrep is not required and a path with [ or spaces matches literally
# - fail closed: a store that cannot be found or read makes LAST_CHAT "?" and holds the row at
# verify-recent-chat, the bucket playbooks/worktree-cleanup.md already names, never `safe`
# - probe GNU or BSD stat and date once, so the audit runs on Linux and macOS alike
# - fail closed the same way when a .env file omp reads (each worktree's, the agent dir's, the
# config root's, home's) sets a key that moves the store, OMP_ spellings included, rather
# than replaying omp's .env loader
# - stat each matched transcript on its own through upstream's GNU/BSD probe, so a path with
# spaces survives and a stat failure fails closed instead of dropping out of an xargs pipe
# - keep the PR list in memory, so the audit writes no scratch file at all, inside the repo or
# out of it
# - parse `git worktree list` records by stripping the record prefix, so a path containing
Expand All @@ -28,8 +32,8 @@
# nowhere else bucket as hold-unpushed
#
# Upstream's -b/--base flag, its origin/HEAD default, its Origin-first PR fetch with the gh
# fallback, its LAST_CHAT column, and its verify-recent-chat bucket are all kept exactly as
# written.
# fallback, its GNU/BSD stat and date probe, its LAST_CHAT column, and its verify-recent-chat
# bucket are all kept exactly as written.
#
# Authored against post-rules text. rules.sed runs first and rewrites the Cursor path in the
# comment this patch deletes, so a hunk quoting the raw upstream comment would not apply.
Expand All @@ -50,9 +54,9 @@

if [ -z "$base" ]; then
for cand in "$(git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null)" origin/main origin/master; do
@@ -47,33 +52,94 @@
esac
git rev-parse --verify --quiet "$base^{commit}" >/dev/null 2>&1 || { echo "error: base ref not found: $base" >&2; exit 1; }
@@ -55,33 +60,105 @@
stat_fmt=(-f '%m %N'); fmt_day() { date -r "$1" '+%Y-%m-%d'; }
fi

-# PR state by branch, fetched once as [{number, state, head}] with a lowercase
-# state. Origin when its CLI can read this repo's PRs, otherwise gh. Empty when
Expand Down Expand Up @@ -88,6 +92,12 @@
+# repo's worktrees are scanned; walking every bucket would scale with total
+# session history. A store that cannot be read makes LAST_CHAT "?" and holds
+# the row at verify-recent-chat instead of reading as no chat at all.
+# omp also fills each unset variable from the .env in the cwd it launched in,
+# then the agent dir's, the config root's and home's, reading an OMP_ key there
+# as its PI_ alias, while the profile comes from the environment alone. The
+# audit does not replay that loader. A store key in any of those files counts
+# as a store it cannot read. An explicit session directory is exempt, since the
+# environment outranks every .env.
+canon() { (CDPATH='' cd -- "$1" 2>/dev/null && pwd -P) || printf '%s\n' "$1"; }
+dash() { printf '%s' "$1" | tr '/\\:' '---'; }
+# Path of $2 relative to $1, failing when $2 is outside it. Like omp, a
Expand All @@ -108,6 +118,7 @@
+ else printf '%s' "--$(dash "${1#/}")--"; fi
+}
+session_dirs=()
+moved=""
+if [ -n "${PI_CODING_AGENT_SESSION_DIR:-}" ]; then
+ sessions=$PI_CODING_AGENT_SESSION_DIR
+ session_dirs=("$sessions/")
Expand All @@ -123,19 +134,23 @@
+ sessions="$data/sessions"
+ home_root=$(canon "$HOME")
+ temp_root=$(canon "${TMPDIR:-${TMP:-${TEMP:-/tmp}}}")
+ dotenvs=("$agent/.env" "$config/.env" "$HOME/.env")
+ while IFS= read -r path; do
+ dir="$sessions/$(bucket_name "$(canon "$path")")"
+ [ -d "$dir" ] && session_dirs+=("$dir/")
+ dotenvs+=("$path/.env")
+ done < <(printf '%s\n' "$worktree_records" | awk '/^worktree /{sub(/^worktree /, ""); print}')
+ moved=$(grep -lsE '^[[:space:]]*(export[[:space:]]+)?((PI|OMP)_(CODING_AGENT_SESSION_DIR|CODING_AGENT_DIR|CONFIG_DIR)|XDG_DATA_HOME)[[:space:]]*[=:]' \
+ -- "${dotenvs[@]}")
+fi
+chat_lookup=ok
+if [ ! -d "$sessions" ]; then
+if [ -n "$moved" ]; then
+ chat_lookup=failed
+ sed 's/^/warn: /; s/$/ can move the omp session store; LAST_CHAT is ? and rows hold at verify-recent-chat/' <<<"$moved" >&2
+elif [ ! -d "$sessions" ]; then
+ chat_lookup=failed
+ echo "warn: no omp session store at $sessions; LAST_CHAT is ? and rows hold at verify-recent-chat" >&2
+fi
+# GNU and BSD spell file mtime and epoch formatting differently; probe once.
+if stat -c %Y / >/dev/null 2>&1; then mtime() { stat -c %Y "$1"; }; else mtime() { stat -f %m "$1"; }; fi
+if date -d @0 +%s >/dev/null 2>&1; then day() { date -d "@$1" '+%Y-%m-%d'; }; else day() { date -r "$1" '+%Y-%m-%d'; }; fi
now=$(date +%s)

echo "# base: $base"
Expand All @@ -159,7 +174,7 @@
head_ts=$(git -C "$wt" log -1 --format='%ct' HEAD 2>/dev/null || echo 0)
age=$([ "$head_ts" -gt 0 ] 2>/dev/null && echo "$(( (now - head_ts) / 86400 ))d" || echo "?")

@@ -81,12 +147,12 @@
@@ -89,12 +166,12 @@
# real signal; merge-base only catches fast-forward/rebase merges.
git merge-base --is-ancestor "$head" "$base" 2>/dev/null && merged=YES || merged=no

Expand All @@ -178,7 +193,7 @@

branch=$(git -C "$wt" symbolic-ref --quiet --short HEAD 2>/dev/null || echo "")
if [ -z "$branch" ]; then remote=detached
@@ -94,33 +160,56 @@
@@ -102,33 +179,57 @@
[ "$(git -C "$wt" rev-parse "origin/$branch" 2>/dev/null)" = "$head" ] \
&& remote=pushed \
|| remote="ahead$(git -C "$wt" rev-list --count "origin/$branch..HEAD" 2>/dev/null)"
Expand All @@ -200,9 +215,9 @@
- last="-"; last_ts=0
- if [ -d "$transcripts" ]; then
- f=$(rg -l -e "${wt}/" -e "${wt}\"" "$transcripts" 2>/dev/null \
- | xargs stat -f '%m %N' 2>/dev/null | sort -rn | head -1)
- | xargs stat "${stat_fmt[@]}" 2>/dev/null | sort -rn | head -1)
- if [ -n "$f" ]; then last_ts=$(echo "$f" | awk '{print $1}')
- last=$(date -r "$last_ts" '+%Y-%m-%d' 2>/dev/null); fi
- last=$(fmt_day "$last_ts" 2>/dev/null); fi
+ # Most recent chat whose transcript operated in this worktree, under the
+ # path git lists or its canonical spelling. Match path followed by "/" or a
+ # quote so glint-482 does not match glint-482-r37.
Expand All @@ -214,12 +229,13 @@
+ [ $? -le 1 ] || chat=failed
+ while IFS= read -r f; do
+ [ -n "$f" ] || continue
+ ts=$(mtime "$f") || { chat=failed; break; }
+ ts=$(stat "${stat_fmt[@]}" "$f") || { chat=failed; break; }
+ ts=${ts%% *}
+ [ "$ts" -gt "$last_ts" ] && last_ts=$ts
+ done <<<"$matched"
fi
+ if [ "$chat" != ok ]; then last="?"
+ elif [ "$last_ts" -gt 0 ]; then last=$(day "$last_ts" 2>/dev/null) || last="?"; fi
+ elif [ "$last_ts" -gt 0 ]; then last=$(fmt_day "$last_ts" 2>/dev/null) || last="?"; fi
recent=$([ "$last_ts" -gt 0 ] 2>/dev/null && [ $(( (now - last_ts) / 86400 )) -le 4 ] && echo yes || echo no)

+ # An exact PR head proves the commits were pushed even if origin has no
Expand Down
Loading
Loading