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
18 changes: 18 additions & 0 deletions WORKLOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# WORKLOG

## 2026-06-02 — qctl implementer-backend seam (Part A, contract-first) — branch `qctl-implementer-backend`

Wire petabit-sysadmin's Qwen Work Fabric (`pi`/`qctl` worker) as a **pluggable implementer backend** so masterplan execute waves + github-coordination followers can offload task implementation to the GPU fleet instead of always dispatching `mp-implementer`. **Contract-first**: the fabric's `qctl` (its task 11) + `gate.py` (its task 12) don't exist yet, so the *live binding* is feature-flagged **off** and a qctl-kind task records a **NotYetBound blocked** digest — the seam, the descriptor union, and the §4 qctl results-contract are built now so binding is later a small swap, not a rewrite. Design: `docs/superpowers/specs/2026-06-01-qctl-implementer-backend-design.md`; plan: `docs/superpowers/plans/2026-06-01-qctl-implementer-backend.md`.

**Hard invariant — flag off ⇒ byte-for-byte identical to today.** Held by: `buildSeedState` never emits `implementer` (audited — 0 matches in `lib/bundle.mjs`); `resolveImplementerBackend` defaults to bare `{kind:'agent'}` (carries no agentType/model — the existing `execute.workflow.js` `implAgentType`/`implModel` dogfood seam stays sole owner); the `implement(t)` guard is `t.backend?.kind === 'qctl'` only, so the absent-backend legacy path and `{kind:'agent'}` are untouched.

Code (4 files, additions-only): `lib/routing.mjs` (`resolveImplementerBackend` beside `routeTask`); `lib/wave.mjs` (`prepareWave` stamps per-task `backend`); `bin/masterplan.mjs` (`prepare-wave` threads `state.implementer ?? {}`); `workflows/execute.workflow.js` (NotYetBound guard — *blocked* digest, never a throw: a throw nulls the pipeline item → silent vanish → re-dispatch loop). Docs: `commands/masterplan.md` follow-row + `docs/config-schema.md` flag block.

Verified: suite **510/0** (scoped `node --test test/` — bare `node --test` ELOOPs on the pre-existing recursive `.worktrees/…/superpowers-masterplan` symlink). Codex defect pass (`review --scope branch --base main`): default agent path confirmed intact; **2 P2s, 0 P0/P1**, both follower-path docs-only, both fixed in `6caaa16` — (1) a github-coordination follower carries no lead `state.yml`, so backend must resolve **lead-side at publish time** and ride the issue/contract, not follower-local state (threading deferred to binding time); (2) NotYetBound now → `mp:blocked` not `mp:open` (re-adding open re-queued the unserviceable task into a claim/comment/release loop). **Nothing pushed** — branch awaits disposition.

## 2026-06-01 — Codex review de-bloat: disable gstack `review` skill globally + harden mp-codex-reviewer

Symptom: every Codex defect/diff review (ad-hoc `codex exec` / `/codex:review` on any repo) ballooned to ~3240 lines because the model **auto-elected the third-party gstack `review` skill** and `cat`'d its **1628-line `SKILL.md`** into the trace, burying the verdict. **Root cause is NOT masterplan:** `mp-codex-reviewer` already returns a digest (the dump never reached the orchestrator) — the visible bloat came from **direct cross-repo reviews** (openxcvr-kmod / xcvr-tools / /tmp). gstack/review is **model-elected** (trace: "Using the `review` skill…"), not hook-injected, and Codex has **no skills-off flag** (`codex features list` confirms — only `skill_mcp_dependency_install`).

**Fix, two surfaces.** (1) **Global, reversible (NOT in repo, edited in `~/.codex/skills/`):** renamed `SKILL.md`→`SKILL.md.mp-disabled` in BOTH gstack review copies (`gstack/review/` + `gstack/.agents/skills/gstack-review/`) so codex can't enumerate the skill — a *dir* rename would NOT work (codex keys identity off SKILL.md presence + frontmatter `name:`). Restore script: `~/.codex/skills/.mp-review-disabled-RESTORE.sh`. `gstack/review/SKILL.md` had uncommitted user edits — **preserved** in the `.mp-disabled` copy. A `gstack upgrade` may restore it → re-run the disable. (2) **In-repo:** `agents/mp-codex-reviewer.md` invocation now tells Codex "do NOT activate/read/echo any skill" (belt-and-suspenders + saves Codex wall-time even though this agent digests).

