Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
27c1c59
fix(evals): disable the honcho plugin in spawned agent sessions
cfdude Jul 29, 2026
0e8d19a
chore(pm): file the two P3 items from the rules-block design discussion
cfdude Jul 29, 2026
ad3c9f5
docs(pm): design doc for the platform-aware rules block
cfdude Jul 29, 2026
a7bea1b
docs(pm): resolve the foreign-file fork in the rules-block design
cfdude Jul 29, 2026
9a28607
docs(pm): resolve all three open questions in the rules-block design
cfdude Jul 29, 2026
9272960
docs(pm): implementation plan for the platform-aware rules block
cfdude Jul 30, 2026
e5425d9
chore(pm): sync + triage 5 inbound issues; consolidate the auto-detou…
cfdude Jul 30, 2026
0e90241
fix(engine): auto-detour must confirm a commit landed HERE (closes #6…
cfdude Jul 30, 2026
172c2c1
docs(pm): changelog the auto-detour fix; file the test-suite perf epic
cfdude Jul 30, 2026
effc410
chore(pm): correct the sync-phantom record; archived tombstones inste…
cfdude Jul 30, 2026
6281ce8
perf(test): split the suite across 11 files for parallelism (118s -> …
cfdude Jul 30, 2026
4c09983
test: close the vacuous-pass holes in the auto-detour guard tests
cfdude Jul 30, 2026
73b2a6c
test: harden the pre-existing absence assertions against vacuous passes
cfdude Jul 30, 2026
9bc389b
docs(pm): changelog the test-suite split; close the perf epic
cfdude Jul 30, 2026
c5e0a93
docs(pm): retarget the rules-block plan at the split test suite
cfdude Jul 30, 2026
d61b6e5
feat(engine): platform-aware rules block — constants, resolution, per…
cfdude Jul 30, 2026
3b5b99c
fix(engine): key rules-block detection on a stable prefix, not the fu…
cfdude Jul 30, 2026
1740f65
feat(engine): write the rules block where the platform will read it
cfdude Jul 30, 2026
199a9b9
feat(engine): record the active platform in state.json (0.24.0)
cfdude Jul 30, 2026
6d756ed
fix(engine): keep pm dormant until /pm:init when resolving the platform
cfdude Jul 30, 2026
350f829
feat(hooks): Claude Code hooks declare --platform claude-code
cfdude Jul 30, 2026
c3f4730
chore(pm): archive rules-block-hardcodes-claude-slash-commands epic
cfdude Jul 30, 2026
b3bca76
docs(readme): refresh the Real Numbers after the platform work
cfdude Jul 30, 2026
f8c6ba2
chore(pm): upgrade this repo to 0.24.0 (real-history dogfood of both …
cfdude Jul 30, 2026
81c1cf8
fix(engine): the HEAD guard was suppressing every commit with a messa…
cfdude Jul 30, 2026
3133f64
refactor(engine): drop the dead CLAUDECODE rung; validate the flag sy…
cfdude Jul 30, 2026
d0da92a
chore(pm): file the -am/-F parser gap from the whole-branch review
cfdude Jul 30, 2026
346d268
docs(pm): changelog the subject-comparison subtlety in the auto-detou…
cfdude Jul 30, 2026
6451954
fix(docs,test): correct a false README claim and a mis-named test
cfdude Jul 30, 2026
98805f4
docs(pm): fold the Unreleased entries into 0.24.0 and recompute Real …
cfdude Jul 30, 2026
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: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pm",
"version": "0.23.1",
"version": "0.24.0",
"description": "A lightweight project-management layer that sits above OpenSpec and Superpowers. Tracks active proposals as epics, maintains an explicit detour stack, and enforces a reconcile gate so nothing is lost when development pivots or context is compacted.",
"author": {
"name": "Rob Sherman",
Expand Down
4 changes: 2 additions & 2 deletions .claude/skills/pr-workflow/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ existed and had to be untangled after the fact.
If you're on `main` with uncommitted work, `git checkout dev` first — working-tree changes
carry over cleanly since `dev` and `main` share history between releases.

2. **Tests green before committing.** `node --test scripts/conductor.test.mjs` — the
2. **Tests green before committing.** `node --test scripts/test/*.test.mjs` — the
`.githooks/pre-commit` hook re-runs this on every commit and blocks on failure, but don't
rely on the hook alone catching a break you already know about.

Expand Down Expand Up @@ -53,7 +53,7 @@ existed and had to be untangled after the fact.
(confirms the squash commit itself is sound, not just the pre-merge state):
```bash
git checkout main && git fetch origin && git reset --hard origin/main
node --test scripts/conductor.test.mjs
node --test scripts/test/*.test.mjs
git checkout dev && git reset --hard main && git push origin dev --force-with-lease
```
`git reset --hard main` (not a plain `git merge`/`--ff-only`) is required here — after a
Expand Down
4 changes: 2 additions & 2 deletions .claude/skills/release-checklist/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ skip straight to the branch dance at the bottom.

## The checklist

1. **Engine + tests.** `node --test scripts/conductor.test.mjs` green, including any new tests
1. **Engine + tests.** `node --test scripts/test/*.test.mjs` green, including any new tests
for the change. No `--no-verify`, ever — the pre-commit hook already enforces this, but
re-run explicitly before touching version/changelog files so a failure is caught here, not
mid-release.
Expand Down Expand Up @@ -52,7 +52,7 @@ skip straight to the branch dance at the bottom.
every release by recomputing, never estimating:
```bash
grep -c '^## \[' CHANGELOG.md # releases shipped
node --test scripts/conductor.test.mjs 2>&1 | grep '^ℹ tests' # tests in the engine
node --test scripts/test/*.test.mjs 2>&1 | grep '^ℹ tests' # tests in the engine
wc -l scripts/conductor.mjs # engine LOC
# external dependencies is always 0 — enforced by the zero-dependency hard constraint
```
Expand Down
4 changes: 2 additions & 2 deletions .conductor/render-stamp.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"renderedAt": "2026-07-28T19:33:54.281Z",
"stateMtimeMs": 1785267234261.489
"renderedAt": "2026-07-30T15:40:51.660Z",
"stateMtimeMs": 1785426051639.6658
}
303 changes: 295 additions & 8 deletions .conductor/state.json
Original file line number Diff line number Diff line change
Expand Up @@ -1136,12 +1136,14 @@
"id": "rules-block-hardcodes-claude-slash-commands",
"title": "rulesBlock() hardcodes Claude Code slash-command syntax (/pm:status, /pm:detour --minimal, /pm:resume) throughout the emitted CLAUDE.md rules text, plus the 'Manage with /pm:status · /pm:next · ...' footer. On any platform without slash commands, that text is actively misleading -- it instructs the agent to run commands that don't exist there. Needs a command-style notion so the block can emit the direct CLI form (node scripts/conductor.mjs render) instead. rulesBlock() already takes (tracker, reviewMode, secondaryTrackers) so an added parameter fits the existing signature. Prerequisite for any multi-platform support; found during the multi-platform-agent-support brainstorm.",
"priority": "P1",
"status": "queued",
"status": "archived",
"role": "epic",
"lane": "claude-code",
"links": [],
"reconcileNeeded": false,
"parent": "multi-platform-agent-support"
"parent": "multi-platform-agent-support",
"startedAt": "2026-07-29T02:28:12.027Z",
"completedAt": "2026-07-30T05:54:27.332Z"
},
{
"id": "platform-parity-mechanism",
Expand Down Expand Up @@ -1222,12 +1224,19 @@
"lane": "claude-code",
"links": [
{
"type": "blocks",
"epic": "hermes-platform-support",
"reason": "observe.py would report rules_block_present false for any AGENTS.md platform -- a confident, wrong parity failure on the first Hermes run"
"type": "depends-on",
"epic": "rules-block-hardcodes-claude-slash-commands",
"reason": "observe.py correctly observes what the engine WRITES; the engine hardcodes CLAUDE.md (constants.mjs:14) and writes only there, so parameterizing observe first would abstract over a filename nothing produces"
}
],
"reconcileNeeded": false
"reconcileNeeded": false,
"startedAt": "2026-07-29T02:27:31.264Z",
"stories": [
{
"title": "fold into the rules-block epic: once the engine writes a per-platform rules-block target, update observe() to match and prove both with one eval",
"done": false
}
]
},
{
"id": "edd-user-memory-confound-control-run",
Expand Down Expand Up @@ -1292,12 +1301,290 @@
"lane": "claude-code",
"links": [],
"reconcileNeeded": false
},
{
"id": "init-detects-foreign-agent-instruction-files",
"title": "pm init should detect a pre-existing AGENTS.md / sol.md from a prior agent and read it before initializing",
"priority": "P3",
"status": "planned",
"role": "epic",
"lane": "claude-code",
"links": [],
"reconcileNeeded": false,
"stories": [
{
"title": "if CLAUDE.md is absent but AGENTS.md (or sol.md) exists, read its contents -- it may dictate what belongs in CLAUDE.md, or at minimum hint how to initialize correctly in a project another agent already used",
"done": false
}
]
},
{
"id": "rules-block-cli-fallback",
"title": "rules block: CLI fallback for any platform lacking slash commands or hitting a namespace collision",
"priority": "P3",
"status": "planned",
"role": "epic",
"lane": "claude-code",
"links": [],
"reconcileNeeded": false,
"stories": [
{
"title": "deferred deliberately: a CLI fallback changes WHO the instruction addresses (slash command = the user, node scripts/conductor.mjs = the agent), which is a design fork, not just longer text",
"done": false
}
]
},
{
"id": "gh-64-sync-duplicate-shipped-plan",
"title": "sync registers a duplicate untriaged epic for a plan whose feature already shipped",
"priority": "P2",
"status": "queued",
"role": "epic",
"lane": "claude-code",
"links": [],
"reconcileNeeded": false,
"externalId": "64",
"externalUrl": "https://github.com/cfdude/pm/issues/64",
"stories": [
{
"title": "CONFIRMED remove-epic is NOT durable: identical 3 phantoms returned on the next sync ~1h later. Another session saw the same across a 2-day gap in agent-dm.",
"done": false
},
{
"title": "WORKAROUND THAT STICKS (no code change): set the phantom to --status archived instead of remove-epic. The id still exists so sync's own 'id already exists' guard fires. Verified: repeated syncs add 0.",
"done": false
},
{
"title": "prefer the tombstone/syncIgnore fix over plan-field matching for the near term: matching is blocked by #66 (no update-epic --plan) while the tombstone has no dependency; archived already IS a working tombstone",
"done": false
}
]
},
{
"id": "gh-65-autodetour-false-positives",
"title": "auto-detour writes false-positive entries: no success check + no repo-scope check",
"priority": "P1",
"status": "queued",
"role": "epic",
"lane": "claude-code",
"links": [],
"reconcileNeeded": false,
"externalId": "65",
"externalUrl": "https://github.com/cfdude/pm/issues/65",
"stories": [
{
"title": "bug 1: guard the write path on HEAD having advanced -- covers BOTH a pre-commit-rejected commit (#65) and a backgrounded commit not yet landed (#68). An exit-code check would NOT cover the latter.",
"done": true
},
{
"title": "bug 2: repo-scope check -- only fire when the commit's repo root matches the pm-managed repo (paired repo / submodule / git -C elsewhere)",
"done": false
},
{
"title": "closes #68 as duplicate; reporter hit it across job-search-agent + a paired repo, so it is not machine-specific",
"done": true
},
{
"title": "bug 2 is functionally covered by the SAME guard (a commit landing elsewhere leaves our HEAD subject unmatched) -- but NO explicit repo-scope check was added; decide whether belt-and-braces is wanted",
"done": false
}
],
"startedAt": "2026-07-30T02:48:10.981Z"
},
{
"id": "gh-66-update-epic-missing-flags",
"title": "update-epic: no --lane, no --plan, undocumented valueless --link for clearing",
"priority": "P3",
"status": "queued",
"role": "epic",
"lane": "claude-code",
"links": [
{
"type": "blocks",
"epic": "gh-69-sync-no-done-signal-for-plans",
"reason": "sync's fix wants to read each epic's plan field, but update-epic has no --plan so that field cannot be populated for existing epics"
}
],
"reconcileNeeded": false,
"externalId": "66",
"externalUrl": "https://github.com/cfdude/pm/issues/66"
},
{
"id": "gh-69-sync-no-done-signal-for-plans",
"title": "sync re-registers COMPLETED Superpowers plans as untriaged epics",
"priority": "P2",
"status": "queued",
"role": "epic",
"lane": "claude-code",
"links": [
{
"type": "relates-to",
"epic": "gh-64-sync-duplicate-shipped-plan",
"reason": "same root cause -- dedup keys on the plan's filename-derived id instead of a plan<->epic association, so any epic whose plan is named differently is re-registered forever"
}
],
"reconcileNeeded": false,
"externalId": "69",
"externalUrl": "https://github.com/cfdude/pm/issues/69"
},
{
"id": "gh-70-link-validation-gap",
"title": "add-epic --link silently accepts malformed format; no update-epic setter to fix it",
"priority": "P2",
"status": "queued",
"role": "epic",
"lane": "claude-code",
"links": [],
"reconcileNeeded": false,
"externalId": "70",
"externalUrl": "https://github.com/cfdude/pm/issues/70"
},
{
"id": "test-suite-perf-and-scaffolding",
"title": "Split the test suite for parallelism (~120s -> ~20s) and close the silent-pass holes in its scaffolding",
"priority": "P1",
"status": "archived",
"role": "epic",
"lane": "superpowers",
"links": [],
"reconcileNeeded": false,
"stories": [
{
"title": "MEASURED: 73ms per engine spawn x 676 run() call sites = ~49s of pure node startup, in ONE file, on 16 cores. node parallelizes across FILES only (proven: 4 files x 3s = 3.3s wall).",
"done": false
},
{
"title": "split conductor.test.mjs (3202 lines, 255 tests) into ~8-10 files along the 29 existing section markers; the 8 exported helpers already exist and nothing imports them -- the file was built to be split",
"done": true
},
{
"title": "do NOT convert to in-process imports to skip node startup: faster, but stops testing the real CLI contract, which is the point of these tests",
"done": true
},
{
"title": "SILENT-PASS HOLE: detourLog() returns '' for a missing file, so assert.doesNotMatch(detourLog(cwd),...) passes even if commit-nudge never ran -- and it exits 0 on every early return, so run() will not throw. Assert the hook EMITTED its JSON first.",
"done": true
},
{
"title": "keep the :996 self-heal test repo-less (it is the only coverage of the unverifiable branch and it caught the two-state guard bug); rename it to say so and ADD a git-repo twin for the verified path",
"done": true
},
{
"title": "audit every doesNotMatch/empty-string assertion in the suite for the same vacuous-pass shape, not just the ones added for gh#65",
"done": true
},
{
"title": "MEASURED RESULT: 118s serial -> ~46s parallel (2.6x) across 11 files. 18 files was WORSE (46s) than 11 -- runner startup overhead. Floor is the slowest file (21s); the 46-21 gap is tmpdir/spawn contention, not concurrency (--test-concurrency=16 changed nothing).",
"done": false
},
{
"title": "GLOB LANDMINE: node --test scripts/test (a DIRECTORY) does not work on Node 26 -- it treats the arg as a module, dies MODULE_NOT_FOUND, reports '1 test'. Only the shell glob works. Documented in the hook so nobody 'simplifies' it.",
"done": false
},
{
"title": "MUTATION-TESTED the guard tests: disabling the guard initially left 4 of 5 still PASSING, because gitRepo()'s baseline commit touches 4 files and looksLikeUnloggedMinimalDetour rejects >3 -- they were passing on the file-count rule, not the guard. Fixed; now 4/5 fail on mutation and the 5th is the positive control that should pass either way.",
"done": false
},
{
"title": "AUDIT RESULT: projectMd/claudeMd THROW on a missing file so they cannot be vacuous. detourLog returns '' -- 4 pre-existing bare assertions in conductor-08 hardened via nudgeAndReadLog(). REMAINING (low): 3 doesNotMatch(parseBrief(...)) can be vacuous since parseBrief returns '' on empty brief output.",
"done": false
}
],
"startedAt": "2026-07-30T04:28:32.303Z",
"completedAt": "2026-07-30T05:02:31.485Z"
},
{
"id": "2026-07-21-conductor-mjs-module-split",
"title": "conductor.mjs Module Split Implementation Plan",
"priority": "P?",
"status": "archived",
"role": "epic",
"lane": "superpowers",
"planPath": "docs/superpowers/plans/2026-07-21-conductor-mjs-module-split.md",
"links": [],
"reconcileNeeded": false,
"completedAt": "2026-07-30T04:00:55.770Z"
},
{
"id": "2026-07-26-edd-harness-agent-behavior-testing",
"title": "EDD Harness for Agent Behavior Testing — Implementation Plan",
"priority": "P?",
"status": "archived",
"role": "epic",
"lane": "superpowers",
"planPath": "docs/superpowers/plans/2026-07-26-edd-harness-agent-behavior-testing.md",
"links": [],
"reconcileNeeded": false,
"completedAt": "2026-07-30T04:00:55.969Z"
},
{
"id": "2026-07-29-platform-aware-rules-block",
"title": "Platform-Aware Rules Block Implementation Plan",
"priority": "P?",
"status": "archived",
"role": "epic",
"lane": "superpowers",
"planPath": "docs/superpowers/plans/2026-07-29-platform-aware-rules-block.md",
"links": [],
"reconcileNeeded": false,
"completedAt": "2026-07-30T04:00:56.179Z"
},
{
"id": "platform-switch-orphans-old-rules-block",
"title": "a platform switch leaves the previous platform's rules block live but unmanaged",
"priority": "P2",
"status": "queued",
"role": "epic",
"lane": "claude-code",
"links": [
{
"type": "blocks",
"epic": "hermes-platform-support",
"reason": "a switched repo would leave stale conductor instructions in the file the previous platform read"
}
],
"reconcileNeeded": false,
"stories": [
{
"title": "writeRules() writes only the newly-resolved target, so switching claude-code -> codex creates AGENTS.md and leaves a frozen CLAUDE.md block an agent may still read -- a STALE-INSTRUCTIONS hazard distinct from 'recreate the artifact tree'",
"done": false
},
{
"title": "unreachable today (no Hermes/Codex hooks ship, needs a hand-passed --platform), but must be resolved before either port",
"done": false
}
]
},
{
"id": "autodetour-parser-misses-am-and-f",
"title": "commit-nudge's -m parser misses -am / -F / editor commits, so #65 is only closed for -m",
"priority": "P2",
"status": "queued",
"role": "epic",
"lane": "claude-code",
"links": [],
"reconcileNeeded": false,
"stories": [
{
"title": "git commit -am \"fix: x\" parses to subject='' which short-circuits the guard; a REJECTED -am commit still writes a false DETOUR-COMMIT line while a detour is active -- the original #65 symptom",
"done": false
},
{
"title": "b2e472f's message claims 'closes #65, #68' -- true for -m only. Correct the claim or close the gap BEFORE those issues are closed upstream.",
"done": false
},
{
"title": "ALSO: an escaped quote inside a double-quoted -m truncates the capture (regex is \"([^\"]*)\"), so a genuinely landed commit is CONTRADICTED and the hook silently suppressed. Fix shape: backslash-aware capture \"((?:[^\"\\\\]|\\\\.)*)\", or treat a value ending in a backslash as unverifiable.",
"done": false
}
]
}
],
"detourStack": [],
"pmVersion": "0.23.1",
"pmVersion": "0.24.0",
"tracker": {
"system": "github-issues",
"repo": "cfdude/pm"
}
},
"platform": "claude-code"
}
Loading
Loading