Verified: a fresh codex rollout's injected catalog has **0** review-skill entries (**71 other gstack skills intact** — surgical); suite **503/0**; doctor exit 0.

## 2026-05-31 — PR-awareness in report verbs + finish gate; autonomy auto-progress contract (`db1b84d`)

Two asks: (1) surface an open GitHub PR for the run's branch in the "what do I do next" routines; (2) **stop the over-asking under `--autonomy=loose`/`full`**. The load-bearing finding for (2): the asking was **not** masterplan's own gates — `decideNextAction` (`lib/resume.mjs`) already stops only at real gates, `routeTask`'s `target:'ask'` is dead code, and `autonomy` is stored-but-never-read by `decide`. The real driver was the **global `~/.claude/hooks/auq-guard.sh` Stop hook** blanket-blocking every substantive turn lacking an AUQ, plus the global `~/.claude/CLAUDE.md` "AUQ mandatory" prose that *contradicted* the loose-autonomy contract in the same file. **Decision: fix the prompt contract + the hook, NOT `mp decide`** — autonomy stays out of `decide` (it isn't the thing asking; advisor-steered, accepted). PR-awareness is **report-only** (never auto-merge) and kept **out of the per-turn `decideNextAction` loop** (a per-resume "merge your PR" would be a *new* nag) — it lives only in human-invoked `status`/`next`/`clean` + the `branch_finish` gate.
Expand Down
11 changes: 10 additions & 1 deletion agents/mp-codex-reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,23 @@ Codex to confine its findings to it:
timeout -k 10 540 codex exec -s read-only \
--dangerously-bypass-approvals-and-sandbox \
-C "<repo-root>" "Review ONLY the scoped diff below for masterplan task <id>; do
not diff or scan the rest of the tree (it holds unrelated work). Diff follows:
not diff or scan the rest of the tree (it holds unrelated work). Do NOT activate,
read, or echo any skill (e.g. a gstack 'review' skill) — this is a self-contained
diff review; produce findings directly. Diff follows:
$SCOPED_DIFF"

- `-s read-only` — Codex may read the tree for context but not mutate it (the real
guardrail; the bypass flag only suppresses the interactive approval prompt so it runs
headless). Read-only context is fine — the scoping is enforced by the prompt: review the
pre-built diff, not a fresh whole-tree `git diff`.
- `-C "<repo-root>"` — run in the repo you're reviewing (your launch cwd).
- **No-skill instruction** — Codex's host may carry a third-party skill catalog (e.g.
gstack) whose `review` skill the model will auto-elect on any "diff review" prompt and
`cat` its multi-hundred-line `SKILL.md` into the trace, burning wall-time and budget for
zero added value here (the review brief is already complete). The explicit "do NOT
activate/read/echo any skill" clause suppresses that election. This agent already returns
a digest (so the dump never reached the orchestrator), but the clause stops Codex wasting
the read in the first place.
- `timeout -k 10 540` — hard 9-minute cap; `-k 10` sends SIGKILL 10s after SIGTERM if
Codex ignores the term (covers the observed MCP-call wedge). A **blocking** exec
cannot orphan the way a *detached* launch did — it returns stdout or `timeout` kills
Expand Down
9 changes: 8 additions & 1 deletion bin/masterplan.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,14 @@ function main() {
const wave = coerceId(need(flags, 'wave'));
// routing config: persisted `codex.routing` wins; --routing overrides; default 'auto'. env facts
// (host-suppression, linked-worktree) are git/host-probed by the shell and passed as flags.
const config = { routing: state.codex?.routing ?? flags.routing ?? 'auto' };
const config = {
routing: state.codex?.routing ?? flags.routing ?? 'auto',
// Pluggable implementer backend (contract-first; default OFF). Always {} today since
// buildSeedState never emits `implementer` -> resolveImplementerBackend returns
// {kind:'agent'} -> byte-identical to shipping. Flipping the live qctl binding is a
// binding-time concern (design spec §5).
implementer: state.implementer ?? {},
};
const env = {
codexHostSuppressed: !!flags['codex-suppressed'],
linkedWorktree: !!flags['linked-worktree'],
Expand Down
2 changes: 1 addition & 1 deletion commands/masterplan.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ the orchestrator root rather than duplicated here.
| `next` | `mp decide` → describe the next action without executing it. **PR-aware:** if the branch has an open PR, append the **advisory** `↪ Open PR #<n> ready — merge on GitHub or via /masterplan finish` (advisory only — never a `decide` action, never a blocking AUQ; this is how "a PR to merge" enters the what-do-I-do-next routine without becoming a per-resume nag). |
| `verbs` | Print the reserved-verb list above. |
| `publish` | **Lead → GitHub coordination** (§7.1 — spec §7). Preflight (`mp coord-status --fail-if-unpublishable`), then provision refs on first use (idempotent): synthesize/push the immutable contract ref `mp-coord/<slug>/<plan_hash>` (tier-1: `spec.md`/`plan.md`/`plan.index.json` only) via `git commit-tree` + `git update-ref` + `git push`; create the integration branch `mp-int/<slug>` from `base_sha` with the bundle dir excluded (`git commit-tree` on a tree that drops `docs/masterplan/<slug>/`) + `git push --no-verify`. Then compute unpublished tasks: read `coordination.issue_map` via `mp coord-status` (empty on first publish → all wave tasks are unpublished); for each unpublished task in the current wave: `gh issue create --title "T<id>: <title>" --body "$(mp gh-issue-body --task="$(jq -c '.tasks[]|select(.id==<id>)' plan.index.json)" --contract-ref=<ref> --integration-branch=<int-branch> --base-sha=<sha> --plan-hash=<hash> --wave=<N> --run-slug=<slug>)" --label "mp:run-<slug>,mp:wave-<N>,mp:open"` + `mp update-issue-map --task-id=<id> --issue=<n> --status=open --wave=<N> --state=<path>`. On unexpected duplicate (two issues, same key — `mp validate-claim`/gh-side `findDuplicates` backstop): fail loud, do NOT silently update. Pin `contract_ref` + `base_sha` into `state.coordination` **and record wave N as published** — `--mark-published` is what populates `published_waves`, which the next wave's `--fail-if-unpublishable` preflight gates on; omit it and the publish-advance guard silently no-ops — via `mp set-coord --state=<path> --wave=N --base-sha=<sha> --contract-ref=<ref> --integration-branch=<int-branch> --mark-published` + `mp event --state=<path> --type=wave_published --wave=N`. Commit the bundle. After follower PRs land, run `mp reconcile-integration --state=<path>`; for each `mark_done` action in the reconcile output, also call `mp update-issue-map --task-id=<id> --merge-sha=<sha> --status=merged --state=<path>`; surface reconcile actions via `AskUserQuestion` before applying. Publish wave N+1 only after wave N is fully merged (guard via `mp coord-status`). |
| `follow` | **Follower session → claim + deliver one task** (§7.1 — spec §7). 1. Preflight (`mp coord-status --fail-if-unconfigured`). 2. **Claim**: `issues="$(gh issue list --label "mp:open,mp:run-<slug>" --json number,title,body,labels,assignees --limit 200)"` → `mp select-claimable --plan-deps="$(jq -c '[.tasks[]|{key:(.id|tostring),value:.deps}]|from_entries' plan.index.json)" --issues="$issues"` to pick one; `gh issue edit <n> --add-assignee @me`; `gh label add mp:claimed`; re-read (`gh issue view <n> --json assignees,labels`) + `actor="$(gh api user --jq .login)"` + `mp validate-claim --actor="$actor"` → won/lost. On lost settle: release (`gh issue edit <n> --remove-assignee @me`; `gh label remove mp:claimed`; `gh label add mp:open`) and retry. 3. **Build**: fetch contract (`git fetch origin refs/mp-coord/<slug>/<plan_hash>:refs/mp-coord/<slug>/<plan_hash>`); create ephemeral bundle outside tracked `docs/masterplan/` (e.g. `.git/mp-coord/<slug>/t<id>/state.yml`) scoped to the single claimed task; cut branch `mp/<slug>/t<id>` from `mp-int/<slug>` at `base_sha`; dispatch the existing `mp-implementer` agent + D6 `verify-scope` + `verify_commands`. 4. **Deliver**: on verify pass — `gh pr create --base mp-int/<slug> --head mp/<slug>/t<id> --title "T<id>: <title>" --body "Closes #<n>"` + `gh label remove mp:claimed` + `gh label add mp:pr-open`. On verify failure — comment on the issue (`gh issue comment <n> --body "Verify failed: <summary>"`); release the claim (`gh label remove mp:claimed`; `gh label add mp:open`). Discard the ephemeral bundle — the lead's canonical state.yml is the source of truth. |
| `follow` | **Follower session → claim + deliver one task** (§7.1 — spec §7). 1. Preflight (`mp coord-status --fail-if-unconfigured`). 2. **Claim**: `issues="$(gh issue list --label "mp:open,mp:run-<slug>" --json number,title,body,labels,assignees --limit 200)"` → `mp select-claimable --plan-deps="$(jq -c '[.tasks[]|{key:(.id|tostring),value:.deps}]|from_entries' plan.index.json)" --issues="$issues"` to pick one; `gh issue edit <n> --add-assignee @me`; `gh label add mp:claimed`; re-read (`gh issue view <n> --json assignees,labels`) + `actor="$(gh api user --jq .login)"` + `mp validate-claim --actor="$actor"` → won/lost. On lost settle: release (`gh issue edit <n> --remove-assignee @me`; `gh label remove mp:claimed`; `gh label add mp:open`) and retry. 3. **Build**: fetch contract (`git fetch origin refs/mp-coord/<slug>/<plan_hash>:refs/mp-coord/<slug>/<plan_hash>`); create ephemeral bundle outside tracked `docs/masterplan/` (e.g. `.git/mp-coord/<slug>/t<id>/state.yml`) scoped to the single claimed task; cut branch `mp/<slug>/t<id>` from `mp-int/<slug>` at `base_sha`; honor the implementer backend **descriptor the lead stamped at publish time** (lead-side `resolveImplementerBackend(task, config={ implementer: state.implementer ?? {} }, env)` — the lead holds the run's `state.implementer`; the follower does **not** carry lead state, so it reads the carried descriptor and never re-resolves from follower-local state, which would always be `{}` → falsely `{kind:'agent'}`). Today no descriptor is carried — threading it onto the issue/contract is deferred to binding time (design spec §2/§5) — so the follower runs the default `{kind:'agent'}` path → dispatch the existing `mp-implementer` agent (**identical to today**). Once binding lands and the carried descriptor is `{kind:'qctl'}` → **NotYetBound** (design spec §A4/§5): comment the blocker on the issue and surface the task as **blocked** — `gh issue edit <n> --remove-assignee @me`; `gh label remove mp:claimed`; `gh label add mp:blocked`; do **not** re-add `mp:open` (a NotYetBound task must leave the claimable queue, else the next follower re-claims it in a comment/release loop) — and **never** silently fall back to `mp-implementer`. Then D6 `verify-scope` + `verify_commands`. 4. **Deliver**: on verify pass — `gh pr create --base mp-int/<slug> --head mp/<slug>/t<id> --title "T<id>: <title>" --body "Closes #<n>"` + `gh label remove mp:claimed` + `gh label add mp:pr-open`. On verify failure — comment on the issue (`gh issue comment <n> --body "Verify failed: <summary>"`); release the claim (`gh label remove mp:claimed`; `gh label add mp:open`). Discard the ephemeral bundle — the lead's canonical state.yml is the source of truth. |

**PR probe (`status` · `next` · `clean` — report-only, never auto-merge).** These three verbs check
for an open PR on the run's branch. Run **shell-side** (the established split — the shell owns git/`gh`,
Expand Down
11 changes: 11 additions & 0 deletions docs/config-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,17 @@ codex:
# Migration: users with explicit `detection_mode: ping` keep ping-only semantics.
# Only the unset default flips from `ping` → `scan-then-ping`.

# Pluggable implementer backend (contract-first; default OFF) — design spec:
# docs/superpowers/specs/2026-06-01-qctl-implementer-backend-design.md
# When qctl.enabled is true, `mp prepare-wave` stamps a {kind:'qctl'} backend descriptor on each
# wave task instead of the default {kind:'agent'}. The LIVE qctl binding is DEFERRED until the Qwen
# Work Fabric ships `qctl` + `gate.py`; until then a qctl-kind task is recorded blocked (NotYetBound).
# buildSeedState never emits this key, so an absent block == default-off == byte-identical to today.
implementer:
qctl:
enabled: false # off | true (strict === true) — true offloads task implementation to
# the Qwen pi/qctl worker. Any non-true value behaves as false.

# Intra-plan task parallelism (v2.0.0+) — Slice α (read-only parallel waves)
# When enabled, contiguous tasks sharing the same `**parallel-group:**` annotation
# in a plan dispatch as one parallel wave (verification, inference, lint,
Expand Down
Loading