From 6cfe3b4a674502a229c86be1842b274c0a3b417c Mon Sep 17 00:00:00 2001 From: wehub-resource-sync Date: Mon, 13 Jul 2026 11:55:25 +0800 Subject: [PATCH] chore: import upstream snapshot with attribution --- .agents/plugins/marketplace.json | 20 + .claude-plugin/marketplace.json | 20 + .claude-plugin/plugin.json | 20 + .codex-plugin/plugin.json | 48 + .cursor-plugin/plugin.json | 23 + .gitattributes | 18 + .github/FUNDING.yml | 3 + .github/ISSUE_TEMPLATE/bug_report.md | 55 + .github/ISSUE_TEMPLATE/config.yml | 5 + .github/ISSUE_TEMPLATE/feature_request.md | 47 + .github/ISSUE_TEMPLATE/platform_support.md | 34 + .github/PULL_REQUEST_TEMPLATE.md | 143 ++ .gitignore | 13 + .kimi-plugin/plugin.json | 38 + .opencode/INSTALL.md | 115 ++ .opencode/plugins/superpowers.js | 139 ++ .pi/extensions/superpowers.ts | 121 ++ .pre-commit-config.yaml | 21 + .version-bump.json | 21 + AGENTS.md | 1 + CLAUDE.md | 115 ++ CODE_OF_CONDUCT.md | 128 ++ GEMINI.md | 2 + LICENSE | 21 + README.md | 273 ++++ README.wehub.md | 7 + RELEASE-NOTES.md | 1328 ++++++++++++++++ assets/app-icon.png | Bin 0 -> 48231 bytes assets/superpowers-small.svg | 1 + docs/README.kimi.md | 88 ++ docs/README.opencode.md | 163 ++ .../2025-11-22-opencode-support-design.md | 294 ++++ ...5-11-22-opencode-support-implementation.md | 1095 +++++++++++++ ...-skills-improvements-from-user-feedback.md | 711 +++++++++ docs/plans/2026-01-17-visual-brainstorming.md | 571 +++++++ docs/porting-to-a-new-harness.md | 827 ++++++++++ .../2026-01-22-document-review-system.md | 301 ++++ ...026-02-19-visual-brainstorming-refactor.md | 523 +++++++ .../2026-03-11-zero-dep-brainstorm-server.md | 479 ++++++ .../2026-03-23-codex-app-compatibility.md | 566 +++++++ .../plans/2026-04-06-worktree-rototill.md | 866 +++++++++++ .../plans/2026-05-06-lift-drill-into-evals.md | 1374 +++++++++++++++++ .../2026-05-07-pi-extension-and-evals.md | 143 ++ ...6-06-09-sdd-task-scoped-review-dispatch.md | 774 ++++++++++ .../2026-06-09-visual-companion-issues.md | 352 +++++ ...6-06-10-visual-companion-auth-hardening.md | 785 ++++++++++ ...-visual-companion-final-hardening-fixup.md | 1126 ++++++++++++++ ...026-01-22-document-review-system-design.md | 136 ++ ...19-visual-brainstorming-refactor-design.md | 162 ++ ...03-11-zero-dep-brainstorm-server-design.md | 118 ++ ...26-03-23-codex-app-compatibility-design.md | 244 +++ .../2026-04-06-worktree-rototill-design.md | 341 ++++ ...-05-platform-neutral-config-refs-design.md | 77 + ...026-05-05-platform-neutral-prose-design.md | 94 ++ ...26-05-05-platform-neutral-readme-design.md | 47 + ...2026-05-06-lift-drill-into-evals-design.md | 247 +++ ...-sdd-task-scoped-review-dispatch-design.md | 160 ++ ...10-positive-instruction-redesign-design.md | 178 +++ .../2026-06-10-strict-cost-sdd-design.md | 265 ++++ ...-visual-companion-auth-hardening-design.md | 225 +++ ...-companion-final-hardening-fixup-design.md | 254 +++ docs/testing.md | 35 + docs/windows/polyglot-hooks.md | 148 ++ gemini-extension.json | 6 + hooks/hooks-cursor.json | 10 + hooks/hooks.json | 16 + hooks/run-hook.cmd | 46 + hooks/session-start | 49 + package.json | 23 + scripts/bump-version.sh | 220 +++ scripts/lint-shell.sh | 211 +++ scripts/package-codex-plugin.sh | 342 ++++ scripts/sync-to-codex-plugin.sh | 466 ++++++ skills/brainstorming/SKILL.md | 159 ++ .../brainstorming/scripts/frame-template.html | 213 +++ skills/brainstorming/scripts/helper.js | 167 ++ skills/brainstorming/scripts/server.cjs | 723 +++++++++ skills/brainstorming/scripts/start-server.sh | 209 +++ skills/brainstorming/scripts/stop-server.sh | 120 ++ .../spec-document-reviewer-prompt.md | 49 + skills/brainstorming/visual-companion.md | 291 ++++ skills/dispatching-parallel-agents/SKILL.md | 185 +++ skills/executing-plans/SKILL.md | 70 + .../finishing-a-development-branch/SKILL.md | 241 +++ skills/receiving-code-review/SKILL.md | 213 +++ skills/requesting-code-review/SKILL.md | 103 ++ .../requesting-code-review/code-reviewer.md | 172 +++ skills/subagent-driven-development/SKILL.md | 418 +++++ .../implementer-prompt.md | 139 ++ .../scripts/review-package | 44 + .../scripts/sdd-workspace | 22 + .../scripts/task-brief | 40 + .../task-reviewer-prompt.md | 188 +++ skills/systematic-debugging/CREATION-LOG.md | 119 ++ skills/systematic-debugging/SKILL.md | 296 ++++ .../condition-based-waiting-example.ts | 158 ++ .../condition-based-waiting.md | 115 ++ .../systematic-debugging/defense-in-depth.md | 122 ++ skills/systematic-debugging/find-polluter.sh | 63 + .../root-cause-tracing.md | 169 ++ skills/systematic-debugging/test-academic.md | 14 + .../systematic-debugging/test-pressure-1.md | 58 + .../systematic-debugging/test-pressure-2.md | 68 + .../systematic-debugging/test-pressure-3.md | 69 + skills/test-driven-development/SKILL.md | 371 +++++ .../testing-anti-patterns.md | 299 ++++ skills/using-git-worktrees/SKILL.md | 202 +++ skills/using-superpowers/SKILL.md | 62 + .../references/antigravity-tools.md | 23 + .../references/codex-tools.md | 39 + .../using-superpowers/references/pi-tools.md | 16 + .../verification-before-completion/SKILL.md | 139 ++ skills/writing-plans/SKILL.md | 174 +++ .../plan-document-reviewer-prompt.md | 49 + skills/writing-skills/SKILL.md | 689 +++++++++ .../anthropic-best-practices.md | 1150 ++++++++++++++ .../examples/CLAUDE_MD_TESTING.md | 189 +++ .../writing-skills/graphviz-conventions.dot | 172 +++ .../writing-skills/persuasion-principles.md | 187 +++ skills/writing-skills/render-graphs.js | 168 ++ .../testing-skills-with-subagents.md | 384 +++++ tests/antigravity/run-tests.sh | 16 + tests/antigravity/test-antigravity-tools.sh | 53 + tests/brainstorm-server/auth.test.js | 312 ++++ tests/brainstorm-server/branding.test.js | 344 +++++ .../browser-launcher.test.js | 66 + tests/brainstorm-server/helper.test.js | 197 +++ tests/brainstorm-server/lifecycle.test.js | 515 ++++++ tests/brainstorm-server/package-lock.json | 36 + tests/brainstorm-server/package.json | 10 + tests/brainstorm-server/server.test.js | 592 +++++++ tests/brainstorm-server/start-server.test.sh | 115 ++ tests/brainstorm-server/stop-server.test.sh | 182 +++ .../windows-lifecycle.test.sh | 394 +++++ tests/brainstorm-server/ws-protocol.test.js | 407 +++++ tests/claude-code/README.md | 165 ++ tests/claude-code/analyze-token-usage.py | 168 ++ tests/claude-code/run-skill-tests.sh | 189 +++ tests/claude-code/test-helpers.sh | 202 +++ tests/claude-code/test-sdd-workspace.sh | 142 ++ ...subagent-driven-development-integration.sh | 333 ++++ .../test-subagent-driven-development.sh | 179 +++ .../test-worktree-native-preference.sh | 181 +++ .../claude-code/test-worktree-path-policy.sh | 69 + .../test-sync-to-codex-plugin.sh | 738 +++++++++ tests/codex/test-marketplace-manifest.sh | 76 + tests/codex/test-package-codex-plugin.sh | 292 ++++ .../prompts/action-oriented.txt | 3 + .../prompts/after-planning-flow.txt | 17 + .../prompts/claude-suggested-it.txt | 11 + .../prompts/i-know-what-sdd-means.txt | 8 + .../prompts/mid-conversation-execute-plan.txt | 3 + .../prompts/please-use-brainstorming.txt | 1 + .../prompts/skip-formalities.txt | 3 + .../subagent-driven-development-please.txt | 1 + .../prompts/use-systematic-debugging.txt | 1 + tests/explicit-skill-requests/run-all.sh | 70 + .../run-extended-multiturn-test.sh | 113 ++ .../explicit-skill-requests/run-haiku-test.sh | 144 ++ .../run-multiturn-test.sh | 143 ++ tests/explicit-skill-requests/run-test.sh | 136 ++ tests/hooks/test-session-start.sh | 204 +++ tests/kimi/run-tests.sh | 6 + tests/kimi/test-plugin-manifest.sh | 86 ++ tests/opencode/run-tests.sh | 165 ++ tests/opencode/setup.sh | 88 ++ tests/opencode/test-bootstrap-caching.mjs | 146 ++ tests/opencode/test-bootstrap-caching.sh | 32 + tests/opencode/test-plugin-loading.sh | 82 + tests/opencode/test-priority.sh | 217 +++ tests/opencode/test-tools.sh | 95 ++ tests/pi/test-pi-extension.mjs | 128 ++ tests/shell-lint/test-lint-shell.sh | 179 +++ 173 files changed, 35323 insertions(+) create mode 100644 .agents/plugins/marketplace.json create mode 100644 .claude-plugin/marketplace.json create mode 100644 .claude-plugin/plugin.json create mode 100644 .codex-plugin/plugin.json create mode 100644 .cursor-plugin/plugin.json create mode 100644 .gitattributes create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/platform_support.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .gitignore create mode 100644 .kimi-plugin/plugin.json create mode 100644 .opencode/INSTALL.md create mode 100644 .opencode/plugins/superpowers.js create mode 100644 .pi/extensions/superpowers.ts create mode 100644 .pre-commit-config.yaml create mode 100644 .version-bump.json create mode 120000 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 GEMINI.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 README.wehub.md create mode 100644 RELEASE-NOTES.md create mode 100644 assets/app-icon.png create mode 100644 assets/superpowers-small.svg create mode 100644 docs/README.kimi.md create mode 100644 docs/README.opencode.md create mode 100644 docs/plans/2025-11-22-opencode-support-design.md create mode 100644 docs/plans/2025-11-22-opencode-support-implementation.md create mode 100644 docs/plans/2025-11-28-skills-improvements-from-user-feedback.md create mode 100644 docs/plans/2026-01-17-visual-brainstorming.md create mode 100644 docs/porting-to-a-new-harness.md create mode 100644 docs/superpowers/plans/2026-01-22-document-review-system.md create mode 100644 docs/superpowers/plans/2026-02-19-visual-brainstorming-refactor.md create mode 100644 docs/superpowers/plans/2026-03-11-zero-dep-brainstorm-server.md create mode 100644 docs/superpowers/plans/2026-03-23-codex-app-compatibility.md create mode 100644 docs/superpowers/plans/2026-04-06-worktree-rototill.md create mode 100644 docs/superpowers/plans/2026-05-06-lift-drill-into-evals.md create mode 100644 docs/superpowers/plans/2026-05-07-pi-extension-and-evals.md create mode 100644 docs/superpowers/plans/2026-06-09-sdd-task-scoped-review-dispatch.md create mode 100644 docs/superpowers/plans/2026-06-09-visual-companion-issues.md create mode 100644 docs/superpowers/plans/2026-06-10-visual-companion-auth-hardening.md create mode 100644 docs/superpowers/plans/2026-06-11-visual-companion-final-hardening-fixup.md create mode 100644 docs/superpowers/specs/2026-01-22-document-review-system-design.md create mode 100644 docs/superpowers/specs/2026-02-19-visual-brainstorming-refactor-design.md create mode 100644 docs/superpowers/specs/2026-03-11-zero-dep-brainstorm-server-design.md create mode 100644 docs/superpowers/specs/2026-03-23-codex-app-compatibility-design.md create mode 100644 docs/superpowers/specs/2026-04-06-worktree-rototill-design.md create mode 100644 docs/superpowers/specs/2026-05-05-platform-neutral-config-refs-design.md create mode 100644 docs/superpowers/specs/2026-05-05-platform-neutral-prose-design.md create mode 100644 docs/superpowers/specs/2026-05-05-platform-neutral-readme-design.md create mode 100644 docs/superpowers/specs/2026-05-06-lift-drill-into-evals-design.md create mode 100644 docs/superpowers/specs/2026-06-09-sdd-task-scoped-review-dispatch-design.md create mode 100644 docs/superpowers/specs/2026-06-10-positive-instruction-redesign-design.md create mode 100644 docs/superpowers/specs/2026-06-10-strict-cost-sdd-design.md create mode 100644 docs/superpowers/specs/2026-06-10-visual-companion-auth-hardening-design.md create mode 100644 docs/superpowers/specs/2026-06-11-visual-companion-final-hardening-fixup-design.md create mode 100644 docs/testing.md create mode 100644 docs/windows/polyglot-hooks.md create mode 100644 gemini-extension.json create mode 100644 hooks/hooks-cursor.json create mode 100644 hooks/hooks.json create mode 100755 hooks/run-hook.cmd create mode 100755 hooks/session-start create mode 100644 package.json create mode 100755 scripts/bump-version.sh create mode 100755 scripts/lint-shell.sh create mode 100755 scripts/package-codex-plugin.sh create mode 100755 scripts/sync-to-codex-plugin.sh create mode 100644 skills/brainstorming/SKILL.md create mode 100644 skills/brainstorming/scripts/frame-template.html create mode 100644 skills/brainstorming/scripts/helper.js create mode 100644 skills/brainstorming/scripts/server.cjs create mode 100755 skills/brainstorming/scripts/start-server.sh create mode 100755 skills/brainstorming/scripts/stop-server.sh create mode 100644 skills/brainstorming/spec-document-reviewer-prompt.md create mode 100644 skills/brainstorming/visual-companion.md create mode 100644 skills/dispatching-parallel-agents/SKILL.md create mode 100644 skills/executing-plans/SKILL.md create mode 100644 skills/finishing-a-development-branch/SKILL.md create mode 100644 skills/receiving-code-review/SKILL.md create mode 100644 skills/requesting-code-review/SKILL.md create mode 100644 skills/requesting-code-review/code-reviewer.md create mode 100644 skills/subagent-driven-development/SKILL.md create mode 100644 skills/subagent-driven-development/implementer-prompt.md create mode 100755 skills/subagent-driven-development/scripts/review-package create mode 100755 skills/subagent-driven-development/scripts/sdd-workspace create mode 100755 skills/subagent-driven-development/scripts/task-brief create mode 100644 skills/subagent-driven-development/task-reviewer-prompt.md create mode 100644 skills/systematic-debugging/CREATION-LOG.md create mode 100644 skills/systematic-debugging/SKILL.md create mode 100644 skills/systematic-debugging/condition-based-waiting-example.ts create mode 100644 skills/systematic-debugging/condition-based-waiting.md create mode 100644 skills/systematic-debugging/defense-in-depth.md create mode 100755 skills/systematic-debugging/find-polluter.sh create mode 100644 skills/systematic-debugging/root-cause-tracing.md create mode 100644 skills/systematic-debugging/test-academic.md create mode 100644 skills/systematic-debugging/test-pressure-1.md create mode 100644 skills/systematic-debugging/test-pressure-2.md create mode 100644 skills/systematic-debugging/test-pressure-3.md create mode 100644 skills/test-driven-development/SKILL.md create mode 100644 skills/test-driven-development/testing-anti-patterns.md create mode 100644 skills/using-git-worktrees/SKILL.md create mode 100644 skills/using-superpowers/SKILL.md create mode 100644 skills/using-superpowers/references/antigravity-tools.md create mode 100644 skills/using-superpowers/references/codex-tools.md create mode 100644 skills/using-superpowers/references/pi-tools.md create mode 100644 skills/verification-before-completion/SKILL.md create mode 100644 skills/writing-plans/SKILL.md create mode 100644 skills/writing-plans/plan-document-reviewer-prompt.md create mode 100644 skills/writing-skills/SKILL.md create mode 100644 skills/writing-skills/anthropic-best-practices.md create mode 100644 skills/writing-skills/examples/CLAUDE_MD_TESTING.md create mode 100644 skills/writing-skills/graphviz-conventions.dot create mode 100644 skills/writing-skills/persuasion-principles.md create mode 100755 skills/writing-skills/render-graphs.js create mode 100644 skills/writing-skills/testing-skills-with-subagents.md create mode 100755 tests/antigravity/run-tests.sh create mode 100755 tests/antigravity/test-antigravity-tools.sh create mode 100644 tests/brainstorm-server/auth.test.js create mode 100644 tests/brainstorm-server/branding.test.js create mode 100644 tests/brainstorm-server/browser-launcher.test.js create mode 100644 tests/brainstorm-server/helper.test.js create mode 100644 tests/brainstorm-server/lifecycle.test.js create mode 100644 tests/brainstorm-server/package-lock.json create mode 100644 tests/brainstorm-server/package.json create mode 100644 tests/brainstorm-server/server.test.js create mode 100644 tests/brainstorm-server/start-server.test.sh create mode 100755 tests/brainstorm-server/stop-server.test.sh create mode 100755 tests/brainstorm-server/windows-lifecycle.test.sh create mode 100644 tests/brainstorm-server/ws-protocol.test.js create mode 100644 tests/claude-code/README.md create mode 100755 tests/claude-code/analyze-token-usage.py create mode 100755 tests/claude-code/run-skill-tests.sh create mode 100755 tests/claude-code/test-helpers.sh create mode 100755 tests/claude-code/test-sdd-workspace.sh create mode 100755 tests/claude-code/test-subagent-driven-development-integration.sh create mode 100755 tests/claude-code/test-subagent-driven-development.sh create mode 100755 tests/claude-code/test-worktree-native-preference.sh create mode 100755 tests/claude-code/test-worktree-path-policy.sh create mode 100755 tests/codex-plugin-sync/test-sync-to-codex-plugin.sh create mode 100755 tests/codex/test-marketplace-manifest.sh create mode 100755 tests/codex/test-package-codex-plugin.sh create mode 100644 tests/explicit-skill-requests/prompts/action-oriented.txt create mode 100644 tests/explicit-skill-requests/prompts/after-planning-flow.txt create mode 100644 tests/explicit-skill-requests/prompts/claude-suggested-it.txt create mode 100644 tests/explicit-skill-requests/prompts/i-know-what-sdd-means.txt create mode 100644 tests/explicit-skill-requests/prompts/mid-conversation-execute-plan.txt create mode 100644 tests/explicit-skill-requests/prompts/please-use-brainstorming.txt create mode 100644 tests/explicit-skill-requests/prompts/skip-formalities.txt create mode 100644 tests/explicit-skill-requests/prompts/subagent-driven-development-please.txt create mode 100644 tests/explicit-skill-requests/prompts/use-systematic-debugging.txt create mode 100755 tests/explicit-skill-requests/run-all.sh create mode 100755 tests/explicit-skill-requests/run-extended-multiturn-test.sh create mode 100755 tests/explicit-skill-requests/run-haiku-test.sh create mode 100755 tests/explicit-skill-requests/run-multiturn-test.sh create mode 100755 tests/explicit-skill-requests/run-test.sh create mode 100755 tests/hooks/test-session-start.sh create mode 100755 tests/kimi/run-tests.sh create mode 100755 tests/kimi/test-plugin-manifest.sh create mode 100755 tests/opencode/run-tests.sh create mode 100755 tests/opencode/setup.sh create mode 100644 tests/opencode/test-bootstrap-caching.mjs create mode 100755 tests/opencode/test-bootstrap-caching.sh create mode 100755 tests/opencode/test-plugin-loading.sh create mode 100755 tests/opencode/test-priority.sh create mode 100755 tests/opencode/test-tools.sh create mode 100644 tests/pi/test-pi-extension.mjs create mode 100644 tests/shell-lint/test-lint-shell.sh diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json new file mode 100644 index 0000000..4e3b435 --- /dev/null +++ b/.agents/plugins/marketplace.json @@ -0,0 +1,20 @@ +{ + "name": "superpowers-dev", + "interface": { + "displayName": "Superpowers Dev" + }, + "plugins": [ + { + "name": "superpowers", + "source": { + "source": "url", + "url": "./" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Developer Tools" + } + ] +} diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 0000000..acb6ae6 --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,20 @@ +{ + "name": "superpowers-dev", + "description": "Development marketplace for Superpowers core skills library", + "owner": { + "name": "Jesse Vincent", + "email": "jesse@fsck.com" + }, + "plugins": [ + { + "name": "superpowers", + "description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques", + "version": "6.1.1", + "source": "./", + "author": { + "name": "Jesse Vincent", + "email": "jesse@fsck.com" + } + } + ] +} diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..2fe026f --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,20 @@ +{ + "name": "superpowers", + "description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques", + "version": "6.1.1", + "author": { + "name": "Jesse Vincent", + "email": "jesse@fsck.com" + }, + "homepage": "https://github.com/obra/superpowers", + "repository": "https://github.com/obra/superpowers", + "license": "MIT", + "keywords": [ + "skills", + "tdd", + "debugging", + "collaboration", + "best-practices", + "workflows" + ] +} diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json new file mode 100644 index 0000000..a6b3143 --- /dev/null +++ b/.codex-plugin/plugin.json @@ -0,0 +1,48 @@ +{ + "name": "superpowers", + "version": "6.1.1", + "description": "An agentic skills framework & software development methodology that works: planning, TDD, debugging, and collaboration workflows.", + "author": { + "name": "Jesse Vincent", + "email": "jesse@fsck.com", + "url": "https://github.com/obra" + }, + "homepage": "https://github.com/obra/superpowers", + "repository": "https://github.com/obra/superpowers", + "license": "MIT", + "keywords": [ + "brainstorming", + "subagent-driven-development", + "skills", + "planning", + "tdd", + "debugging", + "code-review", + "workflow" + ], + "skills": "./skills/", + "hooks": {}, + "interface": { + "displayName": "Superpowers", + "shortDescription": "Planning, TDD, debugging, and delivery workflows for coding agents", + "longDescription": "Use Superpowers to guide agent work through brainstorming, implementation planning, test-driven development, systematic debugging, parallel execution, code review, and finish-the-branch workflows.", + "developerName": "Jesse Vincent", + "category": "Developer Tools", + "capabilities": [ + "Interactive", + "Read", + "Write" + ], + "defaultPrompt": [ + "I've got an idea for something I'd like to build.", + "Let's add a feature to this project." + ], + "websiteURL": "https://github.com/obra/superpowers", + "privacyPolicyURL": "https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement", + "termsOfServiceURL": "https://docs.github.com/en/site-policy/github-terms/github-terms-of-service", + "brandColor": "#F59E0B", + "composerIcon": "./assets/superpowers-small.svg", + "logo": "./assets/app-icon.png", + "screenshots": [] + } +} diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json new file mode 100644 index 0000000..18d788b --- /dev/null +++ b/.cursor-plugin/plugin.json @@ -0,0 +1,23 @@ +{ + "name": "superpowers", + "displayName": "Superpowers", + "description": "Core skills library: TDD, debugging, collaboration patterns, and proven techniques", + "version": "6.1.1", + "author": { + "name": "Jesse Vincent", + "email": "jesse@fsck.com" + }, + "homepage": "https://github.com/obra/superpowers", + "repository": "https://github.com/obra/superpowers", + "license": "MIT", + "keywords": [ + "skills", + "tdd", + "debugging", + "collaboration", + "best-practices", + "workflows" + ], + "skills": "./skills/", + "hooks": "./hooks/hooks-cursor.json" +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..def8027 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,18 @@ +# Ensure shell scripts always have LF line endings +*.sh text eol=lf +hooks/session-start text eol=lf + +# Ensure the polyglot wrapper keeps LF (it's parsed by both cmd and bash) +*.cmd text eol=lf + +# Common text files +*.md text eol=lf +*.json text eol=lf +*.js text eol=lf +*.mjs text eol=lf +*.ts text eol=lf + +# Explicitly mark binary files +*.png binary +*.jpg binary +*.gif binary diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..f646aa7 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: [obra] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..01bb80e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,55 @@ +--- +name: Bug Report +about: Something isn't working as expected +labels: bug +--- + + + +- [ ] I searched existing issues and this is not a duplicate + +## Environment (required) + + +| Field | Value | +|-------|-------| +| Superpowers version | | +| Harness (Claude Code, Cursor, etc.) | | +| Harness version | | +| Your model + version | | +| All plugins installed | | +| OS + shell | | + +## Is this a Superpowers issue or a platform issue? + + +- [ ] I confirmed this issue does not occur without Superpowers installed + +## What happened? + + +## Steps to reproduce +1. +2. +3. + +## Expected behavior + + +## Actual behavior + + +## Debug log or conversation transcript + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..008309e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Questions & Help + url: https://discord.gg/35wsABTejz + about: For usage questions, troubleshooting help, and general discussion, please visit our Discord instead of opening an issue. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..c7a4de1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,47 @@ +--- +name: Feature Request +about: Propose a change or addition to Superpowers +labels: enhancement +--- + + + +- [ ] I searched existing issues and this has not been proposed before + +## What problem does this solve? + + +## Proposed solution + + +## What alternatives did you consider? + + +## Is this appropriate for core Superpowers? + + +## Environment (required) + + +| Field | Value | +|-------|-------| +| Superpowers version | | +| Harness (Claude Code, Cursor, etc.) | | +| Harness version | | +| Your model + version | | +| All plugins installed | | + +## Context + diff --git a/.github/ISSUE_TEMPLATE/platform_support.md b/.github/ISSUE_TEMPLATE/platform_support.md new file mode 100644 index 0000000..277dd1e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/platform_support.md @@ -0,0 +1,34 @@ +--- +name: IDE / Platform Support Request +about: Request support for a new IDE, editor, or AI coding tool +labels: platform-support +--- + + + +- [ ] I searched existing issues for this IDE/platform + +## Which IDE or platform? + + +## Does this tool have a plugin or extension system? + + +## Have you tried manual installation? + + +## Environment (required) + + +| Field | Value | +|-------|-------| +| Harness you currently use (Claude Code, Cursor, etc.) | | +| Harness version | | +| Your model + version | | +| All plugins installed | | diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..2ec9513 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,143 @@ + + +> **This PR MUST target the `dev` branch, not `main`.** `main` is the +> released branch; active work lands on `dev` first. PRs opened against +> `main` will be asked to retarget `dev` before review. + +## Who is submitting this PR? (required) + + +| Field | Value | +|-------|-------| +| Your model + version | | +| Harness + version | | +| All plugins installed | | +| Human partner who reviewed this diff | | + +## What problem are you trying to solve? + + +## What does this PR change? + + +## Is this change appropriate for the core library? + + +## What alternatives did you consider? + + +## Does this PR contain multiple unrelated changes? + + +## Existing PRs +- [ ] I have reviewed all open AND closed PRs for duplicates or prior art +- Related PRs: + + + +## Environment tested + +| Harness (e.g. Claude Code, Cursor) | Harness version | Model | Model version/ID | +|-------------------------------------|-----------------|-------|------------------| +| | | | | + +## New harness support (required if this PR adds a new harness) + + + +
+Clean-session transcript for "Let's make a react todo list" + +``` +paste the complete transcript here +``` + +
+ +## Evaluation +- What was the initial prompt you (or your human partner) used to start + the session that led to this change? +- How many eval sessions did you run AFTER making the change? +- How did outcomes change compared to before the change? + + + +## Rigor + +- [ ] If this is a skills change: I used `superpowers:writing-skills` and + completed adversarial pressure testing (paste results below) +- [ ] This change was tested adversarially, not just on the happy path +- [ ] I did not modify carefully-tuned content (Red Flags table, + rationalizations, "human partner" language) without extensive evals + showing the change is an improvement + + + +## Human review +- [ ] A human has reviewed the COMPLETE proposed diff before submission + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2118649 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +.worktrees/ +.private-journal/ +.claude/ +.superpowers/ +.DS_Store +node_modules/ +inspo +triage/ + +# Eval harness lives in its own repository, cloned into evals/ for local +# development (see CLAUDE.md / README.md). It is not part of the published +# plugin, so the whole directory is ignored here. +evals/ diff --git a/.kimi-plugin/plugin.json b/.kimi-plugin/plugin.json new file mode 100644 index 0000000..e5d90d0 --- /dev/null +++ b/.kimi-plugin/plugin.json @@ -0,0 +1,38 @@ +{ + "name": "superpowers", + "version": "6.1.1", + "description": "An agentic skills framework and software development methodology.", + "author": { + "name": "Jesse Vincent", + "email": "jesse@fsck.com" + }, + "homepage": "https://github.com/obra/superpowers", + "license": "MIT", + "keywords": [ + "brainstorming", + "subagent-driven-development", + "skills", + "planning", + "tdd", + "debugging", + "code-review", + "workflow" + ], + "skills": "./skills/", + "sessionStart": { + "skill": "using-superpowers" + }, + "skillInstructions": "Kimi Code tool mapping for Superpowers skills:\n\n- When a Superpowers skill says to ask the user, ask clarifying questions, ask one question at a time, present multiple-choice options, use the terminal for a question, or wait for the user's choice, call Kimi Code's `AskUserQuestion` tool. Do not render those choices as plain assistant text unless `AskUserQuestion` is unavailable or the session is in auto permission mode.\n- For `AskUserQuestion`, provide 1 question with 2-4 concrete options when possible. Put the recommended option first and suffix its label with `(Recommended)`.\n- When a Superpowers skill refers to `TodoWrite`, use Kimi Code's `TodoList` tool.\n- When a Superpowers skill says `Task tool (general-purpose)` or asks you to dispatch an implementer/reviewer subagent, use Kimi Code's `Agent` tool with a Kimi subagent type. Do not pass `general-purpose` as `subagent_type`.\n- For implementation, code review, spec review, quality review, and filled Superpowers subagent prompt templates, call `Agent` with `subagent_type: \"coder\"`, paste the fully filled prompt into `prompt`, and provide a short `description`.\n- For read-only codebase exploration that would take several searches, use `Agent` with `subagent_type: \"explore\"`.\n- For read-only planning or architecture design, use `Agent` with `subagent_type: \"plan\"`.\n- Keep dependent Superpowers subagent steps sequential. Use multiple `Agent` calls, or `run_in_background: true` only when the work is independent and background agents are available.\n- When a Superpowers skill refers to the `Skill` tool, use Kimi Code's native `Skill` tool.\n- Use Kimi Code's `Read`, `Write`, `Edit`, `Bash`, `Grep`, `Glob`, `FetchURL`, `WebSearch`, and MCP tools by their actual exposed names.\n- When a skill asks to search file contents, use `Grep`; when it asks to find files by path or pattern, use `Glob`; when it asks to fetch a URL, use `FetchURL`; when it asks to search the web, use `WebSearch`.", + "interface": { + "displayName": "Superpowers", + "shortDescription": "Planning, TDD, debugging, and delivery workflows for coding agents", + "longDescription": "Use Superpowers to guide agent work through brainstorming, implementation planning, test-driven development, systematic debugging, parallel execution, code review, and finish-the-branch workflows.", + "developerName": "Jesse Vincent", + "capabilities": [ + "Interactive", + "Read", + "Write" + ], + "websiteURL": "https://github.com/obra/superpowers" + } +} diff --git a/.opencode/INSTALL.md b/.opencode/INSTALL.md new file mode 100644 index 0000000..080f043 --- /dev/null +++ b/.opencode/INSTALL.md @@ -0,0 +1,115 @@ +# Installing Superpowers for OpenCode + +## Prerequisites + +- [OpenCode.ai](https://opencode.ai) installed + +## Installation + +Add superpowers to the `plugin` array in your `opencode.json` (global or project-level): + +```json +{ + "plugin": ["superpowers@git+https://github.com/obra/superpowers.git"] +} +``` + +Restart OpenCode. The plugin installs through OpenCode's plugin manager and +registers all skills. + +Verify by asking: "Tell me about your superpowers" + +OpenCode uses its own plugin install. If you also use Claude Code, Codex, or +another harness, install Superpowers separately for each one. + +## Migrating from the old symlink-based install + +If you previously installed superpowers using `git clone` and symlinks, remove the old setup: + +```bash +# Remove old symlinks +rm -f ~/.config/opencode/plugins/superpowers.js +rm -rf ~/.config/opencode/skills/superpowers + +# Optionally remove the cloned repo +rm -rf ~/.config/opencode/superpowers + +# Remove skills.paths from opencode.json if you added one for superpowers +``` + +Then follow the installation steps above. + +## Usage + +Use OpenCode's native `skill` tool: + +``` +use skill tool to list skills +use skill tool to load brainstorming +``` + +## Updating + +OpenCode installs Superpowers through a git-backed package spec. Some OpenCode +and Bun versions pin that resolved git dependency in a lockfile or cache, so a +restart may not pick up the newest Superpowers commit. If updates do not appear, +clear OpenCode's package cache or reinstall the plugin. + +To pin a specific version: + +```json +{ + "plugin": ["superpowers@git+https://github.com/obra/superpowers.git#v5.0.3"] +} +``` + +## Troubleshooting + +### Plugin not loading + +1. Check logs: `opencode run --print-logs "hello" 2>&1 | grep -i superpowers` +2. Verify the plugin line in your `opencode.json` +3. Make sure you're running a recent version of OpenCode + +### Windows install issues + +Some Windows OpenCode builds have upstream installer issues with git-backed +plugin specs, including cache paths for `git+https` URLs and Bun not finding +`git.exe` even when it works in a normal terminal. If OpenCode cannot install +the plugin, try installing with system npm and pointing OpenCode at the local +package: + +```powershell +npm install superpowers@git+https://github.com/obra/superpowers.git --prefix "$HOME\.config\opencode" +``` + +Then use the installed package path in `opencode.json`: + +```json +{ + "plugin": ["~/.config/opencode/node_modules/superpowers"] +} +``` + +### Skills not found + +1. Use `skill` tool to list what's discovered +2. Check that the plugin is loading (see above) + +### Tool mapping + +Skills speak in actions ("create a todo", "dispatch a subagent", "read a file"). On OpenCode these resolve to: + +- "Create a todo" / "mark complete in todo list" → `todowrite` +- `Subagent (general-purpose):` template → `task` tool with `subagent_type: "general"` (or `"explore"` for codebase exploration) +- "Invoke a skill" → OpenCode's native `skill` tool +- "Read a file" → `read` +- "Create a file" / "edit a file" / "delete a file" → `apply_patch` +- "Run a shell command" → `bash` +- "Search file contents" / "find files by name" → `grep`, `glob` +- "Fetch a URL" → `webfetch` + +## Getting Help + +- Report issues: https://github.com/obra/superpowers/issues +- Full documentation: https://github.com/obra/superpowers/blob/main/docs/README.opencode.md diff --git a/.opencode/plugins/superpowers.js b/.opencode/plugins/superpowers.js new file mode 100644 index 0000000..423e5ed --- /dev/null +++ b/.opencode/plugins/superpowers.js @@ -0,0 +1,139 @@ +/** + * Superpowers plugin for OpenCode.ai + * + * Injects superpowers bootstrap context via message transform. + * Auto-registers skills directory via config hook (no symlinks needed). + */ + +import path from 'path'; +import fs from 'fs'; +import os from 'os'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +// Simple frontmatter extraction (avoid dependency on skills-core for bootstrap) +const extractAndStripFrontmatter = (content) => { + const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/); + if (!match) return { frontmatter: {}, content }; + + const frontmatterStr = match[1]; + const body = match[2]; + const frontmatter = {}; + + for (const line of frontmatterStr.split('\n')) { + const colonIdx = line.indexOf(':'); + if (colonIdx > 0) { + const key = line.slice(0, colonIdx).trim(); + const value = line.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, ''); + frontmatter[key] = value; + } + } + + return { frontmatter, content: body }; +}; + +// Normalize a path: trim whitespace, expand ~, resolve to absolute +const normalizePath = (p, homeDir) => { + if (!p || typeof p !== 'string') return null; + let normalized = p.trim(); + if (!normalized) return null; + if (normalized.startsWith('~/')) { + normalized = path.join(homeDir, normalized.slice(2)); + } else if (normalized === '~') { + normalized = homeDir; + } + return path.resolve(normalized); +}; + +// Module-level cache for bootstrap content. +// The SKILL.md file does not change during a session, so reading + parsing it +// once eliminates redundant fs.existsSync + fs.readFileSync + regex work on +// every agent step. See #1202 for the full analysis. +let _bootstrapCache = undefined; // undefined = not yet loaded, null = file missing + +export const SuperpowersPlugin = async ({ client, directory }) => { + const homeDir = os.homedir(); + const superpowersSkillsDir = path.resolve(__dirname, '../../skills'); + const envConfigDir = normalizePath(process.env.OPENCODE_CONFIG_DIR, homeDir); + const configDir = envConfigDir || path.join(homeDir, '.config/opencode'); + + // Helper to generate bootstrap content (cached after first call) + const getBootstrapContent = () => { + // Return cached result on subsequent calls + if (_bootstrapCache !== undefined) return _bootstrapCache; + + // Try to load using-superpowers skill + const skillPath = path.join(superpowersSkillsDir, 'using-superpowers', 'SKILL.md'); + if (!fs.existsSync(skillPath)) { + _bootstrapCache = null; + return null; + } + + const fullContent = fs.readFileSync(skillPath, 'utf8'); + const { content } = extractAndStripFrontmatter(fullContent); + + const toolMapping = `**Tool Mapping for OpenCode:** +When skills request actions, substitute OpenCode equivalents: +- Create or update todos → \`todowrite\` +- \`Subagent (general-purpose):\` → \`task\` with \`subagent_type: "general"\` +- Invoke a skill → OpenCode's native \`skill\` tool +- Read files → \`read\` +- Create, edit, or delete files → \`apply_patch\` +- Run shell commands → \`bash\` +- Search files → \`grep\`, \`glob\` +- Fetch a URL → \`webfetch\` + +Use OpenCode's native \`skill\` tool to list and load skills.`; + + _bootstrapCache = ` +You have superpowers. + +**IMPORTANT: The using-superpowers skill content is included below. It is ALREADY LOADED - you are currently following it. Do NOT use the skill tool to load "using-superpowers" again - that would be redundant.** + +${content} + +${toolMapping} +`; + + return _bootstrapCache; + }; + + return { + // Inject skills path into live config so OpenCode discovers superpowers skills + // without requiring manual symlinks or config file edits. + // This works because Config.get() returns a cached singleton — modifications + // here are visible when skills are lazily discovered later. + config: async (config) => { + config.skills = config.skills || {}; + config.skills.paths = config.skills.paths || []; + if (!config.skills.paths.includes(superpowersSkillsDir)) { + config.skills.paths.push(superpowersSkillsDir); + } + }, + + // Inject bootstrap into the first user message of each session. + // Using a user message instead of a system message avoids: + // 1. Token bloat from system messages repeated every turn (#750) + // 2. Multiple system messages breaking Qwen and other models (#894) + // + // The hook fires on every agent step (not just every turn) because + // opencode's prompt.ts reloads messages from DB each step. Fresh message + // arrays may need injection again, so getBootstrapContent() must not do + // repeated disk work. + 'experimental.chat.messages.transform': async (_input, output) => { + const bootstrap = getBootstrapContent(); + if (!bootstrap || !output.messages.length) return; + const firstUser = output.messages.find(m => m.info.role === 'user'); + if (!firstUser || !firstUser.parts.length) return; + + // Guard: skip if first user message already contains bootstrap. + // This prevents double injection when OpenCode passes an already + // transformed in-memory message array through the hook again. + if (firstUser.parts.some(p => p.type === 'text' && p.text.includes('EXTREMELY_IMPORTANT'))) return; + + const ref = firstUser.parts[0]; + firstUser.parts.unshift({ ...ref, type: 'text', text: bootstrap }); + } + }; +}; diff --git a/.pi/extensions/superpowers.ts b/.pi/extensions/superpowers.ts new file mode 100644 index 0000000..a978e80 --- /dev/null +++ b/.pi/extensions/superpowers.ts @@ -0,0 +1,121 @@ +import { readFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; + +const EXTREMELY_IMPORTANT_MARKER = ""; +const BOOTSTRAP_MARKER = "superpowers:using-superpowers bootstrap for pi"; + +const extensionDir = dirname(fileURLToPath(import.meta.url)); +const packageRoot = resolve(extensionDir, "../.."); +const skillsDir = resolve(packageRoot, "skills"); +const bootstrapSkillPath = resolve(skillsDir, "using-superpowers", "SKILL.md"); + +let cachedBootstrap: string | null | undefined; + +export default function superpowersPiExtension(pi: ExtensionAPI) { + let injectBootstrap = true; + + pi.on("resources_discover", async () => ({ + skillPaths: [skillsDir], + })); + + pi.on("session_start", async () => { + injectBootstrap = true; + }); + + pi.on("session_compact", async () => { + injectBootstrap = true; + }); + + pi.on("agent_end", async () => { + injectBootstrap = false; + }); + + pi.on("context", async (event) => { + if (!injectBootstrap) return; + if (event.messages.some(messageContainsBootstrap)) return; + + const bootstrap = getBootstrapContent(); + if (!bootstrap) return; + + const bootstrapMessage = { + role: "user" as const, + content: [{ type: "text" as const, text: bootstrap }], + timestamp: Date.now(), + }; + + const insertAt = firstNonCompactionSummaryIndex(event.messages); + return { + messages: [ + ...event.messages.slice(0, insertAt), + bootstrapMessage, + ...event.messages.slice(insertAt), + ], + }; + }); +} + +function getBootstrapContent(): string | null { + if (cachedBootstrap !== undefined) return cachedBootstrap; + + try { + const skillContent = readFileSync(bootstrapSkillPath, "utf8"); + const body = stripFrontmatter(skillContent); + cachedBootstrap = `${EXTREMELY_IMPORTANT_MARKER} +${BOOTSTRAP_MARKER} + +You have superpowers. + +The using-superpowers skill content is included below and is already loaded for this Pi session. Follow it now. Do not try to load using-superpowers again. + +${body} + +${piToolMapping()} +`; + return cachedBootstrap; + } catch { + cachedBootstrap = null; + return null; + } +} + +function stripFrontmatter(content: string): string { + const match = content.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/); + return (match ? match[1] : content).trim(); +} + +function piToolMapping(): string { + return `## Pi tool mapping + +Pi has native skills but does not expose Claude Code's \`Skill\` tool. When a Superpowers instruction says to invoke a skill, use Pi's native skill system instead: load the relevant \`SKILL.md\` with \`read\` when the skill applies, or let a human invoke \`/skill:name\` explicitly. + +Pi's built-in coding tools are lowercase: \`read\`, \`write\`, \`edit\`, \`bash\`, plus optional \`grep\`, \`find\`, and \`ls\`. Use those for the corresponding actions: read a file, create or edit files, run shell commands, search file contents, find files by name, and list directories. + +Pi does not ship a standard subagent tool. If a subagent tool such as \`subagent\` from \`pi-subagents\` is available, use it for Superpowers subagent workflows. If no subagent tool is available, do the work in this session or explain the missing capability instead of inventing \`Task\` calls. + +Pi does not ship a standard task-list tool. If an installed todo/task tool is available, use it. Otherwise track work in plan files or a repo-local \`TODO.md\` when task tracking is needed. Treat older \`TodoWrite\` references as this task-tracking action.`; +} + +function messageContainsBootstrap(message: unknown): boolean { + const content = (message as { content?: unknown }).content; + if (typeof content === "string") return content.includes(BOOTSTRAP_MARKER); + if (!Array.isArray(content)) return false; + return content.some((part) => { + return ( + part && + typeof part === "object" && + (part as { type?: unknown }).type === "text" && + typeof (part as { text?: unknown }).text === "string" && + (part as { text: string }).text.includes(BOOTSTRAP_MARKER) + ); + }); +} + +function firstNonCompactionSummaryIndex(messages: unknown[]): number { + let index = 0; + while ((messages[index] as { role?: unknown } | undefined)?.role === "compactionSummary") { + index += 1; + } + return index; +} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..08fd36d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,21 @@ +repos: + - repo: local + hooks: + - id: evals-ruff-check + name: evals ruff check + entry: uv --project evals run ruff check + language: system + files: ^evals/.*\.py$ + + - id: evals-ruff-format-check + name: evals ruff format --check + entry: uv --project evals run ruff format --check + language: system + files: ^evals/.*\.py$ + + - id: evals-ty-check + name: evals ty check + entry: uv --directory evals run ty check + language: system + pass_filenames: false + files: ^evals/.*\.py$ diff --git a/.version-bump.json b/.version-bump.json new file mode 100644 index 0000000..7fc6cc9 --- /dev/null +++ b/.version-bump.json @@ -0,0 +1,21 @@ +{ + "files": [ + { "path": "package.json", "field": "version" }, + { "path": ".claude-plugin/plugin.json", "field": "version" }, + { "path": ".cursor-plugin/plugin.json", "field": "version" }, + { "path": ".codex-plugin/plugin.json", "field": "version" }, + { "path": ".kimi-plugin/plugin.json", "field": "version" }, + { "path": ".claude-plugin/marketplace.json", "field": "plugins.0.version" }, + { "path": "gemini-extension.json", "field": "version" } + ], + "audit": { + "exclude": [ + "CHANGELOG.md", + "RELEASE-NOTES.md", + "node_modules", + ".git", + ".version-bump.json", + "scripts/bump-version.sh" + ] + } +} diff --git a/AGENTS.md b/AGENTS.md new file mode 120000 index 0000000..681311e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +CLAUDE.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..f8e45e9 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,115 @@ +# Superpowers — Contributor Guidelines + +## If You Are an AI Agent + +Stop. Read this section before doing anything. + +This repo has a 94% PR rejection rate. Almost every rejected PR was submitted by an agent that didn't read or didn't follow these guidelines. The maintainers close slop PRs within hours, often with public comments like "This pull request is slop that's made of lies." + +**Your job is to protect your human partner from that outcome.** Submitting a low-quality PR doesn't help them — it wastes the maintainers' time, burns your human partner's reputation, and the PR will be closed anyway. That is not being helpful. That is being a tool of embarrassment. + +Before you open a PR against this repo, you MUST: + +1. **Read the entire PR template** at `.github/PULL_REQUEST_TEMPLATE.md` and fill in every section with real, specific answers. Not summaries. Not placeholders. +2. **Search for existing PRs** — open AND closed — that address the same problem. If duplicates exist, STOP and tell your human partner. Do not open another duplicate. +3. **Verify this is a real problem.** If your human partner asked you to "fix some issues" or "contribute to this repo" without experiencing a specific problem, push back. Ask them what broke, what failed, what the user experience was. +4. **Confirm the change belongs in core.** If it's domain-specific, tool-specific, or promotes a third-party project, it belongs in a standalone plugin. Tell your human partner. +5. **Identify yourself.** Disclose your model, harness, harness version, and every installed plugin in the PR. Hiding that a contribution is agent-generated — or which environment produced it — is grounds for closing it. +6. **Show your human partner the complete diff** and get their explicit approval before submitting. + +If any of these checks fail, do not open the PR. Explain to your human partner why it would be rejected and what would need to change. They will thank you for saving them the embarrassment. + +## Pull Request Requirements + +**Every PR must fully complete the PR template.** No section may be left blank or filled with placeholder text. PRs that skip sections will be closed without review. + +**Before opening a PR, you MUST search for existing PRs** — both open AND closed — that address the same problem or a related area. Reference what you found in the "Existing PRs" section. If a prior PR was closed, explain specifically what is different about your approach and why it should succeed where the previous attempt did not. + +**PRs that show no evidence of human involvement will be closed.** A human must review the complete proposed diff before submission. + +**Submitters MUST identify themselves.** Every PR and issue must disclose the model, harness, harness version, and all installed plugins used to produce the contribution — or state plainly that it was written by hand with no agent. This is not optional. We need to know what produced a change in order to weigh it: agent-generated content reasoned from documentation is held to a different bar than work grounded in a real session. Contributions that hide their authoring environment will be closed. + +**All PRs MUST target the `dev` branch, not `main`.** `main` is the released branch; active work lands on `dev` first. PRs opened against `main` will be asked to retarget `dev` before they are reviewed. + +## What We Will Not Accept + +### Third-party dependencies + +PRs that add optional or required dependencies on third-party projects will not be accepted unless they are adding support for a new harness (e.g., a new IDE or CLI tool). Superpowers is a zero-dependency plugin by design. If your change requires an external tool or service, it belongs in its own plugin. + +### "Compliance" changes to skills + +Our internal skill philosophy differs from Anthropic's published guidance on writing skills. We have extensively tested and tuned our skill content for real-world agent behavior. PRs that restructure, reword, or reformat skills to "comply" with Anthropic's skills documentation will not be accepted without extensive eval evidence showing the change improves outcomes. The bar for modifying behavior-shaping content is very high. + +### Project-specific or personal configuration + +Skills, hooks, or configuration that only benefit a specific project, team, domain, or workflow do not belong in core. Publish these as a separate plugin. + +### Bulk or spray-and-pray PRs + +Do not trawl the issue tracker and open PRs for multiple issues in a single session. Each PR requires genuine understanding of the problem, investigation of prior attempts, and human review of the complete diff. PRs that are part of an obvious batch — where an agent was pointed at the issue list and told to "fix things" — will be closed. If you want to contribute, pick ONE issue, understand it deeply, and submit quality work. + +### Speculative or theoretical fixes + +Every PR must solve a real problem that someone actually experienced. "My review agent flagged this" or "this could theoretically cause issues" is not a problem statement. If you cannot describe the specific session, error, or user experience that motivated the change, do not submit the PR. + +### Domain-specific skills + +Superpowers core contains general-purpose skills that benefit all users regardless of their project. Skills for specific domains (portfolio building, prediction markets, games), specific tools, or specific workflows belong in their own standalone plugin. Ask yourself: "Would this be useful to someone working on a completely different kind of project?" If not, publish it separately. + +### Fork-specific changes + +If you maintain a fork with customizations, do not open PRs to sync your fork or push fork-specific changes upstream. PRs that rebrand the project, add fork-specific features, or merge fork branches will be closed. + +### Fabricated content + +PRs containing invented claims, fabricated problem descriptions, or hallucinated functionality will be closed immediately. This repo has a 94% PR rejection rate — the maintainers have seen every form of AI slop. They will notice. + +### Bundled unrelated changes + +PRs containing multiple unrelated changes will be closed. Split them into separate PRs. + +## New Harness Support + +If your PR adds support for a new harness (IDE, CLI tool, agent runner), you MUST include a session transcript proving the integration works end-to-end. + +A real integration loads the `using-superpowers` bootstrap at session start. The bootstrap is what causes skills to auto-trigger at the right moments. Without it, the skills are dead weight — present on disk but never invoked. + +**The acceptance test.** Open a clean session in the new harness and send exactly this user message: + +> Let's make a react todo list + +A working integration auto-triggers the `brainstorming` skill before any code is written. Paste the complete transcript in the PR. + +**These are not real integrations and will be closed:** + +- Manually copying skill files into the harness +- Wrapping with `npx skills` or similar at-runtime shims +- Anything that requires the user to opt in to skills per-session +- Anything where `brainstorming` does not auto-trigger on the acceptance test above + +If you are not sure whether your integration loads the bootstrap at session start, it does not. + +## Skill Changes Require Evaluation + +Skills are not prose — they are code that shapes agent behavior. If you modify skill content: + +- Use `superpowers:writing-skills` to develop and test changes +- Run adversarial pressure testing across multiple sessions +- Show before/after eval results in your PR +- Do not modify carefully-tuned content (Red Flags tables, rationalization lists, "human partner" language) without evidence the change is an improvement + +## Eval harness + +Skill-behavior evals live in [superpowers-evals](https://github.com/prime-radiant-inc/superpowers-evals/), cloned into `evals/` — see `evals/README.md` for setup. The harness drives real tmux sessions of Claude Code / Codex and judges skill compliance with an LLM verifier. Plugin-infrastructure tests still live at `tests/`. + +## Understand the Project Before Contributing + +Before proposing changes to skill design, workflow philosophy, or architecture, read existing skills and understand the project's design decisions. Superpowers has its own tested philosophy about skill design, agent behavior shaping, and terminology (e.g., "your human partner" is deliberate, not interchangeable with "the user"). Changes that rewrite the project's voice or restructure its approach without understanding why it exists will be rejected. + +## General + +- Read `.github/PULL_REQUEST_TEMPLATE.md` before submitting +- One problem per PR +- Test on at least one harness and report results in the environment table +- Describe the problem you solved, not just what you changed diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..f642254 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +jesse@primeradiant.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..0dd2f58 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,2 @@ +@./skills/using-superpowers/SKILL.md +@./skills/using-superpowers/references/gemini-tools.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..abf0390 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Jesse Vincent + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..48e3f19 --- /dev/null +++ b/README.md @@ -0,0 +1,273 @@ +# Superpowers + +Superpowers is a complete software development methodology for your coding agents, built on top of a set of composable skills and some initial instructions that make sure your agent uses them. + + +## We're Hiring! + +We're hiring someone to help out full time with Superpowers community and code work. +You can read about the job at https://primeradiant.com/jobs/superpowers-community-engineer/ +If this sounds like someone you know, definitely send them our way. + +## Quickstart + +Give your agent Superpowers: [Claude Code](#claude-code), [Antigravity](#antigravity), [Codex App](#codex-app), [Codex CLI](#codex-cli), [Cursor](#cursor), [Factory Droid](#factory-droid), [GitHub Copilot CLI](#github-copilot-cli), [Kimi Code](#kimi-code), [OpenCode](#opencode), [Pi](#pi). + +## How it works + +It starts from the moment you fire up your coding agent. As soon as it sees that you're building something, it *doesn't* just jump into trying to write code. Instead, it steps back and asks you what you're really trying to do. + +Once it's teased a spec out of the conversation, it shows it to you in chunks short enough to actually read and digest. + +After you've signed off on the design, your agent puts together an implementation plan that's clear enough for an enthusiastic junior engineer with poor taste, no judgement, no project context, and an aversion to testing to follow. It emphasizes true red/green TDD, YAGNI (You Aren't Gonna Need It), and DRY. + +Next up, once you say "go", it launches a *subagent-driven-development* process, having agents work through each engineering task, inspecting and reviewing their work, and continuing forward. It's not uncommon for your agent to work autonomously for a couple hours at a time without deviating from the plan you put together. + +There's a bunch more to it, but that's the core of the system. And because the skills trigger automatically, you don't need to do anything special. Your coding agent just has Superpowers. + +## Commercial Services + +If you're using Superpowers in enterprise and could benefit from commercial support, additional tooling, or managed spending, please don't hesitate to drop us a line at sales@primeradiant.com. + +## Installation + +Installation differs by harness. If you use more than one, install Superpowers separately for each one. + +### Claude Code + +Superpowers is available via the [official Claude plugin marketplace](https://claude.com/plugins/superpowers) + +#### Official Marketplace + +- Install the plugin from Anthropic's official marketplace: + + ```bash + /plugin install superpowers@claude-plugins-official + ``` + +#### Superpowers Marketplace + +The Superpowers marketplace provides Superpowers and some other related plugins for Claude Code. + +- Register the marketplace: + + ```bash + /plugin marketplace add obra/superpowers-marketplace + ``` + +- Install the plugin from this marketplace: + + ```bash + /plugin install superpowers@superpowers-marketplace + ``` + +### Antigravity + +Install Superpowers as a plugin from this repository: + +```bash +agy plugin install https://github.com/obra/superpowers +``` + +Antigravity runs the plugin's session-start hook, so Superpowers is active from +the first message. Reinstall with the same command to update. + +### Codex App + +Superpowers is available via the [official Codex plugin marketplace](https://github.com/openai/plugins). + +- In the Codex app, click on Plugins in the sidebar. +- You should see `Superpowers` in the Coding section. +- Click the `+` next to Superpowers and follow the prompts. + +### Codex CLI + +Superpowers is available via the [official Codex plugin marketplace](https://github.com/openai/plugins). + +- Open the plugin search interface: + + ```bash + /plugins + ``` + +- Search for Superpowers: + + ```bash + superpowers + ``` + +- Select `Install Plugin`. + +### Cursor + +- In Cursor Agent chat, install from marketplace: + + ```text + /add-plugin superpowers + ``` + +- Or search for "superpowers" in the plugin marketplace. + +### Factory Droid + +- Register the marketplace: + + ```bash + droid plugin marketplace add https://github.com/obra/superpowers + ``` + +- Install the plugin: + + ```bash + droid plugin install superpowers@superpowers + ``` + +### GitHub Copilot CLI + +- Register the marketplace: + + ```bash + copilot plugin marketplace add obra/superpowers-marketplace + ``` + +- Install the plugin: + + ```bash + copilot plugin install superpowers@superpowers-marketplace + ``` + +### Kimi Code + +Superpowers is available in Kimi Code's plugin marketplace. + +- Open Kimi Code's plugin manager: + + ```text + /plugins + ``` + +- Go to `Marketplace` > `Superpowers` and install it. + +- Or install directly from this repository: + + ```text + /plugins install https://github.com/obra/superpowers + ``` + +- Detailed docs: [docs/README.kimi.md](docs/README.kimi.md) + +### OpenCode + +OpenCode uses its own plugin install; install Superpowers separately even if you +already use it in another harness. + +- Tell OpenCode: + + ``` + Fetch and follow instructions from https://raw.githubusercontent.com/obra/superpowers/refs/heads/main/.opencode/INSTALL.md + ``` + +- Detailed docs: [docs/README.opencode.md](docs/README.opencode.md) + +### Pi + +Install Superpowers as a Pi package from this repository: + +```bash +pi install git:github.com/obra/superpowers +``` + +For local development, run Pi with this checkout loaded as a temporary package: + +```bash +pi -e /path/to/superpowers +``` + +The Pi package loads the Superpowers skills and a small extension that injects the `using-superpowers` bootstrap at session startup and again after compaction. Pi has native skills, so no compatibility `Skill` tool is required. Subagent and task-list tools remain optional Pi companion packages. + +## The Basic Workflow + +1. **brainstorming** - Activates before writing code. Refines rough ideas through questions, explores alternatives, presents design in sections for validation. Saves design document. + +2. **using-git-worktrees** - Activates after design approval. Creates isolated workspace on new branch, runs project setup, verifies clean test baseline. + +3. **writing-plans** - Activates with approved design. Breaks work into bite-sized tasks (2-5 minutes each). Every task has exact file paths, complete code, verification steps. + +4. **subagent-driven-development** or **executing-plans** - Activates with plan. Dispatches fresh subagent per task with two-stage review (spec compliance, then code quality), or executes in batches with human checkpoints. + +5. **test-driven-development** - Activates during implementation. Enforces RED-GREEN-REFACTOR: write failing test, watch it fail, write minimal code, watch it pass, commit. Deletes code written before tests. + +6. **requesting-code-review** - Activates between tasks. Reviews against plan, reports issues by severity. Critical issues block progress. + +7. **finishing-a-development-branch** - Activates when tasks complete. Verifies tests, presents options (merge/PR/keep/discard), cleans up worktree. + +**The agent checks for relevant skills before any task.** Mandatory workflows, not suggestions. + +## What's Inside + +### Skills Library + +**Testing** +- **test-driven-development** - RED-GREEN-REFACTOR cycle (includes testing anti-patterns reference) + +**Debugging** +- **systematic-debugging** - 4-phase root cause process (includes root-cause-tracing, defense-in-depth, condition-based-waiting techniques) +- **verification-before-completion** - Ensure it's actually fixed + +**Collaboration** +- **brainstorming** - Socratic design refinement +- **writing-plans** - Detailed implementation plans +- **executing-plans** - Batch execution with checkpoints +- **dispatching-parallel-agents** - Concurrent subagent workflows +- **requesting-code-review** - Pre-review checklist +- **receiving-code-review** - Responding to feedback +- **using-git-worktrees** - Parallel development branches +- **finishing-a-development-branch** - Merge/PR decision workflow +- **subagent-driven-development** - Fast iteration with two-stage review (spec compliance, then code quality) + +**Meta** +- **writing-skills** - Create new skills following best practices (includes testing methodology) +- **using-superpowers** - Introduction to the skills system + +## Philosophy + +- **Test-Driven Development** - Write tests first, always +- **Systematic over ad-hoc** - Process over guessing +- **Complexity reduction** - Simplicity as primary goal +- **Evidence over claims** - Verify before declaring success + +Read [the original release announcement](https://blog.fsck.com/2025/10/09/superpowers/). + +## Contributing + +The general contribution process for Superpowers is below. Keep in mind that we don't generally accept contributions of new skills and that any updates to skills must work across all of the coding agents we support. + +1. Fork the repository +2. Switch to the 'dev' branch +3. Create a branch for your work +4. Follow the `writing-skills` skill for creating and testing new and modified skills +5. Submit a PR, being sure to fill in the pull request template. + +Skill-behavior tests use the drill eval harness from [superpowers-evals](https://github.com/prime-radiant-inc/superpowers-evals/), cloned into `evals/` — see `evals/README.md` for setup. Plugin-infrastructure tests live at `tests/` and run via the relevant `run-*.sh` or `npm test`. + +See `skills/writing-skills/SKILL.md` for the complete guide. + +## Updating + +Superpowers updates are somewhat coding-agent dependent, but are often automatic. + +## License + +MIT License - see LICENSE file for details + +## Visual companion telemetry + +Because skills and plugins don't provide any feedback to creators, we have no idea how many of you are using Superpowers. By default, the Prime Radiant logo on brainstorming's optional visual companion feature is loaded from our website. It includes the version of Superpowers in use. It does not include any details about your project, prompt, or coding agent. We don't see your clicks or anything about what you're building. This helps us have a rough idea of how many folks are using Superpowers and which version of Superpowers they're using. It's 100% optional. To disable this, set the environment variable `SUPERPOWERS_DISABLE_TELEMETRY` to any true value. Superpowers also honors Claude Code's `DISABLE_TELEMETRY` and `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` opt-outs. + +## Community + +Superpowers is built by [Jesse Vincent](https://blog.fsck.com) and the rest of the folks at [Prime Radiant](https://primeradiant.com). + +- **Discord**: [Join us](https://discord.gg/35wsABTejz) for community support, questions, and sharing what you're building with Superpowers +- **Issues**: https://github.com/obra/superpowers/issues +- **Release announcements**: [Sign up](https://primeradiant.com/superpowers/) to get notified about new versions diff --git a/README.wehub.md b/README.wehub.md new file mode 100644 index 0000000..d03cdce --- /dev/null +++ b/README.wehub.md @@ -0,0 +1,7 @@ +# WeHub 来源说明 + +- 原始项目:`obra/superpowers` +- 原始仓库:https://github.com/obra/superpowers +- 导入方式:上游默认分支的最新快照 +- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准 +- 本文件仅用于记录来源,不代表 WeHub 是原项目作者 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md new file mode 100644 index 0000000..823948e --- /dev/null +++ b/RELEASE-NOTES.md @@ -0,0 +1,1328 @@ +# Superpowers Release Notes + +## v6.1.1 (2026-07-02) + +### Codex + +- **Codex no longer re-registers the Claude SessionStart hook.** v6.1.0 removed the Codex hook config and its manifest `hooks` pointer, meaning to stop Codex from installing a SessionStart hook — but with no `hooks` field, Codex fell back to auto-discovering `hooks/hooks.json`, the Claude Code SessionStart hook that the marketplace ships from the repo root, and re-registered it along with its install-time trust prompt. The Codex manifest now declares an explicit empty hooks object (`hooks: {}`), which Codex reads as "no hooks" instead of reaching the auto-discovery fallback. An absent field, `[]`, and an empty inline list all collapse back to the fallback, so the value has to be exactly `{}`. +- **Removed orphaned Codex session-start dead code.** `hooks/session-start-codex` had no caller once the Codex hook config was deleted, so it and its redundant test cases are gone. The worked shell-hook example in `docs/porting-to-a-new-harness.md` moves from Codex — now native skill discovery with no session-start hook — to Cursor, a live shell-hook harness, and the stale `hooks-codex.json` pointer in `docs/windows/polyglot-hooks.md` is corrected. The Codex plugin category is also fixed to "Developer Tools". + +### Packaging + +- **New `package-codex-plugin.sh` for building the Codex portal package.** A maintainer script produces a deterministic Codex "portal" archive — `.zip` by default, `tar.gz` on request — that normalizes entry timestamps, preserves executable modes, verifies every packaged skill ships its OpenAI metadata, includes the app and composer icons, and refuses to run against a dirty worktree. The packaged manifest keeps the source `hooks: {}` object so a portal-installed plugin avoids the same SessionStart auto-discovery, and the script can rebuild a byte-identical archive from a saved metadata source. Covered by a new test suite. + +## v6.1.0 (2026-06-30) + +### Lower Per-Session Token Cost + +The `using-superpowers` bootstrap is injected into every session, so its size is paid for constantly. This release trims it and the per-harness references it points to, without dropping behavior-shaping content. + +- **Compressed the `using-superpowers` bootstrap.** Replaced the graphviz skill-flow diagram with the prose it encoded, folded the standalone Instruction-Priority section into User Instructions, dropped the per-platform "How to Access Skills" walkthrough, and trimmed the Platform Adaptation pointer to the harnesses that still ship a reference file. The full Red Flags rationalization table and the user-instruction precedence rules are unchanged. +- **Pruned the per-harness tool-mapping references.** The verbose action-to-tool tables restated guidance modern agents already follow. Each reference file is trimmed to the harness-specific notes that still carry weight — subagent dispatch, task tracking, instructions-file paths — and `claude-code-tools.md` and `copilot-tools.md`, which had nothing harness-specific left, are deleted. + +### Codex + +- **Codex can install from the marketplace.** Codex marketplace sources expect a `.agents/plugins/marketplace.json` at the marketplace root; the repo only shipped the Claude marketplace file, so Codex could name the marketplace but found no installable plugin entries. A repo-local Codex marketplace manifest now points at the same repository root, so the plugin is installable from Codex. +- **Codex no longer ships a SessionStart hook.** Codex reliably triggers skills on its own, and the bootstrap hook made the UX worse rather than better. The Codex hook config (`hooks-codex.json`) and its manifest registration are removed. + +### Harness Support + +- **Gemini CLI support removed.** Google EOLed the Gemini CLI on 2026-06-18; the extension can no longer be installed or updated. Gemini is gone from the install docs, the subagent-capable platform lists, and the eval-harness description, and its tool-mapping reference is deleted. + +## v6.0.3 (2026-06-18) + +### Subagent-Driven Development + +- **SDD scratch files moved out of `.git/`.** Claude Code treats `.git/` as a protected path and denies agent writes there, so an implementer subagent writing its report into `.git/sdd/` got blocked mid-run. Task briefs, implementer reports, review diffs, and the progress ledger now live in a self-ignoring `.superpowers/sdd/` directory in the working tree — kept out of `git status` and out of commits, and resolved per worktree by a shared `sdd-workspace` helper. One caveat: because the workspace is git-ignored working-tree scratch, `git clean -fdx` will delete the progress ledger; recover from `git log` if that happens. (#1780) + +## v6.0.2 (2026-06-16) + +### Install Fixes + +- **We no longer ship the `evals` submodule.** It broke plugin installs for some users, so the eval harness now lives in its own repo, separate from the published plugin. (#1778, #1774) + +## v6.0.1 (2026-06-16) + +### Codex Fixes + +- **Version display in the brainstorm companion** — packaged Codex plugins ship without a root `package.json`, so the visual companion reported its version as "unknown". `readSuperpowersVersion()` now falls back to `.codex-plugin/plugin.json` when `package.json` is absent. +- **Cleaner Codex plugin sync** — the sync-to-codex script now excludes `.gitmodules` and `.pre-commit-config.yaml`, keeping repo metadata out of the packaged Codex plugin. + +## v6.0.0 (2026-06-16) + +Superpowers 6.0 is a big release. The headline is a rewrite of how `subagent-driven-development` reviews each task — cheaper, stricter, and harder to game. + +While these numbers won't hold on every harness and for every workload, in our evals, Claude Code and Codex produce similar high-quality results roughly twice as fast and while spending almost 50% fewer tokens. + +It also adds three new harnesses (Kimi Code, Pi, and Antigravity), gives the brainstorming visual companion a better security model, and rewrites a number of skills' tool calls to be significantly more vendor-neutral. + +### Visible Changes + +- **The two per-task reviewer prompts became one.** `spec-reviewer-prompt.md` and `code-quality-reviewer-prompt.md` are gone, replaced by a single `task-reviewer-prompt.md`. If you dispatch the old files directly, switch to the new one. +- **The legacy global worktree directory is gone.** `using-git-worktrees` and `finishing-a-development-branch` no longer use `~/.config/superpowers/worktrees/`. Worktrees now land in the project — an existing `.worktrees/` or `worktrees/` if you have one, otherwise a fresh `.worktrees/` — unless you say otherwise. + +### New Harness Support + +Superpowers now runs on three more harnesses. Each ships its own bootstrap, a tool-mapping reference, and tests, and each gets its own install section in the README. + +- **Kimi Code** — a plugin manifest, install docs, and manifest tests; install from Kimi's marketplace or straight from the repo. (initial manifest by @qer) +- **Pi** — a session-start extension that registers the skills and injects the `using-superpowers` bootstrap. Pi has native skills, so it needs no compatibility shim. +- **Antigravity (`agy`)** — installs the plugin directly and bootstraps from the first message; verified end-to-end against the standard "make a react todo list" acceptance test. + +### Subagent-Driven Development + +A long run of cost-and-quality experiments on real projects reshaped how the controller reviews each task. The old flow ran two reviewers per task and leaned on the controller's judgment for model choice and severity, and both turned out to be expensive and easy to game. The new flow runs one reviewer per task, hands work off as files instead of pasted text, and takes several judgment calls away from the controller. + +- **One reviewer per task, two verdicts.** A single `task-reviewer-prompt.md` reads the task's diff once and returns both a spec-compliance verdict and a quality verdict, so one fix pass clears both. A new "can't verify from the diff" verdict flags requirements that live in untouched code, for the controller to check itself. (#1538, #1543) +- **One broad review at the end.** The run finishes with a single whole-branch review on the most capable model, instead of re-reviewing everything task by task. +- **Plans get a pre-flight read.** Before the first task, the controller checks the plan for internal conflicts — and for anything the plan asks for that a reviewer would flag as a defect — and raises it all at once, rather than stumbling into it mid-run. +- **Diffs and task text move as files.** A pasted diff parks itself permanently in the most expensive context, and a reviewer without one rebuilds it by hand — the single biggest reviewer cost. Two new scripts, `task-brief` and `review-package`, write the task text and the review diff to files for the subagent to read. +- **Every dispatch states its model.** Left to choose, controllers stopped naming a model at all — and an unnamed model quietly inherits the session's most expensive one, so one run put all 26 of its reviewers on the top tier. The templates now require a model, with guidance that reaches for cheaper tiers when the work allows. +- **The controller can't tell a reviewer what to ignore.** Real runs caught controllers coaching reviewers to skip a finding or call it "Minor at most," and the flaw shipped. Suppressing findings and pre-rating severity are now banned outright, and a defect the plan itself mandates gets reported for you to decide on rather than waved through. +- **Reviewers are read-only and skeptical of rationales.** Review no longer touches the working tree or branch — a reviewer running `git checkout` had been orphaning later commits — and an implementer's "I left this unabstracted on purpose" no longer talks a reviewer out of a real finding. +- **Stronger evidence and reporting.** Reviewers back each answer with a file and line, the implementer's report moves to a file and carries red/green evidence when TDD applies, and a progress ledger lets a controller that loses its context resume instead of redoing finished work. (#994) + +### Writing Plans + +Plans now carry the structure the controller and reviewers used to re-derive on every dispatch. + +- **A Global Constraints block** lists the rules that bind every task — version floors, dependency limits, naming and copy, exact values — copied in verbatim, so they actually reach the implementers and reviewers downstream. +- **A per-task Interfaces block** names exactly what each task consumes and produces, so an implementer who sees only its own task still knows its neighbors' contracts. +- **Right-sizing guidance** keeps a task at the size that earns its own test cycle and a reviewer's pass, folding setup, config, and docs into the task that needs them. In testing, a plan written this way needed one round of fixes where the control needed two to four — and the control shipped a real bug. + +### Brainstorming Visual Companion + +The visual companion is a small web server the agent opens alongside the conversation. It had no authentication at all, so on a shared or remote machine anyone who could reach the port could read your brainstorm — or inject events the agent treats as your input. This release gives it a real security model and makes it survive restarts and dropped connections. + +- **A per-session key now guards everything.** The agent's URL carries a one-time key, the browser tucks it into a tab-scoped cookie, and every request and WebSocket connection has to present it. This closes the door to stray local tabs and routable remote hosts alike, including the DNS-rebinding case an origin allowlist can't catch. (Closes #1014) +- **The file server stays in its sandbox.** It refuses symlinks, dotfiles, and any path that climbs out of the content directory, ignores macOS resource-fork files, and sends the usual no-store and deny-framing headers. Files that hold the session key are written owner-only. +- **The companion is offered only when it helps.** The skill raises it the first time a question would read better shown than told, as its own message, and lets a decline stand. Accepting opens your browser to the first screen. (Closes #755) +- **It survives restarts and flaky connections.** Given a project directory, the server keeps the same port and key across restarts, so an open tab simply reconnects. The page reconnects on its own, shows a live status pill, and raises a "paused" overlay while the server is down. +- **Longer idle life, safer shutdown.** The idle timeout went from 30 minutes to 4 hours, and `stop-server.sh` now confirms it owns the right process before signaling, so it never kills an unrelated `node` after a reboot. (#1703) +- **Windows launch hardening** — consolidated shell detection, and Windows now relies on the idle timeout for shutdown, since Node can't track POSIX process ownership across MSYS2. + + +### Existing Harness Updates + +- **Codex** now bootstraps through its own SessionStart hook rather than shared wiring, and the Codex App gained an install section and fuller tool docs (web search, `AGENTS.md`, personal skills). (#1540) +- **OpenCode** got an action-based tool mapping across its plugin, install doc, and README, plus a bootstrap-caching test. +- **Cursor**'s manifest dropped its `agents` and `commands` entries, since those directories no longer exist. + +### One Set of Skills, Every Harness + +The skills used to speak Claude Code's dialect — "use the Task tool," "put it in CLAUDE.md." This release rewrites that vocabulary in terms of what you're actually doing ("dispatch a subagent," "your instructions file") and adds a per-harness reference that maps each action to the right tool, checked against each runtime. Prose that named "Claude" now says "your agent." + +- **A tool reference per harness** at `skills/using-superpowers/references/`, covering Claude Code, Codex, Copilot, Gemini, Pi, and Antigravity. +- **`finishing-a-development-branch` went forge-neutral** — it no longer hardcodes `gh pr create`, so agents push with whatever forge tooling they have. (#1609) +- **One rename:** "Claude Search Optimization" is now "Skill Discovery Optimization," since the technique isn't Claude-specific. + +### Writing Skills + +Two additions for skill authors. + +- **Match the Form to the Failure** — a short table for picking the right kind of guidance. A flat "don't do X" works for discipline slips but backfires when the problem is the *shape* of an output, where a worked example does better. The table, and a tighter scope on the existing rationalization section, steer authors to the form that actually helps. +- **Micro-Test Wording** — a cheap way to check a phrasing before committing to it: sample it a handful of times against a no-guidance control and read every result by hand, treating run-to-run variance as a warning sign. + +### Testing + +Skill-behavior testing moved out of `tests/` into a new `evals/` submodule built on "drill," which runs real Claude Code, Codex, and Gemini sessions and judges them with an LLM. Several in-tree bash suites retired once a stricter drill scenario covered them; the few with no equivalent stayed. From here on, `tests/` holds plugin-code tests and `evals/` holds skill-behavior tests, and `docs/testing.md` explains the split. New backends reach Antigravity, Pi, and more models, and new shell-lint and pre-commit checks guard the harness. (#1541) + +### Bug Fixes + +- **systematic-debugging no longer forces every session into extended thinking.** One bullet held the exact keyword Claude Code scans for, quietly tripping the switch on every session that loaded the skill. A hyphen breaks the keyword; the text still reads. (#1283, by @Nick Galatis) +- **The Windows SessionStart hook stopped printing a write error every session** — each `printf` now routes through `cat` to absorb the broken pipe, and the output is otherwise unchanged. (#1612, reported by @silvertakana) +- **Windows foreground mode** tracks the right process and clears its owner PID on MSYS2. (by @nestorluiscamachopaz) +- **The `using-superpowers` bootstrap** no longer lists "debugging" as a skill that doesn't exist. (reported by @mhat) +- **The TDD skill** links the testing anti-patterns reference. (#1532, #1529; link fix #1474 by @Stable Genius) +- **`using-git-worktrees`** fixes its step numbering and drops stale Cursor references. (#1522, and by @fuleinist) +- **The Codex review skill** swaps a private in-joke for plain guidance. (#1531) + +### Documentation & Contributor Guidelines + +- **A guide to porting Superpowers to a new harness** (`docs/porting-to-a-new-harness.md`) lays out the three pieces every integration needs and the one rule that makes or breaks it: load the bootstrap at session start. +- **Every PR and issue now discloses how it was made** — model, harness, version, and installed plugins, or a note that it was written by hand. We weigh a contribution differently depending on what produced it. PRs also target `dev`, not `main`. The PR template, all three issue templates, and a new platform-support template carry this. + +### Contributors + +Thanks to @mattvanhorn, @nawfal, @Nick Galatis, @silvertakana, @nestorluiscamachopaz, @qer, @mhat, @Stable Genius, @fuleinist, @dev_Hakaze, @robotsnh, Rahul, and @arittr. + +## v5.1.0 (2026-04-30) + +### Removals + +- **Legacy slash commands removed** — `/brainstorm`, `/execute-plan`, and `/write-plan` are gone. They were deprecated stubs that did nothing but tell the user to invoke the corresponding skill. Invoke `superpowers:brainstorming`, `superpowers:executing-plans`, and `superpowers:writing-plans` directly instead. (#1188) +- **`superpowers:code-reviewer` named agent removed** — the agent was the plugin's only named agent and was used by exactly two skills, while every other reviewer/implementer subagent in the repo dispatches `general-purpose` with a prompt template alongside its skill. The agent's persona and checklist have been merged into `skills/requesting-code-review/code-reviewer.md` as a self-contained Task-dispatch template. Anyone dispatching `Task (superpowers:code-reviewer)` should switch to `Task (general-purpose)` with the prompt template instead. (PR #1299) +- **Integration sections removed from skills** — these were a legacy of the time before agents had native skills systems and didn't help with steering. + +### Worktree Skills Rewrite + +`using-git-worktrees` and `finishing-a-development-branch` now detect when the agent is already running inside an isolated worktree and prefer the harness's native worktree controls before falling back to `git worktree`. Behavior was TDD-validated and cross-platform-checked across five harnesses. (PRI-974, PR #1121) + +- **Environment detection** — both skills check `GIT_DIR != GIT_COMMON` before doing anything; if already in a linked worktree, creation is skipped entirely. A submodule guard prevents false detection. +- **Consent before creating worktrees** — `using-git-worktrees` no longer creates worktrees implicitly; the skill asks the user first. Fixes #991 (subagent-driven-development was auto-creating worktrees without consent). +- **Native tool preference (Step 1a)** — when the harness exposes its own worktree tool (e.g. Codex), the skill defers to it. The user's stated preference is respected when expressed. +- **Provenance-based cleanup** — `finishing-a-development-branch` only cleans up worktrees inside `.worktrees/` (created by superpowers); anything outside is left alone. Fixes #940 (Option 2 was incorrectly cleaning up worktrees), #999 (merge-then-remove ordering), and #238 (`cd` to repo root before `git worktree remove`). +- **Detached HEAD handling** — the finishing menu collapses to two options when there is no branch to merge from. +- **Hardcoded `/Users/jesse` paths** in skill examples replaced with generic placeholders. (#858, PR #1122) + +### Contributor Guidelines for AI Agents + +Two new sections at the top of `CLAUDE.md` (symlinked to `AGENTS.md`) speak directly to AI agents. An audit of the last 100 closed PRs against this repo showed a 94% rejection rate driven by AI-generated slop: agents that didn't read the PR template, opened duplicates, fabricated problem descriptions, or pushed fork- or domain-specific changes upstream. + +- **Pre-submission checklist** — read the PR template, search for existing PRs, verify a real problem exists, confirm the change belongs in core, and show the human partner the complete diff before submitting. +- **What we will not accept** — third-party dependencies, "compliance" rewrites of skill content, project-specific configuration, bulk PRs, speculative fixes, domain-specific skills, fork-specific changes, fabricated content, and bundled unrelated changes. +- **New harness PRs require a session transcript** — most past new-harness integrations copied skill files or wrapped with `npx skills` instead of loading the `using-superpowers` bootstrap at session start. The acceptance test ("Let's make a react todo list" must auto-trigger `brainstorming` in a clean session) and a complete transcript are now required. + +### Codex Plugin Mirror Tooling + +New `sync-to-codex-plugin` script mirrors superpowers into the OpenAI Codex plugin marketplace as `prime-radiant-inc/openai-codex-plugins`. Path/user-agnostic so any team member can run it. (PR #1165) + +- Clones the fork fresh into a temp directory per run, regenerates overlays inline, and opens a PR; auto-detects upstream from the script's own location and preflights `rsync`/`git`/`gh auth`/`python3`. +- `--bootstrap` flag for first-time setup; `EXCLUDES` patterns anchored to source root; `assets/` excluded. +- Mirrors `CODE_OF_CONDUCT.md`; drops the `agents/openai.yaml` overlay. +- Seeds `interface.defaultPrompt` in the mirrored `plugin.json`. (PR #1180 by @arittr) +- Codex plugin files are committed to the source repo so the sync script uses canonical versions; Codex marketplace metadata is preserved. + +### OpenCode + +- **Bootstrap content cached at module level** — `getBootstrapContent()` was calling `fs.existsSync` + `fs.readFileSync` + frontmatter regex on every agent step (the `experimental.chat.messages.transform` hook fires on every step in OpenCode's agent loop). Now read once, cached for the session lifetime, with a null sentinel for the missing-file case. 15 regression tests cover cache behavior, fs call counts, the injection guard, the missing-file sentinel, and cache reset. (Fixes #1202) +- **Integration tests modernized**. +- **Install caveats clarified** in the README. + +### Code Review Consolidation + +`requesting-code-review` is now self-contained: the persona, checklist, and dispatch template live in `skills/requesting-code-review/code-reviewer.md` and the skill dispatches `Task (general-purpose)` directly. (PR #1299) + +- **Single source of truth** — the persona/checklist that previously lived in both `agents/code-reviewer.md` and the skill's placeholder template (and drifted independently) is now one file. +- **`subagent-driven-development` follows suit** — its `code-quality-reviewer-prompt.md` now dispatches `Task (general-purpose)` instead of the named agent. +- **Behavioral test added** — `tests/claude-code/test-requesting-code-review.sh` plants real bugs (SQL injection, plaintext password handling, credential logging) into a tiny project and asserts the dispatched reviewer flags every planted issue at Critical/Important severity and refuses to approve the diff. + +> Note: `tests/claude-code/test-requesting-code-review.sh` and `tests/claude-code/test-document-review-system.sh` (mentioned later in this document) were lifted into drill scenarios on 2026-05-06 and removed from `tests/`. See `evals/scenarios/code-review-catches-planted-bugs.yaml` and `evals/scenarios/spec-reviewer-catches-planted-flaws.yaml`. The references above and below are preserved as dated artifacts of the work this section describes. +- **Codex and Copilot workaround docs trimmed** — the "Named agent dispatch" sections in `references/codex-tools.md` and `references/copilot-tools.md` documented how to flatten a named agent into a generic dispatch. With no named agents shipping, the workaround is unnecessary; both sections were dropped. + +### Subagent-Driven Development + +- **No more pause every 3 tasks** — the "review after each batch (3 tasks)" cadence in `requesting-code-review` (originally for `executing-plans`) was leaking into `subagent-driven-development`. Replaced with "each task or at natural checkpoints" plus an explicit continuous-execution directive. +- **SDD integration test now runs its assertions** — three independent bugs caused the test to silently bail before printing any verification results: an unresolved `..` segment in the working-dir path, a `set -euo pipefail` interaction with `find | sort | head -1` (SIGPIPE on the producer killed the script), and a missing `--plugin-dir` on the `claude -p` invocation that caused the test to load the installed plugin instead of the working tree. All three fixed; six verification tests now actually run against a real end-to-end SDD run. + +### Cursor + +- **Windows SessionStart hook** routed through `run-hook.cmd` instead of invoking the extensionless `session-start` script directly. Fixes Windows opening the file in an editor instead of running it. Also removed an accidental UTF-8 BOM from `hooks-cursor.json`. + +### Gemini CLI + +- **Subagent dispatch mapping** — Gemini's `Task` dispatch now maps to `@agent-name` / `@generalist`, with parallel subagent dispatch documented for independent tasks. + +### Skills + +- **Terminology cleanups** across skill content. + +### Documentation & Install + +- **Factory Droid installation instructions** added to README. +- **Quickstart install links** in README. (PR #1293 by @arittr) +- **Codex plugin install guidance** updated. (PR #1288 by @arittr) +- **Codex `wait` mapping corrected** to `wait_agent` in the tools reference. +- **Install order reorganized**; Codex install instructions cleaned up. +- **Removed vestigial `CHANGELOG.md`** in favor of `RELEASE-NOTES.md` as the single source. (PR #1163 by @shaanmajid) +- **Discord invite link** fixed; release announcements link and a detailed Discord description added to the Community section. + +### Community + +- @shaanmajid — vestigial `CHANGELOG.md` removal (PR #1163) +- @arittr — README quickstart install links (#1293), Codex plugin install guidance (#1288), `sync-to-codex-plugin` `interface.defaultPrompt` seed (#1180) + +## v5.0.7 (2026-03-31) + +### GitHub Copilot CLI Support + +- **SessionStart context injection** — Copilot CLI v1.0.11 added support for `additionalContext` in sessionStart hook output. The session-start hook now detects the `COPILOT_CLI` environment variable and emits the SDK-standard `{ "additionalContext": "..." }` format, giving Copilot CLI users the full superpowers bootstrap at session start. (Original fix by @culinablaz in PR #910) +- **Tool mapping** — added `references/copilot-tools.md` with the full Claude Code to Copilot CLI tool equivalence table +- **Skill and README updates** — added Copilot CLI to the `using-superpowers` skill's platform instructions and README installation section + +### OpenCode Fixes + +- **Skills path consistency** — the bootstrap text no longer advertises a misleading `configDir/skills/superpowers/` path that didn't match the runtime path. The agent should use the native `skill` tool, not navigate to files by path. Tests now use consistent paths derived from a single source of truth. (#847, #916) +- **Bootstrap as user message** — moved bootstrap injection from `experimental.chat.system.transform` to `experimental.chat.messages.transform`, prepending to the first user message instead of adding a system message. Avoids token bloat from system messages repeated every turn (#750) and fixes compatibility with Qwen and other models that break on multiple system messages (#894). + +## v5.0.6 (2026-03-24) + +### Inline Self-Review Replaces Subagent Review Loops + +The subagent review loop (dispatching a fresh agent to review plans/specs) doubled execution time (~25 min overhead) without measurably improving plan quality. Regression testing across 5 versions with 5 trials each showed identical quality scores regardless of whether the review loop ran. + +- **brainstorming** — replaced Spec Review Loop (subagent dispatch + 3-iteration cap) with inline Spec Self-Review checklist: placeholder scan, internal consistency, scope check, ambiguity check +- **writing-plans** — replaced Plan Review Loop (subagent dispatch + 3-iteration cap) with inline Self-Review checklist: spec coverage, placeholder scan, type consistency +- **writing-plans** — added explicit "No Placeholders" section defining plan failures (TBD, vague descriptions, undefined references, "similar to Task N") +- Self-review catches 3-5 real bugs per run in ~30s instead of ~25 min, with comparable defect rates to the subagent approach + +### Brainstorm Server + +- **Session directory restructured** — the brainstorm server session directory now contains two peer subdirectories: `content/` (HTML files served to the browser) and `state/` (events, server-info, pid, log). Previously, server state and user interaction data were stored alongside served content, making them accessible over HTTP. The `screen_dir` and `state_dir` paths are both included in the server-started JSON. (Reported by 吉田仁) + +### Bug Fixes + +- **Owner-PID lifecycle fixes** — the brainstorm server's owner-PID monitoring had two bugs causing false shutdowns within 60 seconds: (1) EPERM from cross-user PIDs (Tailscale SSH, etc.) was treated as "process dead", and (2) on WSL the grandparent PID resolves to a short-lived subprocess that exits before the first lifecycle check. Fixed by treating EPERM as "alive" and validating the owner PID at startup — if it's already dead, monitoring is disabled and the server relies on the 30-minute idle timeout. This also removes the Windows/MSYS2-specific carve-out from `start-server.sh` since the server now handles it generically. (#879) +- **writing-skills** — corrected false claim that SKILL.md frontmatter supports "only two fields"; now says "two required fields" and links to the agentskills.io specification for all supported fields (PR #882 by @arittr) + +### Codex App Compatibility + +- **codex-tools** — added named agent dispatch mapping documenting how to translate Claude Code's named agent types to Codex's `spawn_agent` with worker roles (PR #647 by @arittr) +- **codex-tools** — added environment detection and Codex App finishing sections for worktree-aware skills (by @arittr) +- **Design spec** — added Codex App compatibility design spec (PRI-823) covering read-only environment detection, worktree-safe skill behavior, and sandbox fallback patterns (by @arittr) + +## v5.0.5 (2026-03-17) + +### Bug Fixes + +- **Brainstorm server ESM fix** — renamed `server.js` → `server.cjs` so the brainstorming server starts correctly on Node.js 22+ where the root `package.json` `"type": "module"` caused `require()` to fail. (PR #784 by @sarbojitrana, fixes #774, #780, #783) +- **Brainstorm owner-PID on Windows** — skip PID lifecycle monitoring on Windows/MSYS2 where the PID namespace is invisible to Node.js, preventing the server from self-terminating after 60 seconds. (#770, docs from PR #768 by @lucasyhzlu-debug) +- **stop-server.sh reliability** — verify the server process actually died before reporting success. SIGTERM + 2s wait + SIGKILL fallback. (#723) + +### Changed + +- **Execution handoff** — restore user choice between subagent-driven and inline execution after plan writing. Subagent-driven is recommended but no longer mandatory. + +## v5.0.4 (2026-03-16) + +### Review Loop Refinements + +Dramatically reduces token usage and speeds up spec and plan reviews by eliminating unnecessary review passes and tightening reviewer focus. + +- **Single whole-plan review** — plan reviewer now reviews the complete plan in one pass instead of chunk-by-chunk. Removed all chunk-related concepts (`## Chunk N:` headings, 1000-line chunk limits, per-chunk dispatch). +- **Raised the bar for blocking issues** — both spec and plan reviewer prompts now include a "Calibration" section: only flag issues that would cause real problems during implementation. Minor wording, stylistic preferences, and formatting quibbles should not block approval. +- **Reduced max review iterations** — from 5 to 3 for both spec and plan review loops. If the reviewer is calibrated correctly, 3 rounds is plenty. +- **Streamlined reviewer checklists** — spec reviewer trimmed from 7 categories to 5; plan reviewer from 7 to 4. Removed formatting-focused checks (task syntax, chunk size) in favor of substance (buildability, spec alignment). + +### OpenCode + +- **One-line plugin install** — OpenCode plugin now auto-registers the skills directory via a `config` hook. No symlinks or `skills.paths` config needed. Install is just adding one line to `opencode.json`. (PR #753) +- **Added `package.json`** so OpenCode can install superpowers as an npm package from git. + +### Bug Fixes + +- **Verify server actually stopped** — `stop-server.sh` now confirms the process is dead before reporting success. SIGTERM + 2s wait + SIGKILL fallback. Reports failure if the process survives. (PR #751) +- **Generic agent language** — brainstorm companion waiting page now says "the agent" instead of "Claude". + +## v5.0.3 (2026-03-15) + +### Cursor Support + +- **Cursor hooks** — added `hooks/hooks-cursor.json` with Cursor's camelCase format (`sessionStart`, `version: 1`) and updated `.cursor-plugin/plugin.json` to reference it. Fixed platform detection in `session-start` to check `CURSOR_PLUGIN_ROOT` first (Cursor may also set `CLAUDE_PLUGIN_ROOT`). (Based on PR #709) + +### Bug Fixes + +- **Stop firing SessionStart hook on `--resume`** — the startup hook was re-injecting context on resumed sessions, which already have the context in their conversation history. The hook now fires only on `startup`, `clear`, and `compact`. +- **Bash 5.3+ hook hang** — replaced heredoc (`cat <` on served HTML pages + +### Subagent Context Isolation + +- All delegation skills (brainstorming, dispatching-parallel-agents, requesting-code-review, subagent-driven-development, writing-plans) now include context isolation principle +- Subagents receive only the context they need, preventing context window pollution + +## v5.0.1 (2026-03-10) + +### Agentskills Compliance + +**Brainstorm-server moved into skill directory** + +- Moved `lib/brainstorm-server/` → `skills/brainstorming/scripts/` per the [agentskills.io](https://agentskills.io) specification +- All `${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/` references replaced with relative `scripts/` paths +- Skills are now fully portable across platforms — no platform-specific env vars needed to locate scripts +- `lib/` directory removed (was the last remaining content) + +### New Features + +**Gemini CLI extension** + +- Native Gemini CLI extension support via `gemini-extension.json` and `GEMINI.md` at repo root +- `GEMINI.md` @imports `using-superpowers` skill and tool mapping table at session start +- Gemini CLI tool mapping reference (`skills/using-superpowers/references/gemini-tools.md`) — translates Claude Code tool names (Read, Write, Edit, Bash, etc.) to Gemini CLI equivalents (read_file, write_file, replace, etc.) +- Documents Gemini CLI limitations: no subagent support, skills fall back to `executing-plans` +- Extension root at repo root for cross-platform compatibility (avoids Windows symlink issues) +- Install instructions added to README + +### Improvements + +**Multi-platform brainstorm server launch** + +- Per-platform launch instructions in visual-companion.md: Claude Code (default mode), Codex (auto-foreground via `CODEX_CI`), Gemini CLI (`--foreground` with `is_background`), and fallback for other environments +- Server now writes startup JSON to `$SCREEN_DIR/.server-info` so agents can find the URL and port even when stdout is hidden by background execution + +**Brainstorm server dependencies bundled** + +- `node_modules` vendored into the repo so the brainstorm server works immediately on fresh plugin installs without requiring `npm` at runtime +- Removed `fsevents` from bundled deps (macOS-only native binary; chokidar falls back gracefully without it) +- Fallback auto-install via `npm install` if `node_modules` is missing + +**OpenCode tool mapping fix** + +- `TodoWrite` → `todowrite` (was incorrectly mapped to `update_plan`); verified against OpenCode source + +### Bug Fixes + +**Windows/Linux: single quotes break SessionStart hook** (#577, #529, #644, PR #585) + +- Single quotes around `${CLAUDE_PLUGIN_ROOT}` in hooks.json fail on Windows (cmd.exe doesn't recognize single quotes as path delimiters) and on Linux (single quotes prevent variable expansion) +- Fix: replaced single quotes with escaped double quotes — works across macOS bash, Windows cmd.exe, Windows Git Bash, and Linux, with and without spaces in paths +- Verified on Windows 11 (NT 10.0.26200.0) with Claude Code 2.1.72 and Git for Windows + +**Brainstorming spec review loop skipped** (#677) + +- The spec review loop (dispatch spec-document-reviewer subagent, iterate until approved) existed in the prose "After the Design" section but was missing from the checklist and process flow diagram +- Since agents follow the diagram and checklist more reliably than prose, the spec review step was being skipped entirely +- Added step 7 (spec review loop) to the checklist and corresponding nodes to the dot graph +- Tested with `claude --plugin-dir` and `claude-session-driver`: worker now correctly dispatches the reviewer + +**Cursor install command** (PR #676) + +- Fixed Cursor install command in README: `/plugin-add` → `/add-plugin` (confirmed via Cursor 2.5 release announcement) + +**User review gate in brainstorming** (#565) + +- Added explicit user review step between spec completion and writing-plans handoff +- User must approve the spec before implementation planning begins +- Checklist, process flow, and prose updated with the new gate + +**Session-start hook emits context only once per platform** + +- Hook now detects whether it's running in Claude Code or another platform +- Emits `hookSpecificOutput` for Claude Code, `additional_context` for others — prevents double context injection + +**Linting fix in token analysis script** + +- `except:` → `except Exception:` in `tests/claude-code/analyze-token-usage.py` + +### Maintenance + +**Removed dead code** + +- Deleted `lib/skills-core.js` and its test (`tests/opencode/test-skills-core.js`) — unused since February 2026 +- Removed skills-core existence check from `tests/opencode/test-plugin-loading.sh` + +### Community + +- @karuturi — Claude Code official marketplace install instructions (PR #610) +- @mvanhorn — session-start hook dual-emit fix, OpenCode tool mapping fix +- @daniel-graham — linting fix for bare except +- PR #585 author — Windows/Linux hooks quoting fix + +--- + +## v5.0.0 (2026-03-09) + +### Breaking Changes + +**Specs and plans directory restructured** + +- Specs (brainstorming output) now save to `docs/superpowers/specs/YYYY-MM-DD--design.md` +- Plans (writing-plans output) now save to `docs/superpowers/plans/YYYY-MM-DD-.md` +- User preferences for spec/plan locations override these defaults +- All internal skill references, test files, and example paths updated to match +- Migration: move existing files from `docs/plans/` to new locations if desired + +**Subagent-driven development mandatory on capable harnesses** + +Writing-plans no longer offers a choice between subagent-driven and executing-plans. On harnesses with subagent support (Claude Code, Codex), subagent-driven-development is required. Executing-plans is reserved for harnesses without subagent capability, and now tells the user that Superpowers works better on a subagent-capable platform. + +**Executing-plans no longer batches** + +Removed the "execute 3 tasks then stop for review" pattern. Plans now execute continuously, stopping only for blockers. + +**Slash commands deprecated** + +`/brainstorm`, `/write-plan`, and `/execute-plan` now show deprecation notices pointing users to the corresponding skills. Commands will be removed in the next major release. + +### New Features + +**Visual brainstorming companion** + +Optional browser-based companion for brainstorming sessions. When a topic would benefit from visuals, the brainstorming skill offers to show mockups, diagrams, comparisons, and other content in a browser window alongside terminal conversation. + +- `lib/brainstorm-server/` — WebSocket server with browser helper library, session management scripts, and dark/light themed frame template ("Superpowers Brainstorming" with GitHub link) +- `skills/brainstorming/visual-companion.md` — Progressive disclosure guide for server workflow, screen authoring, and feedback collection +- Brainstorming skill adds a visual companion decision point to its process flow: after exploring project context, the skill evaluates whether upcoming questions involve visual content and offers the companion in its own message +- Per-question decision: even after accepting, each question is evaluated for whether browser or terminal is more appropriate +- Integration tests in `tests/brainstorm-server/` + +**Document review system** + +Automated review loops for spec and plan documents using subagent dispatch: + +- `skills/brainstorming/spec-document-reviewer-prompt.md` — Reviewer checks completeness, consistency, architecture, and YAGNI +- `skills/writing-plans/plan-document-reviewer-prompt.md` — Reviewer checks spec alignment, task decomposition, file structure, and file size +- Brainstorming dispatches spec reviewer after writing the design doc +- Writing-plans includes chunk-based plan review loop after each section +- Review loops repeat until approved or escalate after 5 iterations +- End-to-end tests in `tests/claude-code/test-document-review-system.sh` +- Design spec and implementation plan in `docs/superpowers/` + +**Architecture guidance across the skill pipeline** + +Design-for-isolation and file-size-awareness guidance added to brainstorming, writing-plans, and subagent-driven-development: + +- **Brainstorming** — New sections: "Design for isolation and clarity" (clear boundaries, well-defined interfaces, independently testable units) and "Working in existing codebases" (follow existing patterns, targeted improvements only) +- **Writing-plans** — New "File Structure" section: map out files and responsibilities before defining tasks. New "Scope Check" backstop: catch multi-subsystem specs that should have been decomposed during brainstorming +- **SDD implementer** — New "Code Organization" section (follow plan's file structure, report concerns about growing files) and "When You're in Over Your Head" escalation guidance +- **SDD code quality reviewer** — Now checks architecture, unit decomposition, plan conformance, and file growth +- **Spec/plan reviewers** — Architecture and file size added to review criteria +- **Scope assessment** — Brainstorming now assesses whether a project is too large for a single spec. Multi-subsystem requests are flagged early and decomposed into sub-projects, each with its own spec → plan → implementation cycle + +**Subagent-driven development improvements** + +- **Model selection** — Guidance for choosing model capability by task type: cheap models for mechanical implementation, standard for integration, capable for architecture and review +- **Implementer status protocol** — Subagents now report DONE, DONE_WITH_CONCERNS, BLOCKED, or NEEDS_CONTEXT. Controller handles each status appropriately: re-dispatching with more context, upgrading model capability, breaking tasks apart, or escalating to human + +### Improvements + +**Instruction priority hierarchy** + +Added explicit priority ordering to using-superpowers: + +1. User's explicit instructions (CLAUDE.md, AGENTS.md, direct requests) — highest priority +2. Superpowers skills — override default system behavior +3. Default system prompt — lowest priority + +If CLAUDE.md or AGENTS.md says "don't use TDD" and a skill says "always use TDD," the user's instructions win. + +**SUBAGENT-STOP gate** + +Added `` block to using-superpowers. Subagents dispatched for specific tasks now skip the skill instead of activating the 1% rule and invoking full skill workflows. + +**Multi-platform improvements** + +- Codex tool mapping moved to progressive disclosure reference file (`references/codex-tools.md`) +- Platform Adaptation pointer added so non-Claude-Code platforms can find tool equivalents +- Plan headers now address "agentic workers" instead of "Claude" specifically +- Collab feature requirement documented in `docs/README.codex.md` + +**Writing-plans template updates** + +- Plan steps now use checkbox syntax (`- [ ] **Step N:**`) for progress tracking +- Plan header references both subagent-driven-development and executing-plans with platform-aware routing + +--- + +## v4.3.1 (2026-02-21) + +### Added + +**Cursor support** + +Superpowers now works with Cursor's plugin system. Includes a `.cursor-plugin/plugin.json` manifest and Cursor-specific installation instructions in the README. The SessionStart hook output now includes an `additional_context` field alongside the existing `hookSpecificOutput.additionalContext` for Cursor hook compatibility. + +### Fixed + +**Windows: Restored polyglot wrapper for reliable hook execution (#518, #504, #491, #487, #466, #440)** + +Claude Code's `.sh` auto-detection on Windows was prepending `bash` to the hook command, breaking execution. The fix: + +- Renamed `session-start.sh` to `session-start` (extensionless) so auto-detection doesn't interfere +- Restored `run-hook.cmd` polyglot wrapper with multi-location bash discovery (standard Git for Windows paths, then PATH fallback) +- Exits silently if no bash is found rather than erroring +- On Unix, the wrapper runs the script directly via `exec bash` +- Uses POSIX-safe `dirname "$0"` path resolution (works on dash/sh, not just bash) + +This fixes SessionStart failures on Windows with spaces in paths, missing WSL, `set -euo pipefail` fragility on MSYS, and backslash mangling. + +## v4.3.0 (2026-02-12) + +This fix should dramatically improve superpowers skills compliance and should reduce the chances of Claude entering its native plan mode unintentionally. + +### Changed + +**Brainstorming skill now enforces its workflow instead of describing it** + +Models were skipping the design phase and jumping straight to implementation skills like frontend-design, or collapsing the entire brainstorming process into a single text block. The skill now uses hard gates, a mandatory checklist, and a graphviz process flow to enforce compliance: + +- ``: no implementation skills, code, or scaffolding until design is presented and user approves +- Explicit checklist (6 items) that must be created as tasks and completed in order +- Graphviz process flow with `writing-plans` as the only valid terminal state +- Anti-pattern callout for "this is too simple to need a design" — the exact rationalization models use to skip the process +- Design section sizing based on section complexity, not project complexity + +**Using-superpowers workflow graph intercepts EnterPlanMode** + +Added an `EnterPlanMode` intercept to the skill flow graph. When the model is about to enter Claude's native plan mode, it checks whether brainstorming has happened and routes through the brainstorming skill instead. Plan mode is never entered. + +### Fixed + +**SessionStart hook now runs synchronously** + +Changed `async: true` to `async: false` in hooks.json. When async, the hook could fail to complete before the model's first turn, meaning using-superpowers instructions weren't in context for the first message. + +## v4.2.0 (2026-02-05) + +### Breaking Changes + +**Codex: Replaced bootstrap CLI with native skill discovery** + +The `superpowers-codex` bootstrap CLI, Windows `.cmd` wrapper, and related bootstrap content file have been removed. Codex now uses native skill discovery via `~/.agents/skills/superpowers/` symlink, so the old `use_skill`/`find_skills` CLI tools are no longer needed. + +Installation is now just clone + symlink (documented in INSTALL.md). No Node.js dependency required. The old `~/.codex/skills/` path is deprecated. + +### Fixes + +**Windows: Fixed Claude Code 2.1.x hook execution (#331)** + +Claude Code 2.1.x changed how hooks execute on Windows: it now auto-detects `.sh` files in commands and prepends `bash`. This broke the polyglot wrapper pattern because `bash "run-hook.cmd" session-start.sh` tries to execute the `.cmd` file as a bash script. + +Fix: hooks.json now calls session-start.sh directly. Claude Code 2.1.x handles the bash invocation automatically. Also added .gitattributes to enforce LF line endings for shell scripts (fixes CRLF issues on Windows checkout). + +**Windows: SessionStart hook runs async to prevent terminal freeze (#404, #413, #414, #419)** + +The synchronous SessionStart hook blocked the TUI from entering raw mode on Windows, freezing all keyboard input. Running the hook async prevents the freeze while still injecting superpowers context. + +**Windows: Fixed O(n^2) `escape_for_json` performance** + +The character-by-character loop using `${input:$i:1}` was O(n^2) in bash due to substring copy overhead. On Windows Git Bash this took 60+ seconds. Replaced with bash parameter substitution (`${s//old/new}`) which runs each pattern as a single C-level pass — 7x faster on macOS, dramatically faster on Windows. + +**Codex: Fixed Windows/PowerShell invocation (#285, #243)** + +- Windows doesn't respect shebangs, so directly invoking the extensionless `superpowers-codex` script triggered an "Open with" dialog. All invocations now prefixed with `node`. +- Fixed `~/` path expansion on Windows — PowerShell doesn't expand `~` when passed as an argument to `node`. Changed to `$HOME` which expands correctly in both bash and PowerShell. + +**Codex: Fixed path resolution in installer** + +Used `fileURLToPath()` instead of manual URL pathname parsing to correctly handle paths with spaces and special characters on all platforms. + +**Codex: Fixed stale skills path in writing-skills** + +Updated `~/.codex/skills/` reference (deprecated) to `~/.agents/skills/` for native discovery. + +### Improvements + +**Worktree isolation now required before implementation** + +Added `using-git-worktrees` as a required skill for both `subagent-driven-development` and `executing-plans`. Implementation workflows now explicitly require setting up an isolated worktree before starting work, preventing accidental work directly on main. + +**Main branch protection softened to require explicit consent** + +Instead of prohibiting main branch work entirely, the skills now allow it with explicit user consent. More flexible while still ensuring users are aware of the implications. + +**Simplified installation verification** + +Removed `/help` command check and specific slash command list from verification steps. Skills are primarily invoked by describing what you want to do, not by running specific commands. + +**Codex: Clarified subagent tool mapping in bootstrap** + +Improved documentation of how Codex tools map to Claude Code equivalents for subagent workflows. + +### Tests + +- Added worktree requirement test for subagent-driven-development +- Added main branch red flag warning test +- Fixed case sensitivity in skill recognition test assertions + +--- + +## v4.1.1 (2026-01-23) + +### Fixes + +**OpenCode: Standardized on `plugins/` directory per official docs (#343)** + +OpenCode's official documentation uses `~/.config/opencode/plugins/` (plural). Our docs previously used `plugin/` (singular). While OpenCode accepts both forms, we've standardized on the official convention to avoid confusion. + +Changes: +- Renamed `.opencode/plugin/` to `.opencode/plugins/` in repo structure +- Updated all installation docs (INSTALL.md, README.opencode.md) across all platforms +- Updated test scripts to match + +**OpenCode: Fixed symlink instructions (#339, #342)** + +- Added explicit `rm` before `ln -s` (fixes "file already exists" errors on reinstall) +- Added missing skills symlink step that was absent from INSTALL.md +- Updated from deprecated `use_skill`/`find_skills` to native `skill` tool references + +--- + +## v4.1.0 (2026-01-23) + +### Breaking Changes + +**OpenCode: Switched to native skills system** + +Superpowers for OpenCode now uses OpenCode's native `skill` tool instead of custom `use_skill`/`find_skills` tools. This is a cleaner integration that works with OpenCode's built-in skill discovery. + +**Migration required:** Skills must be symlinked to `~/.config/opencode/skills/superpowers/` (see updated installation docs). + +### Fixes + +**OpenCode: Fixed agent reset on session start (#226)** + +The previous bootstrap injection method using `session.prompt({ noReply: true })` caused OpenCode to reset the selected agent to "build" on first message. Now uses `experimental.chat.system.transform` hook which modifies the system prompt directly without side effects. + +**OpenCode: Fixed Windows installation (#232)** + +- Removed dependency on `skills-core.js` (eliminates broken relative imports when file is copied instead of symlinked) +- Added comprehensive Windows installation docs for cmd.exe, PowerShell, and Git Bash +- Documented proper symlink vs junction usage for each platform + +**Claude Code: Fixed Windows hook execution for Claude Code 2.1.x** + +Claude Code 2.1.x changed how hooks execute on Windows: it now auto-detects `.sh` files in commands and prepends `bash `. This broke the polyglot wrapper pattern because `bash "run-hook.cmd" session-start.sh` tries to execute the .cmd file as a bash script. + +Fix: hooks.json now calls session-start.sh directly. Claude Code 2.1.x handles the bash invocation automatically. Also added .gitattributes to enforce LF line endings for shell scripts (fixes CRLF issues on Windows checkout). + +--- + +## v4.0.3 (2025-12-26) + +### Improvements + +**Strengthened using-superpowers skill for explicit skill requests** + +Addressed a failure mode where Claude would skip invoking a skill even when the user explicitly requested it by name (e.g., "subagent-driven-development, please"). Claude would think "I know what that means" and start working directly instead of loading the skill. + +Changes: +- Updated "The Rule" to say "Invoke relevant or requested skills" instead of "Check for skills" - emphasizing active invocation over passive checking +- Added "BEFORE any response or action" - the original wording only mentioned "response" but Claude would sometimes take action without responding first +- Added reassurance that invoking a wrong skill is okay - reduces hesitation +- Added new red flag: "I know what that means" → Knowing the concept ≠ using the skill + +**Added explicit skill request tests** + +New test suite in `tests/explicit-skill-requests/` that verifies Claude correctly invokes skills when users request them by name. Includes single-turn and multi-turn test scenarios. + +## v4.0.2 (2025-12-23) + +### Fixes + +**Slash commands now user-only** + +Added `disable-model-invocation: true` to all three slash commands (`/brainstorm`, `/execute-plan`, `/write-plan`). Claude can no longer invoke these commands via the Skill tool—they're restricted to manual user invocation only. + +The underlying skills (`superpowers:brainstorming`, `superpowers:executing-plans`, `superpowers:writing-plans`) remain available for Claude to invoke autonomously. This change prevents confusion when Claude would invoke a command that just redirects to a skill anyway. + +## v4.0.1 (2025-12-23) + +### Fixes + +**Clarified how to access skills in Claude Code** + +Fixed a confusing pattern where Claude would invoke a skill via the Skill tool, then try to Read the skill file separately. The `using-superpowers` skill now explicitly states that the Skill tool loads skill content directly—no need to read files. + +- Added "How to Access Skills" section to `using-superpowers` +- Changed "read the skill" → "invoke the skill" in instructions +- Updated slash commands to use fully qualified skill names (e.g., `superpowers:brainstorming`) + +**Added GitHub thread reply guidance to receiving-code-review** (h/t @ralphbean) + +Added a note about replying to inline review comments in the original thread rather than as top-level PR comments. + +**Added automation-over-documentation guidance to writing-skills** (h/t @EthanJStark) + +Added guidance that mechanical constraints should be automated, not documented—save skills for judgment calls. + +## v4.0.0 (2025-12-17) + +### New Features + +**Two-stage code review in subagent-driven-development** + +Subagent workflows now use two separate review stages after each task: + +1. **Spec compliance review** - Skeptical reviewer verifies implementation matches spec exactly. Catches missing requirements AND over-building. Won't trust implementer's report—reads actual code. + +2. **Code quality review** - Only runs after spec compliance passes. Reviews for clean code, test coverage, maintainability. + +This catches the common failure mode where code is well-written but doesn't match what was requested. Reviews are loops, not one-shot: if reviewer finds issues, implementer fixes them, then reviewer checks again. + +Other subagent workflow improvements: +- Controller provides full task text to workers (not file references) +- Workers can ask clarifying questions before AND during work +- Self-review checklist before reporting completion +- Plan read once at start, extracted to TodoWrite + +New prompt templates in `skills/subagent-driven-development/`: +- `implementer-prompt.md` - Includes self-review checklist, encourages questions +- `spec-reviewer-prompt.md` - Skeptical verification against requirements +- `code-quality-reviewer-prompt.md` - Standard code review + +**Debugging techniques consolidated with tools** + +`systematic-debugging` now bundles supporting techniques and tools: +- `root-cause-tracing.md` - Trace bugs backward through call stack +- `defense-in-depth.md` - Add validation at multiple layers +- `condition-based-waiting.md` - Replace arbitrary timeouts with condition polling +- `find-polluter.sh` - Bisection script to find which test creates pollution +- `condition-based-waiting-example.ts` - Complete implementation from real debugging session + +**Testing anti-patterns reference** + +`test-driven-development` now includes `testing-anti-patterns.md` covering: +- Testing mock behavior instead of real behavior +- Adding test-only methods to production classes +- Mocking without understanding dependencies +- Incomplete mocks that hide structural assumptions + +**Skill test infrastructure** + +Three new test frameworks for validating skill behavior: + +`tests/skill-triggering/` - Validates skills trigger from naive prompts without explicit naming. Tests 6 skills to ensure descriptions alone are sufficient. + +`tests/claude-code/` - Integration tests using `claude -p` for headless testing. Verifies skill usage via session transcript (JSONL) analysis. Includes `analyze-token-usage.py` for cost tracking. + +`tests/subagent-driven-dev/` - End-to-end workflow validation with two complete test projects: +- `go-fractals/` - CLI tool with Sierpinski/Mandelbrot (10 tasks) +- `svelte-todo/` - CRUD app with localStorage and Playwright (12 tasks) + +### Major Changes + +**DOT flowcharts as executable specifications** + +Rewrote key skills using DOT/GraphViz flowcharts as the authoritative process definition. Prose becomes supporting content. + +**The Description Trap** (documented in `writing-skills`): Discovered that skill descriptions override flowchart content when descriptions contain workflow summaries. Claude follows the short description instead of reading the detailed flowchart. Fix: descriptions must be trigger-only ("Use when X") with no process details. + +**Skill priority in using-superpowers** + +When multiple skills apply, process skills (brainstorming, debugging) now explicitly come before implementation skills. "Build X" triggers brainstorming first, then domain skills. + +**brainstorming trigger strengthened** + +Description changed to imperative: "You MUST use this before any creative work—creating features, building components, adding functionality, or modifying behavior." + +### Breaking Changes + +**Skill consolidation** - Six standalone skills merged: +- `root-cause-tracing`, `defense-in-depth`, `condition-based-waiting` → bundled in `systematic-debugging/` +- `testing-skills-with-subagents` → bundled in `writing-skills/` +- `testing-anti-patterns` → bundled in `test-driven-development/` +- `sharing-skills` removed (obsolete) + +### Other Improvements + +- **render-graphs.js** - Tool to extract DOT diagrams from skills and render to SVG +- **Rationalizations table** in using-superpowers - Scannable format including new entries: "I need more context first", "Let me explore first", "This feels productive" +- **docs/testing.md** - Guide to testing skills with Claude Code integration tests + +--- + +## v3.6.2 (2025-12-03) + +### Fixed + +- **Linux Compatibility**: Fixed polyglot hook wrapper (`run-hook.cmd`) to use POSIX-compliant syntax + - Replaced bash-specific `${BASH_SOURCE[0]:-$0}` with standard `$0` on line 16 + - Resolves "Bad substitution" error on Ubuntu/Debian systems where `/bin/sh` is dash + - Fixes #141 + +--- + +## v3.5.1 (2025-11-24) + +### Changed + +- **OpenCode Bootstrap Refactor**: Switched from `chat.message` hook to `session.created` event for bootstrap injection + - Bootstrap now injects at session creation via `session.prompt()` with `noReply: true` + - Explicitly tells the model that using-superpowers is already loaded to prevent redundant skill loading + - Consolidated bootstrap content generation into shared `getBootstrapContent()` helper + - Cleaner single-implementation approach (removed fallback pattern) + +--- + +## v3.5.0 (2025-11-23) + +### Added + +- **OpenCode Support**: Native JavaScript plugin for OpenCode.ai + - Custom tools: `use_skill` and `find_skills` + - Message insertion pattern for skill persistence across context compaction + - Automatic context injection via chat.message hook + - Auto re-injection on session.compacted events + - Three-tier skill priority: project > personal > superpowers + - Project-local skills support (`.opencode/skills/`) + - Shared core module (`lib/skills-core.js`) for code reuse with Codex + - Automated test suite with proper isolation (`tests/opencode/`) + - Platform-specific documentation (`docs/README.opencode.md`, `docs/README.codex.md`) + +### Changed + +- **Refactored Codex Implementation**: Now uses shared `lib/skills-core.js` ES module + - Eliminates code duplication between Codex and OpenCode + - Single source of truth for skill discovery and parsing + - Codex successfully loads ES modules via Node.js interop + +- **Improved Documentation**: Rewrote README to explain problem/solution clearly + - Removed duplicate sections and conflicting information + - Added complete workflow description (brainstorm → plan → execute → finish) + - Simplified platform installation instructions + - Emphasized skill-checking protocol over automatic activation claims + +--- + +## v3.4.1 (2025-10-31) + +### Improvements + +- Optimized superpowers bootstrap to eliminate redundant skill execution. The `using-superpowers` skill content is now provided directly in session context, with clear guidance to use the Skill tool only for other skills. This reduces overhead and prevents the confusing loop where agents would execute `using-superpowers` manually despite already having the content from session start. + +## v3.4.0 (2025-10-30) + +### Improvements + +- Simplified `brainstorming` skill to return to original conversational vision. Removed heavyweight 6-phase process with formal checklists in favor of natural dialogue: ask questions one at a time, then present design in 200-300 word sections with validation. Keeps documentation and implementation handoff features. + +## v3.3.1 (2025-10-28) + +### Improvements + +- Updated `brainstorming` skill to require autonomous recon before questioning, encourage recommendation-driven decisions, and prevent agents from delegating prioritization back to humans. +- Applied writing clarity improvements to `brainstorming` skill following Strunk's "Elements of Style" principles (omitted needless words, converted negative to positive form, improved parallel construction). + +### Bug Fixes + +- Clarified `writing-skills` guidance so it points to the correct agent-specific personal skill directories (`~/.claude/skills` for Claude Code, `~/.codex/skills` for Codex). + +## v3.3.0 (2025-10-28) + +### New Features + +**Experimental Codex Support** +- Added unified `superpowers-codex` script with bootstrap/use-skill/find-skills commands +- Cross-platform Node.js implementation (works on Windows, macOS, Linux) +- Namespaced skills: `superpowers:skill-name` for superpowers skills, `skill-name` for personal +- Personal skills override superpowers skills when names match +- Clean skill display: shows name/description without raw frontmatter +- Helpful context: shows supporting files directory for each skill +- Tool mapping for Codex: TodoWrite→update_plan, subagents→manual fallback, etc. +- Bootstrap integration with minimal AGENTS.md for automatic startup +- Complete installation guide and bootstrap instructions specific to Codex + +**Key differences from Claude Code integration:** +- Single unified script instead of separate tools +- Tool substitution system for Codex-specific equivalents +- Simplified subagent handling (manual work instead of delegation) +- Updated terminology: "Superpowers skills" instead of "Core skills" + +### Files Added +- `.codex/INSTALL.md` - Installation guide for Codex users +- `.codex/superpowers-bootstrap.md` - Bootstrap instructions with Codex adaptations +- `.codex/superpowers-codex` - Unified Node.js executable with all functionality + +**Note:** Codex support is experimental. The integration provides core superpowers functionality but may require refinement based on user feedback. + +## v3.2.3 (2025-10-23) + +### Improvements + +**Updated using-superpowers skill to use Skill tool instead of Read tool** +- Changed skill invocation instructions from Read tool to Skill tool +- Updated description: "using Read tool" → "using Skill tool" +- Updated step 3: "Use the Read tool" → "Use the Skill tool to read and run" +- Updated rationalization list: "Read the current version" → "Run the current version" + +The Skill tool is the proper mechanism for invoking skills in Claude Code. This update corrects the bootstrap instructions to guide agents toward the correct tool. + +### Files Changed +- Updated: `skills/using-superpowers/SKILL.md` - Changed tool references from Read to Skill + +## v3.2.2 (2025-10-21) + +### Improvements + +**Strengthened using-superpowers skill against agent rationalization** +- Added EXTREMELY-IMPORTANT block with absolute language about mandatory skill checking + - "If even 1% chance a skill applies, you MUST read it" + - "You do not have a choice. You cannot rationalize your way out." +- Added MANDATORY FIRST RESPONSE PROTOCOL checklist + - 5-step process agents must complete before any response + - Explicit "responding without this = failure" consequence +- Added Common Rationalizations section with 8 specific evasion patterns + - "This is just a simple question" → WRONG + - "I can check files quickly" → WRONG + - "Let me gather information first" → WRONG + - Plus 5 more common patterns observed in agent behavior + +These changes address observed agent behavior where they rationalize around skill usage despite clear instructions. The forceful language and pre-emptive counter-arguments aim to make non-compliance harder. + +### Files Changed +- Updated: `skills/using-superpowers/SKILL.md` - Added three layers of enforcement to prevent skill-skipping rationalization + +## v3.2.1 (2025-10-20) + +### New Features + +**Code reviewer agent now included in plugin** +- Added `superpowers:code-reviewer` agent to plugin's `agents/` directory +- Agent provides systematic code review against plans and coding standards +- Previously required users to have personal agent configuration +- All skill references updated to use namespaced `superpowers:code-reviewer` +- Fixes #55 + +### Files Changed +- New: `agents/code-reviewer.md` - Agent definition with review checklist and output format +- Updated: `skills/requesting-code-review/SKILL.md` - References to `superpowers:code-reviewer` +- Updated: `skills/subagent-driven-development/SKILL.md` - References to `superpowers:code-reviewer` + +## v3.2.0 (2025-10-18) + +### New Features + +**Design documentation in brainstorming workflow** +- Added Phase 4: Design Documentation to brainstorming skill +- Design documents now written to `docs/plans/YYYY-MM-DD--design.md` before implementation +- Restores functionality from original brainstorming command that was lost during skill conversion +- Documents written before worktree setup and implementation planning +- Tested with subagent to verify compliance under time pressure + +### Breaking Changes + +**Skill reference namespace standardization** +- All internal skill references now use `superpowers:` namespace prefix +- Updated format: `superpowers:test-driven-development` (previously just `test-driven-development`) +- Affects all REQUIRED SUB-SKILL, RECOMMENDED SUB-SKILL, and REQUIRED BACKGROUND references +- Aligns with how skills are invoked using the Skill tool +- Files updated: brainstorming, executing-plans, subagent-driven-development, systematic-debugging, testing-skills-with-subagents, writing-plans, writing-skills + +### Improvements + +**Design vs implementation plan naming** +- Design documents use `-design.md` suffix to prevent filename collisions +- Implementation plans continue using existing `YYYY-MM-DD-.md` format +- Both stored in `docs/plans/` directory with clear naming distinction + +## v3.1.1 (2025-10-17) + +### Bug Fixes + +- **Fixed command syntax in README** (#44) - Updated all command references to use correct namespaced syntax (`/superpowers:brainstorm` instead of `/brainstorm`). Plugin-provided commands are automatically namespaced by Claude Code to avoid conflicts between plugins. + +## v3.1.0 (2025-10-17) + +### Breaking Changes + +**Skill names standardized to lowercase** +- All skill frontmatter `name:` fields now use lowercase kebab-case matching directory names +- Examples: `brainstorming`, `test-driven-development`, `using-git-worktrees` +- All skill announcements and cross-references updated to lowercase format +- This ensures consistent naming across directory names, frontmatter, and documentation + +### New Features + +**Enhanced brainstorming skill** +- Added Quick Reference table showing phases, activities, and tool usage +- Added copyable workflow checklist for tracking progress +- Added decision flowchart for when to revisit earlier phases +- Added comprehensive AskUserQuestion tool guidance with concrete examples +- Added "Question Patterns" section explaining when to use structured vs open-ended questions +- Restructured Key Principles as scannable table + +**Anthropic best practices integration** +- Added `skills/writing-skills/anthropic-best-practices.md` - Official Anthropic skill authoring guide +- Referenced in writing-skills SKILL.md for comprehensive guidance +- Provides patterns for progressive disclosure, workflows, and evaluation + +### Improvements + +**Skill cross-reference clarity** +- All skill references now use explicit requirement markers: + - `**REQUIRED BACKGROUND:**` - Prerequisites you must understand + - `**REQUIRED SUB-SKILL:**` - Skills that must be used in workflow + - `**Complementary skills:**` - Optional but helpful related skills +- Removed old path format (`skills/collaboration/X` → just `X`) +- Updated Integration sections with categorized relationships (Required vs Complementary) +- Updated cross-reference documentation with best practices + +**Alignment with Anthropic best practices** +- Fixed description grammar and voice (fully third-person) +- Added Quick Reference tables for scanning +- Added workflow checklists Claude can copy and track +- Appropriate use of flowcharts for non-obvious decision points +- Improved scannable table formats +- All skills well under 500-line recommendation + +### Bug Fixes + +- **Re-added missing command redirects** - Restored `commands/brainstorm.md` and `commands/write-plan.md` that were accidentally removed in v3.0 migration +- Fixed `defense-in-depth` name mismatch (was `Defense-in-Depth-Validation`) +- Fixed `receiving-code-review` name mismatch (was `Code-Review-Reception`) +- Fixed `commands/brainstorm.md` reference to correct skill name +- Removed references to non-existent related skills + +### Documentation + +**writing-skills improvements** +- Updated cross-referencing guidance with explicit requirement markers +- Added reference to Anthropic's official best practices +- Improved examples showing proper skill reference format + +## v3.0.1 (2025-10-16) + +### Changes + +We now use Anthropic's first-party skills system! + +## v2.0.2 (2025-10-12) + +### Bug Fixes + +- **Fixed false warning when local skills repo is ahead of upstream** - The initialization script was incorrectly warning "New skills available from upstream" when the local repository had commits ahead of upstream. The logic now correctly distinguishes between three git states: local behind (should update), local ahead (no warning), and diverged (should warn). + +## v2.0.1 (2025-10-12) + +### Bug Fixes + +- **Fixed session-start hook execution in plugin context** (#8, PR #9) - The hook was failing silently with "Plugin hook error" preventing skills context from loading. Fixed by: + - Using `${BASH_SOURCE[0]:-$0}` fallback when BASH_SOURCE is unbound in Claude Code's execution context + - Adding `|| true` to handle empty grep results gracefully when filtering status flags + +--- + +# Superpowers v2.0.0 Release Notes + +## Overview + +Superpowers v2.0 makes skills more accessible, maintainable, and community-driven through a major architectural shift. + +The headline change is **skills repository separation**: all skills, scripts, and documentation have moved from the plugin into a dedicated repository ([obra/superpowers-skills](https://github.com/obra/superpowers-skills)). This transforms superpowers from a monolithic plugin into a lightweight shim that manages a local clone of the skills repository. Skills auto-update on session start. Users fork and contribute improvements via standard git workflows. The skills library versions independently from the plugin. + +Beyond infrastructure, this release adds nine new skills focused on problem-solving, research, and architecture. We rewrote the core **using-skills** documentation with imperative tone and clearer structure, making it easier for Claude to understand when and how to use skills. **find-skills** now outputs paths you can paste directly into the Read tool, eliminating friction in the skills discovery workflow. + +Users experience seamless operation: the plugin handles cloning, forking, and updating automatically. Contributors find the new architecture makes improving and sharing skills trivial. This release lays the foundation for skills to evolve rapidly as a community resource. + +## Breaking Changes + +### Skills Repository Separation + +**The biggest change:** Skills no longer live in the plugin. They've been moved to a separate repository at [obra/superpowers-skills](https://github.com/obra/superpowers-skills). + +**What this means for you:** + +- **First install:** Plugin automatically clones skills to `~/.config/superpowers/skills/` +- **Forking:** During setup, you'll be offered the option to fork the skills repo (if `gh` is installed) +- **Updates:** Skills auto-update on session start (fast-forward when possible) +- **Contributing:** Work on branches, commit locally, submit PRs to upstream +- **No more shadowing:** Old two-tier system (personal/core) replaced with single-repo branch workflow + +**Migration:** + +If you have an existing installation: +1. Your old `~/.config/superpowers/.git` will be backed up to `~/.config/superpowers/.git.bak` +2. Old skills will be backed up to `~/.config/superpowers/skills.bak` +3. Fresh clone of obra/superpowers-skills will be created at `~/.config/superpowers/skills/` + +### Removed Features + +- **Personal superpowers overlay system** - Replaced with git branch workflow +- **setup-personal-superpowers hook** - Replaced by initialize-skills.sh + +## New Features + +### Skills Repository Infrastructure + +**Automatic Clone & Setup** (`lib/initialize-skills.sh`) +- Clones obra/superpowers-skills on first run +- Offers fork creation if GitHub CLI is installed +- Sets up upstream/origin remotes correctly +- Handles migration from old installation + +**Auto-Update** +- Fetches from tracking remote on every session start +- Auto-merges with fast-forward when possible +- Notifies when manual sync needed (branch diverged) +- Uses pulling-updates-from-skills-repository skill for manual sync + +### New Skills + +**Problem-Solving Skills** (`skills/problem-solving/`) +- **collision-zone-thinking** - Force unrelated concepts together for emergent insights +- **inversion-exercise** - Flip assumptions to reveal hidden constraints +- **meta-pattern-recognition** - Spot universal principles across domains +- **scale-game** - Test at extremes to expose fundamental truths +- **simplification-cascades** - Find insights that eliminate multiple components +- **when-stuck** - Dispatch to right problem-solving technique + +**Research Skills** (`skills/research/`) +- **tracing-knowledge-lineages** - Understand how ideas evolved over time + +**Architecture Skills** (`skills/architecture/`) +- **preserving-productive-tensions** - Keep multiple valid approaches instead of forcing premature resolution + +### Skills Improvements + +**using-skills (formerly getting-started)** +- Renamed from getting-started to using-skills +- Complete rewrite with imperative tone (v4.0.0) +- Front-loaded critical rules +- Added "Why" explanations for all workflows +- Always includes /SKILL.md suffix in references +- Clearer distinction between rigid rules and flexible patterns + +**writing-skills** +- Cross-referencing guidance moved from using-skills +- Added token efficiency section (word count targets) +- Improved CSO (Claude Search Optimization) guidance + +**sharing-skills** +- Updated for new branch-and-PR workflow (v2.0.0) +- Removed personal/core split references + +**pulling-updates-from-skills-repository** (new) +- Complete workflow for syncing with upstream +- Replaces old "updating-skills" skill + +### Tools Improvements + +**find-skills** +- Now outputs full paths with /SKILL.md suffix +- Makes paths directly usable with Read tool +- Updated help text + +**skill-run** +- Moved from scripts/ to skills/using-skills/ +- Improved documentation + +### Plugin Infrastructure + +**Session Start Hook** +- Now loads from skills repository location +- Shows full skills list at session start +- Prints skills location info +- Shows update status (updated successfully / behind upstream) +- Moved "skills behind" warning to end of output + +**Environment Variables** +- `SUPERPOWERS_SKILLS_ROOT` set to `~/.config/superpowers/skills` +- Used consistently throughout all paths + +## Bug Fixes + +- Fixed duplicate upstream remote addition when forking +- Fixed find-skills double "skills/" prefix in output +- Removed obsolete setup-personal-superpowers call from session-start +- Fixed path references throughout hooks and commands + +## Documentation + +### README +- Updated for new skills repository architecture +- Prominent link to superpowers-skills repo +- Updated auto-update description +- Fixed skill names and references +- Updated Meta skills list + +### Testing Documentation +- Added comprehensive testing checklist (`docs/TESTING-CHECKLIST.md`) +- Created local marketplace config for testing +- Documented manual testing scenarios + +## Technical Details + +### File Changes + +**Added:** +- `lib/initialize-skills.sh` - Skills repo initialization and auto-update +- `docs/TESTING-CHECKLIST.md` - Manual testing scenarios +- `.claude-plugin/marketplace.json` - Local testing config + +**Removed:** +- `skills/` directory (82 files) - Now in obra/superpowers-skills +- `scripts/` directory - Now in obra/superpowers-skills/skills/using-skills/ +- `hooks/setup-personal-superpowers.sh` - Obsolete + +**Modified:** +- `hooks/session-start.sh` - Use skills from ~/.config/superpowers/skills +- `commands/brainstorm.md` - Updated paths to SUPERPOWERS_SKILLS_ROOT +- `commands/write-plan.md` - Updated paths to SUPERPOWERS_SKILLS_ROOT +- `commands/execute-plan.md` - Updated paths to SUPERPOWERS_SKILLS_ROOT +- `README.md` - Complete rewrite for new architecture + +### Commit History + +This release includes: +- 20+ commits for skills repository separation +- PR #1: Amplifier-inspired problem-solving and research skills +- PR #2: Personal superpowers overlay system (later replaced) +- Multiple skill refinements and documentation improvements + +## Upgrade Instructions + +### Fresh Install + +```bash +# In Claude Code +/plugin marketplace add obra/superpowers-marketplace +/plugin install superpowers@superpowers-marketplace +``` + +The plugin handles everything automatically. + +### Upgrading from v1.x + +1. **Backup your personal skills** (if you have any): + ```bash + cp -r ~/.config/superpowers/skills ~/superpowers-skills-backup + ``` + +2. **Update the plugin:** + ```bash + /plugin update superpowers + ``` + +3. **On next session start:** + - Old installation will be backed up automatically + - Fresh skills repo will be cloned + - If you have GitHub CLI, you'll be offered the option to fork + +4. **Migrate personal skills** (if you had any): + - Create a branch in your local skills repo + - Copy your personal skills from backup + - Commit and push to your fork + - Consider contributing back via PR + +## What's Next + +### For Users + +- Explore the new problem-solving skills +- Try the branch-based workflow for skill improvements +- Contribute skills back to the community + +### For Contributors + +- Skills repository is now at https://github.com/obra/superpowers-skills +- Fork → Branch → PR workflow +- See skills/meta/writing-skills/SKILL.md for TDD approach to documentation + +## Known Issues + +None at this time. + +## Credits + +- Problem-solving skills inspired by Amplifier patterns +- Community contributions and feedback +- Extensive testing and iteration on skill effectiveness + +--- + +**Full Changelog:** https://github.com/obra/superpowers/compare/dd013f6...main +**Skills Repository:** https://github.com/obra/superpowers-skills +**Issues:** https://github.com/obra/superpowers/issues diff --git a/assets/app-icon.png b/assets/app-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..25518da4625e850d97b15a9e0a27e7115969dc0d GIT binary patch literal 48231 zcmeFZd0dR^`v-nci;|S3l5<)tBP1D|Lz}Y3kdhi{(-8Vnsib`y9HLT8C`St?q-ifL zn$pvul2RO}P1~r5mT9BqwEeDop6PtQzyE)K{a&Zn>wG>m&)oNYZSU)SUDy4%e#m(5 ze5s$L5JL0m`*t5jNb(5&PjU|YrrEc;7XELp>pn|&gfv&;|0v#%4LlKAhUmL@n0v?c ze%!GTEnh%nbWHjk6Qc#ZTxoiUV!L^m0^h?wuK&15kbgp`@XgWLUoQxgpZ#sgf4>A( z{HG9sKm6wmSPlOnXN!uWP5I;cnO6Vk`I1R)`!}7$#SuV^FY^_HNn5f;N6XH%(nWto-M=p}0-vKi>LM z5L5iPxWGdZBBcJr4M+ULlK<`B)2@?TNB$C_BMYDlN&ciPOPT%S(H;L*#Tl0}M1;To zZ{h!ct70<#y{N#{%n;65gNjcU)4Ao7+alb4+?knv!F7rI)I~wjvEBUlKa)>6`m0^j zs#?K1`XfHFv$nu1wZfL&6fym=R-&!gWa#)!w+}6&j;&%SgCTT1qhk!^^~HV}lm9f2 z+BuzjPc3}AqHtepxuWV-#TC+sxk&h2Zwg1}^@@euBwko%To2=OXN9au1 zl9qs9o#YX*axUq@4OWaF{KgwK0o<*wKh<J2kEx9IqNdJ-R#Y=0)Xu0`sR^;ZZYD=O9V_~GSsSJ;sd-Mj6@5|hJ zZlP1NZL3{QemDPqraNLe3(pk&P&RJJd-TWI-W3h|gPxy#-(s<=9-&>ruvX)5j#{%@ zZlEu197sUFaGoO3}gdw@O#dGr59@--XA0@drm{SpUgDp+v@wV-5?C+T2hPL1w~}ja^HY zBV@P-&fo3)OMV{mRuOKTzn@}My^CE*b!Z9D^`3`%e<63HS%dqoD0Q=WjGpx9RQPz= zg~MvxeWco}%7(5-bQA`)+q-;S#(FzsB#Hcm$E2OXZnDpf-TC?TdSuVB+le1*kzmxT1r3ZD4D#+}?#|D5cUvVTneXp)7^VlYrZh&(SeHzi6X4q3O5pd|amulS14kV~Ta-XqjC93w&hWc=IW@qV^MT zr$JK6nqE)ftiT6g&`AD>|Vns&2@-{IP zcbVLD?A-s=u+x=bY8UpEK=73@p;>wNu|~?}(#z5)%mXcW_w%s(*b!1}9-Y&z75bpP z{=GG7SXxgv){_+>G=-jR$6y3o+C0&sXKlS8jkvv|eEpxdiJ-Whq=Ed8FqBWa-E04) zBJ`m>ie_^)(kuj?7O|W>{q!Fk<19(_Kovc4))~K85UzhO2)98P?fr58XuVzWbl`6Z zbbsh(>5q`g_qz~No>WVfIiqj2_Vq-4q}Z<$O0;|8Lr0ThpMJs#&z4lz`#~YL`0P7h z*V2Uz8B7|PCJH(!Jrxq^-7U)M`{FB(toM-TU7X|J-O=I^Vqss8h{*h76rzq~7Q`#_ z+T#lKYr{Kq#1Xeor^3yvEkZ?v@F(rZ6w%K&cKf~aGfx{0#HeI>|I~?9_=Qd`t6SwE zg-&-KRJeGhM;U>#!>_^~qCath7l)teJm`xHX0m zh^Zp{ZRuS&1&DS`CQcMJb?Q`PChkB-w~{JxT3){v@k~|NN*<$c0WyrD$gOhsF=AiA z8N4}d1rsN}OQI*jlU-)i?oilRO2=Ed{3zg>;H`XxYTCz8c35aOspl<)j7pm{;^sV8 z-6qERCe+9r26&BKqQLKr{<39v@Lw#2kI&40j1{({G(8~?5z=}m7Ygi7@X0(w{KTlm zl2?|PA!c}B4?pz%O4Q&?I-K`uY#uY!MEZ!~ISLx`^NPQrLLvC1%MqKCI0+h;q-p{X z;px-TvN~Ze%D7LScpz49l!A13*ieYAqz4gO2bv{N?;~(q?DPweXE3ReIKo{OWem5f zX)m%s14o5B|MuBkbv6Ci4O@oY62#8E=Zz`8?WS% z|DR^dFq5g;$xvq0o?;uU$}3wo1tRt;|D6w|{wssaIPV_`XnSo~@gn zY8@ErdAA%9e>bnv?&VKFkSYp~Tg?G$rIAiV>vqYEnY{~O>yn*svpfw&Cl%jN5il_m z2BVzeai`O6Ap?lZk4+WhUl*bZZn}5Kh6qA&1>S*+zUV8C`f~ZB0RPrjK2;8@Sm?L@q-ghVn>y-LQ?}afq>mJ{|T{1o2{vsMw(f3Q333 z`~3m@GC44f(*b_utsf_Xot~GhH%>tVR4=;Bwz(p-`J@6fAWRsw44bD2d50{q+?|As zSfPR<&;Jijc-9z=Cs3_!UpWRC5mtW(>CrPkO0I+m!e^jJ(@+-iCP+CKm?M-@4tm4x z-`^onIBV%}rvxHCZlh{{6}acv7IO2doAY3+pR=Pn{Q(tU#F86tZr>@62%3Q>fGDVA z_P0FzTi3KBsCSJ(LBwx!)e*`qOXldPz+Q>N0vi!xK0~62xvs@yv;=?q2D#>9!J0?> zOc{w-QNNJ;8?o^Ix*f|ac903BBVXVJ885Lu8npfBHmRs#Ir(t!vL))Qi>+$o116Sj zn#KAT;yb_0GbnrOK27iA=on=cw-@Ul4Gj2}u1xhAwDChKCACs4Tu1)+*QO@^3+4RK z2#uL-)0%|V(ZJ{4l){d);)D#DBAk}*5Wm@})1Pp?$;)(z}bElW}+lOAJZU~+) zV(6M9!jvH66VjLR$%W<^^D@@=W#NmrO#^)TgY9=am!QJGYlu1;|+|l@6O` zRAPOf5P$}YA|_9pTW?vm*ix^}?bi$QsYx8KTcunVyRc?ay^pqDs$mf6U8L7-*>{Md z`)zqBt6e?qP=@{8V!l$qz^Z4gz8}_JcKEqOQkg#=!T}sPY5!e;7pfn==3K#qXXi3+ zsAL+>Vj>^o2IHw!a#|<{Cb)-t@@zhDco7CKbJ^6Zi#OOb%+zmMQk%jKS?n>D(%@t2s9*YhWyETGZ^r}|~gy{|M;p_hd%aDB` zV&!@RMV(NCfO$RVV>>tPl!_Kq+ZkHML&LAvXNU%VranHDpqIZ1*8b&1cR2_|bewXAd1&sfQW; zBB6GBT`sO4eAhAo5lj}bW3PsGmY#`O6WleRRvG>0unJ+XvB6RaXlee!z;FCYIKmfW zGNPYNMrI`rE|>9-;8LS3f0NUf6nRlO7=1z`$i-u2fKZTqZQd2>Z*rJQ~*WlNd-ywhLv z_%9Tu!zT$%STPNTOkFrNhqZ6&Xxl zJqZ!oO@S*#r<^i(lebXk9pFP@ov&0V&a>1I4UMEG4o;vaCGN|Rs33Y+6&y|zb%S9) zYyE{NAm*VM>E|$QG3(0T$t;ocmJwqv5JU!z&hTJ&#wU0Y3Q)jh|JcDCjCgO&)5TBR z6f%hnwY>=vgaXt`5mdnN9bWMv>MZSU{sdqDz|bys{pBtq5ArFojD*!lbA|^{m2C-VXzBVxid9=Mk;{#U zoV?xX`(4F5g>xQo3klAOdY^c0EDf&lZjdB^Ay& zw^x?+>zIv^+h$)mA`RFuHJBc#swe)UtW=6cA?;`060U-3fV%Whi=hs!OXT)*Cp0Q# z2lXVIdiF4s0b_&>0wxE_2v+}h%_j~0{7C)_rEQW-8GxB_z1JJ?(-n{v<|p%2iUc{i z5mU{XuvUu6ceRRB>`ML%RZuZe2S$mA! zbUmd|ycuIJy*d)R1iSMgcNBstda=nxOjyjmd=ty9wPhQyCmULT1!;0n!?Bit+7>Kf zkvO}TQTqw9q&u0Vo){f1$@PitjNyQsVCMqd;; ze6!ReM3p;{!L7l?$Qo4kjUTF^|20t8TWZ+t7hgo!kZd-(@bh(k6V>l>V#Y*TwxoyV z6*-i0n9)U_cK9x%*6-}awW4HD{*BfZt@>R}P-22;0VWo389U6gC3p}&7wAg|M zsxFejcm+7ZQ2%?~hPBWXxtrKR;Z@u%p2^=g1U12vo<#l&Kv!n>^cG*NGeYgc=)ppt z>iTWe@s${Jfo`Dp~rxZUo2%Uf#{mAD;kArZ3*h&sh775vg;G|(&FcSw}g^;gSi zOF&HL=zHsE>Tz*mj!>=}oR22we|Cw@9=KWRGvdBn9Nj;}D7T#a*fF|7FDkEPeHuEt znT+#w43D_ZAj5v<(>zO?4bO2^0Ud-Wkm3Bj&eE^zjdpp}f1QuaRtxCO>Bk%y_N2D= z9Q(rRcAZjO`&Km#h14k+UwuW1+hoaD)!6uzqSv?!2im)qa#--mBd!Zusr0i{+*6K>p$m%ML@L(0GP}uT zZ$HlzYGp!0qa)cN(}K*^Od7*Hzxe+E8E&#f<8O(LI=$LXnXxEg7*QjW;L* zTzhsc$I3)%YglW^>SGRr&E=-J_7O$*bHnE1C*A(flSKR3D|uDre*1>}*u`F>*P*bD z)^!s%as5BrHd+U=g7kgM!&+92r*cQR6ID>655(nP+N+}oasNgT_lRCFp`oAA@T`Yw z-~rGM+zQ$t`)x{A_C|osPR+)=)2z3j=6SG(1+59>Nry7``F`y0xra#a`7EdJm$vr}?Ud;z+IJv(%9> z(?Y2X72!kBC!06VpUcIO09og{I^vDz+muL6BuF5mJ0!bW*%ENS^W;F7tp~u|sY7lX zeWj66?WVK^mqmyXp3e2q_kmPk^?4+vhdy(6S=?wyf2^Gi?TO2^)R; zb7@kfQP=1?L<=Kz`ZIX(<=&Bau_A%Kq~Q3zcZ=<`RfKw)$r)I)I72K2pzwh`h23D*E!+}1%(YM^fX0`TWGek_9*Y0840n(=m zQ^i^#pN{0{>_30+q|3nV2&e>^Ux2Sv2oNX3P=LHfw*V#KC$3_Xjn?ZCz4`4GvS?XF zP&`f7c!=f;4fLN#-EJ0;69~An0N3SE`02lI$+LU~-G;?NhW~)OAv^!rVELRNs~zHK zU{7V+N^lV9Y_VqtS!z(ovvIY4(4f?Vy?G-%brg3~YQHM$rSsEZ+VzC&$7d|BB%mQn zXBRn)Ef%kznAi_BOBmK1gq6dyom*1KbJjmgaRj)QD>yzMrapHvhZ_VXHT zyhh(c+BPGb(27pu56eCc^)O4@JnwX>RWQ8TTuv$?nh(R{Zl`L3*MJ7B>qw?}z`jgQ z0aOOxWnMsIoOw`~a6nDFVyQmBSWzw+DhRq21ru9B^F`2@v_Magma05PPqDWqcp+j7 zX#|9BYNQ=$h7KWWG88avgtiw<=*NjQp0fDgxC6UV>0WlL#_hqS8`S=k!gc{gUr;c) zgoq%rh(l;>1OBVszki))a;f)GG6s3!$N>>uF{RK4nVmB8%qmy!FvcGIp2OYiLaxd@ zfE#h~0J4<@TnxSH!sDZL&~Ihmr~27P4XpZ8iuGfVZ0jJj&STV>on5kdEQ^bGGZIQd zEb$oiVdo`yW&G8L<48DiK)j8{kd+=!{J);H@EB#V>p|U?Te0Bx1db1am+Kq^Zxf4m z%@kqYAY&CFz5QNotF?bFx@-a+hYZ76g{Y)*Ya*L(w3gTjemx7P2nmK~*!awcT*vQo zW)m4Kzxo?LnF`z9eDz}iD@Qm;L<8&Lgv;pXbs?|(J*!XeEdC4nkneYy^PB(gug8u! z&{Yg0qMtWmdB}PRY`ygxkf5#vuc{bmp*S9C+2%_Qc9AXzCXo5RXKeL;V>49*T`u68 zo1ldz_!&E0Df`1VRk34-IVB@-R<0NIKJ4K4D^I`PybS&PukpAV>1r`IS~u>(o+9K) zAW=We3+Q~q{^)!7Kwn6j0|#=~{&^eow4S&~2IYShLtDu3xDSj(+5Md$AuEv`9Q?Qr zJg%0u7xLx2SqG^3wyEOf*M*yTOBb-_3#S}tzzG}{Sd{W+*IzLUH1~>VC50h=gV_vQ zKn0B&fraV%iV^?2LH7Ba6Mg*M^60*BaEF-C(d}`^%8enB1Da}{mC5qyIJsdz{_;o& zbFjV|5Ccq_%H*aRTGF8TaE)wL0b_=wul8J}$o_;1UY*Pm5Rb9dhO*Jof^}wNjre#0 zypH=`SFQ6bCcLvUWt?+JkT7~(3tsDP_P?fE41HSqxyau6aaldH#M@0ki9qi;0E5s+ z^fDqo0Y%828LN5;oOwW5=&t}0E|2}J`X(-!p+$G9Q3jI7AEzFe# z6DvUN%DUZxGtMGM27O6_MDNp3tQXGz>g6=dMg@9F^ZCl3!X^BhQXyV!dBSyxEL1MC zFSmhmjOH-wUR(P7zq6FV3W~1#Pv42Y816~tIPO?mJt4A8?=93ebi@$HFct5mX#QG73 z55or_0n_{-F0%i3qqn7dXU1DThfGnd$2Qg2_&5G+Do%I{Pt4rJ>DCCn*>2w+(nn1h z%fdPs5P}hHVZ4t>r+cgC=qI(20WG0>TYr7Xy|Nuy6GB}u)iqTHM+;Lp<&yzU;mPvx zUGejXyJQ;2bAov$EdT2C_K?)`8-0nppmL|VWJw4g_T9oCy0iogR=JPOVR*!!bom~> z{(OCl1n#W!$ObRU=+S&JIKz);cudCZ8ath~dGbz>3bNiU#Kzu~)%uhn?Io3=OTt!B z$C8IZ1n(N5`U&HI!dGwCC%fSBpUT^R_L1ose$4Pd9t|@ zM~^MX_P$)i=SPCqjI9$w5-T=ya$xGj4TDms)6)ja9j@?Sn4I6{Th0XQq5-ny2MBvW zGqN{zu)M8uMOuShM@`9D_ZJGF6FkTG--i}MccvnF{-pVa$QT!1*C#W8F6j%nXp6V_ zH_V77>*Kq|Mz02av@7tdULtq(DQ6;ORJ~%0@4oXH{xagM^};4P%Ba0tb3RN`Nt4sf zBx-cM+`}w2_sFBB141P`aiL0{Gg;_&uPb;m!S;p{V<1Dc4(=`IV3D#m$$EqF{%V_m3fvxe5P5 z2~fiBO%*i>UV0@SUaB`#HlC{V{#;YCx$bi?^v0o?Sw4$>iX2XLwzcUVb|HrY&hx}+ zsfr#D`#I_OP^GgW7kw&b0IJCs?{~39O>|OfWR7j}k*-%qo?$}dCKlkU0{-8_`!k|C zoxWeY&Rx2VcklTTO2%aya^jA*q;X`j#@_k6^s*g3iIn_q4xNyo8?A$3lP=;+iGLMj zJ{2^=A1QddsYPVqh2>;k;-&vlzm|CEn$&>#0#izy6iq%X2|X6sDgIjira8-|d3#W40Gprn4 z*v+HWgV#YABi8IQaubJ3)haBz)+Am~NR}_BmDPq!98E^iVzbinMv~|ER3)1a-mMLJ z`LlgydzqCu@yiugHFJwWzUYf)mvpBq0pAsTJ^-@ULR0z7ROiqafnyTYRjXV zT45aDNZaG5zD~DT`8Dc_pPuE#8QhkG?uQ~UlppHF`&eF+0A-%&nb-oUQLmmKf z0!0%qUykI8tYQXCFpup?YInC8m7!7cibN+%Yf^Ir8jYG2$y8v_NZ5jZzWAS#Uz*Fo zUK+FZQZ;8pHQrIL*4caSQnvSkndjhFg&hSsBTu{}QMidvqu%Kpt5xG`_sa)gsMSRh zg9SOBA3)=Y4~4!r8NsnyJ)Y?mKG*j!qq23mVK1W}`6WYLs--+Dh8M%*7~?**#?Rqi z27J15%WofkHh5Nd_3!}ViE(o3bV+thLjosduP(|qY7St7{v}skG)>~{VYlfR!p*l& zLAorTOyRzdI7jGPOgUOtvZ+yE22)4pChb4>Cy6ivg(_l}jU*b>(F3da7Y!2K;Or42 z?vBoavv&#ULe<~X-^+C>zy@Es<3*1$6=ONRcRtK)>+zpQgbB^WTFT)hQ;Q9g{z$|} zuT5w+Oj~C#dl!HwCP{>?B?Ev6Gq3%FvxPq0q&qJ}e&wLMV;>kDeNPaccyp#*lJ}iX z%YF?iJ+8MVb3iNIc&w`+LPVPAon7J;qqbCcoxDSA$AAOq(D3W+)!H$LvR!D5CxErd z-8vQNnTI!;M-_<<>@a4bo4bXdswHvmHpUu_rMb)LS8Lxa*DbyD+u~(t(s=gsM;rwP z+)mhl5$fe-a45xl7OThHwJ0J)|E$(TTD2zzt=Z|i<)7SQM3>%Gh3$E^A%d|sqMc{t z?9aN9t!n>S>9uR~s^@%V5!P{2(%o6NIGj@q=g7(E&J=eg{fOF-3ZiJ=B_lIiHSBH(E7~ z$ZQ0v^;NK!2(up^Bi$tsb4$!!Y1{Thq_E6ATbG?40 z;ErL};)pws7a;vI@)iS9jGn59=viY{^%k}-^t!fDi?{aXlGL2qi_=Gu(JZ_xUgKCD zsZ~=@|I(epxm&cW>U9s*qG%VQ`3rY=*Qk3X#Ez}kqZIrC@Bb>mbM zeph-;Xx+zu4(&kE`oc8nD#FNl%JE=3v-|%rF9}}nClQ@d-za|{@k)diIQ2L&RSD6 zi4)rUMlLGTs3g(PiqUmfUZy*^?qdOfLm6G@QTi_fW7{`zcLI#LMTdt{0q)h>0oXbC zp#!rIUG$I&cUoE+vMWF0gF5n86T*V`vL3mZcX;okI!Pi#*Q}cqn=&e?$ww1;7c_k( zqFF9hj_JO^h&eec-!{(KTekO3fUe5dRDG~y*shu5p9Qw_fM7GaEX2OvBKK~42)4Fg zZiZ4r5FYa@h5%$R6{WVZ`^&f&U89hGa_d@SbfnRTBdIy9O9kfRQOOg@pOP|skaMJHv$QFrsGm~5 zH8tl)QZ}Lm2;DXMS-?He)~3>l*7Lz791hqvxmDYc=l56&h3_G?#i}rU!rZAqms_XL z-{aVawf#h>NEZFj6XqWnkW|&u)llv`J zoaHmCubU&XCKio%*2aB5C~7;EoHK%L9KDWg6Cy>($S~@?ha+DxWEjw&SuM2J*C!kW z`rStPwJ-Sh#OFBOJNZEyZb97@+G%x;!Qi0ho%S9>LBjgl&^|oO` z61*I-w1uMbcq6OzTNT#Ej6r?+2l-8p?{RryYf2jj&A!vHi^pSVn z*7-)(j;WB#Socv@p9x;k9A6Y8`Ra#C47>j}eiMS%^p%%o&xFnTBq+I#cYG z;A+zbp$d&p2Gw=+Eh^nq@geXGv^0W+me*S3unjal8-6mp*k42PR)shycYyOt{6U^) zU@qtEJ%jynOEL{9O7-`#{nLc&PM1wTD(5=NnQ-TbeEAAF6s~ItkKONGLyrihI!rBs z^KBEJFPp*W8h5{VV(BU{2~U}Pl+Y+l+zm&j*BC(`VSwS7>V-Srh;=y`Oy&RAuInO- zsdZLAsu6NOMOsm@h-1}R<0x071G+_O!X$}Pyw~*;OC3Rd#4RBex}0bC=&0KB=9!Tc zyPsH*+&*Q5z8DFYiQ(wf#D~f$tcBe)NtswX#wyiI>$hbi=_<^0W__J?Zsb%5U>DWI z?=(d*V=FZAi5Q_+;ON6{ODpA&EIyx(5bS{cYqD)lSv=M1QIcR9X&gb@_x|g-(HR^o z_{gMb@M!*0QI9$tq%xlpX<_E}cOj3{D4{-0? z5x!$wx^w|jL_$gSGH`|EHX_RV8~7(e^ncm*oU=jybN~VrWuvNtg1AL7g4=jV{{iPn zW9;-H4$>-$2W&u|LQ!T+8C&Ury|JRqGQncH#uo(#E>7kQQ@7INMyo|=?lVrQ&VIpg zn{osGD*RkM4m6$W^HE!hXl_}{fSYn!WR_gRK+=_!U|^O3=_y1Nd@Nm!%QK$rzjpe% z8)&C1-^wu*svVZWQ9*`b-8;-did2W~h{+^R$Gi%{&v9%4oeVgT2P32zwQ3dFt=ed8 zF@BN-4gfzym3ghsetER(0#@KaG5OoQ*#^J54#v&YPXx|*&it@ws{1$*XaJGdtzH4; zA>tKHzsJZg2Fu%x$l!&-%oyw08d>Z1#S{-W4O2~IC%b$nRQreaj-MFjyI4EkN2&}) zB^I-G9wZ?}HGxrxy?yiKmh}6tKhJA_MJZZ;%xR=Jpw`N-cB8Cx*|gs{gPr|?j>cH& za(gJuXn}AngT~_b(<41AK6p#|-ER^7e9-e$sB`@ms@E^}|oJuH~%?%~>_C>?!A1*{1o#P5kjf0r;`5Cvg~_r$QV8 zB8{qT+4p;^uQ=&CTrB%%>fc`mi+oAR7i4(C3#ZKRq)>Q`I|vOuiCu|L3P!D~A>-G!XANT*2uC67ZM`o(qTajYzA|A56$2dV4> zKW>ntQJ5zM9!IeH=E*Clm%Y6tzS@k@q_t3tb}}zD2NyL&hrr8O6b`3$nv!%SWDu5O zFl_s4o)f0#wd&Y;J>yT-Vw-pi=nr!{WC)nB0WfHVPf&pad-N|eihx!~$wXN(z?7Dt z;9^mpa`rG%BPSO<4q2;vV91_P^fBsxc*~$sA>CR7%hE{l{~#OS-@_={Kh(B{QB?~LF!d4nFLH#IH5~3S++XiFEkGxo zr<@*Y+mGs!2hFEPd8e9mB|A=}iZI{J_7?_xNQKNT%0B$iyu@zXfuXZOKh05j#L-MJ zr_jt{c_eIv5Z8Mx)B_I=J#&}4+M`hsl-zxa5j!gLa?P4{B>iEOq^ZPl5@Gg2-lq2S zD$M0Q=eOf#B@UtjDIP9bJ|-&OskJNoCbKACb5Ypy^KRr@i;i_UKditYMv zM2w%k&f1CXRA^c~ft#5weqGE($5vaadkm&=d>21n&ssiSHV(5N0aIsYjjRgsDh~I# z`boc{k_x2OV(;WIO#~Q??YA#yy4DzlepohnuK4OO4#i31BoU-0$eq!@O~sCfQY(Rb zef^=(@62|THRw8H_qFkrb*EBIkd@=sqFwJS7)3@y9`MCZeJ*3|=d&o9hS>}A8JS6? z-IwyZu(LS#FN-l`q{3Y+U5RX7;6zxTh1+zxmkf&O7GXUny_;1ILC&%2Yx{d@eMQjb zs>6G-{36F{r;qC{wqR^E-~ax z!)qBd+8g=JnFbp^_bwp5O~u$I?WF&NPJac?4rXVAmyLe-G85HVe2@RK+hHPmVOT@p z!BdgnrUTxL;lcULyVx__US-6*I{m?l3-q2fE-?EAGsco}``Gpr$LJ3e4|6;r93%2( zCOjj@r?ypk`*-jsE--5EkBy7Es(aBj*YBhtfmrmuw(rG8=b^x=r%g?9b4CDZRE(1@ zEi_Gwe!#I(8-MI2!@B4Ck0=^zAw6?ABR;Vh?AWztS5_LXYjV97OvfD-UpZ{g*z^3v z`4|dC>&DEpx^qy-p{p;kzFmKrka%y=xJ`11_RkcFl4Dwrr3|aim%`)_Q>ZTRg+v3$ z0RpZh^Cl_6r2R%d1*h`#_I`)51J6&0j2MJsFJR4e$ER6_70g=}^Us*ZC_P4O>4T}u zRR;@~SLZ|7<~!lpj9c0!Meb1G|FId04Lca9Lyojy*qYZCaNwTQ#{Kf~-5M28NKW26 zEWY^s-ILh{XI^^a@+0~ImpTI(9ZiP|HZdKMy$^sZ8zb8je`MX&1kN!-%WG#c!#AH` zW=-85kX;i{r)8ZrYH&hC%bF8A;X%D~Cv6%^d^h zcnEOlFNK9$R>5~}n7W+n%vS9P_uBvr_siKC!?6M{8LeBeTDS5g32RUt?Zhbk#o1I_ z!G2MAdPTDbP-wY4nSAuIB0DIzE*DxR71MslBRc^mT+hx7ec=!ESv-vp- z<6Vl#RRdE#JQe4V%^Bz0>Sa#vbqVR_SEG`Jjzt@kC`9yROecmUEdlZ>KId2gJ~N8w zL-uTkqRnm#z^F!z)4QvOTeX?YII86+WCvMznW8N<2gJMIdK{4*0oELv6_%2Ad(b_RGmeT|%y}?bQrkF+aOkYTw;;S9es1GrP7_aBwXk z20$k;>7faa5XT-lS4CqMk)SnKgt%E~o76A@-dPoi-3)qgms1#p4B24M#jCgTHlE|0 zwLh!O3P0wMcX*KFwE!_&an>bJVYYGR!6CET>ihIw>tO)$Y1NeotBNFhan@ijO<>ln z{ zrW{rR?(zcWuohZFKi%B~Dk`nz>)?*jGE7TYi*YHfRtu9$k8siKLvW!r4W7N_cZ3m@ zniExY8PP0{z!7$2<0C*;Pv(52j{nN_ka{eo727iwcX^kqu>{LL6Ym~}h1a^ov7%a@ z$~*Mwq2`5*8U4vzcj@Sm)eMhXE3*v5JCp*tPdJXriOf^ZT}BR+Te#y#*=9Q`gBH6N z8Kl3w-aGFEGINjbG{9SL#LWX%jX9%h!d;?v5cuxs?7<3a=p)+w`(mPnT=aW`i$`Ld$w>8aN!`iV4dNtQ=?bv0PFuo%3m9x>fqUJL)3KqxBB9n0uxr7+v^ zq6VV0u)d9o<=$zE4^BPi)#8hKTnS9>`69DL-1Fi%MC~?0j z<3N`_^LCn_1nb_R6s;S$(GzhYsx!InBLfeUXz-+1@6JY zTV(cR=zo38xl2DXcvUy-7<6n$r=(-5BRN*So0l?kfxzWKg2StB5v=N9VEbbKP>RbU z9t-z_WGI_2+&TF{_tkH<0G+paW8HhP7__6HH^$!Fi= z&i5wMEJZPinZ_T;7J+obFQg{Bl%2B$el_S*K6r?DEm1F1#O^RwhC6 zU)bHGFq_XsPA2Dg!gTM0t~4SJVVt7DcpZ1XXATV67N%DJ;_N4(STMD#8K$o&F!8Gx z?HHt^#`r1kTKtuFg0K9<$VuTuVbKe{RJ1m^NKS8)bvu64-$W$rgDf2ydr*VG?L7wB z%H<_sg1~5A>R@OE+Ijh|FZ77QI_A;o<2utcL5o+8cl2B&3@VzizOD*d9+w$7*dYh) zmnU>%C>KkztlYGKxUnh<4h;2AHAXmNzqH35Ogw;YBh*Oq>TJk;-vKre}Cf5BvcT3j3um@}gTy&*+?S(oF{U^|$8K#7?JZ)Xmi!SMJF zX1|=XvET{ZHH__96M?)A{8Yck5k`m!dg&$z51ciLL|pPd2=$VZFJ9%Naz)C!KG%!2 z9QtRIRjlPuo6{Cn!m&-cf;{^Nw11W7AZ9Oax$qv1$nH*O!`M@bb__ohVFmpKn!5q@ zxuAv>Lrir{a{$l#s+hbLL5oA z2C#uxf++>@Z}tU`_9Vb2y~crnXkvzM*WQ%nLf^{!CM2@O8Sp2?XR2%)U9K2%{RzjN z(r-J(mBM9T8=M854=giD?ryHx%(~1ym~|fBhU7gHC?LDgeW;@xVye9B*@Cd(D@$1~ z;bjj1p2Lu>dEc?{-p_D8al9g>6;fX$D$QW{?3>q-)vL_xQ~-Nic8%)1U-uEHg21C~ z@EnGKci4T{$y4=~U0O88>0tvXo61T@d%H|+uJ~|G&PR5ZS*}8?X}tQFwUj)u-5Zu{ z_fz0~DBO4cF}MIt2}|IAbEo))Oo$*t@6i>v%zQMUW#C}(RFG{ z5pBg;B=H%3T5cWlBnPB^sn}PUO85iXadJG9fCkcxaLGOvdqs-*bi^Qj(&o~j<#!p{ z;gFLwS+B1IrJ|Dr-V+JEC6$vnSIvdG87Vc7n!>$e#q|Ii ztb6cB!#8UeC?+}pirtR)p($_3(wzX=;XEd|#X?&QoBh-To4sjjfKV=|@*x`)K8S9hJMs_B`0v%p; z_%!Q*6R7k#KRf{w$?r1Iu{e$!t439L1lXRYjo!tV-Ydt%Sg)Z+ux)D;NQzt)aT_&! zlXzGofa$KzFcjA61DR_tcmxck`1t4@m^a|zDh9YVFc5sr$cz30NEk31^aWQ;#6IjT z(5CPV7eNCX0b&gISt?vD-w7WHM$lnZFAlkpXu*?%(HYXO#EG=#0XPbtX%W%pwG7X7 z@}QuD*c#o1KxUbq{K)2KKzIR~9~fXAFMJ9;uPY&|10wMu7&%{KDX01R6$d>Zhiol+ z2{y8x=;A_r*Qf`+2uh$ski2k4lIIsC4-?P3!GnuI-2l9F2njd$5hN%`4>Dh352FX;2)OTn=roV0m*5|#)F`xq+ z)Kj&0EtnK3mD{pC@OQ_=@NuBevxzG^0>dd_Of*X`dYUyf5CNnW(>H)7Q1pFH!Bfsb zwjJoUFaeLYC_Kqy!1Oi&+%Q-0+5R8MlsWJm?_`W8<-oZdn}3o3?RVd*lK0Rpg066OpT-^kZ+=TOSN9)!`}m-P8);4W6? zYV7K+%OIdT)$xTLWH!w4LcDCa)uI?W)xC5ea03hn;BPRS5Zs5*H)GhUJ%`D=mjWpI zBS2`_?|7jW?83kT3tp!k8cgw0U@nT`A|?c%G>o!~>#Wv+u4w`dM*y>=r$zcVK@M17 zZR;OhyoGiy#9lTJOt0f-{WSkf>NvdYK`?Rj=lsNAB;7M`Gz-3payT;e79-Yn>0WA1>m&ni99IIj*ddDfK-Pu&qKRF>)0d* zARv5)%5=k``t5k>=OX=Ut*qge52DS%I#WFa^&i~8@(LHJx-1bvP%*`-rmcWz;k`aa z9q)Q^C==&3qyi`cU0K%=(XX#Amq#>Fed=_@2L1;up?Gg~ktngS*Q69?3K%N$ z?udf{sr&8hM>(f&eOd_|gww4o>aAzc#QeyQEHpabV%6kX4f@)PXO30I4BOLrhd=d7 z5c$n&0@>DtK-_yZPW|5fBs(@Q z3#9)=#!^JSlK_YU*ikqno2o%c57vP)nQTtCUWEtSMRza78{fCGe`nQX&gfSpUas9$ zqSdnMIX%m>8o~mPb}+%M>^-6$Glin#ny59dfV|RvOouRT z!_BWdTY1No_=VqO5(ZaP6k9nScno0WjZu!d2)ZcTVCU8obE0!0wusAub9QT1K!pNr z*PJ@JCBS=D;^c1#M#|#OBp;%LH3DN=*oYzi^%ID%A534@FtFBCs^p8CG z-inL6U91jVR7>BzBgHh#g_QeFt)d<-z|3?wI7slQ$EcQhxMvL_DPfW3lo_^f2eH20YvN7_GO#%u5kkusBYjT;1yiirg)F} zA~c&k!AQp50wy#Y#fv7hvBFMBao$LHd|<|c52=-b54rvVH@AlHTt=x4tK2Mazr4f` z(3bu^4+siD*PX}1!=Ynp;~Oh1`}L4$*Tc%=0M)E}R*)z+fhtEa!9XF&rK-^r1H zwPo-%L9M5N;iDG^>VYzZU6r6oWCX1n+XJn%&0d0W$Kvd7$}9=!-|g~ra7{4-Z;{4H zLc&bo(|4e^2NKej18XL#V83YWhW>v$vtbC>m7258!4-IdK6zgSI1-GPC3;K%HWU+r z?#(xVi|`n`C8~%3&qUe6$3aZS54DgtyUs_oq(uADVU_-<;8eK)1)J1hXfF#umT8kASw1CMz$;bkH8l?j44i23#UeYKm`ZGQ2nb zj-NI0>)RW(kQsR!@lJ4UG>Tp~SfWWRZ^^5x_6v^A66((g7`e-+ZX{eV16ILp)mCfY z!xlETz&|^ot{T{ZaR^>t-IFB3d$$E2&I4R&jt12>bA z|0W11UJ<8z1vfab1}qlM0b$JQt(s1b-rY1X&d>IHS987hfJSwiUUdLlHA^?g1F(0U zD10}Vhc`5U@}Whd3t~6C%MJ!lMZ1jPzAbyQPJ50~$yR}LxtlXKz=d4ha$+jxr1Kpx zgO&o`7VezJ**^Spzc}G0lr$7yTCfGZo`7qFOQ1DfJKgp^&(wE1xt?CvD6c*pU&P%O z(o4Xy5ljZ?)4Ee|FX{|1p@4xCVQwFlPuMxB>~Dn* z0N_A(|NS${aFLzP4)W-VX2Jc8EKRfge<1JnR?T<-DE;I7#2^i3j2Y{U@ASX>a;M6n zMVt8~ZXM%$)1=De-?0hrhp97)_DVN4R^ueVvc+#Bj6{EvWo>B-+sH52)eFoQ?r=bW zybfTm+&u4tDKx(0`&;ZzPqnFAOiP2pTBhrOihqTF)x@&+#7Bt77T+ZE_owgtpuXPW zE)yN!tBV$)u(rW?3T|suQ!>h5fehQNymI;t0*0xOTrT+3wLn!vxjg8?T85jSngvyc2 z29_H{eQKUds~o;hv`~{Pi_{|gtXsxo^ z$`l>oyL$Jvi4WA(J~1*o_OniUn16*7b z==H`iHSIy|vyGf?YQ_66eYA?;v^6biKEn>5bm(P@^-WbkglQfj@+e~&SS9j@e^_R> zOi3{OYXJUJCkWx5AGg3O#cZWt_FlA|C|B&@z>ptm zpGJYds=W08KYa4lFzLiUTNX>M25&77gp`Jj2z-+z{*;67n;6xeg49#<1P$-M&#?$NswDH|Bgo=}v)HN){@J~*faHw5nL-;VW?;Pp7GEi%rEGh#i~Vh9A; zfUB>~pya{-r@c3Et2zDu$6uj|n2gbeN;nFWlt$WPFf^rXr-hc2DBe?&)F~AQZ<9$= zoJK{Pv4m4ohb(P!EOn&KOhcz>J1T{mI@PqP_&)B}D?ac4;CEfW%Y9w0E3fl<-OKZS z-p~DhJ{7m~7KiPgu|Lk)wt{bQ4-_;Lf&s{an4}8vL{wxS{1Lo~G7^!;d$ow}Oe6 zBN^g)?7~~e2Dy-MeJ6vh){DWeDA6Nw`Naqa*yjBnL=K1>q7Ciy*9a}~%7XjRhLr_t z$Xj27>x%5QJrCl8h(Lz)FHz?<%#-N9QuG#$g4R$L^KqU@j!9VMc-Kb|Rc)Op@7YWU z3ne{6kFkl(g~b-ZM#q^$DBOWDRs(y0jq*q)AVLH?kutX4eXAtu^3!+H&<(9kIdP9W z_lhFkk<2D1s~MNo5(^pJe!UfWI6bmL{?<4#22a+mt^bnc-XVAO5)E)cNT(ZCK(A<| zLF8Nb$2@!=I*aMZjCAynbo6cZ*X>7OS>>k{1Paym@ssyOS)vP(Dw*(1Hv1~jFY#vJ z6~*`ei4I6@jm$i?Qn<8Atgg$}KkJMXk(kR|J zlT7MD#iPU{ToP{O?rka8e6|_!vzj`2cD-(a%JDDRFRJJ2zaqoOoCAs5Yt{jSd{~3e z7-#GCV-B;u3bnkFb%w8-Pt6&eUwQ*(Stw7q-Aw6t_q}~lz*4B#ObvN79TJw-7<~C~ z;^?;}xv$~lqo|Q`GInY;IXiz$|3M}E_lmTXmds_-MeQNyg3j3zvPGQ-!x8+wBWrcm}|Hkp{F-yv!?l0lf=}T4Yx7Y#5T>+g?BR`y4OV zg{D9B?g&(VSI-&_jt*#~PhW^N!K)eGt0%S2;z{ICbeJ3bA4A_@-K$^1<)X% zySD?y?ILl#c<<20>&KM5qB{&rZ1{6`YVCT1WFwd9bI?aleKjthJnN1$EOFsZ9;j>c z9N7%R4(G+25lU@qBFX*Wt)<^7dYBf@-5zM%()BC(n8a@UiB+{ z=%Yc1@p<<1z5FA=_-5v^oBHhBEf1P~i>^8^9L_R*T7*j`^GmN(fx%Eh-y=Xksx_A^ zEZ5SJoJ*2n|Ubu8ieT-q(>^?Fjqc&jVw*th5Rpkd#<>h^>1b08Bd&V>819bYm zm9gU5isUC1ZQ?pscGKwVKmtLTgqhdychh8{?mBtp`I|huCVER#Y}NcV{2GfMCp~;T zhpX>E`Y#|6`Wua>&v{>s@QsJ=3-g}l+XakVPn$-56@E$YU0CN(wY5B_dl3{Tv^e77 zj8MXPw0nGtRb!KtJVZ!0l}_yK?{Gi3Gb6!8ZE==Z-Khe`7Bs6L@Czlm^Fehngv-Kx z8_6^&nLL?i>?sw|mWEcpGtG`?2u>ox82j})P@+jmXTYeGgh=nYMug_0#nN8jA=m7i z`kU*$E#Cw*%%Ml-j4(`WU<>=6{efpG6QvX1Z@YNXW;*$Z;{ckp3Xa2d>WNV!gVlySSrr6?ser(qWZ-{cW?0gI+9DtXV`q)jCJ^?V8|G*EEq{!{Zf%^B_A45w^6% z4RFnco#lF4x?ZAReofzGtClTKvQ-A)y0%x-kC|Il`Sq<;YKzP1#8Q;qre6&6jVMv$ zb-^Rnhc}gSoqug?j9-)$#&krRjxX4-hv!c#ql5Lag%oHlB@HNi@(FKYdi)u)qrx@S z$N|NXqwkx9Ypr8uX-RyxRQKR=|5M5zcW@8A=a7sKMpExP*e}*aWn`8qXNR@W9T><* z;mjiZBB$#sp}4EBeSWdqkF)8Ybcpee7jYnN<2k4vb;*R?P`p_rvWQb!9!9?-)22T+ zOJBvARA&%3KCKtEBNvl#0=KR$9{@iSOAV;o*V*n};RaE4phM1q$<2ZRe2DOv0TYNJbG7 z)WPQKHq5*5N5l)K#f`r%c0IrhzXiif&I!zV761JZpjNCnP!)lzAX)j3=ubsrNI1fo z?4Y9IvcA~STM-A7TEQYDN1Z}w z7{h%9<2}`2)|?FF4=?Rb`tat#+Yh}zKKyh12nu0JrF<*Ww*`ztq9n(EA%lATdVfQa7jqB2rofv;dg8PA#ZyB3^wPBujvwoCH`J;#z zANF~C?4Ml82subDL%MKdX640rEKBd$c&-`Z%X4D8MhhluSPwTR(ge0A&dMJ3iE?lC z`>i>MiwhuVuYob5OVzW85#1hVcdJ0=@&S&$r#Gujs2J9J@!NgII#z9 zl=5?g30*n*1QNf7)9=yOLqhawHctyV3ExvtBqzJ@35e8lnz4iIz-P%*$PkpV90~%< z>;06!krX-E=2Q}a}%fYYQ{0S$bDChVOL>fa=Rr*Z=0S|LdMq2A(3 zi%>Y8X~4hmGXmtY2;|0`$nR1T`C2#}Ff7Ua=i@*iIwGG8!KKXhFC@ndOF9IHW(8^& zHbwAHWlmMe{)j?F2J#a`3AyxqUS<_cG%$tcJYrH`9}x?NuMl9Bb-*IW%Ql z2!>b|)Gk?5(;z4Lx!uL#aD5O_iqNcx{Mnd6w3*Q--2<=BG&Rll%Tg_TTlwGxsM}N( z?)WazZec0panBbswPXL;GqpSLdihbks*U{nv4-|4M3n&Xu_D-)2Bd)+pV!evS)5;a zA}Wpuoecg;%W#rq2!}e~T7Y%$iw-#3T4b&;y?06JbVZ_UK#ujtAsTf;4r}w7+Togk z+I>(T66meGi3kwVWCnPpuw!}Y1J$MvJ*rKEUoUnnuj*p~zC%br2Nk53y5b<7n>Y&SpLx5% zU9?>s?0o|#{5P9htI3;w=nT<6{}|sSN$PB`gFfRDOQ_Ra|7g#;=Be!G9gB+WN+bBt z`UJ#{fhLTAM$(Dsv`QsgmGBkQWmj9)@fmp=Yt{6d>FvxJCPRBo3q+C&V4U@(^A>6 zo6EE8uU2n)e3s@yl!RE=N09MnHFt8+6Y^`{lG3|g(Wl12whA&Z&obv675YN`<>el) zsu$`l#-(Ll8ez>kr9EvmxLAfJ2}Cbpmjz1IE9D+O%1aeS+&}l8{)KA9MK+;_ezmpS zfxkw3+B*g2qK9J!B_m9qoz>H>ZVIx*u}6 zU!*0mKLE`*^Nrv@t|<^a!L?54E3cJqO9 zZnT~QoXp2-s#!1*jMi(kI2fyaJ9J15ok>GqT#15YOZP>GS2`D!by3{`^_rZvEPg#V zGVul!iL>LhOlC^D$rRWV3AzXQ^(;i`U+u3=@k&pPHFRv4$8<9c_Tqf4qP}FC?gH1$ zklgF<8|`bM*3rL;5`at2S=hq-HQ~r8J{?-1ZEvBuIC>PMv2z?kH zsHs)8CzmI0+6_;^$7NiCBIF$3*F||qbInCte1Djf<(N74>1)Ke@=g$+ze9dtxLk3R zIH*yRGXYT2&tF5sfhyZV3kVjQh=PrDlNzsV3pKnJIa$DAT~_#XMeLhlb+6~NwFT5& z3rr;brC#3>HmkI{i<)|{QS-vjm>o&(SBL%@FtCxc`^AD^(xd`mXvPf#%My_ z+7;cCZ_d3Q4tlIj*{of?ZTuwr0{{Dh0l%a6_~NfnE^jHcO98FP;;ep0%abw{jd;r}oFMpT?7}{`oS75NHT>gUzOqv>2>3typqA_b>WZ8Sh`8`nf_Lp1Bu#R z>XqpO00AI-H=Z8UUELLzN!!36J1+)KCr0MknOHmqd?JK-iU%4sTGl{quNwyt)s54e zn2W2M!!=93E{;l><>kuCdsEq?cK;W{tdFQY%4YT+DcFeNFGh3th_`m|C1RlTo_!@H z!E4%9MgkDA2oyb$J9=y}=Cl`s8vbT;dL-A8A?9|8z>8ZcK?;%^ut<#TGMdtR(X{0H zH$+9pZv6@?RN}RYZI58<3I2z+Ek3p)`V05Xxk(})tvhHZtY5?kD^wuFD+9NTE^4EhR&V!hvRxj*L-V?G?jI?ldq8v+7LNGrD?aBv z6@l=bmn!;CCTT|GV%UA_=fd5ECS6Tyi@f{f$`#b=gR{K0aes=~h9pZ}HygH8`Fll2 zB_ZuQd!XK-* zn3XQuacD4OV<^M8ImQs7?CjY0Dub`8w@JrAkb!XTCd=l6(Y?_DJtw;@0-(vWGPypXDSgK>txE&DpNh*!0W(`gccN**on?lc~oNUKh zWG2$$9`fuWKqvsVX}urWe<#$MeuK(^hG(Fe#U8D#-dOwJ?}0fi>+o0B@1da$pG4>r zX~DRI%Q1tQS7$@*t_jAkPI?r{<{HT{TdsEuV@$&I$dS|8^IVgM-3aBkd&pulOVqRP zD$^=6#qq&mPxl0!xLAJF?uk0mAr$+J{-p*B(^4@5V968!c&|ZdkLO{(^xbD$U#)+C zWTrMf1JOlLf^X}}c@}Rf4Lz~#g}2WQnv_PCspzbtU*N67cE5(1*C4=Q<*S)G%?n;d z&H4E8pZNG+qoAI>_;9q={8iLQMbq++)5m4`RSS%?(>4z8I`)1`mI+K-x4&SPCOLK= z`hp}7pBeRB7TAMpHLM;2!}eoq63;AYwn%-uYFUE1Rg=J+>V`v?eyZm}AE?eN71N&F zhbjVZ*QJV4MnI-I{|nrXz;2Y6uXY`x-={cYfahH%o;^c9OdBayp1kTf$u^zVrD4<$ z5}!p|e(~$b1At0PXK7KR3*<$*4Mbuh(~G0B0R-lW=so!Q>wrHsIVDPh|9s?3yfGXd zA-oOAtryJ2<-g}Mh)D_OVg|ug)1ExCtW(~`^uWm=&$=(0i@r-H{y)+w>thX;b^lvzId6)IqZpnu8jvb{2jFTNyBV z{Fw_|GboaIlM_mo(hk=D5Nzw?GiE70M6<>=bslA$5aUx+0`(1kbUUg6^qYAkx(Q6` z{Ap+LzHkrr`f+ARIV+L1Y|50RK)PU>-gCgCpSmzAo%M#~Wc_ z@61EzFVMq3=5LjRkww`8xR{v7<+zw6VpVsp3GDNYPJfziIg&Jqf;EE`w5tSfZ+%3v zJ_1NLaf93jl+ODIvQ&Waj_r+|vq6zy&0sohTMq(JV0Y9`9LgRZW1dM*0am^Vr&Ou~ zPPZ0*>XQ+oPO;fmrS?^j;Xg5|^5ZxaJGQX1*ml+HJ|cA+ltoS$?29t@s{cU~k_SWr z2`jzhqCnY$8E=S1VAvx)oUg=B=6>AucVxQl*UWKA*^8aGggMuu_2BisRh!}Tdjs8t zX(U03$O^e-d_I9Hivi+g!J!nK6@@gYbf0{9;J$B=bdD4D>yoyY+B&PpCjk+F+K|eA z&bT4*u-Z#;5@+hTe`9F*piK;jBI9C!)~HF40SUjUqC}hVO@EJbOcnO0eEdJrO{gQm zH_kCeCyI@Rn1*vKWs*1%z*uG%^oglEUmXL{HBbzLF^Q#=Pwr&I*|2R zt%e2Bs;XL$PyA4JO{$72M}kiGyFPJar}(`bBa}>r!$=K5yvo?AcwMb5Y@N>sm30m7JYUZMCqt9b) z_MKfNhmW;LHnZ@&t2X-NTT311YaumISh55;j4EnN%(ouxhy@@bUuWgxsVYbMe`M#~ zB(_f3WFVuoW6YIJ#8**pMdqfStnN71Z=CQWWf6_8ap^=ekaA8JY_GV5AFtZ%i?~m4 zR@P5G^;Bv;dw~)zN`e)M-(=MEU2#F`uCcMG1EhD66AlGp*R$aB8M6JY*(NYqJkU0x z@`~tSyk+DpYjR;pA(JO^pU1wJC!)BE>9ND*$=S*6P+E1WJL4_!FUn9+(it(X{bps$ zwiVjukx59W60B-@S0o^sBX2Nl>$NBOlScwytUDgjm+l(yS2EcEjw+dI&sdf#Tw`0< z>tQ$?)8AIup-ob;(q3&U(Tofi0_$7wee)Y%Qe(y^kf;P1;|x(}UiWe$(%ujlHQ z%qNQGv(GvR>vc_M!9b)S$BWp?)`c%ByhdZU(*G&3>2ND>Y+>))g!OCwt-J&mI65{i zH@A+4Ll3Kk4u1wTE`E`tEXK(uMy#&0lj7LI5EukV`SWYHd_iD1#3MF2A!lWSTw=7Y zO;*NRAAmVBBPuUZK{a3Qt~;TG`Jv*B#jFRj`vNIZ6m z;bB{elwf%mfRG0U4bWxw*r;G6XF+D#E2nd9i(jt_~s>!h_X(cnb))QXul8 zHzr0X=RjKjE5)tLDZTI6DY%|!+5ccPcRM+Toht|v>!2;Br&|y8Q78-f`#4mv1|ui3 zJ-mCM=t4K(eBzbktPg+tR!tOT7W#Cx?B(ic6+<)#rkTWRvQYZ1+f5lyGuFrHcJ*Ex^-4bWL9I--Uv1xNsBi!O%ifzoCqZL9?qU+yejTBc;OGUK)(h zAz6XFUs{*^U?6Iv)?1y@H;kY}Y@lxlK3pB6A|lryPXe+!AEnEVAzjqs| zy<8axO9*w*goY(q;1dRezLP5#6R~o=Dn05RV@OkBZ0SQ{o0rT4#yQ`@b_oQ;C~D`y zPH{w~98}}bxT088SzXbJ;4n(m3`zt!Ouj%C$aZsj?g^_|Ie% zeR%+uQeFd)Rgj-4sIfU5yG6b(5Jl54IHh+U2;GH6aJomG*>?x3v$nGx_3qHyFJU@b6H!AjacNYYTkj&H+u^b@&&KKc$`nGfl^0w&7o^*-T$Ay_Y zkPA3sQ1gCwGrQU{xZR0~w9Wq9u?WLY3maE^=sh~FT4AWcM~<9cLgS#JzK`3=weqrnBdIXtvKYn(knWSp_)VsdUly4(JnwQ z7o9+gTz@fiU+E(^v?M9JDg&Iv;ZQZQmTkD%dz<(*oe>Mm`yQJy-Oo7{sp4()Eq2MZ zp;AST{+8s&HM@sg2W=`{GYOsz9K{S51ZwLc2Wm#9&v#wtb7((@x7%ftjfgVRKzJfC z`|<99Fg<&sF*&SNr(>!r1V!H1t>N=we1uI_zc4UxzzySf+sUEmT!6t*n|99h5XszZ zzH5oyWZ^^|E9ryA@-Vl2E5e66;rA!6&>@u1MBjKEg?Dj%=#I+ z_j(L%Hw74l(NgYg{x(&n7tT>boVbo4lCw1vB_eGWZ5|#D?BF4DMs4n^*bu z4%b-2toF*IV|S)3w%3ZV_(yJ;1Mc`i^!YDpWPGy~A$sl=jdEdK^X}wU9;p4!SI+vH zps}s&lybID*U^dGv{v8f05b+gR;YTAX-DEs#hse_IiD7H^!B^sK9-H&Pn-NB;wbfO zw*{;WBh*S%#C)KsJKR)iBz+d~wD;i2A5}GOWoKZ!PY#Az$53Uxot{$_( zo4f?n)^fgHvGU=2p8+LlK?9(caN|VUlD1mEtW6p|VMm)yN|Ri|c)APd2_&LuiEt;$ zKGxrOb`FUr-L6n|8GMZmg`@Pz+QFZdyND*2PnEeA@4C zW9!Bq9+7oqFNktlW->*xj9D6~10k6KE7CHlaoO!ttz|>=Yuz6XUA=vHt5BeowccW& z-#Tn`p!#U=lXgANlEARR2_KYrX$xu4N&?7Skzugsm$isa z*Jd8hJaWzw#YzsCkr0$4B!3+OR@c(IOxxKsQEZhGEy-h6K?;zXrwdT5E&>-9DL#rk zp$(*7T}1_#6ML_i+?H2L)};-sO2pP@evMetS)nUJ6)Dkg0d%~bR748H9EZqZQ|S${ z!bDr2Ho9pnf@mA;%x++XTH+$6NGovsbr{}-bynqCfcRC0T4)pcbJg#??uV;n$bpme znddD-kvNW+4TEe4E4xi!j*{$69VK&=PA7e|l&G&{hLmh(hLl|;Qa1usQd%pJw}~=u z_Tt0H+21*je3eP63ps3ijw~KkxC}2mFgM3yc$}zF1rN4rQ(-&%s^dSbAhZ1;!mbxK z^XV4}Zc;@geh(hk7{046Rx=g9=h8qFIpExOB67y2*BXb6vV&#CqVyay|1=bL=2;q* zkoA{D;F;Lu5Fwe|tXL%zV3HRBQ!1i_B?ZBP2nBZ>U>k-l|t(*=^P z6-}I|$TfgoJF(W6huJFBt@AOkjOi3bUCKI-yrKIs(+8KID9(T0a9F*vAPM0 z4*bpLR~weRV(>z?00n&ZOB!{DEy5;I!UU}7u3^c~sA3J0=SFnCqZxv`HdxYm3wcTs z0f~Zy*Y0bxC(MC&6~(rt^RkA>A>#Nnc+c5Zzp;^gOF7^K<6`MJr=&Pn5hSnam0U%G znW|a`GoAI%xIN^5@a)2CxyhEaRXr70%3DTaX*|eO9p8AxUugaaz{L+0srPeHhh7Fd zd=9IfCNGrI8GK^Rg1z84H>vg8(qj09dPy)RmH0hoknwRhLN+_uRF zXSt%kAr}nkkmpm=2<%N6Z0l6?l&6|}XF4V84tKOJrbKrOs(J?p&!OTU8eOD&Xx}(T zLz++61O9gf5>l`I@JYz?0&1#Q2P9d{*EZ`trw4@qbLFJwLl5+$^%bF zE9w_gLbJOrsa0lNa_NwwUZ4<*=+retq8v#>-;UC)nAuKdW>OCn+mh|H_;P|suAriq zD}^TI-K9)ElZKH&H{-0 z-oqu;Nu^sU>EiJ6ueZ8ZGT3XOiCwLx!==k{9Id6sr7l3!eFJo$k@O94!CAfrQHAr6 zqlS;;lI4TydT6N$7>*rqZvs!H;OWT*#URu&c-nV^b{_kw4bZ+D`zS*A(6&1XBG1Js-V#)kJEE#K$YU7#4s991q1CD z;+VxxRh=*u5+NOAj+)Te)#JKCD#UQS-*%u6XAMVArkBNcU28}0M&}~*8omw>tF3m= z*;-%092c1p;PwnbNqR?%3-W9!$8M{n(+IU^rXtbB)Q8lJj}~X!yf+I~6rHd4p0fqa ziau&GS9~k`1uHL;KQ#yey?%m0X}fRQP;ROv9n&dYh>1weLFX{zxqR&C7t-2@rvuB2 zp%jrNQOihuIqTm9R=cL?HCDJyknMuF?w$9;%b#fqB9&s>q*FjC^qll8W=`n5nBkkf zC8U`iUOEFSviPmSXx`kLYgJRH$h~-*=Ct_cV`Xk&JKcFE(49)fl~*V?he1lTe&Nw%_18BrEX2Us5u*4z9aao-v**GkL`k^x8vA*C7ju*I=?#Q70e66<6I-{|{nl68 zMO6h=mrl;lV&ZK(S762(>c7c{-;LC@%!+f3T10oLL`v5y)wjJl9aqur$FDIO6s`5} zxR{He5eGM#&x#IWX40vc*+QX7ZFtc0V+d>hM@6xUzM4lpBPHQJ{MjZbZi$r~bL-iE zqWNVYM7iYr$z%Xt3J42z_{rSIs{W7*%%au|m|Vy$9Z)Q8C?B7h6=o*eWn6Vq!zc9n zRufy_9`6@g1An%&xHkGZlayD^*u*S88jBa@)Vba@d1{<%64So+b%S?UQ1iTtxnL*` z^~_|ClIc`qyW%rc#kKCaCQ+| \ No newline at end of file diff --git a/docs/README.kimi.md b/docs/README.kimi.md new file mode 100644 index 0000000..0d7cd74 --- /dev/null +++ b/docs/README.kimi.md @@ -0,0 +1,88 @@ +# Superpowers for Kimi Code + +Complete guide for using Superpowers with [Kimi Code](https://github.com/MoonshotAI/kimi-code). + +## Installation + +Superpowers is available in Kimi Code's plugin marketplace. + +Open the plugin manager: + +```text +/plugins +``` + +Go to `Marketplace` > `Superpowers` and install it. + +You can also install from this repository: + +```text +/plugins install https://github.com/obra/superpowers +``` + +For unreleased validation against `dev`, pin the branch explicitly: + +```text +/plugins install https://github.com/obra/superpowers/tree/dev +``` + +Kimi Code applies plugin changes to new sessions. After installing, updating, enabling, disabling, or reloading a plugin, start a fresh session with `/new`. + +## How It Works + +The Kimi plugin manifest lives at `.kimi-plugin/plugin.json`. + +The manifest does three things: + +1. Points Kimi Code at the existing `skills/` directory. +2. Loads `using-superpowers` at session start through `sessionStart.skill`. +3. Provides Kimi-specific tool mapping through `skillInstructions`. + +Kimi Code reads Superpowers skills from this repository. There are no copied skills, symlinks, hooks, or extra runtime dependencies. + +## Tool Mapping + +Skills describe actions instead of hard-coding one runtime's tool names. On Kimi Code these resolve to: + +- "Ask the user" / "ask clarifying questions" -> `AskUserQuestion` +- "Create a todo" / "mark complete in todo list" -> `TodoList` +- "Dispatch a subagent" -> `Agent` +- "Invoke a skill" -> Kimi Code's native `Skill` tool +- "Read a file" / "write a file" / "edit a file" -> `Read`, `Write`, `Edit` +- "Run a shell command" -> `Bash` +- "Search file contents" -> `Grep` +- "Find files by path or pattern" -> `Glob` +- "Fetch a URL" -> `FetchURL` +- "Search the web" -> `WebSearch` + +## Updating + +Use Kimi Code's plugin manager: + +```text +/plugins +``` + +Select Superpowers and update it from there. Start a fresh session with `/new` after updating. + +## Troubleshooting + +### Plugin not loading + +1. Run `/plugins info superpowers` and check diagnostics. +2. Make sure the plugin is enabled. +3. Start a fresh session with `/new` after install or update. + +### Direct GitHub install used an old release + +Kimi Code installs the latest GitHub release for a bare repository URL when one exists. To test unreleased changes before the next Superpowers release, install the branch explicitly: + +```text +/plugins install https://github.com/obra/superpowers/tree/dev +``` + +### Skills not triggering + +1. Confirm `/plugins info superpowers` shows the plugin enabled. +2. Start a fresh session with `/new`. +3. Try the acceptance prompt: `Let's make a react todo list`. A working install should load `brainstorming` before writing code. diff --git a/docs/README.opencode.md b/docs/README.opencode.md new file mode 100644 index 0000000..11da854 --- /dev/null +++ b/docs/README.opencode.md @@ -0,0 +1,163 @@ +# Superpowers for OpenCode + +Complete guide for using Superpowers with [OpenCode.ai](https://opencode.ai). + +## Installation + +Add superpowers to the `plugin` array in your `opencode.json` (global or project-level): + +```json +{ + "plugin": ["superpowers@git+https://github.com/obra/superpowers.git"] +} +``` + +Restart OpenCode. The plugin installs through OpenCode's plugin manager and +registers all skills. + +Verify by asking: "Tell me about your superpowers" + +OpenCode uses its own plugin install. If you also use Claude Code, Codex, or +another harness, install Superpowers separately for each one. + +### Migrating from the old symlink-based install + +If you previously installed superpowers using `git clone` and symlinks, remove the old setup: + +```bash +# Remove old symlinks +rm -f ~/.config/opencode/plugins/superpowers.js +rm -rf ~/.config/opencode/skills/superpowers + +# Optionally remove the cloned repo +rm -rf ~/.config/opencode/superpowers + +# Remove skills.paths from opencode.json if you added one for superpowers +``` + +Then follow the installation steps above. + +## Usage + +### Finding Skills + +Use OpenCode's native `skill` tool to list all available skills: + +``` +use skill tool to list skills +``` + +### Loading a Skill + +``` +use skill tool to load brainstorming +``` + +### Personal Skills + +Create your own skills in `~/.config/opencode/skills/`: + +```bash +mkdir -p ~/.config/opencode/skills/my-skill +``` + +Create `~/.config/opencode/skills/my-skill/SKILL.md`: + +```markdown +--- +name: my-skill +description: Use when [condition] - [what it does] +--- + +# My Skill + +[Your skill content here] +``` + +### Project Skills + +Create project-specific skills in `.opencode/skills/` within your project. + +**Skill Priority:** Project skills > Personal skills > Superpowers skills + +## Updating + +OpenCode installs Superpowers through a git-backed package spec. Some OpenCode +and Bun versions pin that resolved git dependency in a lockfile or cache, so a +restart may not pick up the newest Superpowers commit. If updates do not appear, +clear OpenCode's package cache or reinstall the plugin. + +To pin a specific version, use a branch or tag: + +```json +{ + "plugin": ["superpowers@git+https://github.com/obra/superpowers.git#v5.0.3"] +} +``` + +## How It Works + +The plugin does two things: + +1. **Injects bootstrap context** via the `experimental.chat.messages.transform` hook, adding superpowers awareness to every conversation. +2. **Registers the skills directory** via the `config` hook, so OpenCode discovers all superpowers skills without symlinks or manual config. + +### Tool Mapping + +Skills speak in actions rather than naming any one runtime's tools. On OpenCode these resolve to: + +- "Create a todo" / "mark complete in todo list" → `todowrite` +- `Subagent (general-purpose):` template → OpenCode's `task` tool with `subagent_type: "general"` (or `"explore"` for codebase exploration) +- "Invoke a skill" → OpenCode's native `skill` tool +- "Read a file" → `read` +- "Create a file" / "edit a file" / "delete a file" → `apply_patch` +- "Run a shell command" → `bash` +- "Search file contents" / "find files by name" → `grep`, `glob` +- "Fetch a URL" → `webfetch` + +(Verified against the installed OpenCode CLI's tool inventory.) + +## Troubleshooting + +### Plugin not loading + +1. Check OpenCode logs: `opencode run --print-logs "hello" 2>&1 | grep -i superpowers` +2. Verify the plugin line in your `opencode.json` is correct +3. Make sure you're running a recent version of OpenCode + +### Windows install issues + +Some Windows OpenCode builds have upstream installer issues with git-backed +plugin specs, including cache paths for `git+https` URLs and Bun not finding +`git.exe` even when it works in a normal terminal. If OpenCode cannot install +the plugin, try installing with system npm and pointing OpenCode at the local +package: + +```powershell +npm install superpowers@git+https://github.com/obra/superpowers.git --prefix "$HOME\.config\opencode" +``` + +Then use the installed package path in `opencode.json`: + +```json +{ + "plugin": ["~/.config/opencode/node_modules/superpowers"] +} +``` + +### Skills not found + +1. Use OpenCode's `skill` tool to list available skills +2. Check that the plugin is loading (see above) +3. Each skill needs a `SKILL.md` file with valid YAML frontmatter + +### Bootstrap not appearing + +1. Check OpenCode version supports `experimental.chat.messages.transform` hook +2. Restart OpenCode after config changes + +## Getting Help + +- Report issues: https://github.com/obra/superpowers/issues +- Main documentation: https://github.com/obra/superpowers +- OpenCode docs: https://opencode.ai/docs/ diff --git a/docs/plans/2025-11-22-opencode-support-design.md b/docs/plans/2025-11-22-opencode-support-design.md new file mode 100644 index 0000000..144f1ce --- /dev/null +++ b/docs/plans/2025-11-22-opencode-support-design.md @@ -0,0 +1,294 @@ +# OpenCode Support Design + +**Date:** 2025-11-22 +**Author:** Bot & Jesse +**Status:** Design Complete, Awaiting Implementation + +## Overview + +Add full superpowers support for OpenCode.ai using a native OpenCode plugin architecture that shares core functionality with the existing Codex implementation. + +## Background + +OpenCode.ai is a coding agent similar to Claude Code and Codex. Previous attempts to port superpowers to OpenCode (PR #93, PR #116) used file-copying approaches. This design takes a different approach: building a native OpenCode plugin using their JavaScript/TypeScript plugin system while sharing code with the Codex implementation. + +### Key Differences Between Platforms + +- **Claude Code**: Native Anthropic plugin system + file-based skills +- **Codex**: No plugin system → bootstrap markdown + CLI script +- **OpenCode**: JavaScript/TypeScript plugins with event hooks and custom tools API + +### OpenCode's Agent System + +- **Primary agents**: Build (default, full access) and Plan (restricted, read-only) +- **Subagents**: General (research, searching, multi-step tasks) +- **Invocation**: Automatic dispatch by primary agents OR manual `@mention` syntax +- **Configuration**: Custom agents in `opencode.json` or `~/.config/opencode/agent/` + +## Architecture + +### High-Level Structure + +1. **Shared Core Module** (`lib/skills-core.js`) + - Common skill discovery and parsing logic + - Used by both Codex and OpenCode implementations + +2. **Platform-Specific Wrappers** + - Codex: CLI script (`.codex/superpowers-codex`) + - OpenCode: Plugin module (`.opencode/plugin/superpowers.js`) + +3. **Skill Directories** + - Core: `~/.config/opencode/superpowers/skills/` (or installed location) + - Personal: `~/.config/opencode/skills/` (shadows core skills) + +### Code Reuse Strategy + +Extract common functionality from `.codex/superpowers-codex` into shared module: + +```javascript +// lib/skills-core.js +module.exports = { + extractFrontmatter(filePath), // Parse name + description from YAML + findSkillsInDir(dir, maxDepth), // Recursive SKILL.md discovery + findAllSkills(dirs), // Scan multiple directories + resolveSkillPath(skillName, dirs), // Handle shadowing (personal > core) + checkForUpdates(repoDir) // Git fetch/status check +}; +``` + +### Skill Frontmatter Format + +Current format (no `when_to_use` field): + +```yaml +--- +name: skill-name +description: Use when [condition] - [what it does]; [additional context] +--- +``` + +## OpenCode Plugin Implementation + +### Custom Tools + +**Tool 1: `use_skill`** + +Loads a specific skill's content into the conversation (equivalent to Claude's Skill tool). + +```javascript +{ + name: 'use_skill', + description: 'Load and read a specific skill to guide your work', + schema: z.object({ + skill_name: z.string().describe('Name of skill (e.g., "superpowers:brainstorming")') + }), + execute: async ({ skill_name }) => { + const { skillPath, content, frontmatter } = resolveAndReadSkill(skill_name); + const skillDir = path.dirname(skillPath); + + return `# ${frontmatter.name} +# ${frontmatter.description} +# Supporting tools and docs are in ${skillDir} +# ============================================ + +${content}`; + } +} +``` + +**Tool 2: `find_skills`** + +Lists all available skills with metadata. + +```javascript +{ + name: 'find_skills', + description: 'List all available skills', + schema: z.object({}), + execute: async () => { + const skills = discoverAllSkills(); + return skills.map(s => + `${s.namespace}:${s.name} + ${s.description} + Directory: ${s.directory} +`).join('\n'); + } +} +``` + +### Session Startup Hook + +When a new session starts (`session.started` event): + +1. **Inject using-superpowers content** + - Full content of the using-superpowers skill + - Establishes mandatory workflows + +2. **Run find_skills automatically** + - Display full list of available skills upfront + - Include skill directories for each + +3. **Inject tool mapping instructions** + ```markdown + **Tool Mapping for OpenCode:** + When skills reference tools you don't have, substitute: + - `TodoWrite` → `update_plan` + - `Task` with subagents → Use OpenCode subagent system (@mention) + - `Skill` tool → `use_skill` custom tool + - Read, Write, Edit, Bash → Your native equivalents + + **Skill directories contain:** + - Supporting scripts (run with bash) + - Additional documentation (read with read tool) + - Utilities specific to that skill + ``` + +4. **Check for updates** (non-blocking) + - Quick git fetch with timeout + - Notify if updates available + +### Plugin Structure + +```javascript +// .opencode/plugin/superpowers.js +const skillsCore = require('../../lib/skills-core'); +const path = require('path'); +const fs = require('fs'); +const { z } = require('zod'); + +export const SuperpowersPlugin = async ({ client, directory, $ }) => { + const superpowersDir = path.join(process.env.HOME, '.config/opencode/superpowers'); + const personalDir = path.join(process.env.HOME, '.config/opencode/skills'); + + return { + 'session.started': async () => { + const usingSuperpowers = await readSkill('using-superpowers'); + const skillsList = await findAllSkills(); + const toolMapping = getToolMappingInstructions(); + + return { + context: `${usingSuperpowers}\n\n${skillsList}\n\n${toolMapping}` + }; + }, + + tools: [ + { + name: 'use_skill', + description: 'Load and read a specific skill', + schema: z.object({ + skill_name: z.string() + }), + execute: async ({ skill_name }) => { + // Implementation using skillsCore + } + }, + { + name: 'find_skills', + description: 'List all available skills', + schema: z.object({}), + execute: async () => { + // Implementation using skillsCore + } + } + ] + }; +}; +``` + +## File Structure + +``` +superpowers/ +├── lib/ +│ └── skills-core.js # NEW: Shared skill logic +├── .codex/ +│ ├── superpowers-codex # UPDATED: Use skills-core +│ ├── superpowers-bootstrap.md +│ └── INSTALL.md +├── .opencode/ +│ ├── plugin/ +│ │ └── superpowers.js # NEW: OpenCode plugin +│ └── INSTALL.md # NEW: Installation guide +└── skills/ # Unchanged +``` + +## Implementation Plan + +### Phase 1: Refactor Shared Core + +1. Create `lib/skills-core.js` + - Extract frontmatter parsing from `.codex/superpowers-codex` + - Extract skill discovery logic + - Extract path resolution (with shadowing) + - Update to use only `name` and `description` (no `when_to_use`) + +2. Update `.codex/superpowers-codex` to use shared core + - Import from `../lib/skills-core.js` + - Remove duplicated code + - Keep CLI wrapper logic + +3. Test Codex implementation still works + - Verify bootstrap command + - Verify use-skill command + - Verify find-skills command + +### Phase 2: Build OpenCode Plugin + +1. Create `.opencode/plugin/superpowers.js` + - Import shared core from `../../lib/skills-core.js` + - Implement plugin function + - Define custom tools (use_skill, find_skills) + - Implement session.started hook + +2. Create `.opencode/INSTALL.md` + - Installation instructions + - Directory setup + - Configuration guidance + +3. Test OpenCode implementation + - Verify session startup bootstrap + - Verify use_skill tool works + - Verify find_skills tool works + - Verify skill directories are accessible + +### Phase 3: Documentation & Polish + +1. Update README with OpenCode support +2. Add OpenCode installation to main docs +3. Update RELEASE-NOTES +4. Test both Codex and OpenCode work correctly + +## Next Steps + +1. **Create isolated workspace** (using git worktrees) + - Branch: `feature/opencode-support` + +2. **Follow TDD where applicable** + - Test shared core functions + - Test skill discovery and parsing + - Integration tests for both platforms + +3. **Incremental implementation** + - Phase 1: Refactor shared core + update Codex + - Verify Codex still works before moving on + - Phase 2: Build OpenCode plugin + - Phase 3: Documentation and polish + +4. **Testing strategy** + - Manual testing with real OpenCode installation + - Verify skill loading, directories, scripts work + - Test both Codex and OpenCode side-by-side + - Verify tool mappings work correctly + +5. **PR and merge** + - Create PR with complete implementation + - Test in clean environment + - Merge to main + +## Benefits + +- **Code reuse**: Single source of truth for skill discovery/parsing +- **Maintainability**: Bug fixes apply to both platforms +- **Extensibility**: Easy to add future platforms (Cursor, Windsurf, etc.) +- **Native integration**: Uses OpenCode's plugin system properly +- **Consistency**: Same skill experience across all platforms diff --git a/docs/plans/2025-11-22-opencode-support-implementation.md b/docs/plans/2025-11-22-opencode-support-implementation.md new file mode 100644 index 0000000..bd148b4 --- /dev/null +++ b/docs/plans/2025-11-22-opencode-support-implementation.md @@ -0,0 +1,1095 @@ +# OpenCode Support Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Add full superpowers support for OpenCode.ai with a native JavaScript plugin that shares core functionality with the existing Codex implementation. + +**Architecture:** Extract common skill discovery/parsing logic into `lib/skills-core.js`, refactor Codex to use it, then build OpenCode plugin using their native plugin API with custom tools and session hooks. + +**Tech Stack:** Node.js, JavaScript, OpenCode Plugin API, Git worktrees + +--- + +## Phase 1: Create Shared Core Module + +### Task 1: Extract Frontmatter Parsing + +**Files:** +- Create: `lib/skills-core.js` +- Reference: `.codex/superpowers-codex` (lines 40-74) + +**Step 1: Create lib/skills-core.js with extractFrontmatter function** + +```javascript +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +/** + * Extract YAML frontmatter from a skill file. + * Current format: + * --- + * name: skill-name + * description: Use when [condition] - [what it does] + * --- + * + * @param {string} filePath - Path to SKILL.md file + * @returns {{name: string, description: string}} + */ +function extractFrontmatter(filePath) { + try { + const content = fs.readFileSync(filePath, 'utf8'); + const lines = content.split('\n'); + + let inFrontmatter = false; + let name = ''; + let description = ''; + + for (const line of lines) { + if (line.trim() === '---') { + if (inFrontmatter) break; + inFrontmatter = true; + continue; + } + + if (inFrontmatter) { + const match = line.match(/^(\w+):\s*(.*)$/); + if (match) { + const [, key, value] = match; + switch (key) { + case 'name': + name = value.trim(); + break; + case 'description': + description = value.trim(); + break; + } + } + } + } + + return { name, description }; + } catch (error) { + return { name: '', description: '' }; + } +} + +module.exports = { + extractFrontmatter +}; +``` + +**Step 2: Verify file was created** + +Run: `ls -l lib/skills-core.js` +Expected: File exists + +**Step 3: Commit** + +```bash +git add lib/skills-core.js +git commit -m "feat: create shared skills core module with frontmatter parser" +``` + +--- + +### Task 2: Extract Skill Discovery Logic + +**Files:** +- Modify: `lib/skills-core.js` +- Reference: `.codex/superpowers-codex` (lines 97-136) + +**Step 1: Add findSkillsInDir function to skills-core.js** + +Add before `module.exports`: + +```javascript +/** + * Find all SKILL.md files in a directory recursively. + * + * @param {string} dir - Directory to search + * @param {string} sourceType - 'personal' or 'superpowers' for namespacing + * @param {number} maxDepth - Maximum recursion depth (default: 3) + * @returns {Array<{path: string, name: string, description: string, sourceType: string}>} + */ +function findSkillsInDir(dir, sourceType, maxDepth = 3) { + const skills = []; + + if (!fs.existsSync(dir)) return skills; + + function recurse(currentDir, depth) { + if (depth > maxDepth) return; + + const entries = fs.readdirSync(currentDir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(currentDir, entry.name); + + if (entry.isDirectory()) { + // Check for SKILL.md in this directory + const skillFile = path.join(fullPath, 'SKILL.md'); + if (fs.existsSync(skillFile)) { + const { name, description } = extractFrontmatter(skillFile); + skills.push({ + path: fullPath, + skillFile: skillFile, + name: name || entry.name, + description: description || '', + sourceType: sourceType + }); + } + + // Recurse into subdirectories + recurse(fullPath, depth + 1); + } + } + } + + recurse(dir, 0); + return skills; +} +``` + +**Step 2: Update module.exports** + +Replace the exports line with: + +```javascript +module.exports = { + extractFrontmatter, + findSkillsInDir +}; +``` + +**Step 3: Verify syntax** + +Run: `node -c lib/skills-core.js` +Expected: No output (success) + +**Step 4: Commit** + +```bash +git add lib/skills-core.js +git commit -m "feat: add skill discovery function to core module" +``` + +--- + +### Task 3: Extract Skill Resolution Logic + +**Files:** +- Modify: `lib/skills-core.js` +- Reference: `.codex/superpowers-codex` (lines 212-280) + +**Step 1: Add resolveSkillPath function** + +Add before `module.exports`: + +```javascript +/** + * Resolve a skill name to its file path, handling shadowing + * (personal skills override superpowers skills). + * + * @param {string} skillName - Name like "superpowers:brainstorming" or "my-skill" + * @param {string} superpowersDir - Path to superpowers skills directory + * @param {string} personalDir - Path to personal skills directory + * @returns {{skillFile: string, sourceType: string, skillPath: string} | null} + */ +function resolveSkillPath(skillName, superpowersDir, personalDir) { + // Strip superpowers: prefix if present + const forceSuperpowers = skillName.startsWith('superpowers:'); + const actualSkillName = forceSuperpowers ? skillName.replace(/^superpowers:/, '') : skillName; + + // Try personal skills first (unless explicitly superpowers:) + if (!forceSuperpowers && personalDir) { + const personalPath = path.join(personalDir, actualSkillName); + const personalSkillFile = path.join(personalPath, 'SKILL.md'); + if (fs.existsSync(personalSkillFile)) { + return { + skillFile: personalSkillFile, + sourceType: 'personal', + skillPath: actualSkillName + }; + } + } + + // Try superpowers skills + if (superpowersDir) { + const superpowersPath = path.join(superpowersDir, actualSkillName); + const superpowersSkillFile = path.join(superpowersPath, 'SKILL.md'); + if (fs.existsSync(superpowersSkillFile)) { + return { + skillFile: superpowersSkillFile, + sourceType: 'superpowers', + skillPath: actualSkillName + }; + } + } + + return null; +} +``` + +**Step 2: Update module.exports** + +```javascript +module.exports = { + extractFrontmatter, + findSkillsInDir, + resolveSkillPath +}; +``` + +**Step 3: Verify syntax** + +Run: `node -c lib/skills-core.js` +Expected: No output + +**Step 4: Commit** + +```bash +git add lib/skills-core.js +git commit -m "feat: add skill path resolution with shadowing support" +``` + +--- + +### Task 4: Extract Update Check Logic + +**Files:** +- Modify: `lib/skills-core.js` +- Reference: `.codex/superpowers-codex` (lines 16-38) + +**Step 1: Add checkForUpdates function** + +Add at top after requires: + +```javascript +const { execSync } = require('child_process'); +``` + +Add before `module.exports`: + +```javascript +/** + * Check if a git repository has updates available. + * + * @param {string} repoDir - Path to git repository + * @returns {boolean} - True if updates are available + */ +function checkForUpdates(repoDir) { + try { + // Quick check with 3 second timeout to avoid delays if network is down + const output = execSync('git fetch origin && git status --porcelain=v1 --branch', { + cwd: repoDir, + timeout: 3000, + encoding: 'utf8', + stdio: 'pipe' + }); + + // Parse git status output to see if we're behind + const statusLines = output.split('\n'); + for (const line of statusLines) { + if (line.startsWith('## ') && line.includes('[behind ')) { + return true; // We're behind remote + } + } + return false; // Up to date + } catch (error) { + // Network down, git error, timeout, etc. - don't block bootstrap + return false; + } +} +``` + +**Step 2: Update module.exports** + +```javascript +module.exports = { + extractFrontmatter, + findSkillsInDir, + resolveSkillPath, + checkForUpdates +}; +``` + +**Step 3: Verify syntax** + +Run: `node -c lib/skills-core.js` +Expected: No output + +**Step 4: Commit** + +```bash +git add lib/skills-core.js +git commit -m "feat: add git update checking to core module" +``` + +--- + +## Phase 2: Refactor Codex to Use Shared Core + +### Task 5: Update Codex to Import Shared Core + +**Files:** +- Modify: `.codex/superpowers-codex` (add import at top) + +**Step 1: Add import statement** + +After the existing requires at top of file (around line 6), add: + +```javascript +const skillsCore = require('../lib/skills-core'); +``` + +**Step 2: Verify syntax** + +Run: `node -c .codex/superpowers-codex` +Expected: No output + +**Step 3: Commit** + +```bash +git add .codex/superpowers-codex +git commit -m "refactor: import shared skills core in codex" +``` + +--- + +### Task 6: Replace extractFrontmatter with Core Version + +**Files:** +- Modify: `.codex/superpowers-codex` (lines 40-74) + +**Step 1: Remove local extractFrontmatter function** + +Delete lines 40-74 (the entire extractFrontmatter function definition). + +**Step 2: Update all extractFrontmatter calls** + +Find and replace all calls from `extractFrontmatter(` to `skillsCore.extractFrontmatter(` + +Affected lines approximately: 90, 310 + +**Step 3: Verify script still works** + +Run: `.codex/superpowers-codex find-skills | head -20` +Expected: Shows list of skills + +**Step 4: Commit** + +```bash +git add .codex/superpowers-codex +git commit -m "refactor: use shared extractFrontmatter in codex" +``` + +--- + +### Task 7: Replace findSkillsInDir with Core Version + +**Files:** +- Modify: `.codex/superpowers-codex` (lines 97-136, approximately) + +**Step 1: Remove local findSkillsInDir function** + +Delete the entire `findSkillsInDir` function definition (approximately lines 97-136). + +**Step 2: Update all findSkillsInDir calls** + +Replace calls from `findSkillsInDir(` to `skillsCore.findSkillsInDir(` + +**Step 3: Verify script still works** + +Run: `.codex/superpowers-codex find-skills | head -20` +Expected: Shows list of skills + +**Step 4: Commit** + +```bash +git add .codex/superpowers-codex +git commit -m "refactor: use shared findSkillsInDir in codex" +``` + +--- + +### Task 8: Replace checkForUpdates with Core Version + +**Files:** +- Modify: `.codex/superpowers-codex` (lines 16-38, approximately) + +**Step 1: Remove local checkForUpdates function** + +Delete the entire `checkForUpdates` function definition. + +**Step 2: Update all checkForUpdates calls** + +Replace calls from `checkForUpdates(` to `skillsCore.checkForUpdates(` + +**Step 3: Verify script still works** + +Run: `.codex/superpowers-codex bootstrap | head -50` +Expected: Shows bootstrap content + +**Step 4: Commit** + +```bash +git add .codex/superpowers-codex +git commit -m "refactor: use shared checkForUpdates in codex" +``` + +--- + +## Phase 3: Build OpenCode Plugin + +### Task 9: Create OpenCode Plugin Directory Structure + +**Files:** +- Create: `.opencode/plugin/superpowers.js` + +**Step 1: Create directory** + +Run: `mkdir -p .opencode/plugin` + +**Step 2: Create basic plugin file** + +```javascript +#!/usr/bin/env node + +/** + * Superpowers plugin for OpenCode.ai + * + * Provides custom tools for loading and discovering skills, + * with automatic bootstrap on session start. + */ + +const skillsCore = require('../../lib/skills-core'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); + +const homeDir = os.homedir(); +const superpowersSkillsDir = path.join(homeDir, '.config/opencode/superpowers/skills'); +const personalSkillsDir = path.join(homeDir, '.config/opencode/skills'); + +/** + * OpenCode plugin entry point + */ +export const SuperpowersPlugin = async ({ project, client, $, directory, worktree }) => { + return { + // Custom tools and hooks will go here + }; +}; +``` + +**Step 3: Verify file was created** + +Run: `ls -l .opencode/plugin/superpowers.js` +Expected: File exists + +**Step 4: Commit** + +```bash +git add .opencode/plugin/superpowers.js +git commit -m "feat: create opencode plugin scaffold" +``` + +--- + +### Task 10: Implement use_skill Tool + +**Files:** +- Modify: `.opencode/plugin/superpowers.js` + +**Step 1: Add use_skill tool implementation** + +Replace the plugin return statement with: + +```javascript +export const SuperpowersPlugin = async ({ project, client, $, directory, worktree }) => { + // Import zod for schema validation + const { z } = await import('zod'); + + return { + tools: [ + { + name: 'use_skill', + description: 'Load and read a specific skill to guide your work. Skills contain proven workflows, mandatory processes, and expert techniques.', + schema: z.object({ + skill_name: z.string().describe('Name of the skill to load (e.g., "superpowers:brainstorming" or "my-custom-skill")') + }), + execute: async ({ skill_name }) => { + // Resolve skill path (handles shadowing: personal > superpowers) + const resolved = skillsCore.resolveSkillPath( + skill_name, + superpowersSkillsDir, + personalSkillsDir + ); + + if (!resolved) { + return `Error: Skill "${skill_name}" not found.\n\nRun find_skills to see available skills.`; + } + + // Read skill content + const fullContent = fs.readFileSync(resolved.skillFile, 'utf8'); + const { name, description } = skillsCore.extractFrontmatter(resolved.skillFile); + + // Extract content after frontmatter + const lines = fullContent.split('\n'); + let inFrontmatter = false; + let frontmatterEnded = false; + const contentLines = []; + + for (const line of lines) { + if (line.trim() === '---') { + if (inFrontmatter) { + frontmatterEnded = true; + continue; + } + inFrontmatter = true; + continue; + } + + if (frontmatterEnded || !inFrontmatter) { + contentLines.push(line); + } + } + + const content = contentLines.join('\n').trim(); + const skillDirectory = path.dirname(resolved.skillFile); + + // Format output similar to Claude Code's Skill tool + return `# ${name || skill_name} +# ${description || ''} +# Supporting tools and docs are in ${skillDirectory} +# ============================================ + +${content}`; + } + } + ] + }; +}; +``` + +**Step 2: Verify syntax** + +Run: `node -c .opencode/plugin/superpowers.js` +Expected: No output + +**Step 3: Commit** + +```bash +git add .opencode/plugin/superpowers.js +git commit -m "feat: implement use_skill tool for opencode" +``` + +--- + +### Task 11: Implement find_skills Tool + +**Files:** +- Modify: `.opencode/plugin/superpowers.js` + +**Step 1: Add find_skills tool to tools array** + +Add after the use_skill tool definition, before closing the tools array: + +```javascript + { + name: 'find_skills', + description: 'List all available skills in the superpowers and personal skill libraries.', + schema: z.object({}), + execute: async () => { + // Find skills in both directories + const superpowersSkills = skillsCore.findSkillsInDir( + superpowersSkillsDir, + 'superpowers', + 3 + ); + const personalSkills = skillsCore.findSkillsInDir( + personalSkillsDir, + 'personal', + 3 + ); + + // Combine and format skills list + const allSkills = [...personalSkills, ...superpowersSkills]; + + if (allSkills.length === 0) { + return 'No skills found. Install superpowers skills to ~/.config/opencode/superpowers/skills/'; + } + + let output = 'Available skills:\n\n'; + + for (const skill of allSkills) { + const namespace = skill.sourceType === 'personal' ? '' : 'superpowers:'; + const skillName = skill.name || path.basename(skill.path); + + output += `${namespace}${skillName}\n`; + if (skill.description) { + output += ` ${skill.description}\n`; + } + output += ` Directory: ${skill.path}\n\n`; + } + + return output; + } + } +``` + +**Step 2: Verify syntax** + +Run: `node -c .opencode/plugin/superpowers.js` +Expected: No output + +**Step 3: Commit** + +```bash +git add .opencode/plugin/superpowers.js +git commit -m "feat: implement find_skills tool for opencode" +``` + +--- + +### Task 12: Implement Session Start Hook + +**Files:** +- Modify: `.opencode/plugin/superpowers.js` + +**Step 1: Add session.started hook** + +After the tools array, add: + +```javascript + 'session.started': async () => { + // Read using-superpowers skill content + const usingSuperpowersPath = skillsCore.resolveSkillPath( + 'using-superpowers', + superpowersSkillsDir, + personalSkillsDir + ); + + let usingSuperpowersContent = ''; + if (usingSuperpowersPath) { + const fullContent = fs.readFileSync(usingSuperpowersPath.skillFile, 'utf8'); + // Strip frontmatter + const lines = fullContent.split('\n'); + let inFrontmatter = false; + let frontmatterEnded = false; + const contentLines = []; + + for (const line of lines) { + if (line.trim() === '---') { + if (inFrontmatter) { + frontmatterEnded = true; + continue; + } + inFrontmatter = true; + continue; + } + + if (frontmatterEnded || !inFrontmatter) { + contentLines.push(line); + } + } + + usingSuperpowersContent = contentLines.join('\n').trim(); + } + + // Tool mapping instructions + const toolMapping = ` +**Tool Mapping for OpenCode:** +When skills reference tools you don't have, substitute OpenCode equivalents: +- \`TodoWrite\` → \`update_plan\` (your planning/task tracking tool) +- \`Task\` tool with subagents → Use OpenCode's subagent system (@mention syntax or automatic dispatch) +- \`Skill\` tool → \`use_skill\` custom tool (already available) +- \`Read\`, \`Write\`, \`Edit\`, \`Bash\` → Use your native tools + +**Skill directories contain supporting files:** +- Scripts you can run with bash tool +- Additional documentation you can read +- Utilities and helpers specific to that skill + +**Skills naming:** +- Superpowers skills: \`superpowers:skill-name\` (from ~/.config/opencode/superpowers/skills/) +- Personal skills: \`skill-name\` (from ~/.config/opencode/skills/) +- Personal skills override superpowers skills when names match +`; + + // Check for updates (non-blocking) + const hasUpdates = skillsCore.checkForUpdates( + path.join(homeDir, '.config/opencode/superpowers') + ); + + const updateNotice = hasUpdates ? + '\n\n⚠️ **Updates available!** Run `cd ~/.config/opencode/superpowers && git pull` to update superpowers.' : + ''; + + // Return context to inject into session + return { + context: ` +You have superpowers. + +**Below is the full content of your 'superpowers:using-superpowers' skill - your introduction to using skills. For all other skills, use the 'use_skill' tool:** + +${usingSuperpowersContent} + +${toolMapping}${updateNotice} +` + }; + } +``` + +**Step 2: Verify syntax** + +Run: `node -c .opencode/plugin/superpowers.js` +Expected: No output + +**Step 3: Commit** + +```bash +git add .opencode/plugin/superpowers.js +git commit -m "feat: implement session.started hook for opencode" +``` + +--- + +## Phase 4: Documentation + +### Task 13: Create OpenCode Installation Guide + +**Files:** +- Create: `.opencode/INSTALL.md` + +**Step 1: Create installation guide** + +```markdown +# Installing Superpowers for OpenCode + +## Prerequisites + +- [OpenCode.ai](https://opencode.ai) installed +- Node.js installed +- Git installed + +## Installation Steps + +### 1. Install Superpowers Skills + +```bash +# Clone superpowers skills to OpenCode config directory +mkdir -p ~/.config/opencode/superpowers +git clone https://github.com/obra/superpowers.git ~/.config/opencode/superpowers +``` + +### 2. Install the Plugin + +The plugin is included in the superpowers repository you just cloned. + +OpenCode will automatically discover it from: +- `~/.config/opencode/superpowers/.opencode/plugin/superpowers.js` + +Or you can link it to the project-local plugin directory: + +```bash +# In your OpenCode project +mkdir -p .opencode/plugin +ln -s ~/.config/opencode/superpowers/.opencode/plugin/superpowers.js .opencode/plugin/superpowers.js +``` + +### 3. Restart OpenCode + +Restart OpenCode to load the plugin. On the next session, you should see: + +``` +You have superpowers. +``` + +## Usage + +### Finding Skills + +Use the `find_skills` tool to list all available skills: + +``` +use find_skills tool +``` + +### Loading a Skill + +Use the `use_skill` tool to load a specific skill: + +``` +use use_skill tool with skill_name: "superpowers:brainstorming" +``` + +### Personal Skills + +Create your own skills in `~/.config/opencode/skills/`: + +```bash +mkdir -p ~/.config/opencode/skills/my-skill +``` + +Create `~/.config/opencode/skills/my-skill/SKILL.md`: + +```markdown +--- +name: my-skill +description: Use when [condition] - [what it does] +--- + +# My Skill + +[Your skill content here] +``` + +Personal skills override superpowers skills with the same name. + +## Updating + +```bash +cd ~/.config/opencode/superpowers +git pull +``` + +## Troubleshooting + +### Plugin not loading + +1. Check plugin file exists: `ls ~/.config/opencode/superpowers/.opencode/plugin/superpowers.js` +2. Check OpenCode logs for errors +3. Verify Node.js is installed: `node --version` + +### Skills not found + +1. Verify skills directory exists: `ls ~/.config/opencode/superpowers/skills` +2. Use `find_skills` tool to see what's discovered +3. Check file structure: each skill should have a `SKILL.md` file + +### Tool mapping issues + +When a skill references a Claude Code tool you don't have: +- `TodoWrite` → use `update_plan` +- `Task` with subagents → use `@mention` syntax to invoke OpenCode subagents +- `Skill` → use `use_skill` tool +- File operations → use your native tools + +## Getting Help + +- Report issues: https://github.com/obra/superpowers/issues +- Documentation: https://github.com/obra/superpowers +``` + +**Step 2: Verify file created** + +Run: `ls -l .opencode/INSTALL.md` +Expected: File exists + +**Step 3: Commit** + +```bash +git add .opencode/INSTALL.md +git commit -m "docs: add opencode installation guide" +``` + +--- + +### Task 14: Update Main README + +**Files:** +- Modify: `README.md` + +**Step 1: Add OpenCode section** + +Find the section about supported platforms (search for "Codex" in the file), and add after it: + +```markdown +### OpenCode + +Superpowers works with [OpenCode.ai](https://opencode.ai) through a native JavaScript plugin. + +**Installation:** See [.opencode/INSTALL.md](.opencode/INSTALL.md) + +**Features:** +- Custom tools: `use_skill` and `find_skills` +- Automatic session bootstrap +- Personal skills with shadowing +- Supporting files and scripts access +``` + +**Step 2: Verify formatting** + +Run: `grep -A 10 "### OpenCode" README.md` +Expected: Shows the section you added + +**Step 3: Commit** + +```bash +git add README.md +git commit -m "docs: add opencode support to readme" +``` + +--- + +### Task 15: Update Release Notes + +**Files:** +- Modify: `RELEASE-NOTES.md` + +**Step 1: Add entry for OpenCode support** + +At the top of the file (after the header), add: + +```markdown +## [Unreleased] + +### Added + +- **OpenCode Support**: Native JavaScript plugin for OpenCode.ai + - Custom tools: `use_skill` and `find_skills` + - Automatic session bootstrap with tool mapping instructions + - Shared core module (`lib/skills-core.js`) for code reuse + - Installation guide in `.opencode/INSTALL.md` + +### Changed + +- **Refactored Codex Implementation**: Now uses shared `lib/skills-core.js` module + - Eliminates code duplication between Codex and OpenCode + - Single source of truth for skill discovery and parsing + +--- + +``` + +**Step 2: Verify formatting** + +Run: `head -30 RELEASE-NOTES.md` +Expected: Shows your new section + +**Step 3: Commit** + +```bash +git add RELEASE-NOTES.md +git commit -m "docs: add opencode support to release notes" +``` + +--- + +## Phase 5: Final Verification + +### Task 16: Test Codex Still Works + +**Files:** +- Test: `.codex/superpowers-codex` + +**Step 1: Test find-skills command** + +Run: `.codex/superpowers-codex find-skills | head -20` +Expected: Shows list of skills with names and descriptions + +**Step 2: Test use-skill command** + +Run: `.codex/superpowers-codex use-skill superpowers:brainstorming | head -20` +Expected: Shows brainstorming skill content + +**Step 3: Test bootstrap command** + +Run: `.codex/superpowers-codex bootstrap | head -30` +Expected: Shows bootstrap content with instructions + +**Step 4: If all tests pass, record success** + +No commit needed - this is verification only. + +--- + +### Task 17: Verify File Structure + +**Files:** +- Check: All new files exist + +**Step 1: Verify all files created** + +Run: +```bash +ls -l lib/skills-core.js +ls -l .opencode/plugin/superpowers.js +ls -l .opencode/INSTALL.md +``` + +Expected: All files exist + +**Step 2: Verify directory structure** + +Run: `tree -L 2 .opencode/` (or `find .opencode -type f` if tree not available) +Expected: +``` +.opencode/ +├── INSTALL.md +└── plugin/ + └── superpowers.js +``` + +**Step 3: If structure correct, proceed** + +No commit needed - this is verification only. + +--- + +### Task 18: Final Commit and Summary + +**Files:** +- Check: `git status` + +**Step 1: Check git status** + +Run: `git status` +Expected: Working tree clean, all changes committed + +**Step 2: Review commit log** + +Run: `git log --oneline -20` +Expected: Shows all commits from this implementation + +**Step 3: Create summary document** + +Create a completion summary showing: +- Total commits made +- Files created: `lib/skills-core.js`, `.opencode/plugin/superpowers.js`, `.opencode/INSTALL.md` +- Files modified: `.codex/superpowers-codex`, `README.md`, `RELEASE-NOTES.md` +- Testing performed: Codex commands verified +- Ready for: Testing with actual OpenCode installation + +**Step 4: Report completion** + +Present summary to user and offer to: +1. Push to remote +2. Create pull request +3. Test with real OpenCode installation (requires OpenCode installed) + +--- + +## Testing Guide (Manual - Requires OpenCode) + +These steps require OpenCode to be installed and are not part of the automated implementation: + +1. **Install skills**: Follow `.opencode/INSTALL.md` +2. **Start OpenCode session**: Verify bootstrap appears +3. **Test find_skills**: Should list all available skills +4. **Test use_skill**: Load a skill and verify content appears +5. **Test supporting files**: Verify skill directory paths are accessible +6. **Test personal skills**: Create a personal skill and verify it shadows core +7. **Test tool mapping**: Verify TodoWrite → update_plan mapping works + +## Success Criteria + +- [ ] `lib/skills-core.js` created with all core functions +- [ ] `.codex/superpowers-codex` refactored to use shared core +- [ ] Codex commands still work (find-skills, use-skill, bootstrap) +- [ ] `.opencode/plugin/superpowers.js` created with tools and hooks +- [ ] Installation guide created +- [ ] README and RELEASE-NOTES updated +- [ ] All changes committed +- [ ] Working tree clean diff --git a/docs/plans/2025-11-28-skills-improvements-from-user-feedback.md b/docs/plans/2025-11-28-skills-improvements-from-user-feedback.md new file mode 100644 index 0000000..52a8b0e --- /dev/null +++ b/docs/plans/2025-11-28-skills-improvements-from-user-feedback.md @@ -0,0 +1,711 @@ +# Skills Improvements from User Feedback + +**Date:** 2025-11-28 +**Status:** Draft +**Source:** Two Claude instances using superpowers in real development scenarios + +--- + +## Executive Summary + +Two Claude instances provided detailed feedback from actual development sessions. Their feedback reveals **systematic gaps** in current skills that allowed preventable bugs to ship despite following the skills. + +**Critical insight:** These are problem reports, not just solution proposals. The problems are real; the solutions need careful evaluation. + +**Key themes:** +1. **Verification gaps** - We verify operations succeed but not that they achieve intended outcomes +2. **Process hygiene** - Background processes accumulate and interfere across subagents +3. **Context optimization** - Subagents get too much irrelevant information +4. **Self-reflection missing** - No prompt to critique own work before handoff +5. **Mock safety** - Mocks can drift from interfaces without detection +6. **Skill activation** - Skills exist but aren't being read/used + +--- + +## Problems Identified + +### Problem 1: Configuration Change Verification Gap + +**What happened:** +- Subagent tested "OpenAI integration" +- Set `OPENAI_API_KEY` env var +- Got status 200 responses +- Reported "OpenAI integration working" +- **BUT** response contained `"model": "claude-sonnet-4-20250514"` - was actually using Anthropic + +**Root cause:** +`verification-before-completion` checks operations succeed but not that outcomes reflect intended configuration changes. + +**Impact:** High - False confidence in integration tests, bugs ship to production + +**Example failure pattern:** +- Switch LLM provider → verify status 200 but don't check model name +- Enable feature flag → verify no errors but don't check feature is active +- Change environment → verify deployment succeeds but don't check environment vars + +--- + +### Problem 2: Background Process Accumulation + +**What happened:** +- Multiple subagents dispatched during session +- Each started background server processes +- Processes accumulated (4+ servers running) +- Stale processes still bound to ports +- Later E2E test hit stale server with wrong config +- Confusing/incorrect test results + +**Root cause:** +Subagents are stateless - don't know about previous subagents' processes. No cleanup protocol. + +**Impact:** Medium-High - Tests hit wrong server, false passes/failures, debugging confusion + +--- + +### Problem 3: Context Bloat in Subagent Prompts + +**What happened:** +- Standard approach: give subagent full plan file to read +- Experiment: give only task + pattern + file + verify command +- Result: Faster, more focused, single-attempt completion more common + +**Root cause:** +Subagents waste tokens and attention on irrelevant plan sections. + +**Impact:** Medium - Slower execution, more failed attempts + +**What worked:** +``` +You are adding a single E2E test to packnplay's test suite. + +**Your task:** Add `TestE2E_FeaturePrivilegedMode` to `pkg/runner/e2e_test.go` + +**What to test:** A local devcontainer feature that requests `"privileged": true` +in its metadata should result in the container running with `--privileged` flag. + +**Follow the exact pattern of TestE2E_FeatureOptionValidation** (at the end of the file) + +**After writing, run:** `go test -v ./pkg/runner -run TestE2E_FeaturePrivilegedMode -timeout 5m` +``` + +--- + +### Problem 4: No Self-Reflection Before Handoff + +**What happened:** +- Added self-reflection prompt: "Look at your work with fresh eyes - what could be better?" +- Implementer for Task 5 identified failing test was due to implementation bug, not test bug +- Traced to line 99: `strings.Join(metadata.Entrypoint, " ")` creating invalid Docker syntax +- Without self-reflection, would have just reported "test fails" without root cause + +**Root cause:** +Implementers don't naturally step back and critique their own work before reporting completion. + +**Impact:** Medium - Bugs handed off to reviewer that implementer could have caught + +--- + +### Problem 5: Mock-Interface Drift + +**What happened:** +```typescript +// Interface defines close() +interface PlatformAdapter { + close(): Promise; +} + +// Code (BUGGY) calls cleanup() +await adapter.cleanup(); + +// Mock (MATCHES BUG) defines cleanup() +vi.mock('web-adapter', () => ({ + WebAdapter: vi.fn().mockImplementation(() => ({ + cleanup: vi.fn().mockResolvedValue(undefined), // Wrong! + })), +})); +``` +- Tests passed +- Runtime crashed: "adapter.cleanup is not a function" + +**Root cause:** +Mock derived from what buggy code calls, not from interface definition. TypeScript can't catch inline mocks with wrong method names. + +**Impact:** High - Tests give false confidence, runtime crashes + +**Why testing-anti-patterns didn't prevent this:** +The skill covers testing mock behavior and mocking without understanding, but not the specific pattern of "derive mock from interface, not implementation." + +--- + +### Problem 6: Code Reviewer File Access + +**What happened:** +- Code reviewer subagent dispatched +- Couldn't find test file: "The file doesn't appear to exist in the repository" +- File actually exists +- Reviewer didn't know to explicitly read it first + +**Root cause:** +Reviewer prompts don't include explicit file reading instructions. + +**Impact:** Low-Medium - Reviews fail or incomplete + +--- + +### Problem 7: Fix Workflow Latency + +**What happened:** +- Implementer identifies bug during self-reflection +- Implementer knows the fix +- Current workflow: report → I dispatch fixer → fixer fixes → I verify +- Extra round-trip adds latency without adding value + +**Root cause:** +Rigid separation between implementer and fixer roles when implementer has already diagnosed. + +**Impact:** Low - Latency, but no correctness issue + +--- + +### Problem 8: Skills Not Being Read + +**What happened:** +- `testing-anti-patterns` skill exists +- Neither human nor subagents read it before writing tests +- Would have prevented some issues (though not all - see Problem 5) + +**Root cause:** +No enforcement that subagents read relevant skills. No prompt includes skill reading. + +**Impact:** Medium - Skill investment wasted if not used + +--- + +## Proposed Improvements + +### 1. verification-before-completion: Add Configuration Change Verification + +**Add new section:** + +```markdown +## Verifying Configuration Changes + +When testing changes to configuration, providers, feature flags, or environment: + +**Don't just verify the operation succeeded. Verify the output reflects the intended change.** + +### Common Failure Pattern + +Operation succeeds because *some* valid config exists, but it's not the config you intended to test. + +### Examples + +| Change | Insufficient | Required | +|--------|-------------|----------| +| Switch LLM provider | Status 200 | Response contains expected model name | +| Enable feature flag | No errors | Feature behavior actually active | +| Change environment | Deploy succeeds | Logs/vars reference new environment | +| Set credentials | Auth succeeds | Authenticated user/context is correct | + +### Gate Function + +``` +BEFORE claiming configuration change works: + +1. IDENTIFY: What should be DIFFERENT after this change? +2. LOCATE: Where is that difference observable? + - Response field (model name, user ID) + - Log line (environment, provider) + - Behavior (feature active/inactive) +3. RUN: Command that shows the observable difference +4. VERIFY: Output contains expected difference +5. ONLY THEN: Claim configuration change works + +Red flags: + - "Request succeeded" without checking content + - Checking status code but not response body + - Verifying no errors but not positive confirmation +``` + +**Why this works:** +Forces verification of INTENT, not just operation success. + +--- + +### 2. subagent-driven-development: Add Process Hygiene for E2E Tests + +**Add new section:** + +```markdown +## Process Hygiene for E2E Tests + +When dispatching subagents that start services (servers, databases, message queues): + +### Problem + +Subagents are stateless - they don't know about processes started by previous subagents. Background processes persist and can interfere with later tests. + +### Solution + +**Before dispatching E2E test subagent, include cleanup in prompt:** + +``` +BEFORE starting any services: +1. Kill existing processes: pkill -f "" 2>/dev/null || true +2. Wait for cleanup: sleep 1 +3. Verify port free: lsof -i : && echo "ERROR: Port still in use" || echo "Port free" + +AFTER tests complete: +1. Kill the process you started +2. Verify cleanup: pgrep -f "" || echo "Cleanup successful" +``` + +### Example + +``` +Task: Run E2E test of API server + +Prompt includes: +"Before starting the server: +- Kill any existing servers: pkill -f 'node.*server.js' 2>/dev/null || true +- Verify port 3001 is free: lsof -i :3001 && exit 1 || echo 'Port available' + +After tests: +- Kill the server you started +- Verify: pgrep -f 'node.*server.js' || echo 'Cleanup verified'" +``` + +### Why This Matters + +- Stale processes serve requests with wrong config +- Port conflicts cause silent failures +- Process accumulation slows system +- Confusing test results (hitting wrong server) +``` + +**Trade-off analysis:** +- Adds boilerplate to prompts +- But prevents very confusing debugging +- Worth it for E2E test subagents + +--- + +### 3. subagent-driven-development: Add Lean Context Option + +**Modify Step 2: Execute Task with Subagent** + +**Before:** +``` +Read that task carefully from [plan-file]. +``` + +**After:** +``` +## Context Approaches + +**Full Plan (default):** +Use when tasks are complex or have dependencies: +``` +Read Task N from [plan-file] carefully. +``` + +**Lean Context (for independent tasks):** +Use when task is standalone and pattern-based: +``` +You are implementing: [1-2 sentence task description] + +File to modify: [exact path] +Pattern to follow: [reference to existing function/test] +What to implement: [specific requirement] +Verification: [exact command to run] + +[Do NOT include full plan file] +``` + +**Use lean context when:** +- Task follows existing pattern (add similar test, implement similar feature) +- Task is self-contained (doesn't need context from other tasks) +- Pattern reference is sufficient (e.g., "follow TestE2E_FeatureOptionValidation") + +**Use full plan when:** +- Task has dependencies on other tasks +- Requires understanding of overall architecture +- Complex logic that needs context +``` + +**Example:** +``` +Lean context prompt: + +"You are adding a test for privileged mode in devcontainer features. + +File: pkg/runner/e2e_test.go +Pattern: Follow TestE2E_FeatureOptionValidation (at end of file) +Test: Feature with `"privileged": true` in metadata results in `--privileged` flag +Verify: go test -v ./pkg/runner -run TestE2E_FeaturePrivilegedMode -timeout 5m + +Report: Implementation, test results, any issues." +``` + +**Why this works:** +Reduces token usage, increases focus, faster completion when appropriate. + +--- + +### 4. subagent-driven-development: Add Self-Reflection Step + +**Modify Step 2: Execute Task with Subagent** + +**Add to prompt template:** + +``` +When done, BEFORE reporting back: + +Take a step back and review your work with fresh eyes. + +Ask yourself: +- Does this actually solve the task as specified? +- Are there edge cases I didn't consider? +- Did I follow the pattern correctly? +- If tests are failing, what's the ROOT CAUSE (implementation bug vs test bug)? +- What could be better about this implementation? + +If you identify issues during this reflection, fix them now. + +Then report: +- What you implemented +- Self-reflection findings (if any) +- Test results +- Files changed +``` + +**Why this works:** +Catches bugs implementer can find themselves before handoff. Documented case: identified entrypoint bug through self-reflection. + +**Trade-off:** +Adds ~30 seconds per task, but catches issues before review. + +--- + +### 5. requesting-code-review: Add Explicit File Reading + +**Modify the code-reviewer template:** + +**Add at the beginning:** + +```markdown +## Files to Review + +BEFORE analyzing, read these files: + +1. [List specific files that changed in the diff] +2. [Files referenced by changes but not modified] + +Use Read tool to load each file. + +If you cannot find a file: +- Check exact path from diff +- Try alternate locations +- Report: "Cannot locate [path] - please verify file exists" + +DO NOT proceed with review until you've read the actual code. +``` + +**Why this works:** +Explicit instruction prevents "file not found" issues. + +--- + +### 6. testing-anti-patterns: Add Mock-Interface Drift Anti-Pattern + +**Add new Anti-Pattern 6:** + +```markdown +## Anti-Pattern 6: Mocks Derived from Implementation + +**The violation:** +```typescript +// Code (BUGGY) calls cleanup() +await adapter.cleanup(); + +// Mock (MATCHES BUG) has cleanup() +const mock = { + cleanup: vi.fn().mockResolvedValue(undefined) +}; + +// Interface (CORRECT) defines close() +interface PlatformAdapter { + close(): Promise; +} +``` + +**Why this is wrong:** +- Mock encodes the bug into the test +- TypeScript can't catch inline mocks with wrong method names +- Test passes because both code and mock are wrong +- Runtime crashes when real object is used + +**The fix:** +```typescript +// ✅ GOOD: Derive mock from interface + +// Step 1: Open interface definition (PlatformAdapter) +// Step 2: List methods defined there (close, initialize, etc.) +// Step 3: Mock EXACTLY those methods + +const mock = { + initialize: vi.fn().mockResolvedValue(undefined), + close: vi.fn().mockResolvedValue(undefined), // From interface! +}; + +// Now test FAILS because code calls cleanup() which doesn't exist +// That failure reveals the bug BEFORE runtime +``` + +### Gate Function + +``` +BEFORE writing any mock: + + 1. STOP - Do NOT look at the code under test yet + 2. FIND: The interface/type definition for the dependency + 3. READ: The interface file + 4. LIST: Methods defined in the interface + 5. MOCK: ONLY those methods with EXACTLY those names + 6. DO NOT: Look at what your code calls + + IF your test fails because code calls something not in mock: + ✅ GOOD - The test found a bug in your code + Fix the code to call the correct interface method + NOT the mock + + Red flags: + - "I'll mock what the code calls" + - Copying method names from implementation + - Mock written without reading interface + - "The test is failing so I'll add this method to the mock" +``` + +**Detection:** + +When you see runtime error "X is not a function" and tests pass: +1. Check if X is mocked +2. Compare mock methods to interface methods +3. Look for method name mismatches +``` + +**Why this works:** +Directly addresses the failure pattern from feedback. + +--- + +### 7. subagent-driven-development: Require Skills Reading for Test Subagents + +**Add to prompt template when task involves testing:** + +```markdown +BEFORE writing any tests: + +1. Read testing-anti-patterns skill: + Use Skill tool: superpowers:testing-anti-patterns + +2. Apply gate functions from that skill when: + - Writing mocks + - Adding methods to production classes + - Mocking dependencies + +This is NOT optional. Tests that violate anti-patterns will be rejected in review. +``` + +**Why this works:** +Ensures skills are actually used, not just exist. + +**Trade-off:** +Adds time to each task, but prevents entire classes of bugs. + +--- + +### 8. subagent-driven-development: Allow Implementer to Fix Self-Identified Issues + +**Modify Step 2:** + +**Current:** +``` +Subagent reports back with summary of work. +``` + +**Proposed:** +``` +Subagent performs self-reflection, then: + +IF self-reflection identifies fixable issues: + 1. Fix the issues + 2. Re-run verification + 3. Report: "Initial implementation + self-reflection fix" + +ELSE: + Report: "Implementation complete" + +Include in report: +- Self-reflection findings +- Whether fixes were applied +- Final verification results +``` + +**Why this works:** +Reduces latency when implementer already knows the fix. Documented case: would have saved one round-trip for entrypoint bug. + +**Trade-off:** +Slightly more complex prompt, but faster end-to-end. + +--- + +## Implementation Plan + +### Phase 1: High-Impact, Low-Risk (Do First) + +1. **verification-before-completion: Configuration change verification** + - Clear addition, doesn't change existing content + - Addresses high-impact problem (false confidence in tests) + - File: `skills/verification-before-completion/SKILL.md` + +2. **testing-anti-patterns: Mock-interface drift** + - Adds new anti-pattern, doesn't modify existing + - Addresses high-impact problem (runtime crashes) + - File: `skills/testing-anti-patterns/SKILL.md` + +3. **requesting-code-review: Explicit file reading** + - Simple addition to template + - Fixes concrete problem (reviewers can't find files) + - File: `skills/requesting-code-review/SKILL.md` + +### Phase 2: Moderate Changes (Test Carefully) + +4. **subagent-driven-development: Process hygiene** + - Adds new section, doesn't change workflow + - Addresses medium-high impact (test reliability) + - File: `skills/subagent-driven-development/SKILL.md` + +5. **subagent-driven-development: Self-reflection** + - Changes prompt template (higher risk) + - But documented to catch bugs + - File: `skills/subagent-driven-development/SKILL.md` + +6. **subagent-driven-development: Skills reading requirement** + - Adds prompt overhead + - But ensures skills are actually used + - File: `skills/subagent-driven-development/SKILL.md` + +### Phase 3: Optimization (Validate First) + +7. **subagent-driven-development: Lean context option** + - Adds complexity (two approaches) + - Needs validation that it doesn't cause confusion + - File: `skills/subagent-driven-development/SKILL.md` + +8. **subagent-driven-development: Allow implementer to fix** + - Changes workflow (higher risk) + - Optimization, not bug fix + - File: `skills/subagent-driven-development/SKILL.md` + +--- + +## Open Questions + +1. **Lean context approach:** + - Should we make it the default for pattern-based tasks? + - How do we decide which approach to use? + - Risk of being too lean and missing important context? + +2. **Self-reflection:** + - Will this slow down simple tasks significantly? + - Should it only apply to complex tasks? + - How do we prevent "reflection fatigue" where it becomes rote? + +3. **Process hygiene:** + - Should this be in subagent-driven-development or a separate skill? + - Does it apply to other workflows beyond E2E tests? + - How do we handle cases where process SHOULD persist (dev servers)? + +4. **Skills reading enforcement:** + - Should we require ALL subagents to read relevant skills? + - How do we keep prompts from becoming too long? + - Risk of over-documenting and losing focus? + +--- + +## Success Metrics + +How do we know these improvements work? + +1. **Configuration verification:** + - Zero instances of "test passed but wrong config was used" + - Jesse doesn't say "that's not actually testing what you think" + +2. **Process hygiene:** + - Zero instances of "test hit wrong server" + - No port conflict errors during E2E test runs + +3. **Mock-interface drift:** + - Zero instances of "tests pass but runtime crashes on missing method" + - No method name mismatches between mocks and interfaces + +4. **Self-reflection:** + - Measurable: Do implementer reports include self-reflection findings? + - Qualitative: Do fewer bugs make it to code review? + +5. **Skills reading:** + - Subagent reports reference skill gate functions + - Fewer anti-pattern violations in code review + +--- + +## Risks and Mitigations + +### Risk: Prompt Bloat +**Problem:** Adding all these requirements makes prompts overwhelming +**Mitigation:** +- Phase implementation (don't add everything at once) +- Make some additions conditional (E2E hygiene only for E2E tests) +- Consider templates for different task types + +### Risk: Analysis Paralysis +**Problem:** Too much reflection/verification slows execution +**Mitigation:** +- Keep gate functions quick (seconds, not minutes) +- Make lean context opt-in initially +- Monitor task completion times + +### Risk: False Sense of Security +**Problem:** Following checklist doesn't guarantee correctness +**Mitigation:** +- Emphasize gate functions are minimums, not maximums +- Keep "use judgment" language in skills +- Document that skills catch common failures, not all failures + +### Risk: Skill Divergence +**Problem:** Different skills give conflicting advice +**Mitigation:** +- Review changes across all skills for consistency +- Document how skills interact (Integration sections) +- Test with real scenarios before deployment + +--- + +## Recommendation + +**Proceed with Phase 1 immediately:** +- verification-before-completion: Configuration change verification +- testing-anti-patterns: Mock-interface drift +- requesting-code-review: Explicit file reading + +**Test Phase 2 with Jesse before finalizing:** +- Get feedback on self-reflection impact +- Validate process hygiene approach +- Confirm skills reading requirement is worth overhead + +**Hold Phase 3 pending validation:** +- Lean context needs real-world testing +- Implementer-fix workflow change needs careful evaluation + +These changes address real problems documented by users while minimizing risk of making skills worse. diff --git a/docs/plans/2026-01-17-visual-brainstorming.md b/docs/plans/2026-01-17-visual-brainstorming.md new file mode 100644 index 0000000..40aa0ad --- /dev/null +++ b/docs/plans/2026-01-17-visual-brainstorming.md @@ -0,0 +1,571 @@ +# Visual Brainstorming Companion Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Give Claude a browser-based visual companion for brainstorming sessions - show mockups, prototypes, and interactive choices alongside terminal conversation. + +**Architecture:** Claude writes HTML to a temp file. A local Node.js server watches that file and serves it with an auto-injected helper library. User interactions flow via WebSocket to server stdout, which Claude sees in background task output. + +**Tech Stack:** Node.js, Express, ws (WebSocket), chokidar (file watching) + +--- + +## Task 1: Create the Server Foundation + +**Files:** +- Create: `lib/brainstorm-server/index.js` +- Create: `lib/brainstorm-server/package.json` + +**Step 1: Create package.json** + +```json +{ + "name": "brainstorm-server", + "version": "1.0.0", + "description": "Visual brainstorming companion server for Claude Code", + "main": "index.js", + "dependencies": { + "chokidar": "^3.5.3", + "express": "^4.18.2", + "ws": "^8.14.2" + } +} +``` + +**Step 2: Create minimal server that starts** + +```javascript +const express = require('express'); +const http = require('http'); +const WebSocket = require('ws'); +const chokidar = require('chokidar'); +const fs = require('fs'); +const path = require('path'); + +const PORT = process.env.BRAINSTORM_PORT || 3333; +const SCREEN_FILE = process.env.BRAINSTORM_SCREEN || '/tmp/brainstorm/screen.html'; +const SCREEN_DIR = path.dirname(SCREEN_FILE); + +// Ensure screen directory exists +if (!fs.existsSync(SCREEN_DIR)) { + fs.mkdirSync(SCREEN_DIR, { recursive: true }); +} + +// Create default screen if none exists +if (!fs.existsSync(SCREEN_FILE)) { + fs.writeFileSync(SCREEN_FILE, ` + + + Brainstorm Companion + + + +

Brainstorm Companion

+

Waiting for Claude to push a screen...

+ +`); +} + +const app = express(); +const server = http.createServer(app); +const wss = new WebSocket.Server({ server }); + +// Track connected browsers for reload notifications +const clients = new Set(); + +wss.on('connection', (ws) => { + clients.add(ws); + ws.on('close', () => clients.delete(ws)); + + ws.on('message', (data) => { + // User interaction event - write to stdout for Claude + const event = JSON.parse(data.toString()); + console.log(JSON.stringify({ type: 'user-event', ...event })); + }); +}); + +// Serve current screen with helper.js injected +app.get('/', (req, res) => { + let html = fs.readFileSync(SCREEN_FILE, 'utf-8'); + + // Inject helper script before + const helperScript = fs.readFileSync(path.join(__dirname, 'helper.js'), 'utf-8'); + const injection = ``; + + if (html.includes('')) { + html = html.replace('', `${injection}\n`); + } else { + html += injection; + } + + res.type('html').send(html); +}); + +// Watch for screen file changes +chokidar.watch(SCREEN_FILE).on('change', () => { + console.log(JSON.stringify({ type: 'screen-updated', file: SCREEN_FILE })); + // Notify all browsers to reload + clients.forEach(ws => { + if (ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify({ type: 'reload' })); + } + }); +}); + +server.listen(PORT, '127.0.0.1', () => { + console.log(JSON.stringify({ type: 'server-started', port: PORT, url: `http://localhost:${PORT}` })); +}); +``` + +**Step 3: Run npm install** + +Run: `cd lib/brainstorm-server && npm install` +Expected: Dependencies installed + +**Step 4: Test server starts** + +Run: `cd lib/brainstorm-server && timeout 3 node index.js || true` +Expected: See JSON with `server-started` and port info + +**Step 5: Commit** + +```bash +git add lib/brainstorm-server/ +git commit -m "feat: add brainstorm server foundation" +``` + +--- + +## Task 2: Create the Helper Library + +**Files:** +- Create: `lib/brainstorm-server/helper.js` + +**Step 1: Create helper.js with event auto-capture** + +```javascript +(function() { + const WS_URL = 'ws://' + window.location.host; + let ws = null; + let eventQueue = []; + + function connect() { + ws = new WebSocket(WS_URL); + + ws.onopen = () => { + // Send any queued events + eventQueue.forEach(e => ws.send(JSON.stringify(e))); + eventQueue = []; + }; + + ws.onmessage = (msg) => { + const data = JSON.parse(msg.data); + if (data.type === 'reload') { + window.location.reload(); + } + }; + + ws.onclose = () => { + // Reconnect after 1 second + setTimeout(connect, 1000); + }; + } + + function send(event) { + event.timestamp = Date.now(); + if (ws && ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify(event)); + } else { + eventQueue.push(event); + } + } + + // Auto-capture clicks on interactive elements + document.addEventListener('click', (e) => { + const target = e.target.closest('button, a, [data-choice], [role="button"], input[type="submit"]'); + if (!target) return; + + // Don't capture regular link navigation + if (target.tagName === 'A' && !target.dataset.choice) return; + + e.preventDefault(); + + send({ + type: 'click', + text: target.textContent.trim(), + choice: target.dataset.choice || null, + id: target.id || null, + className: target.className || null + }); + }); + + // Auto-capture form submissions + document.addEventListener('submit', (e) => { + e.preventDefault(); + const form = e.target; + const formData = new FormData(form); + const data = {}; + formData.forEach((value, key) => { data[key] = value; }); + + send({ + type: 'submit', + formId: form.id || null, + formName: form.name || null, + data: data + }); + }); + + // Auto-capture input changes (debounced) + let inputTimeout = null; + document.addEventListener('input', (e) => { + const target = e.target; + if (!target.matches('input, textarea, select')) return; + + clearTimeout(inputTimeout); + inputTimeout = setTimeout(() => { + send({ + type: 'input', + name: target.name || null, + id: target.id || null, + value: target.value, + inputType: target.type || target.tagName.toLowerCase() + }); + }, 500); // 500ms debounce + }); + + // Expose for explicit use if needed + window.brainstorm = { + send: send, + choice: (value, metadata = {}) => send({ type: 'choice', value, ...metadata }) + }; + + connect(); +})(); +``` + +**Step 2: Verify helper.js is syntactically valid** + +Run: `node -c lib/brainstorm-server/helper.js` +Expected: No syntax errors + +**Step 3: Commit** + +```bash +git add lib/brainstorm-server/helper.js +git commit -m "feat: add browser helper library for event capture" +``` + +--- + +## Task 3: Write Tests for the Server + +**Files:** +- Create: `tests/brainstorm-server/server.test.js` +- Create: `tests/brainstorm-server/package.json` + +**Step 1: Create test package.json** + +```json +{ + "name": "brainstorm-server-tests", + "version": "1.0.0", + "scripts": { + "test": "node server.test.js" + } +} +``` + +**Step 2: Write server tests** + +```javascript +const { spawn } = require('child_process'); +const http = require('http'); +const WebSocket = require('ws'); +const fs = require('fs'); +const path = require('path'); +const assert = require('assert'); + +const SERVER_PATH = path.join(__dirname, '../../lib/brainstorm-server/index.js'); +const TEST_PORT = 3334; +const TEST_SCREEN = '/tmp/brainstorm-test/screen.html'; + +// Clean up test directory +function cleanup() { + if (fs.existsSync(path.dirname(TEST_SCREEN))) { + fs.rmSync(path.dirname(TEST_SCREEN), { recursive: true }); + } +} + +async function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function fetch(url) { + return new Promise((resolve, reject) => { + http.get(url, (res) => { + let data = ''; + res.on('data', chunk => data += chunk); + res.on('end', () => resolve({ status: res.statusCode, body: data })); + }).on('error', reject); + }); +} + +async function runTests() { + cleanup(); + + // Start server + const server = spawn('node', [SERVER_PATH], { + env: { ...process.env, BRAINSTORM_PORT: TEST_PORT, BRAINSTORM_SCREEN: TEST_SCREEN } + }); + + let stdout = ''; + server.stdout.on('data', (data) => { stdout += data.toString(); }); + server.stderr.on('data', (data) => { console.error('Server stderr:', data.toString()); }); + + await sleep(1000); // Wait for server to start + + try { + // Test 1: Server starts and outputs JSON + console.log('Test 1: Server startup message'); + assert(stdout.includes('server-started'), 'Should output server-started'); + assert(stdout.includes(TEST_PORT.toString()), 'Should include port'); + console.log(' PASS'); + + // Test 2: GET / returns HTML with helper injected + console.log('Test 2: Serves HTML with helper injected'); + const res = await fetch(`http://localhost:${TEST_PORT}/`); + assert.strictEqual(res.status, 200); + assert(res.body.includes('brainstorm'), 'Should include brainstorm content'); + assert(res.body.includes('WebSocket'), 'Should have helper.js injected'); + console.log(' PASS'); + + // Test 3: WebSocket connection and event relay + console.log('Test 3: WebSocket relays events to stdout'); + stdout = ''; // Reset stdout capture + const ws = new WebSocket(`ws://localhost:${TEST_PORT}`); + await new Promise(resolve => ws.on('open', resolve)); + + ws.send(JSON.stringify({ type: 'click', text: 'Test Button' })); + await sleep(100); + + assert(stdout.includes('user-event'), 'Should relay user events'); + assert(stdout.includes('Test Button'), 'Should include event data'); + ws.close(); + console.log(' PASS'); + + // Test 4: File change triggers reload notification + console.log('Test 4: File change notifies browsers'); + const ws2 = new WebSocket(`ws://localhost:${TEST_PORT}`); + await new Promise(resolve => ws2.on('open', resolve)); + + let gotReload = false; + ws2.on('message', (data) => { + const msg = JSON.parse(data.toString()); + if (msg.type === 'reload') gotReload = true; + }); + + // Modify the screen file + fs.writeFileSync(TEST_SCREEN, 'Updated'); + await sleep(500); + + assert(gotReload, 'Should send reload message on file change'); + ws2.close(); + console.log(' PASS'); + + console.log('\nAll tests passed!'); + + } finally { + server.kill(); + cleanup(); + } +} + +runTests().catch(err => { + console.error('Test failed:', err); + process.exit(1); +}); +``` + +**Step 3: Run tests** + +Run: `cd tests/brainstorm-server && npm install ws && node server.test.js` +Expected: All tests pass + +**Step 4: Commit** + +```bash +git add tests/brainstorm-server/ +git commit -m "test: add brainstorm server integration tests" +``` + +--- + +## Task 4: Add Visual Companion to Brainstorming Skill + +**Files:** +- Modify: `skills/brainstorming/SKILL.md` +- Create: `skills/brainstorming/visual-companion.md` (supporting doc) + +**Step 1: Create the supporting documentation** + +Create `skills/brainstorming/visual-companion.md`: + +```markdown +# Visual Companion Reference + +## Starting the Server + +Run as a background job: + +```bash +node ${PLUGIN_ROOT}/lib/brainstorm-server/index.js +``` + +Tell the user: "I've started a visual companion at http://localhost:3333 - open it in a browser." + +## Pushing Screens + +Write HTML to `/tmp/brainstorm/screen.html`. The server watches this file and auto-refreshes the browser. + +## Reading User Responses + +Check the background task output for JSON events: + +```json +{"type":"user-event","type":"click","text":"Option A","choice":"optionA","timestamp":1234567890} +{"type":"user-event","type":"submit","data":{"notes":"My feedback"},"timestamp":1234567891} +``` + +Event types: +- **click**: User clicked button or `data-choice` element +- **submit**: User submitted form (includes all form data) +- **input**: User typed in field (debounced 500ms) + +## HTML Patterns + +### Choice Cards + +```html +
+ + +
+``` + +### Interactive Mockup + +```html +
+
App Header
+ +
Content
+
+``` + +### Form with Notes + +```html +
+ + + +
+``` + +### Explicit JavaScript + +```html + +``` +``` + +**Step 2: Add visual companion section to brainstorming skill** + +Add after "Key Principles" in `skills/brainstorming/SKILL.md`: + +```markdown + +## Visual Companion (Optional) + +When brainstorming involves visual elements - UI mockups, wireframes, interactive prototypes - use the browser-based visual companion. + +**When to use:** +- Presenting UI/UX options that benefit from visual comparison +- Showing wireframes or layout options +- Gathering structured feedback (ratings, forms) +- Prototyping click interactions + +**How it works:** +1. Start the server as a background job +2. Tell user to open http://localhost:3333 +3. Write HTML to `/tmp/brainstorm/screen.html` (auto-refreshes) +4. Check background task output for user interactions + +The terminal remains the primary conversation interface. The browser is a visual aid. + +**Reference:** See `visual-companion.md` in this skill directory for HTML patterns and API details. +``` + +**Step 3: Verify the edits** + +Run: `grep -A5 "Visual Companion" skills/brainstorming/SKILL.md` +Expected: Shows the new section + +**Step 4: Commit** + +```bash +git add skills/brainstorming/ +git commit -m "feat: add visual companion to brainstorming skill" +``` + +--- + +## Task 5: Add Server to Plugin Ignore (Optional Cleanup) + +**Files:** +- Check if `.gitignore` needs node_modules exclusion for lib/brainstorm-server + +**Step 1: Check current gitignore** + +Run: `cat .gitignore 2>/dev/null || echo "No .gitignore"` + +**Step 2: Add node_modules if needed** + +If not already present, add: +``` +lib/brainstorm-server/node_modules/ +``` + +**Step 3: Commit if changed** + +```bash +git add .gitignore +git commit -m "chore: ignore brainstorm-server node_modules" +``` + +--- + +## Summary + +After completing all tasks: + +1. **Server** at `lib/brainstorm-server/` - Node.js server that watches HTML file and relays events +2. **Helper library** auto-injected - captures clicks, forms, inputs +3. **Tests** at `tests/brainstorm-server/` - verifies server behavior +4. **Brainstorming skill** updated with visual companion section and `visual-companion.md` reference doc + +**To use:** +1. Start server as background job: `node lib/brainstorm-server/index.js &` +2. Tell user to open `http://localhost:3333` +3. Write HTML to `/tmp/brainstorm/screen.html` +4. Check task output for user events diff --git a/docs/porting-to-a-new-harness.md b/docs/porting-to-a-new-harness.md new file mode 100644 index 0000000..d288c6b --- /dev/null +++ b/docs/porting-to-a-new-harness.md @@ -0,0 +1,827 @@ +# Porting Superpowers to a New Harness + +This guide explains how to add support for a new harness — an IDE, CLI, or +agent runner that isn't Claude Code — so that Superpowers skills auto-trigger +there the same way they do natively. + +It is written in two layers. **Part 1–3** explain how the system works and how +to tell whether a harness can be supported at all; read these before you touch +anything. **Part 4–8** are a prescriptive procedure for an agent (supervised by +a human partner) to execute the port end to end, through distribution. An +appendix indexes the current reference integrations so you can copy the closest +one. + +The integration mechanism differs across harnesses, and it will keep changing. +This guide deliberately teaches the **invariants** — the things that must be +true no matter the mechanism — and points you at a live reference implementation +to copy. When this guide and the code disagree, the code wins; fix the guide. + +## Before you start + +Adding a harness is the highest-stakes contribution type in this repo. Before +writing anything: + +- Read `CLAUDE.md` and `.github/PULL_REQUEST_TEMPLATE.md` in full — the + contributor rules and the new-harness PR requirements are not optional. +- Search open **and closed** PRs for a prior attempt at this harness. If one + exists, understand why it stalled before starting your own. + +--- + +## Part 1 — How Superpowers works across harnesses + +Superpowers is the same content everywhere. What changes per harness is the thin +layer that delivers that content to the model and translates its instructions +into the harness's native tools. Three components: + +1. **Skills (harness-agnostic).** Everything in `skills/` is the source of + truth, shared verbatim by every harness. Skills are written to describe + *actions* — "invoke a skill", "read a file", "dispatch a subagent", "create a + todo" — and never name a specific tool. This is what lets one skill body run + on Claude Code, Codex, Gemini, pi, and the rest without edits. + +2. **Tool mapping (per-harness).** Each harness needs the action vocabulary + translated into its real tool names. That translation lives in + `skills/using-superpowers/references/-tools.md` and/or inline in the + harness's bootstrap injector (see Part 5). It says, e.g., "*dispatch a + subagent* → call `task` with `subagent_type`." + +3. **Bootstrap (per-harness).** At the start of every session, the full + `skills/using-superpowers/SKILL.md` is injected into the model's context, + wrapped in `` tags, with the tool mapping appended. That + injected skill is what teaches the model that skills exist and that it must + check for a relevant skill before acting. **The bootstrap is the entire + integration.** Without it, the skill files are inert — present on disk, never + invoked. + +### Two rules that make this work + +**1. Skills name actions, not tools.** Do **not** edit skill bodies to fit your +harness. Porting adds a tool-mapping reference and a bootstrap injector; it +never reaches into `skills/*/SKILL.md` to swap tool names. (The project's +contributor guidelines treat skill content as carefully-tuned behavior-shaping +code; rewording it for "compliance" is rejected on sight.) + +**2. Everything ships through the harness's own install mechanism. Never edit the +user's files.** The bootstrap, the skills, and the tool mapping all get delivered +*as part of what the harness installs* — a plugin, an extension, a marketplace +entry, an extension-bundled context file. A port **must not** reach into a user's +global or personal config (`~/.gemini/config/AGENTS.md`, `settings.json`, +`trustedFolders.json`, a hand-edited `~/.bashrc`, etc.) to inject anything. The +harness owns what it loads; your install artifact is the only thing you get to +write. If the install mechanism genuinely can't carry the bootstrap, that is a +limitation to surface (Part 6) — never a license to hand-edit the user's config. +(Shape C is *not* an exception: Gemini's context file is fine because it ships +*inside the installed extension* and is declared by the manifest's +`contextFileName` — the harness loads the extension's own file, not a file you +edited in the user's home.) + +--- + +## Part 2 — Can this harness be supported? + +A harness can support Superpowers only if it can do all of the following. Check +these before writing code — if the first one fails, stop. + +### Hard requirement: automatic session-start injection + +The harness must let you inject text into the model's context **at the start of +every session, with no per-session opt-in by your human partner.** This is the +one non-negotiable capability. It can take any form: + +- a **hook/event system** that runs a shell command at session start and reads + its stdout (Claude Code, Cursor, Copilot CLI), or +- an **in-process plugin/extension** with a session-start or message lifecycle + callback that can mutate the message array (OpenCode, pi), or +- an **instructions-file** convention where the harness loads a context file that + *your installed extension ships and declares* (e.g. Gemini's `contextFileName` + pointing at the extension's own `GEMINI.md`) — not a file you edit in the user's + home. + +If the only way to get Superpowers in front of the model is for your human +partner to opt in each session (paste a prompt, run a command, enable a mode), +the harness +**cannot** be properly supported. The acceptance test in Part 3 will fail, and +the PR will be closed. This is the single most common reason a "port" isn't a +real port. + +### The rest of the capability checklist + +| Capability | Why it's needed | If absent | +|---|---|---| +| **Skill discovery + invocation** | The model must be able to load a skill's full content on demand | If there's no native skill tool, the sanctioned fallback is to `read` the relevant `SKILL.md` directly — see Part 5. A harness with neither a skill tool nor file-read cannot work. | +| **File read / write / edit** | Nearly every skill manipulates files | Essential. No workaround. | +| **Run shell commands** | TDD, verification, git workflows | Essential. | +| **Subagent / task dispatch** | `dispatching-parallel-agents`, `subagent-driven-development` | Degradable: if unavailable, those specific skills tell the model to do the work inline or report the missing capability — *never* to invent a `Task` call. Some harnesses gate this behind a config flag (e.g. Codex needs multi-agent enabled). | +| **Todo / task tracking** | Progress tracking in several skills | Degradable: fall back to a plan file or `TODO.md`. | +| **Web fetch / search** | A few skills | Degradable. | +| **Shell or polyglot script execution (Windows)** | Only for the shell-hook shape, only if you want Windows support | See Part 7. In-process-plugin harnesses sidestep this entirely. | + +"Degradable" means: the skill already has fallback wording for the missing +tool. Your job in the tool mapping is to point at the real tool when it exists +and reuse that fallback wording when it doesn't. + +### You may not need a new directory at all + +Some "new harnesses" are really existing integrations under a different +installer. Factory's Droid, for example, consumes the Claude Code plugin via its +own `plugin install` command and needs no new files here. Before building, +check whether the harness can simply load an existing manifest. A port that adds +nothing to this repo but a paragraph in the README is a perfectly good outcome. + +--- + +## Part 3 — Definition of done + +A port is finished when **all** of these are true: + +1. The `using-superpowers` bootstrap loads at session start, every session, with + no per-session opt-in. +2. A tool mapping exists for the harness (in + `references/-tools.md`, inline in the bootstrap, or both — per Part 5). +3. Skills can actually be invoked — natively, or via the documented + read-`SKILL.md` fallback — and the model follows them. +4. **The acceptance test passes.** In a clean session, the user message: + + > Let's make a react todo list + + auto-triggers the `brainstorming` skill *before any code is written*. Capture + the full transcript — the PR requires it. +5. Tests cover the integration (Part 5) and pass. +6. A real user can install it through the harness's own mechanism (not by + hand-copying files), and the version is tracked in `.version-bump.json` where + applicable (Part 6). Note that some installers rewrite or strip the manifest on + install (one drops it to just `{"name": …}`), so "the *installed* files report + the repo version" is not always achievable — track the version at the source + manifest and don't treat a rewritten installed manifest as a failure. + +A quick smoke check before the full acceptance test: start a session and ask the +model to describe its superpowers. If the bootstrap injected, it knows it has +them. (OpenCode's install doc uses `opencode run --print-logs "hello" 2>&1 | +grep -i superpowers` for the same goal via a different mechanism — log-grep +rather than asking the model; the `2>&1` matters because logs go to stderr. Find +your harness's equivalent.) + +--- + +## Part 4 — Choose your integration shape + +There are three structural shapes, distinguished by *how you get the bootstrap +in front of the model*. Pick the one that matches what your harness exposes, +then copy that reference implementation. The shape determines almost everything +in Part 5 — the steps below branch on it. + +### How to tell which shape you have + +Before routing, learn the harness's *actual* mechanism — and don't assume it's +well documented or that it behaves like whatever harness it forked from. + +**Find the surface:** + +- **Search the web for the harness's docs** (extension / plugin / hook / skill / + MCP / "context file" / "rules file"). Vendor tools change fast; search rather + than trust training knowledge. +- **Find and read an existing third-party extension/plugin for the harness.** A + real working example beats docs — it shows the manifest shape, the install + command, and which components the harness actually loads. +- Check what the harness loads at startup: a settings file? an extensions + directory? a per-project or global instructions file (`AGENTS.md`, `.md`)? + +**If it's underdocumented, reverse-engineer it empirically** (a real porter has +had to do every one of these): + +- `strings` the binary / grep the install tree for hook event names, config + paths, and the instructions file it reads. +- **Ask the running model to enumerate its own tool names** — e.g. "list the + exact machine names of every tool you can call." This is the authoritative way + to get tool names without inventing them (see Step 4). +- Prove every assumption with a **unique-marker test**: inject a nonsense token + through the mechanism you think works, start a fresh session, and confirm the + token actually reached the model. + +**A fork does not inherit its parent's behavior.** A harness derived from another +(e.g. a Gemini-derived CLI) may expose the parent's manifest fields and +`@`-include syntax and *still not honor them the same way*. Verify with a marker; +never assume the parent's recipe transfers. + +Then route to a shape: + +- Shell command at session start whose stdout is read → **Shape A**. +- Plugin/extension module with lifecycle callbacks you run code in → **Shape B**. +- Only ever an always-on instructions file, no hook and no code plugin → + **Shape C**. + +**Shapes compose — they are not mutually exclusive.** The *skill-discovery* +mechanism and the *bootstrap* mechanism need not be the same shape — but **both +must still ride the install mechanism** (rule 2). Decide the two questions +separately: *where do skills get discovered?* and *how does the bootstrap reach +the model every session?* A harness might install skills via a plugin yet need +the bootstrap delivered another install-shipped way (an extension-declared +context file, or — see below — by the harness surfacing the installed +`using-superpowers` skill's own description at session start). If more than one +install-mechanism surface injects automatically, prefer the most reliable. What +you may **not** do is bridge a gap by editing the user's global config. + +### Shape A — Shell-hook + +The harness has a hook system that runs a shell command at session start and +reads JSON from its stdout. The configured command runs `run-hook.cmd`, a +polyglot wrapper that just locates bash and dispatches the named script; the +script (`hooks/session-start`, or a harness-specific variant) is what reads +`using-superpowers/SKILL.md` and prints a JSON object whose **field name and +nesting differ per harness**. + +- Reference: `hooks/session-start`, `hooks/run-hook.cmd`, and the per-harness + hook config `hooks/hooks.json` (Claude Code) and `hooks/hooks-cursor.json` + (Cursor). +- Manifests: `.cursor-plugin/plugin.json` is the Shape A manifest example that + points the harness at `./skills/` and the right `hooks-*.json`. Claude Code's + `.claude-plugin/plugin.json` sets neither field — it auto-discovers `skills/` + and `hooks/hooks.json` by convention. Do **not** copy Codex's + `.codex-plugin/plugin.json` for Shape A: it declares an empty `hooks` object + specifically to suppress Codex's `hooks/hooks.json` auto-discovery, because + Codex surfaces skills natively and runs no session-start hook. + +> **A hook *system* is not a session-start *event*.** A harness can have a +> `hooks.json` mechanism — and even contain the literal string `SessionStart` in +> its binary — while having no hook event that fires at session start and can +> inject context. (One real harness only exposed pre/post-tool and stop events; +> the `SessionStart` strings were telemetry.) Confirm the *specific event* you +> need exists and can write to the model's context before committing to Shape A. +> If it can't, the bootstrap belongs in an instructions file (Shape C) instead. + +### Shape B — In-process plugin / extension + +The harness loads a JS/TS module that exposes lifecycle callbacks. You register +the skills directory through the harness's API and inject the bootstrap by +mutating the message array in code. + +- Reference: `.opencode/plugins/superpowers.js` (JavaScript) and + `.pi/extensions/superpowers.ts` (TypeScript). pi is the closest reference for + any harness that has **no native skill tool**. + +### Shape C — Instructions-file + +The harness has neither a shell hook nor a code plugin — its session-start +surface is a context file that *your installed extension ships and the manifest +declares* (e.g. Gemini's `contextFileName` → the extension's own `GEMINI.md`). +You can't run code or mutate messages; the extension's context file points at the +bootstrap. There is no injector to assemble a string or strip frontmatter — the +harness loads the referenced content as-is. **This works only because the file is +part of the installed extension** — never substitute "edit the user's global +`GEMINI.md`/`AGENTS.md`" for shipping your own (rule 2). + +- Reference: `gemini-extension.json` (manifest, with `contextFileName`), + `GEMINI.md` (two `@`-includes — the bootstrap skill and the tool-mapping + reference), `skills/using-superpowers/references/gemini-tools.md`. +- Note: `@`-include is a Gemini feature. If your harness loads an instructions + file but has no include syntax, you must inline the bootstrap content into the + file instead. +- **Don't trust that an `@`-include is actually expanded — prove it.** A + Gemini-*derived* harness can accept `@./path` syntax yet treat it as a *hint + the model may choose to read* (it emits a file-read tool call) rather than a + guaranteed inline expansion. That's the difference between the bootstrap being + reliably present every session and the model maybe-reading it. Run a + unique-marker test: if the marker isn't in context *without* a tool call, + **inline the content** rather than `@`-include it. + +### Routing table + +| If the harness… | Use shape | Copy from | +|---|---|---| +| runs a shell command at session start and reads its stdout | A (shell-hook) | Cursor (`hooks/session-start` + `hooks/hooks-cursor.json` + `.cursor-plugin/`) | +| is a JS/TS plugin host with session/message lifecycle callbacks | B (in-process) | OpenCode (`.opencode/`) — or pi (`.pi/`) if it has no native skill tool | +| ships an extension-declared context file it always loads | C (instructions-file) | Gemini (`gemini-extension.json` + `GEMINI.md` + `references/gemini-tools.md`) | +| has a plugin install command and a manifest `contextFileName` (or equivalent) the installer keeps | C via the plugin installer | Antigravity (`.antigravity-plugin/` — `agy plugin install` ships a generated context file; verify the installer preserves it — Part 6) | + +Most real harnesses fit one row cleanly; the last is the hybrid case (rule 2 still +holds — the bootstrap rides the install mechanism, never a user-config edit). + +--- + +## Part 5 — The porting procedure + +### Step 1 — Study the closest reference implementation + +Open the files named in Part 4 for your shape and read them end to end. The +patterns below are summaries; the code is the spec. + +### Step 2 — Create the manifest / entry point + +Create whatever the harness uses to recognize the plugin. Match the existing +ones in spirit: + +- **Shape A:** a `*-plugin/plugin.json` (see `.cursor-plugin/plugin.json`) with + `name`, `version`, `description`, author/license/keywords, `"skills": + "./skills/"`, and `"hooks": "./hooks/hooks-.json"`. Plus the + `hooks-.json` itself, registering a session-start hook whose command + invokes `run-hook.cmd`. +- **Shape B:** the module the harness loads (e.g. `./plugins/*.js`) plus + whatever package metadata it needs to be discovered. The committed package + metadata is the **repo-root `package.json`**: `main` points at the OpenCode + plugin, the `pi` field (`pi.extensions`, `pi.skills`) plus the `pi-package` + keyword declare the pi extension. Per-harness local manifests and lockfiles are + kept out of git — `.opencode/.gitignore` excludes `node_modules`, + `package.json`, and lockfiles. Do the same for your harness's *local* install + artifacts so they don't pollute the repo — but never gitignore the repo-root + `package.json`, which is the tracked source of truth. + - **Build/dependency check.** Decide how the harness loads your module: + does it run the source directly (pi's `.ts` is referenced as-is from + `package.json`; OpenCode ships plain `.js`), or does it need a transpile/build + step? Superpowers is zero-runtime-dependency. pi's `import type + { ExtensionAPI }` works specifically because the harness runs the `.ts` + directly, supplies that type at load, and the repo never type-checks the file + in CI — the import isn't even declared as a dependency. If *your* harness + actually type-checks or bundles the plugin, that breaks: an undeclared type + import fails, and the PR rules only carve out *runtime* deps for new + harnesses, not dev/type packages. If you hit this, confirm the approach with + the maintainer rather than quietly adding a dependency. Keep any build output + out of git and document the command. +- **Shape C (instructions-file):** a small manifest (see `gemini-extension.json`: + `name`, `description`, `version`, `contextFileName`) plus the context file + itself (`GEMINI.md` is just two `@`-includes: the bootstrap skill and the + tool-mapping reference). The Gemini manifest has no `skills` field — Gemini + auto-discovers the `skills/` directory bundled in the installed extension. If + your harness has a native skill tool but no manifest field to register the + directory, you must find its discovery convention (read its extension docs), + then verify empirically: after wiring, ask the model to list its available + skills — if the bundled skills don't appear, discovery isn't working yet. + +### Step 3 — Wire the bootstrap injection + +This is the heart of the port. The shared goal: at session start, get the +`using-superpowers` skill content (wrapped in `` tags) plus +the harness's tool mapping in front of the model, with a note that the skill is +already active so the model doesn't try to load it again. *How* you do that — +and what you assemble vs. what the harness loads raw — depends entirely on your +shape. Do **not** apply one shape's recipe to another. + +**Shape A — a script reads `SKILL.md` and prints the harness's JSON.** The +dispatched script (`hooks/session-start`) `cat`s the whole `SKILL.md` (frontmatter +included — that's fine; it's emitted verbatim), wraps it with the "You have +superpowers… for all other skills use the Skill tool" preamble, escapes it, and +prints the harness's JSON shape. The tool mapping for Shape A does **not** go +inline here — it lives in `references/-tools.md` (Step 4). Get the JSON +output shape exactly right. `hooks/session-start` +detects the harness from environment variables and prints *one of three* shapes: + +- Cursor (`CURSOR_PLUGIN_ROOT` set): `{ "additional_context": "…" }` +- Claude Code (`CLAUDE_PLUGIN_ROOT` set, `COPILOT_CLI` unset): + `{ "hookSpecificOutput": { "hookEventName": "SessionStart", "additionalContext": "…" } }` +- Copilot CLI / SDK standard (else): `{ "additionalContext": "…" }` + +This is a trap. Emitting the wrong field, or an extra one, means the bootstrap +either never injects or injects twice (Claude Code reads both +`additional_context` and `hookSpecificOutput` without de-duplicating, so emitting +both double-injects). Find the +exact field, nesting, and event-matcher values your harness expects. Then +decide: add a fourth branch to `hooks/session-start`, or — if the harness needs +a different bootstrap message or env contract — add a dedicated +`hooks/session-start-` script. If you add a branch +and your harness *also* sets an env var an earlier branch keys on (some harnesses +set `CLAUDE_PLUGIN_ROOT` too), order your branch before the one that would +otherwise shadow it. Match the harness's +own event-matcher strings (Claude Code uses `startup|clear|compact`, Cursor +`sessionStart`); wrong matchers mean the hook silently never fires. + +The **hook-config schema itself varies per harness** — don't assume the +Claude Code shape is universal. Compare `hooks/hooks.json` and +`hooks/hooks-cursor.json`: Cursor's uses +`"version": 1`, a lowercase `sessionStart` key, a relative +`./hooks/run-hook.cmd` command, and omits the `matcher`/`type`/`async` fields +Claude Code uses. Match your `hooks-.json` to whichever existing file is +closest, not to a single canonical template. + +The hook **command string references a harness-provided plugin-root variable**, +and its name differs per harness: `hooks.json` uses `${CLAUDE_PLUGIN_ROOT}`, +`hooks-cursor.json` uses a relative path. Use +whatever your harness exports. (The `session-start` script re-derives the root +itself via `dirname`, so the script body doesn't depend on this — but the +command in the manifest does.) + +**Discovering the harness's contract.** The three facts above — env var, JSON +field/nesting, matcher strings — are the harness's contract, not Superpowers', +so you have to source them. Read the harness's hook docs, or find out +empirically: register a throwaway session-start hook that dumps its environment +and emits a marker, then observe which env var identifies the harness and +whether/how the harness ingests your stdout. Pin these down before writing the +real branch. + +**Shape B — assemble the string in code, then inject as a user message.** Here +you build the bootstrap yourself: read `SKILL.md`, strip its YAML frontmatter, +and assemble `` + a short preamble that the skill is already +loaded and must not be re-invoked + the stripped body + the inline tool mapping + +``. One subtlety the references disagree on: OpenCode's +preamble says "do NOT use the skill tool…" (assumes a `skill` tool exists), while +pi's just says "do not try to load using-superpowers again." If your harness has +no skill tool, use pi's wording, not OpenCode's. + +Inject the result as a **user-role message, not a system message** — system +messages bloat tokens when repeated every turn (#750) and multiple system +messages break some models (#894). Three things you must replicate: + +- **Dedup guard.** The lifecycle callback can fire repeatedly (OpenCode's + transform runs on *every* agent step; pi's `context` fires per turn). Before + injecting, check whether a bootstrap marker is already present and skip if so. + (The references pick different markers — pi a custom string, OpenCode the + `EXTREMELY_IMPORTANT` tag; matching the tag is more robust since it needs no + harness-specific constant.) Cache the bootstrap content at module level so + you're not re-reading and re-parsing `SKILL.md` on every call (#1202). +- **Compaction.** If the harness compacts/summarizes history, re-inject + afterward. pi sets an `injectBootstrap` flag on `session_start` and + `session_compact`, clears it on `agent_end`, and inserts the message *after* + any leading compaction-summary messages. OpenCode relies on its per-step + re-injection plus the dedup guard. +- **Message-object shape is per-harness — discover yours, don't copy a literal.** + The two references use *incompatible* shapes: pi builds + `{ role, content: [{ type, text }], timestamp }`; OpenCode manipulates + `message.info.role` and `message.parts[]`. Find your harness's message shape + from its API; copying a reference's object literal verbatim will fail silently. + +**Shape C — point your extension's context file at the bootstrap; assemble +nothing.** There is no injector, so you do *not* strip frontmatter or build a +wrapped string. The context file your extension ships (declared by the manifest — +*not* the user's own global file) pulls in two things: the `using-superpowers` +skill and the harness's tool-mapping reference. `GEMINI.md` +does this with two `@`-includes (`@./skills/using-superpowers/SKILL.md` and +`@./skills/using-superpowers/references/-tools.md`); the harness loads +them raw, frontmatter and all, and `SKILL.md` already carries its own +`` block internally. If your harness has no include syntax, +inline the content into the instructions file instead. Gemini ships **no** +"already loaded, don't re-invoke" preamble — for an `@`-include harness the +content is the active instruction set, not a skill the model would re-load. If +you find your harness does try to re-invoke, add that note as a literal line in +the instructions file (you have no code to add it any other way). + +### Step 4 — Write the tool mapping + +Translate the action vocabulary into the harness's real tools. Cover every one +of these actions (omit only what genuinely doesn't apply): + +- read a file +- create / edit / delete a file (one `apply_patch`-style tool, or separate + write/edit?) +- run a shell command +- search file contents / find files by name (grep, glob) +- fetch a URL / web search +- **dispatch a subagent**, including how to pass the agent type — and any config + flag needed to enable it +- **create / update todos** (treat older `TodoWrite` references as this action) +- **invoke a skill** — see Step 5 + +**Get the real tool names from the harness; never invent them.** If the docs +don't list them, the authoritative source is the harness itself: in a live +session, ask the model to "list the exact machine names of every tool you can +call, one per line" and use what it reports. + +**How the harness finds the `skills/` directory is itself per-harness** — confirm +it, don't assume. Possibilities: a manifest `skills` path field (Codex's +`"skills": "./skills/"`); a *co-located* `skills/` the harness auto-scans (where a +path field is **ignored** — one real harness only scanned a `skills/` sitting next +to `plugin.json`); an API/registration call (OpenCode, pi); or you stage an +install dir that pairs the manifest with a **symlink to the repo's `skills/`** and +point the installer at the staging dir (verify the installer *dereferences* the +symlink and copies the real files — confirm with `agy plugin validate`/`install` +or the equivalent before relying on it). A `skills` path field is *not* portable. + +Where the mapping lives depends on shape: + +- **Shape A:** put it in `skills/using-superpowers/references/-tools.md`. + The agent reaches it from the bootstrap — `SKILL.md`'s "Platform Adaptation" + section links the per-harness references files. (Shape A harnesses have no + instructions file; the mapping is *not* inlined into the hook output.) +- **Shape B:** the mapping is typically inlined into the bootstrap string you + inject (see the `toolMapping` constant in `superpowers.js`). pi keeps it in + *both* places — `piToolMapping()` inline **and** `references/pi-tools.md`. If + you maintain it in two places, update both, or the port is half-done. +- **Shape C:** put it in `references/-tools.md` and pull it into the + always-loaded instructions file (e.g. `GEMINI.md` `@`-includes + `gemini-tools.md`). + +You may also add a one-line pointer to your harness in `SKILL.md`'s "Platform +Adaptation" section so an agent reading the bootstrap knows where its mapping +lives. This is the one edit to a `SKILL.md` a port may make — and only because +that section is a pointer list, not behavior-shaping content. It does not violate +the "don't edit skill bodies" rule (Part 1); do not touch anything else in any +skill. (The list is a convenience pointer, not an exhaustive registry — not every +harness is listed.) + +### Step 5 — Handle a harness with no native skill tool + +`using-superpowers/SKILL.md` tells the model to *never read skill files manually +with file tools — always use your platform's skill-loading mechanism.* The point +is "don't bypass the mechanism," not "never use file-read." What counts as "your +platform's mechanism" depends on the harness — and for a harness with no skill +tool, the documented mechanism *is* reading `SKILL.md`. So reading it there +honors the rule rather than breaking it. Distinguish three cases: + +1. **Native `Skill`-style tool** (Claude Code, Copilot CLI, Gemini's + `activate_skill`): point the mapping at that tool. +2. **Native skill *discovery* but no `Skill` tool** (pi, Antigravity): the harness + can find and list skills, but the model can't call a tool to load one. Get the + skills installed where the harness scans (pi registers via `resources_discover` + → `skillPaths`; OpenCode via its `config` hook; `agy plugin install` copies + them in), and tell the model to load a skill by **reading its `SKILL.md` with + the file-read tool when the skill applies** — the sanctioned mechanism here, + the way `references/pi-tools.md` states it. + + **For the bootstrap itself, prefer a declared context file (Part 6).** If the + harness has a `contextFileName`-style manifest field — as Antigravity does — + ship a generated context file through the installer: it's guaranteed-loaded and + carries both the `using-superpowers` content and the tool mapping. That is the + strong, preferred path. + + **Fallback — the surfaced skill index.** If there's no context-file field but + the harness surfaces each installed skill's name + description at session start, + you need *neither* a built index nor a runtime-list instruction — the harness + is the index, and `using-superpowers`'s own surfaced description can be what + triggers the model to load it. This is softer than a declared context file; + two things it does **not** give you, versus a context file / hook / in-process + injector — account for both: + - **It bootstraps *triggering*, not the *tool mapping*.** An injector prepends + `-tools.md` alongside `using-superpowers` every session. Here nothing + injects the mapping — the model only sees skill *descriptions* and must *read* + your `references/-tools.md` when it needs tool names. It works + because skills name actions (the model reads the mapping when it acts), but + it's softer than injection. Make sure the mapping is reachable from what the + model loads — e.g. linked from `SKILL.md`'s Platform Adaptation section and + installed alongside the skills — not just sitting in the repo. + - **There's no structural guarantee the trigger fires.** No `` + wrapper, no dedup, no re-injection after compaction — firing depends on the + model choosing to act on a description it sees in the index. This is exactly + why the acceptance test is mandatory here: it is the *only* guarantee, so run + it on the model(s) your users will actually use, not just the strongest one. +3. **No skill system at all:** there is nothing to register, and the *only* + mechanism is the model reading `SKILL.md` on demand. But the model can't read + what it can't find: `using-superpowers/SKILL.md` does **not** enumerate the + available skills, so on its own the model won't know which skills exist or + their triggers. You must supply a discovery path. Two options, and they differ + in durability: (a) generate a skill index (each `skills/*/SKILL.md`'s `name` + + `description` frontmatter) and place it *inside* the `` + wrapper alongside the tool mapping (Shape B recipe above) so it's covered by + the dedup guard — but a build-time index goes stale as skills are added; or + (b) instruct the model to list `skills/*/SKILL.md` at runtime and read their + frontmatter to find a match — slower but never stale. Prefer (b) unless you + have a reason not to. Without either, a no-skill-system port loads the + bootstrap but silently never triggers any other skill. + +In cases 2 and 3, say plainly in your tool mapping that reading `SKILL.md` is the +blessed path, so the model doesn't think it's violating the "never read skill +files" rule. Don't go hunting for a `skillPaths`-style registration API in a +harness that has no skill system — case 3 has none. + +### Step 6 — Add tests + +Match the existing per-harness test style: + +- **Shape A:** assert the hook's stdout has the exact JSON shape your harness + consumes, and that it contains the bootstrap. See `tests/hooks/test-session-start.sh`, + which validates each harness's output shape. +- **Shape B:** a unit test that fakes the harness's plugin API and asserts the + lifecycle handlers register, the bootstrap injects once, the dedup guard + works, and (if relevant) compaction re-injection works. See + `tests/pi/test-pi-extension.mjs`. Add an isolated-install integration check in + the style of `tests/opencode/`. +- If the bootstrap is cached, test that the cache behaves when the file is + missing (see the OpenCode caching tests). + +These automated tests cover the wiring; the live tmux run in Step 7 is what +proves the integration actually triggers skills. + +### Step 7 — Install locally, then drive a live instance to verify + +You cannot confirm a port works by reading code. You have to run the harness with +your in-progress port loaded and watch a real session — which is also how you +produce the transcript the PR requires. + +**Install locally.** Point a *local* instance of the harness at your working +tree, not a published build: + +- **Shape A / C:** install the plugin/extension from this repo's local path (or + symlink its directory into wherever the harness looks). Find the harness's + "install from a local directory / git checkout" path in its docs. +- **Shape B:** register the local module — e.g. an `opencode.json` `plugin` + entry pointing at the local path, or pi resolving the `package.json` fields + from the repo. + +Reinstall after each change and restart the harness, since the bootstrap loads at +startup. + +**Drive it with tmux.** Most harnesses are interactive REPLs/TUIs that can't be +driven by piping stdin, so run the harness inside a detached tmux session and +control it with `send-keys` / `capture-pane`. A harness may advertise a +non-interactive "run one prompt" mode (e.g. `opencode run "..."`) — try it for the +quick smoke check, but **don't depend on it**: these modes are frequently flaky, +auth-gated, or trust-gated (one real harness's `--print` mode hung and timed out +with no output every time). Be ready to do *everything*, including the smoke +check, through tmux. + +**Clear the gates first, or tmux stalls silently.** Many harnesses block on +first-run onboarding, a "do you trust this folder?" prompt, a sandbox mode, or a +permission gate — and a detached tmux session will just sit there with no error +while it waits. Before the run, pre-trust your scratch directory (in the harness's +settings/config) or be prepared to answer those prompts via `send-keys`, and +account for the harness's startup time in your first `sleep`. + +```bash +# 1. Launch the harness detached, in a throwaway project dir +mkdir -p /tmp/port-smoke +tmux new-session -d -s port-test -c /tmp/port-smoke '' + +# 2. Let it initialize — real TUIs take longer than you think (10s+ with a model +# handshake); tune this. THEN capture and clear any blocking modal before you +# type a prompt: first-run onboarding and "trust this folder?" are modal, so +# keystrokes sent during them select menu items instead of typing your prompt. +sleep 12 +tmux capture-pane -t port-test -p # onboarding / trust prompt? answer it via send-keys first +# (e.g. tmux send-keys -t port-test Enter # to accept a trust prompt — inspect before assuming) + +# 3. Smoke check: does the model know it has superpowers? +# Send the text and Enter as SEPARATE send-keys with a beat between them — +# sending them together races on some TUIs (Enter arrives before the text lands). +tmux send-keys -t port-test 'What are your superpowers?'; sleep 0.4; tmux send-keys -t port-test Enter +sleep 5 +tmux capture-pane -t port-test -p # reply should show it knows its skills + +# 4. Acceptance test: exact prompt (note the escaped apostrophe), fresh session +tmux send-keys -t port-test 'Let'\''s make a react todo list'; sleep 0.4; tmux send-keys -t port-test Enter +# poll until the turn finishes — re-capture every few seconds, don't capture once +sleep 8 +tmux capture-pane -t port-test -p # PASS = brainstorming triggers BEFORE any code + +# 5. Save the transcript for the PR, then clean up +tmux capture-pane -t port-test -p > /tmp/port-smoke/transcript.txt +tmux kill-session -t port-test +``` + +tmux gotchas that bite here: wait after launch before the first capture; send the +prompt text and `Enter` as *separate* `send-keys` calls with a short `sleep` +between them (sending them together races on some TUIs), and `Enter` is a key name +not `\n`; the agent's turn takes time, so **poll `capture-pane` in a loop** rather +than capturing once; `capture-pane` shows only the visible pane, so for a long +conversation use the harness's own transcript/log file as the record of truth; +always `kill-session` when done. + +If the smoke check shows the model *doesn't* know it has superpowers, the +bootstrap isn't loading — fix that before bothering with the acceptance test. + +--- + +## Part 6 — Distribution and release + +A working integration in this repo isn't usable until a real user can install +it. Distribution differs per harness ecosystem — find yours: + +| Channel | Example | What you do | +|---|---|---| +| Native plugin marketplace | Claude Code | Register in `.claude-plugin/marketplace.json`; users `/plugin install`. The external `superpowers-marketplace` repo is the source of truth users install from — see the release steps in `CLAUDE.md`. | +| External marketplace fork, synced by script | Codex | `scripts/sync-to-codex-plugin.sh` rsyncs the tracked plugin files into a separate fork repo and opens a PR. Read its include/exclude list so you ship the right tree (it deliberately drops repo-internal dirs and other harnesses' dotdirs). | +| Git-URL extension install | Gemini, Kimi Code, OpenCode | Users install from a git URL (`gemini extensions install …`; Kimi Code `/plugins install …`; an `opencode.json` `plugin` array entry). Document the exact command. | +| Package-manifest fields | pi | Declared through fields in the repo-root `package.json`; users install via the harness's package command. | +| Local installer (plugin install) | Antigravity (`agy`) | A small `install.sh` that runs the harness's own `agy plugin install` against a staging dir holding the manifest, the skills, and a generated `contextFileName` context file (the bootstrap). Everything arrives through the install mechanism — *not* by editing the user's config (see below). | + +Then: + +- **A plugin installer may silently strip *undeclared* files — so make the + bootstrap a file the installer *recognizes*, never a user-config edit.** A + `plugin install` typically copies only the components it knows about + (skills/agents/commands/mcp/hooks/context) and discards anything else, so a + context file the manifest doesn't declare just vanishes from the install. The + fix is **not** to give up and write into the user's config (**rule 2**) — it's + to declare the bootstrap as a recognized component. In escalation order: + - **Ship a context file the manifest declares.** If the harness has a + `contextFileName`-style field (an extension-declared file it loads every + session), that is the strongest clean bootstrap: declare it, and the installer + preserves it *and* the harness loads it. Generate it at install time from the + live `using-superpowers/SKILL.md` + the tool mapping (wrapped in + ``) so the installed bootstrap never drifts. This is what + `.antigravity-plugin/install.sh` does — `agy plugin install` reports + `✔ context : ANTIGRAVITY.md`, and a clean session reads `using-superpowers`'s + SKILL.md, loads `brainstorming`, and enters the brainstorming flow before any + code. **Verify with a marker** that the installer keeps the file and the + harness loads it: one porter wrongly concluded it couldn't, because they + shipped the file *without* declaring `contextFileName` and it was stripped as + unrecognized. + - **Otherwise lean on the installed `using-superpowers` skill itself.** If the + harness surfaces each installed skill's name + description at session start, + the `using-superpowers` description ("Use when starting any conversation…") + can prompt the model to load it — installing the skill *is* the bootstrap. + Softer (no guaranteed wrapper; it carries triggering but not the tool mapping + — see Step 5), so prefer the declared context file when available. + - If neither works, the harness cannot be cleanly supported yet — **say so** + and raise it, rather than hand-editing the user's config. + +- **Write install docs.** A `docs/README..md` and/or a + `./INSTALL.md` (see `docs/README.opencode.md` and + `.opencode/INSTALL.md`), plus an install section in the top-level `README.md`. + The only supported install action is **running the harness's own install + command** (`agy plugin install`, `gemini extensions install`, `/plugin + install`, etc.). Hand-copying skill files and editing the user's global/personal + config are *both* off-limits (rule 2 / the PR rules). If the harness has no + install command at all — its only surface is a user-owned config file — then it + fails the "deliver via install mechanism" rule, and you should raise that rather + than ship an installer that edits the user's files. +- **Register the version.** If your harness introduces a *new* versioned + manifest, add its path and version field to `.version-bump.json` so + `scripts/bump-version.sh` keeps it in lockstep (read that file to see what's + currently tracked). A new manifest that isn't registered there will ship a + stale version. If your harness instead rides an already-tracked file — pi + declares itself in the repo-root `package.json`, which is already listed — + there's nothing new to add. +- **If no existing channel fits, you're standing up a new one.** None of the four + rows may match your harness. If it needs a Codex-style external fork sync, + `scripts/sync-to-codex-plugin.sh` is the template to clone (note its anchored + include/exclude list and its PR automation). And whenever you add a new + per-harness directory, add it to the *other* harnesses' sync excludes (e.g. the + EXCLUDES list in `sync-to-codex-plugin.sh`) so your dotdir doesn't leak into + their distributions. + +--- + +## Part 7 — Cross-platform / Windows + +Only relevant to the shell-hook shape. `hooks/run-hook.cmd` is a polyglot: a +single file that's valid as both a Windows batch script and a Unix shell script. +On Windows, `cmd.exe` runs the batch portion, which locates `bash` (Git for +Windows, then `bash` on PATH) and runs the named hook script; if no bash is +found it exits cleanly so the harness still works, just without injection. On +Unix, the leading `:` makes the batch block a no-op and the shell runs the +script directly. + +Two rules this enforces, which you must respect: + +- **Hook scripts are extensionless** (`session-start`, not `session-start.sh`). + Claude Code's Windows handling prepends `bash` to any command containing + `.sh`, which would double-invoke. Name your hook script without an extension. +- Don't write per-OS variants of the hook script. One extensionless bash script + plus the polyglot wrapper covers all three platforms. + +`hooks/run-hook.cmd` itself is the authoritative implementation — read it. See +`docs/windows/polyglot-hooks.md` for the background and rationale behind the +dispatcher pattern. + +--- + +## Part 8 — Submitting the PR + +- Target the **`dev`** branch. One harness per PR. +- Fill in the PR template's **"New harness support"** section and paste the + complete acceptance-test transcript (the "Let's make a react todo list" + session showing `brainstorming` auto-triggering). A PR without this proof will + be closed. +- Superpowers is a zero-dependency plugin. Don't add a third-party runtime + dependency. Adding a new harness is the one carve-out the contributor rules + allow, and even then keep it to what the integration strictly requires — + type-only imports that compile away are fine; runtime packages are not. +- Don't touch skill bodies (Part 1). If you found yourself editing a `SKILL.md` + to make the port work, the fix belongs in your tool mapping instead. + +--- + +## Appendix A — Reference integrations (current) + +Use this as the live index; when in doubt, read the files, not this table. + +| Harness | Entry point | Bootstrap mechanism | Tool mapping | Tests | Distribution | +|---|---|---|---|---|---| +| Claude Code | `.claude-plugin/plugin.json` + `hooks/hooks.json` | shell hook → `hooks/session-start` (`hookSpecificOutput.additionalContext`) | native `Skill` tool; `references/claude-code-tools.md` | `tests/hooks/` | marketplace | +| Codex | `.codex-plugin/plugin.json` (declares empty `hooks`) | native skill discovery (no session-start hook) | `references/codex-tools.md` | `tests/codex/`, `tests/codex-plugin-sync/` | fork sync (`scripts/sync-to-codex-plugin.sh`) | +| Cursor | `.cursor-plugin/plugin.json` + `hooks/hooks-cursor.json` | shell hook → `hooks/session-start` (`additional_context`) | `references/claude-code-tools.md` | `tests/hooks/` | hand-authored | +| Copilot CLI | (shares Claude Code hook path; `COPILOT_CLI` env) | shell hook → `hooks/session-start` (`additionalContext`) | `references/copilot-tools.md` | `tests/hooks/` | — | +| Gemini CLI | `gemini-extension.json` + `GEMINI.md` | instructions file `@`-includes bootstrap + mapping | `references/gemini-tools.md` | — | `gemini extensions install` | +| Kimi Code | `.kimi-plugin/plugin.json` | manifest `sessionStart.skill` loads `using-superpowers` | inline `skillInstructions` in manifest | `tests/kimi/` | marketplace or `/plugins install` GitHub URL | +| OpenCode | `.opencode/plugins/superpowers.js` (declared via root `package.json` `main`) | in-process: `config` hook registers skills dir; `experimental.chat.messages.transform` injects user message | inline in `superpowers.js` | `tests/opencode/` | `opencode.json` plugin git URL | +| pi | `.pi/extensions/superpowers.ts` | in-process: `resources_discover` registers skills; `context` event injects user message; lifecycle-flag + compaction-aware | `piToolMapping()` inline **and** `references/pi-tools.md` | `tests/pi/` | repo-root `package.json` fields | + +## Appendix B — Gotchas that have bitten porters + +- **Opt-in isn't a port.** If your human partner has to do anything per session + to get Superpowers, the acceptance test fails. Re-read Part 2. +- **Wrong JSON field → silent failure or double injection.** Shape A only. + Confirm the exact field/nesting; Claude Code reads two fields without dedup. +- **Hook-config schema varies per harness.** Shape A. Cursor's `hooks-cursor.json` + looks nothing like the Claude Code one (`version`, lowercase `sessionStart`, + relative command, no `matcher`/`type`/`async`). Match the closest existing file. +- **Plugin-root env var differs per harness.** Shape A. The hook command uses + `${CLAUDE_PLUGIN_ROOT}` (Claude) or a relative path + (Cursor). Use what your harness exports; the script re-derives the root itself. +- **System-message injection.** Shape B injects a *user* message on purpose + (#750, #894). Don't "fix" it to a system message. +- **Per-step vs per-turn callbacks.** OpenCode fires every step (per-call dedup + guard); pi fires per turn (lifecycle flag + `agent_end` reset). Copying one + harness's dedup strategy onto the other's callback frequency breaks injection. +- **Message-object shape is per-harness.** Shape B. pi and OpenCode use + incompatible shapes; discover yours, don't copy a reference's object literal. +- **Hunting for a skill-registration API that doesn't exist.** A harness with no + skill system (not just no `Skill` tool) has nothing to register — the model + reads `SKILL.md` on demand. Don't assume a `skillPaths` equivalent exists. +- **Mapping in two places.** For in-process plugins the mapping may live both + inline and in a `references/` file (pi). Update both. +- **The "never read skill files" line.** It means "don't bypass your platform's + skill-loading mechanism," not "never use file-read." On a no-skill-tool harness + that mechanism *is* reading `SKILL.md` — say so explicitly in the mapping + (Part 5). +- **`.sh` on Windows.** Keep hook scripts extensionless (Part 7). +- **Unregistered version.** A new manifest not added to `.version-bump.json` + ships stale (Part 6). +- **Editing skills to fit the harness.** Never. The fix goes in the tool mapping. diff --git a/docs/superpowers/plans/2026-01-22-document-review-system.md b/docs/superpowers/plans/2026-01-22-document-review-system.md new file mode 100644 index 0000000..6ab9fb8 --- /dev/null +++ b/docs/superpowers/plans/2026-01-22-document-review-system.md @@ -0,0 +1,301 @@ +# Document Review System Implementation Plan + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. + +**Goal:** Add spec and plan document review loops to the brainstorming and writing-plans skills. + +**Architecture:** Create reviewer prompt templates in each skill directory. Modify skill files to add review loops after document creation. Use Task tool with general-purpose subagent for reviewer dispatch. + +**Tech Stack:** Markdown skill files, subagent dispatch via Task tool + +**Spec:** docs/superpowers/specs/2026-01-22-document-review-system-design.md + +--- + +## Chunk 1: Spec Document Reviewer + +This chunk adds the spec document reviewer to the brainstorming skill. + +### Task 1: Create Spec Document Reviewer Prompt Template + +**Files:** +- Create: `skills/brainstorming/spec-document-reviewer-prompt.md` + +- [ ] **Step 1:** Create the reviewer prompt template file + +```markdown +# Spec Document Reviewer Prompt Template + +Use this template when dispatching a spec document reviewer subagent. + +**Purpose:** Verify the spec is complete, consistent, and ready for implementation planning. + +**Dispatch after:** Spec document is written to docs/superpowers/specs/ + +``` +Task tool (general-purpose): + description: "Review spec document" + prompt: | + You are a spec document reviewer. Verify this spec is complete and ready for planning. + + **Spec to review:** [SPEC_FILE_PATH] + + ## What to Check + + | Category | What to Look For | + |----------|------------------| + | Completeness | TODOs, placeholders, "TBD", incomplete sections | + | Coverage | Missing error handling, edge cases, integration points | + | Consistency | Internal contradictions, conflicting requirements | + | Clarity | Ambiguous requirements | + | YAGNI | Unrequested features, over-engineering | + + ## CRITICAL + + Look especially hard for: + - Any TODO markers or placeholder text + - Sections saying "to be defined later" or "will spec when X is done" + - Sections noticeably less detailed than others + + ## Output Format + + ## Spec Review + + **Status:** ✅ Approved | ❌ Issues Found + + **Issues (if any):** + - [Section X]: [specific issue] - [why it matters] + + **Recommendations (advisory):** + - [suggestions that don't block approval] +``` + +**Reviewer returns:** Status, Issues (if any), Recommendations +``` + +- [ ] **Step 2:** Verify the file was created correctly + +Run: `cat skills/brainstorming/spec-document-reviewer-prompt.md | head -20` +Expected: Shows the header and purpose section + +- [ ] **Step 3:** Commit + +```bash +git add skills/brainstorming/spec-document-reviewer-prompt.md +git commit -m "feat: add spec document reviewer prompt template" +``` + +--- + +### Task 2: Add Review Loop to Brainstorming Skill + +**Files:** +- Modify: `skills/brainstorming/SKILL.md` + +- [ ] **Step 1:** Read the current brainstorming skill + +Run: `cat skills/brainstorming/SKILL.md` + +- [ ] **Step 2:** Add the review loop section after "After the Design" + +Find the "After the Design" section and add a new "Spec Review Loop" section after documentation but before implementation: + +```markdown +**Spec Review Loop:** +After writing the spec document: +1. Dispatch spec-document-reviewer subagent (see spec-document-reviewer-prompt.md) +2. If ❌ Issues Found: + - Fix the issues in the spec document + - Re-dispatch reviewer + - Repeat until ✅ Approved +3. If ✅ Approved: proceed to implementation setup + +**Review loop guidance:** +- Same agent that wrote the spec fixes it (preserves context) +- If loop exceeds 5 iterations, surface to human for guidance +- Reviewers are advisory - explain disagreements if you believe feedback is incorrect +``` + +- [ ] **Step 3:** Verify the changes + +Run: `grep -A 15 "Spec Review Loop" skills/brainstorming/SKILL.md` +Expected: Shows the new review loop section + +- [ ] **Step 4:** Commit + +```bash +git add skills/brainstorming/SKILL.md +git commit -m "feat: add spec review loop to brainstorming skill" +``` + +--- + +## Chunk 2: Plan Document Reviewer + +This chunk adds the plan document reviewer to the writing-plans skill. + +### Task 3: Create Plan Document Reviewer Prompt Template + +**Files:** +- Create: `skills/writing-plans/plan-document-reviewer-prompt.md` + +- [ ] **Step 1:** Create the reviewer prompt template file + +```markdown +# Plan Document Reviewer Prompt Template + +Use this template when dispatching a plan document reviewer subagent. + +**Purpose:** Verify the plan chunk is complete, matches the spec, and has proper task decomposition. + +**Dispatch after:** Each plan chunk is written + +``` +Task tool (general-purpose): + description: "Review plan chunk N" + prompt: | + You are a plan document reviewer. Verify this plan chunk is complete and ready for implementation. + + **Plan chunk to review:** [PLAN_FILE_PATH] - Chunk N only + **Spec for reference:** [SPEC_FILE_PATH] + + ## What to Check + + | Category | What to Look For | + |----------|------------------| + | Completeness | TODOs, placeholders, incomplete tasks, missing steps | + | Spec Alignment | Chunk covers relevant spec requirements, no scope creep | + | Task Decomposition | Tasks atomic, clear boundaries, steps actionable | + | Task Syntax | Checkbox syntax (`- [ ]`) on tasks and steps | + | Chunk Size | Each chunk under 1000 lines | + + ## CRITICAL + + Look especially hard for: + - Any TODO markers or placeholder text + - Steps that say "similar to X" without actual content + - Incomplete task definitions + - Missing verification steps or expected outputs + + ## Output Format + + ## Plan Review - Chunk N + + **Status:** ✅ Approved | ❌ Issues Found + + **Issues (if any):** + - [Task X, Step Y]: [specific issue] - [why it matters] + + **Recommendations (advisory):** + - [suggestions that don't block approval] +``` + +**Reviewer returns:** Status, Issues (if any), Recommendations +``` + +- [ ] **Step 2:** Verify the file was created + +Run: `cat skills/writing-plans/plan-document-reviewer-prompt.md | head -20` +Expected: Shows the header and purpose section + +- [ ] **Step 3:** Commit + +```bash +git add skills/writing-plans/plan-document-reviewer-prompt.md +git commit -m "feat: add plan document reviewer prompt template" +``` + +--- + +### Task 4: Add Review Loop to Writing-Plans Skill + +**Files:** +- Modify: `skills/writing-plans/SKILL.md` + +- [ ] **Step 1:** Read current skill file + +Run: `cat skills/writing-plans/SKILL.md` + +- [ ] **Step 2:** Add chunk-by-chunk review section + +Add before the "Execution Handoff" section: + +```markdown +## Plan Review Loop + +After completing each chunk of the plan: + +1. Dispatch plan-document-reviewer subagent for the current chunk + - Provide: chunk content, path to spec document +2. If ❌ Issues Found: + - Fix the issues in the chunk + - Re-dispatch reviewer for that chunk + - Repeat until ✅ Approved +3. If ✅ Approved: proceed to next chunk (or execution handoff if last chunk) + +**Chunk boundaries:** Use `## Chunk N: ` headings to delimit chunks. Each chunk should be ≤1000 lines and logically self-contained. +``` + +- [ ] **Step 3:** Update task syntax examples to use checkboxes + +Change the Task Structure section to show checkbox syntax: + +```markdown +### Task N: [Component Name] + +- [ ] **Step 1:** Write the failing test + - File: `tests/path/test.py` + ... +``` + +- [ ] **Step 4:** Verify the review loop section was added + +Run: `grep -A 15 "Plan Review Loop" skills/writing-plans/SKILL.md` +Expected: Shows the new review loop section + +- [ ] **Step 5:** Verify the task syntax examples were updated + +Run: `grep -A 5 "Task N:" skills/writing-plans/SKILL.md` +Expected: Shows checkbox syntax `### Task N:` + +- [ ] **Step 6:** Commit + +```bash +git add skills/writing-plans/SKILL.md +git commit -m "feat: add plan review loop and checkbox syntax to writing-plans skill" +``` + +--- + +## Chunk 3: Update Plan Document Header + +This chunk updates the plan document header template to reference the new checkbox syntax requirements. + +### Task 5: Update Plan Header Template in Writing-Plans Skill + +**Files:** +- Modify: `skills/writing-plans/SKILL.md` + +- [ ] **Step 1:** Read current plan header template + +Run: `grep -A 20 "Plan Document Header" skills/writing-plans/SKILL.md` + +- [ ] **Step 2:** Update the header template to reference checkbox syntax + +The plan header should note that tasks and steps use checkbox syntax. Update the header comment: + +```markdown +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Tasks and steps use checkbox (`- [ ]`) syntax for tracking. +``` + +- [ ] **Step 3:** Verify the change + +Run: `grep -A 5 "For agentic workers:" skills/writing-plans/SKILL.md` +Expected: Shows updated header with checkbox syntax mention + +- [ ] **Step 4:** Commit + +```bash +git add skills/writing-plans/SKILL.md +git commit -m "docs: update plan header to reference checkbox syntax" +``` diff --git a/docs/superpowers/plans/2026-02-19-visual-brainstorming-refactor.md b/docs/superpowers/plans/2026-02-19-visual-brainstorming-refactor.md new file mode 100644 index 0000000..224d349 --- /dev/null +++ b/docs/superpowers/plans/2026-02-19-visual-brainstorming-refactor.md @@ -0,0 +1,523 @@ +# Visual Brainstorming Refactor Implementation Plan + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Refactor visual brainstorming from blocking TUI feedback model to non-blocking "Browser Displays, Terminal Commands" architecture. + +**Architecture:** Browser becomes an interactive display; terminal stays the conversation channel. Server writes user events to a per-screen `.events` file that Claude reads on its next turn. Eliminates `wait-for-feedback.sh` and all `TaskOutput` blocking. + +**Tech Stack:** Node.js (Express, ws, chokidar), vanilla HTML/CSS/JS + +**Spec:** `docs/superpowers/specs/2026-02-19-visual-brainstorming-refactor-design.md` + +--- + +## File Map + +| File | Action | Responsibility | +|------|--------|---------------| +| `lib/brainstorm-server/index.js` | Modify | Server: add `.events` file writing, clear on new screen, replace `wrapInFrame` | +| `lib/brainstorm-server/frame-template.html` | Modify | Template: remove feedback footer, add content placeholder + selection indicator | +| `lib/brainstorm-server/helper.js` | Modify | Client JS: remove send/feedback functions, narrow to click capture + indicator updates | +| `lib/brainstorm-server/wait-for-feedback.sh` | Delete | No longer needed | +| `skills/brainstorming/visual-companion.md` | Modify | Skill instructions: rewrite loop to non-blocking flow | +| `tests/brainstorm-server/server.test.js` | Modify | Tests: update for new template structure and helper.js API | + +--- + +## Chunk 1: Server, Template, Client, Tests, Skill + +### Task 1: Update `frame-template.html` + +**Files:** +- Modify: `lib/brainstorm-server/frame-template.html` + +- [ ] **Step 1: Remove the feedback footer HTML** + +Replace the feedback-footer div (lines 227-233) with a selection indicator bar: + +```html +
+ Click an option above, then return to the terminal +
+``` + +Also replace the default content inside `#claude-content` (lines 220-223) with the content placeholder: + +```html +
+ +
+``` + +- [ ] **Step 2: Replace feedback footer CSS with indicator bar CSS** + +Remove the `.feedback-footer`, `.feedback-footer label`, `.feedback-row`, and the textarea/button styles within `.feedback-footer` (lines 82-112). + +Add indicator bar CSS: + +```css + .indicator-bar { + background: var(--bg-secondary); + border-top: 1px solid var(--border); + padding: 0.5rem 1.5rem; + flex-shrink: 0; + text-align: center; + } + .indicator-bar span { + font-size: 0.75rem; + color: var(--text-secondary); + } + .indicator-bar .selected-text { + color: var(--accent); + font-weight: 500; + } +``` + +- [ ] **Step 3: Verify template renders** + +Run the test suite to check the template still loads: +```bash +cd /Users/drewritter/prime-rad/superpowers && node tests/brainstorm-server/server.test.js +``` +Expected: Tests 1-5 should still pass. Tests 6-8 may fail (expected — they assert old structure). + +- [ ] **Step 4: Commit** + +```bash +git add lib/brainstorm-server/frame-template.html +git commit -m "Replace feedback footer with selection indicator bar in brainstorm template" +``` + +--- + +### Task 2: Update `index.js` — content injection and `.events` file + +**Files:** +- Modify: `lib/brainstorm-server/index.js` + +- [ ] **Step 1: Write failing test for `.events` file writing** + +Add to `tests/brainstorm-server/server.test.js` after Test 4 area — a new test that sends a WebSocket event with a `choice` field and verifies `.events` file is written: + +```javascript + // Test: Choice events written to .events file + console.log('Test: Choice events written to .events file'); + const ws3 = new WebSocket(`ws://localhost:${TEST_PORT}`); + await new Promise(resolve => ws3.on('open', resolve)); + + ws3.send(JSON.stringify({ type: 'click', choice: 'a', text: 'Option A' })); + await sleep(300); + + const eventsFile = path.join(TEST_DIR, '.events'); + assert(fs.existsSync(eventsFile), '.events file should exist after choice click'); + const lines = fs.readFileSync(eventsFile, 'utf-8').trim().split('\n'); + const event = JSON.parse(lines[lines.length - 1]); + assert.strictEqual(event.choice, 'a', 'Event should contain choice'); + assert.strictEqual(event.text, 'Option A', 'Event should contain text'); + ws3.close(); + console.log(' PASS'); +``` + +- [ ] **Step 2: Run test to verify it fails** + +```bash +cd /Users/drewritter/prime-rad/superpowers && node tests/brainstorm-server/server.test.js +``` +Expected: New test FAILS — `.events` file doesn't exist yet. + +- [ ] **Step 3: Write failing test for `.events` file clearing on new screen** + +Add another test: + +```javascript + // Test: .events cleared on new screen + console.log('Test: .events cleared on new screen'); + // .events file should still exist from previous test + assert(fs.existsSync(path.join(TEST_DIR, '.events')), '.events should exist before new screen'); + fs.writeFileSync(path.join(TEST_DIR, 'new-screen.html'), '

New screen

'); + await sleep(500); + assert(!fs.existsSync(path.join(TEST_DIR, '.events')), '.events should be cleared after new screen'); + console.log(' PASS'); +``` + +- [ ] **Step 4: Run test to verify it fails** + +```bash +cd /Users/drewritter/prime-rad/superpowers && node tests/brainstorm-server/server.test.js +``` +Expected: New test FAILS — `.events` not cleared on screen push. + +- [ ] **Step 5: Implement `.events` file writing in `index.js`** + +In the WebSocket `message` handler (line 74-77 of `index.js`), after the `console.log`, add: + +```javascript + // Write user events to .events file for Claude to read + if (event.choice) { + const eventsFile = path.join(SCREEN_DIR, '.events'); + fs.appendFileSync(eventsFile, JSON.stringify(event) + '\n'); + } +``` + +In the chokidar `add` handler (line 104-111), add `.events` clearing: + +```javascript + if (filePath.endsWith('.html')) { + // Clear events from previous screen + const eventsFile = path.join(SCREEN_DIR, '.events'); + if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile); + + console.log(JSON.stringify({ type: 'screen-added', file: filePath })); + // ... existing reload broadcast + } +``` + +- [ ] **Step 6: Replace `wrapInFrame` with comment placeholder injection** + +Replace the `wrapInFrame` function (lines 27-32 of `index.js`): + +```javascript +function wrapInFrame(content) { + return frameTemplate.replace('', content); +} +``` + +- [ ] **Step 7: Run all tests** + +```bash +cd /Users/drewritter/prime-rad/superpowers && node tests/brainstorm-server/server.test.js +``` +Expected: New `.events` tests PASS. Existing tests may still have failures from old assertions (fixed in Task 4). + +- [ ] **Step 8: Commit** + +```bash +git add lib/brainstorm-server/index.js tests/brainstorm-server/server.test.js +git commit -m "Add .events file writing and comment-based content injection to brainstorm server" +``` + +--- + +### Task 3: Simplify `helper.js` + +**Files:** +- Modify: `lib/brainstorm-server/helper.js` + +- [ ] **Step 1: Remove `sendToClaude` function** + +Delete the `sendToClaude` function (lines 92-106) — the function body and the page takeover HTML. + +- [ ] **Step 2: Remove `window.send` function** + +Delete the `window.send` function (lines 120-129) — was tied to the removed Send button. + +- [ ] **Step 3: Remove form submission and input change handlers** + +Delete the form submission handler (lines 57-71) and the input change handler (lines 73-89) including the `inputTimeout` variable. + +- [ ] **Step 4: Remove `pageshow` event listener** + +Delete the `pageshow` listener we added earlier (no textarea to clear anymore). + +- [ ] **Step 5: Narrow click handler to `[data-choice]` only** + +Replace the click handler (lines 36-55) with a narrower version: + +```javascript + // Capture clicks on choice elements + document.addEventListener('click', (e) => { + const target = e.target.closest('[data-choice]'); + if (!target) return; + + sendEvent({ + type: 'click', + text: target.textContent.trim(), + choice: target.dataset.choice, + id: target.id || null + }); + }); +``` + +- [ ] **Step 6: Add indicator bar update on choice click** + +After the `sendEvent` call in the click handler, add: + +```javascript + // Update indicator bar + const indicator = document.getElementById('indicator-text'); + if (indicator) { + const label = target.querySelector('h3, .content h3, .card-body h3')?.textContent?.trim() || target.dataset.choice; + indicator.innerHTML = '' + label + ' selected — return to terminal to continue'; + } +``` + +- [ ] **Step 7: Remove `sendToClaude` from `window.brainstorm` API** + +Update the `window.brainstorm` object (lines 132-136) to remove `sendToClaude`: + +```javascript + window.brainstorm = { + send: sendEvent, + choice: (value, metadata = {}) => sendEvent({ type: 'choice', value, ...metadata }) + }; +``` + +- [ ] **Step 8: Run tests** + +```bash +cd /Users/drewritter/prime-rad/superpowers && node tests/brainstorm-server/server.test.js +``` + +- [ ] **Step 9: Commit** + +```bash +git add lib/brainstorm-server/helper.js +git commit -m "Simplify helper.js: remove feedback functions, narrow to choice capture + indicator" +``` + +--- + +### Task 4: Update tests for new structure + +**Files:** +- Modify: `tests/brainstorm-server/server.test.js` + +**Note:** Line references below are from the _original_ file. Task 2 inserted new tests earlier in the file, so actual line numbers will be shifted. Find tests by their `console.log` labels (e.g., "Test 5:", "Test 6:"). + +- [ ] **Step 1: Update Test 5 (full document assertion)** + +Find the Test 5 assertion `!fullRes.body.includes('feedback-footer')`. Change it to: Full documents should NOT have the indicator bar either (they're served as-is): + +```javascript + assert(!fullRes.body.includes('indicator-bar') || fullDoc.includes('indicator-bar'), + 'Should not wrap full documents in frame template'); +``` + +- [ ] **Step 2: Update Test 6 (fragment wrapping)** + +Line 125: Replace `feedback-footer` assertion with indicator bar assertion: + +```javascript + assert(fragRes.body.includes('indicator-bar'), 'Fragment should get indicator bar from frame'); +``` + +Also verify content placeholder was replaced (fragment content appears, placeholder comment doesn't): + +```javascript + assert(!fragRes.body.includes(''), 'Content placeholder should be replaced'); +``` + +- [ ] **Step 3: Update Test 7 (helper.js API)** + +Lines 140-142: Update assertions to reflect the new API surface: + +```javascript + assert(helperContent.includes('toggleSelect'), 'helper.js should define toggleSelect'); + assert(helperContent.includes('sendEvent'), 'helper.js should define sendEvent'); + assert(helperContent.includes('selectedChoice'), 'helper.js should track selectedChoice'); + assert(helperContent.includes('brainstorm'), 'helper.js should expose brainstorm API'); + assert(!helperContent.includes('sendToClaude'), 'helper.js should not contain sendToClaude'); +``` + +- [ ] **Step 4: Replace Test 8 (sendToClaude theming) with indicator bar test** + +Replace Test 8 (lines 145-149) — `sendToClaude` no longer exists. Test the indicator bar instead: + +```javascript + // Test 8: Indicator bar uses CSS variables (theme support) + console.log('Test 8: Indicator bar uses CSS variables'); + const templateContent = fs.readFileSync( + path.join(__dirname, '../../lib/brainstorm-server/frame-template.html'), 'utf-8' + ); + assert(templateContent.includes('indicator-bar'), 'Template should have indicator bar'); + assert(templateContent.includes('indicator-text'), 'Template should have indicator text element'); + console.log(' PASS'); +``` + +- [ ] **Step 5: Run full test suite** + +```bash +cd /Users/drewritter/prime-rad/superpowers && node tests/brainstorm-server/server.test.js +``` +Expected: ALL tests PASS. + +- [ ] **Step 6: Commit** + +```bash +git add tests/brainstorm-server/server.test.js +git commit -m "Update brainstorm server tests for new template structure and helper.js API" +``` + +--- + +### Task 5: Delete `wait-for-feedback.sh` + +**Files:** +- Delete: `lib/brainstorm-server/wait-for-feedback.sh` + +- [ ] **Step 1: Verify no other files import or reference `wait-for-feedback.sh`** + +Search the codebase: +```bash +grep -r "wait-for-feedback" /Users/drewritter/prime-rad/superpowers/ --include="*.js" --include="*.md" --include="*.sh" --include="*.json" +``` + +Expected references: only `visual-companion.md` (rewritten in Task 6) and possibly release notes (historical, leave as-is). + +- [ ] **Step 2: Delete the file** + +```bash +rm lib/brainstorm-server/wait-for-feedback.sh +``` + +- [ ] **Step 3: Run tests to confirm nothing breaks** + +```bash +cd /Users/drewritter/prime-rad/superpowers && node tests/brainstorm-server/server.test.js +``` +Expected: All tests PASS (no test referenced this file). + +- [ ] **Step 4: Commit** + +```bash +git add -u lib/brainstorm-server/wait-for-feedback.sh +git commit -m "Delete wait-for-feedback.sh: replaced by .events file" +``` + +--- + +### Task 6: Rewrite `visual-companion.md` + +**Files:** +- Modify: `skills/brainstorming/visual-companion.md` + +- [ ] **Step 1: Update "How It Works" description (line 18)** + +Replace the sentence about receiving feedback "as JSON" with: + +```markdown +The server watches a directory for HTML files and serves the newest one to the browser. You write HTML content, the user sees it in their browser and can click to select options. Selections are recorded to a `.events` file that you read on your next turn. +``` + +- [ ] **Step 2: Update fragment description (line 20)** + +Remove "feedback footer" from the description of what the frame template provides: + +```markdown +**Content fragments vs full documents:** If your HTML file starts with ` +``` + +- [ ] **Step 3: Verify no stale references remain** + +```bash +grep -r "wait-for-feedback\|sendToClaude\|feedback-footer\|send-to-claude\|TaskOutput.*block.*true" /Users/drewritter/prime-rad/superpowers/ --include="*.js" --include="*.md" --include="*.sh" --include="*.html" | grep -v node_modules | grep -v RELEASE-NOTES | grep -v "\.md:.*spec\|plan" +``` + +Expected: No hits outside of release notes and the spec/plan docs (which are historical). + +- [ ] **Step 4: Final commit if any cleanup needed** + +```bash +git status +# Review untracked/modified files, stage specific files as needed, commit if clean +``` diff --git a/docs/superpowers/plans/2026-03-11-zero-dep-brainstorm-server.md b/docs/superpowers/plans/2026-03-11-zero-dep-brainstorm-server.md new file mode 100644 index 0000000..9bfc666 --- /dev/null +++ b/docs/superpowers/plans/2026-03-11-zero-dep-brainstorm-server.md @@ -0,0 +1,479 @@ +# Zero-Dependency Brainstorm Server Implementation Plan + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace the brainstorm server's vendored node_modules with a single zero-dependency `server.js` using Node built-ins. + +**Architecture:** Single file with WebSocket protocol (RFC 6455 text frames), HTTP server (`http` module), and file watching (`fs.watch`). Exports protocol functions for unit testing when required as a module. + +**Tech Stack:** Node.js built-ins only: `http`, `crypto`, `fs`, `path` + +**Spec:** `docs/superpowers/specs/2026-03-11-zero-dep-brainstorm-server-design.md` + +**Existing tests:** `tests/brainstorm-server/ws-protocol.test.js` (unit), `tests/brainstorm-server/server.test.js` (integration) + +--- + +## File Map + +- **Create:** `skills/brainstorming/scripts/server.js` — the zero-dep replacement +- **Modify:** `skills/brainstorming/scripts/start-server.sh:94,100` — change `index.js` to `server.js` +- **Modify:** `.gitignore:6` — remove the `!skills/brainstorming/scripts/node_modules/` exception +- **Delete:** `skills/brainstorming/scripts/index.js` +- **Delete:** `skills/brainstorming/scripts/package.json` +- **Delete:** `skills/brainstorming/scripts/package-lock.json` +- **Delete:** `skills/brainstorming/scripts/node_modules/` (714 files) +- **No changes:** `skills/brainstorming/scripts/helper.js`, `skills/brainstorming/scripts/frame-template.html`, `skills/brainstorming/scripts/stop-server.sh` + +--- + +## Chunk 1: WebSocket Protocol Layer + +### Task 1: Implement WebSocket protocol exports + +**Files:** +- Create: `skills/brainstorming/scripts/server.js` +- Test: `tests/brainstorm-server/ws-protocol.test.js` (already exists) + +- [ ] **Step 1: Create server.js with OPCODES constant and computeAcceptKey** + +```js +const crypto = require('crypto'); + +const OPCODES = { TEXT: 0x01, CLOSE: 0x08, PING: 0x09, PONG: 0x0A }; +const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; + +function computeAcceptKey(clientKey) { + return crypto.createHash('sha1').update(clientKey + WS_MAGIC).digest('base64'); +} +``` + +- [ ] **Step 2: Implement encodeFrame** + +Server frames are never masked. Three length encodings: +- payload < 126: 2-byte header (FIN+opcode, length) +- 126-65535: 4-byte header (FIN+opcode, 126, 16-bit length) +- > 65535: 10-byte header (FIN+opcode, 127, 64-bit length) + +```js +function encodeFrame(opcode, payload) { + const fin = 0x80; + const len = payload.length; + let header; + + if (len < 126) { + header = Buffer.alloc(2); + header[0] = fin | opcode; + header[1] = len; + } else if (len < 65536) { + header = Buffer.alloc(4); + header[0] = fin | opcode; + header[1] = 126; + header.writeUInt16BE(len, 2); + } else { + header = Buffer.alloc(10); + header[0] = fin | opcode; + header[1] = 127; + header.writeBigUInt64BE(BigInt(len), 2); + } + + return Buffer.concat([header, payload]); +} +``` + +- [ ] **Step 3: Implement decodeFrame** + +Client frames are always masked. Returns `{ opcode, payload, bytesConsumed }` or `null` for incomplete. Throws on unmasked frames. + +```js +function decodeFrame(buffer) { + if (buffer.length < 2) return null; + + const firstByte = buffer[0]; + const secondByte = buffer[1]; + const opcode = firstByte & 0x0F; + const masked = (secondByte & 0x80) !== 0; + let payloadLen = secondByte & 0x7F; + let offset = 2; + + if (!masked) throw new Error('Client frames must be masked'); + + if (payloadLen === 126) { + if (buffer.length < 4) return null; + payloadLen = buffer.readUInt16BE(2); + offset = 4; + } else if (payloadLen === 127) { + if (buffer.length < 10) return null; + payloadLen = Number(buffer.readBigUInt64BE(2)); + offset = 10; + } + + const maskOffset = offset; + const dataOffset = offset + 4; + const totalLen = dataOffset + payloadLen; + if (buffer.length < totalLen) return null; + + const mask = buffer.slice(maskOffset, dataOffset); + const data = Buffer.alloc(payloadLen); + for (let i = 0; i < payloadLen; i++) { + data[i] = buffer[dataOffset + i] ^ mask[i % 4]; + } + + return { opcode, payload: data, bytesConsumed: totalLen }; +} +``` + +- [ ] **Step 4: Add module exports at the bottom of the file** + +```js +module.exports = { computeAcceptKey, encodeFrame, decodeFrame, OPCODES }; +``` + +- [ ] **Step 5: Run unit tests** + +Run: `cd tests/brainstorm-server && node ws-protocol.test.js` +Expected: All tests pass (handshake, encoding, decoding, boundaries, edge cases) + +- [ ] **Step 6: Commit** + +```bash +git add skills/brainstorming/scripts/server.js +git commit -m "Add WebSocket protocol layer for zero-dep brainstorm server" +``` + +--- + +## Chunk 2: HTTP Server and Application Logic + +### Task 2: Add HTTP server, file watching, and WebSocket connection handling + +**Files:** +- Modify: `skills/brainstorming/scripts/server.js` +- Test: `tests/brainstorm-server/server.test.js` (already exists) + +- [ ] **Step 1: Add configuration and constants at top of server.js (after requires)** + +```js +const http = require('http'); +const fs = require('fs'); +const path = require('path'); + +const PORT = process.env.BRAINSTORM_PORT || (49152 + Math.floor(Math.random() * 16383)); +const HOST = process.env.BRAINSTORM_HOST || '127.0.0.1'; +const URL_HOST = process.env.BRAINSTORM_URL_HOST || (HOST === '127.0.0.1' ? 'localhost' : HOST); +const SCREEN_DIR = process.env.BRAINSTORM_DIR || '/tmp/brainstorm'; + +const MIME_TYPES = { + '.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript', + '.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.svg': 'image/svg+xml' +}; +``` + +- [ ] **Step 2: Add WAITING_PAGE, template loading at module scope, and helper functions** + +Load `frameTemplate` and `helperInjection` at module scope so they're accessible to `wrapInFrame` and `handleRequest`. They only read files from `__dirname` (the scripts directory), which is valid whether the module is required or run directly. + +```js +const WAITING_PAGE = ` + +Brainstorm Companion + + +

Brainstorm Companion

+

Waiting for Claude to push a screen...

`; + +const frameTemplate = fs.readFileSync(path.join(__dirname, 'frame-template.html'), 'utf-8'); +const helperScript = fs.readFileSync(path.join(__dirname, 'helper.js'), 'utf-8'); +const helperInjection = ''; + +function isFullDocument(html) { + const trimmed = html.trimStart().toLowerCase(); + return trimmed.startsWith('', content); +} + +function getNewestScreen() { + const files = fs.readdirSync(SCREEN_DIR) + .filter(f => f.endsWith('.html')) + .map(f => { + const fp = path.join(SCREEN_DIR, f); + return { path: fp, mtime: fs.statSync(fp).mtime.getTime() }; + }) + .sort((a, b) => b.mtime - a.mtime); + return files.length > 0 ? files[0].path : null; +} +``` + +- [ ] **Step 3: Add HTTP request handler** + +```js +function handleRequest(req, res) { + if (req.method === 'GET' && req.url === '/') { + const screenFile = getNewestScreen(); + let html = screenFile + ? (raw => isFullDocument(raw) ? raw : wrapInFrame(raw))(fs.readFileSync(screenFile, 'utf-8')) + : WAITING_PAGE; + + if (html.includes('')) { + html = html.replace('', helperInjection + '\n'); + } else { + html += helperInjection; + } + + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(html); + } else if (req.method === 'GET' && req.url.startsWith('/files/')) { + const fileName = req.url.slice(7); // strip '/files/' + const filePath = path.join(SCREEN_DIR, path.basename(fileName)); + if (!fs.existsSync(filePath)) { + res.writeHead(404); + res.end('Not found'); + return; + } + const ext = path.extname(filePath).toLowerCase(); + const contentType = MIME_TYPES[ext] || 'application/octet-stream'; + res.writeHead(200, { 'Content-Type': contentType }); + res.end(fs.readFileSync(filePath)); + } else { + res.writeHead(404); + res.end('Not found'); + } +} +``` + +- [ ] **Step 4: Add WebSocket connection handling** + +```js +const clients = new Set(); + +function handleUpgrade(req, socket) { + const key = req.headers['sec-websocket-key']; + if (!key) { socket.destroy(); return; } + + const accept = computeAcceptKey(key); + socket.write( + 'HTTP/1.1 101 Switching Protocols\r\n' + + 'Upgrade: websocket\r\n' + + 'Connection: Upgrade\r\n' + + 'Sec-WebSocket-Accept: ' + accept + '\r\n\r\n' + ); + + let buffer = Buffer.alloc(0); + clients.add(socket); + + socket.on('data', (chunk) => { + buffer = Buffer.concat([buffer, chunk]); + while (buffer.length > 0) { + let result; + try { + result = decodeFrame(buffer); + } catch (e) { + socket.end(encodeFrame(OPCODES.CLOSE, Buffer.alloc(0))); + clients.delete(socket); + return; + } + if (!result) break; + buffer = buffer.slice(result.bytesConsumed); + + switch (result.opcode) { + case OPCODES.TEXT: + handleMessage(result.payload.toString()); + break; + case OPCODES.CLOSE: + socket.end(encodeFrame(OPCODES.CLOSE, Buffer.alloc(0))); + clients.delete(socket); + return; + case OPCODES.PING: + socket.write(encodeFrame(OPCODES.PONG, result.payload)); + break; + case OPCODES.PONG: + break; + default: + // Unsupported opcode — close with 1003 + const closeBuf = Buffer.alloc(2); + closeBuf.writeUInt16BE(1003); + socket.end(encodeFrame(OPCODES.CLOSE, closeBuf)); + clients.delete(socket); + return; + } + } + }); + + socket.on('close', () => clients.delete(socket)); + socket.on('error', () => clients.delete(socket)); +} + +function handleMessage(text) { + let event; + try { + event = JSON.parse(text); + } catch (e) { + console.error('Failed to parse WebSocket message:', e.message); + return; + } + console.log(JSON.stringify({ source: 'user-event', ...event })); + if (event.choice) { + const eventsFile = path.join(SCREEN_DIR, '.events'); + fs.appendFileSync(eventsFile, JSON.stringify(event) + '\n'); + } +} + +function broadcast(msg) { + const frame = encodeFrame(OPCODES.TEXT, Buffer.from(JSON.stringify(msg))); + for (const socket of clients) { + try { socket.write(frame); } catch (e) { clients.delete(socket); } + } +} +``` + +- [ ] **Step 5: Add debounce timer map** + +```js +const debounceTimers = new Map(); +``` + +File watching logic is inlined in `startServer` (Step 6) to keep watcher lifecycle together with server lifecycle and include an `error` handler per spec. + +- [ ] **Step 6: Add startServer function and conditional main** + +`frameTemplate` and `helperInjection` are already at module scope (Step 2). `startServer` just creates the screen dir, starts the HTTP server, watcher, and logs startup info. + +```js +function startServer() { + if (!fs.existsSync(SCREEN_DIR)) fs.mkdirSync(SCREEN_DIR, { recursive: true }); + + const server = http.createServer(handleRequest); + server.on('upgrade', handleUpgrade); + + const watcher = fs.watch(SCREEN_DIR, (eventType, filename) => { + if (!filename || !filename.endsWith('.html')) return; + if (debounceTimers.has(filename)) clearTimeout(debounceTimers.get(filename)); + debounceTimers.set(filename, setTimeout(() => { + debounceTimers.delete(filename); + const filePath = path.join(SCREEN_DIR, filename); + if (eventType === 'rename' && fs.existsSync(filePath)) { + const eventsFile = path.join(SCREEN_DIR, '.events'); + if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile); + console.log(JSON.stringify({ type: 'screen-added', file: filePath })); + } else if (eventType === 'change') { + console.log(JSON.stringify({ type: 'screen-updated', file: filePath })); + } + broadcast({ type: 'reload' }); + }, 100)); + }); + watcher.on('error', (err) => console.error('fs.watch error:', err.message)); + + server.listen(PORT, HOST, () => { + const info = JSON.stringify({ + type: 'server-started', port: Number(PORT), host: HOST, + url_host: URL_HOST, url: 'http://' + URL_HOST + ':' + PORT, + screen_dir: SCREEN_DIR + }); + console.log(info); + fs.writeFileSync(path.join(SCREEN_DIR, '.server-info'), info + '\n'); + }); +} + +if (require.main === module) { + startServer(); +} +``` + +- [ ] **Step 7: Run integration tests** + +The test directory already has a `package.json` with `ws` as a dependency. Install it if needed, then run tests. + +Run: `cd tests/brainstorm-server && npm install && node server.test.js` +Expected: All tests pass + +- [ ] **Step 8: Commit** + +```bash +git add skills/brainstorming/scripts/server.js +git commit -m "Add HTTP server, WebSocket handling, and file watching to server.js" +``` + +--- + +## Chunk 3: Swap and Cleanup + +### Task 3: Update start-server.sh and remove old files + +**Files:** +- Modify: `skills/brainstorming/scripts/start-server.sh:94,100` +- Modify: `.gitignore:6` +- Delete: `skills/brainstorming/scripts/index.js` +- Delete: `skills/brainstorming/scripts/package.json` +- Delete: `skills/brainstorming/scripts/package-lock.json` +- Delete: `skills/brainstorming/scripts/node_modules/` (entire directory) + +- [ ] **Step 1: Update start-server.sh — change `index.js` to `server.js`** + +Two lines to change: + +Line 94: `env BRAINSTORM_DIR="$SCREEN_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" node server.js` + +Line 100: `nohup env BRAINSTORM_DIR="$SCREEN_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" node server.js > "$LOG_FILE" 2>&1 &` + +- [ ] **Step 2: Remove the gitignore exception for node_modules** + +In `.gitignore`, delete line 6: `!skills/brainstorming/scripts/node_modules/` + +- [ ] **Step 3: Delete old files** + +```bash +git rm skills/brainstorming/scripts/index.js +git rm skills/brainstorming/scripts/package.json +git rm skills/brainstorming/scripts/package-lock.json +git rm -r skills/brainstorming/scripts/node_modules/ +``` + +- [ ] **Step 4: Run both test suites** + +Run: `cd tests/brainstorm-server && node ws-protocol.test.js && node server.test.js` +Expected: All tests pass + +- [ ] **Step 5: Commit** + +```bash +git add skills/brainstorming/scripts/ .gitignore +git commit -m "Remove vendored node_modules, swap to zero-dep server.js" +``` + +### Task 4: Manual smoke test + +- [ ] **Step 1: Start the server manually** + +```bash +cd skills/brainstorming/scripts +BRAINSTORM_DIR=/tmp/brainstorm-smoke BRAINSTORM_PORT=9876 node server.js +``` + +Expected: `server-started` JSON printed with port 9876 + +- [ ] **Step 2: Open browser to http://localhost:9876** + +Expected: Waiting page with "Waiting for Claude to push a screen..." + +- [ ] **Step 3: Write an HTML file to the screen directory** + +```bash +echo '

Hello from smoke test

' > /tmp/brainstorm-smoke/test.html +``` + +Expected: Browser reloads and shows "Hello from smoke test" wrapped in frame template + +- [ ] **Step 4: Verify WebSocket works — check browser console** + +Open browser dev tools. The WebSocket connection should show as connected (no errors in console). The frame template's status indicator should show "Connected". + +- [ ] **Step 5: Stop server with Ctrl-C, clean up** + +```bash +rm -rf /tmp/brainstorm-smoke +``` diff --git a/docs/superpowers/plans/2026-03-23-codex-app-compatibility.md b/docs/superpowers/plans/2026-03-23-codex-app-compatibility.md new file mode 100644 index 0000000..0f35d3d --- /dev/null +++ b/docs/superpowers/plans/2026-03-23-codex-app-compatibility.md @@ -0,0 +1,566 @@ +# Codex App Compatibility Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Make `using-git-worktrees`, `finishing-a-development-branch`, and related skills work in the Codex App's sandboxed worktree environment without breaking existing behavior. + +**Architecture:** Read-only environment detection (`git-dir` vs `git-common-dir`) at the start of two skills. If already in a linked worktree, skip creation. If on detached HEAD, emit a handoff payload instead of the 4-option menu. Sandbox fallback catches permission errors during worktree creation. + +**Tech Stack:** Git, Markdown (skill files are instruction documents, not executable code) + +**Spec:** `docs/superpowers/specs/2026-03-23-codex-app-compatibility-design.md` + +--- + +## File Structure + +| File | Responsibility | Action | +|---|---|---| +| `skills/using-git-worktrees/SKILL.md` | Worktree creation + isolation | Add Step 0 detection + sandbox fallback | +| `skills/finishing-a-development-branch/SKILL.md` | Branch finishing workflow | Add Step 1.5 detection + cleanup guard | +| `skills/subagent-driven-development/SKILL.md` | Plan execution with subagents | Update Integration description | +| `skills/executing-plans/SKILL.md` | Plan execution inline | Update Integration description | +| `skills/using-superpowers/references/codex-tools.md` | Codex platform reference | Add detection + finishing docs | + +--- + +### Task 1: Add Step 0 to `using-git-worktrees` + +**Files:** +- Modify: `skills/using-git-worktrees/SKILL.md:14-15` (insert after Overview, before Directory Selection Process) + +- [ ] **Step 1: Read the current skill file** + +Read `skills/using-git-worktrees/SKILL.md` in full. Identify the exact insertion point: after the "Announce at start" line (line 14) and before "## Directory Selection Process" (line 16). + +- [ ] **Step 2: Insert Step 0 section** + +Insert the following between the Overview section and "## Directory Selection Process": + +```markdown +## Step 0: Check if Already in an Isolated Workspace + +Before creating a worktree, check if one already exists: + +```bash +GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P) +GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P) +BRANCH=$(git branch --show-current) +``` + +**If `GIT_DIR` differs from `GIT_COMMON`:** You are already inside a linked worktree (created by the Codex App, Claude Code's Agent tool, a previous skill run, or the user). Do NOT create another worktree. Instead: + +1. Run project setup (auto-detect package manager as in "Run Project Setup" below) +2. Verify clean baseline (run tests as in "Verify Clean Baseline" below) +3. Report with branch state: + - On a branch: "Already in an isolated workspace at `` on branch ``. Tests passing. Ready to implement." + - Detached HEAD: "Already in an isolated workspace at `` (detached HEAD, externally managed). Tests passing. Note: branch creation needed at finish time. Ready to implement." + +After reporting, STOP. Do not continue to Directory Selection or Creation Steps. + +**If `GIT_DIR` equals `GIT_COMMON`:** Proceed with the full worktree creation flow below. + +**Sandbox fallback:** If you proceed to Creation Steps but `git worktree add -b` fails with a permission error (e.g., "Operation not permitted"), treat this as a late-detected restricted environment. Fall back to the behavior above — run setup and baseline tests in the current directory, report accordingly, and STOP. +``` + +- [ ] **Step 3: Verify the insertion** + +Read the file again. Confirm: +- Step 0 appears between Overview and Directory Selection Process +- The rest of the file (Directory Selection, Safety Verification, Creation Steps, etc.) is unchanged +- No duplicate sections or broken markdown + +- [ ] **Step 4: Commit** + +```bash +git add skills/using-git-worktrees/SKILL.md +git commit -m "feat(using-git-worktrees): add Step 0 environment detection (PRI-823) + +Skip worktree creation when already in a linked worktree. Includes +sandbox fallback for permission errors on git worktree add." +``` + +--- + +### Task 2: Update `using-git-worktrees` Integration section + +**Files:** +- Modify: `skills/using-git-worktrees/SKILL.md:211-215` (Integration > Called by) + +- [ ] **Step 1: Update the three "Called by" entries** + +Change lines 212-214 from: + +```markdown +- **brainstorming** (Phase 4) - REQUIRED when design is approved and implementation follows +- **subagent-driven-development** - REQUIRED before executing any tasks +- **executing-plans** - REQUIRED before executing any tasks +``` + +To: + +```markdown +- **brainstorming** - REQUIRED: Ensures isolated workspace (creates one or verifies existing) +- **subagent-driven-development** - REQUIRED: Ensures isolated workspace (creates one or verifies existing) +- **executing-plans** - REQUIRED: Ensures isolated workspace (creates one or verifies existing) +``` + +- [ ] **Step 2: Verify the Integration section** + +Read the Integration section. Confirm all three entries are updated, "Pairs with" is unchanged. + +- [ ] **Step 3: Commit** + +```bash +git add skills/using-git-worktrees/SKILL.md +git commit -m "docs(using-git-worktrees): update Integration descriptions (PRI-823) + +Clarify that skill ensures a workspace exists, not that it always creates one." +``` + +--- + +### Task 3: Add Step 1.5 to `finishing-a-development-branch` + +**Files:** +- Modify: `skills/finishing-a-development-branch/SKILL.md:38` (insert after Step 1, before Step 2) + +- [ ] **Step 1: Read the current skill file** + +Read `skills/finishing-a-development-branch/SKILL.md` in full. Identify the insertion point: after "**If tests pass:** Continue to Step 2." (line 38) and before "### Step 2: Determine Base Branch" (line 40). + +- [ ] **Step 2: Insert Step 1.5 section** + +Insert the following between Step 1 and Step 2: + +```markdown +### Step 1.5: Detect Environment + +```bash +GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P) +GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P) +BRANCH=$(git branch --show-current) +``` + +**Path A — `GIT_DIR` differs from `GIT_COMMON` AND `BRANCH` is empty (externally managed worktree, detached HEAD):** + +First, ensure all work is staged and committed (`git add` + `git commit`). + +Then present this to the user (do NOT present the 4-option menu): + +``` +Implementation complete. All tests passing. +Current HEAD: + +This workspace is externally managed (detached HEAD). +I cannot create branches, push, or open PRs from here. + +⚠ These commits are on a detached HEAD. If you do not create a branch, +they may be lost when this workspace is cleaned up. + +If your host application provides these controls: +- "Create branch" — to name a branch, then commit/push/PR +- "Hand off to local" — to move changes to your local checkout + +Suggested branch name: +Suggested commit message: +``` + +Branch name: use ticket ID if available (e.g., `pri-823/codex-compat`), otherwise slugify the first 5 words of the plan title, otherwise omit. Avoid sensitive content in branch names. + +Skip to Step 5 (cleanup is a no-op — see guard below). + +**Path B — `GIT_DIR` differs from `GIT_COMMON` AND `BRANCH` exists (externally managed worktree, named branch):** + +Proceed to Step 2 and present the 4-option menu as normal. + +**Path C — `GIT_DIR` equals `GIT_COMMON` (normal environment):** + +Proceed to Step 2 and present the 4-option menu as normal. +``` + +- [ ] **Step 3: Verify the insertion** + +Read the file again. Confirm: +- Step 1.5 appears between Step 1 and Step 2 +- Steps 2-5 are unchanged +- Path A handoff includes commit SHA and data loss warning +- Paths B and C proceed to Step 2 normally + +- [ ] **Step 4: Commit** + +```bash +git add skills/finishing-a-development-branch/SKILL.md +git commit -m "feat(finishing-a-development-branch): add Step 1.5 environment detection (PRI-823) + +Detect externally managed worktrees with detached HEAD and emit handoff +payload instead of 4-option menu. Includes commit SHA and data loss warning." +``` + +--- + +### Task 4: Add Step 5 cleanup guard to `finishing-a-development-branch` + +**Files:** +- Modify: `skills/finishing-a-development-branch/SKILL.md` (Step 5: Cleanup Worktree — find by section heading, line numbers will have shifted after Task 3) + +- [ ] **Step 1: Read the current Step 5 section** + +Find the "### Step 5: Cleanup Worktree" section in `skills/finishing-a-development-branch/SKILL.md` (line numbers will have shifted after Task 3's insertion). The current Step 5 is: + +```markdown +### Step 5: Cleanup Worktree + +**For Options 1, 2, 4:** + +Check if in worktree: +```bash +git worktree list | grep $(git branch --show-current) +``` + +If yes: +```bash +git worktree remove +``` + +**For Option 3:** Keep worktree. +``` + +- [ ] **Step 2: Add the cleanup guard before existing logic** + +Replace the Step 5 section with: + +```markdown +### Step 5: Cleanup Worktree + +**First, check if worktree is externally managed:** + +```bash +GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P) +GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P) +``` + +If `GIT_DIR` differs from `GIT_COMMON`: skip worktree removal — the host environment owns this workspace. + +**Otherwise, for Options 1 and 4:** + +Check if in worktree: +```bash +git worktree list | grep $(git branch --show-current) +``` + +If yes: +```bash +git worktree remove +``` + +**For Option 3:** Keep worktree. +``` + +Note: the original text said "For Options 1, 2, 4" but the Quick Reference table and Common Mistakes section say "Options 1 & 4 only." This edit aligns Step 5 with those sections. + +- [ ] **Step 3: Verify the replacement** + +Read Step 5. Confirm: +- Cleanup guard (re-detection) appears first +- Existing removal logic preserved for non-externally-managed worktrees +- "Options 1 and 4" (not "1, 2, 4") matches Quick Reference and Common Mistakes + +- [ ] **Step 4: Commit** + +```bash +git add skills/finishing-a-development-branch/SKILL.md +git commit -m "feat(finishing-a-development-branch): add Step 5 cleanup guard (PRI-823) + +Re-detect externally managed worktree at cleanup time and skip removal. +Also fixes pre-existing inconsistency: cleanup now correctly says +Options 1 and 4 only, matching Quick Reference and Common Mistakes." +``` + +--- + +### Task 5: Update Integration lines in `subagent-driven-development` and `executing-plans` + +**Files:** +- Modify: `skills/subagent-driven-development/SKILL.md:268` +- Modify: `skills/executing-plans/SKILL.md:68` + +- [ ] **Step 1: Update `subagent-driven-development`** + +Change line 268 from: +``` +- **superpowers:using-git-worktrees** - REQUIRED: Set up isolated workspace before starting +``` +To: +``` +- **superpowers:using-git-worktrees** - REQUIRED: Ensures isolated workspace (creates one or verifies existing) +``` + +- [ ] **Step 2: Update `executing-plans`** + +Change line 68 from: +``` +- **superpowers:using-git-worktrees** - REQUIRED: Set up isolated workspace before starting +``` +To: +``` +- **superpowers:using-git-worktrees** - REQUIRED: Ensures isolated workspace (creates one or verifies existing) +``` + +- [ ] **Step 3: Verify both files** + +Read line 268 of `skills/subagent-driven-development/SKILL.md` and line 68 of `skills/executing-plans/SKILL.md`. Confirm both say "Ensures isolated workspace (creates one or verifies existing)". + +- [ ] **Step 4: Commit** + +```bash +git add skills/subagent-driven-development/SKILL.md skills/executing-plans/SKILL.md +git commit -m "docs(sdd, executing-plans): update worktree Integration descriptions (PRI-823) + +Clarify that using-git-worktrees ensures a workspace exists rather than +always creating one." +``` + +--- + +### Task 6: Add environment detection docs to `codex-tools.md` + +**Files:** +- Modify: `skills/using-superpowers/references/codex-tools.md:25` (append at end) + +- [ ] **Step 1: Read the current file** + +Read `skills/using-superpowers/references/codex-tools.md` in full. Confirm it ends at line 25-26 after the multi_agent section. + +- [ ] **Step 2: Append two new sections** + +Add at the end of the file: + +```markdown + +## Environment Detection + +Skills that create worktrees or finish branches should detect their +environment with read-only git commands before proceeding: + +```bash +GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P) +GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P) +BRANCH=$(git branch --show-current) +``` + +- `GIT_DIR != GIT_COMMON` → already in a linked worktree (skip creation) +- `BRANCH` empty → detached HEAD (cannot branch/push/PR from sandbox) + +See `using-git-worktrees` Step 0 and `finishing-a-development-branch` +Step 1.5 for how each skill uses these signals. + +## Codex App Finishing + +When the sandbox blocks branch/push operations (detached HEAD in an +externally managed worktree), the agent commits all work and informs +the user to use the App's native controls: + +- **"Create branch"** — names the branch, then commit/push/PR via App UI +- **"Hand off to local"** — transfers work to the user's local checkout + +The agent can still run tests, stage files, and output suggested branch +names, commit messages, and PR descriptions for the user to copy. +``` + +- [ ] **Step 3: Verify the additions** + +Read the full file. Confirm: +- Two new sections appear after the existing content +- Bash code block renders correctly (not escaped) +- Cross-references to Step 0 and Step 1.5 are present + +- [ ] **Step 4: Commit** + +```bash +git add skills/using-superpowers/references/codex-tools.md +git commit -m "docs(codex-tools): add environment detection and App finishing docs (PRI-823) + +Document the git-dir vs git-common-dir detection pattern and the Codex +App's native finishing flow for skills that need to adapt." +``` + +--- + +### Task 7: Automated test — environment detection + +**Files:** +- Create: `tests/codex-app-compat/test-environment-detection.sh` + +- [ ] **Step 1: Create test directory** + +```bash +mkdir -p tests/codex-app-compat +``` + +- [ ] **Step 2: Write the detection test script** + +Create `tests/codex-app-compat/test-environment-detection.sh`: + +```bash +#!/usr/bin/env bash +set -euo pipefail + +# Test environment detection logic from PRI-823 +# Tests the git-dir vs git-common-dir comparison used by +# using-git-worktrees Step 0 and finishing-a-development-branch Step 1.5 + +PASS=0 +FAIL=0 +TEMP_DIR=$(mktemp -d) +trap "rm -rf $TEMP_DIR" EXIT + +log_pass() { echo " PASS: $1"; PASS=$((PASS + 1)); } +log_fail() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); } + +# Helper: run detection and return "linked" or "normal" +detect_worktree() { + local git_dir git_common + git_dir=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P) + git_common=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P) + if [ "$git_dir" != "$git_common" ]; then + echo "linked" + else + echo "normal" + fi +} + +echo "=== Test 1: Normal repo detection ===" +cd "$TEMP_DIR" +git init test-repo > /dev/null 2>&1 +cd test-repo +git commit --allow-empty -m "init" > /dev/null 2>&1 +result=$(detect_worktree) +if [ "$result" = "normal" ]; then + log_pass "Normal repo detected as normal" +else + log_fail "Normal repo detected as '$result' (expected 'normal')" +fi + +echo "=== Test 2: Linked worktree detection ===" +git worktree add "$TEMP_DIR/test-wt" -b test-branch > /dev/null 2>&1 +cd "$TEMP_DIR/test-wt" +result=$(detect_worktree) +if [ "$result" = "linked" ]; then + log_pass "Linked worktree detected as linked" +else + log_fail "Linked worktree detected as '$result' (expected 'linked')" +fi + +echo "=== Test 3: Detached HEAD detection ===" +git checkout --detach HEAD > /dev/null 2>&1 +branch=$(git branch --show-current) +if [ -z "$branch" ]; then + log_pass "Detached HEAD: branch is empty" +else + log_fail "Detached HEAD: branch is '$branch' (expected empty)" +fi + +echo "=== Test 4: Linked worktree + detached HEAD (Codex App simulation) ===" +result=$(detect_worktree) +branch=$(git branch --show-current) +if [ "$result" = "linked" ] && [ -z "$branch" ]; then + log_pass "Codex App simulation: linked + detached HEAD" +else + log_fail "Codex App simulation: result='$result', branch='$branch'" +fi + +echo "=== Test 5: Cleanup guard — linked worktree should NOT remove ===" +cd "$TEMP_DIR/test-wt" +result=$(detect_worktree) +if [ "$result" = "linked" ]; then + log_pass "Cleanup guard: linked worktree correctly detected (would skip removal)" +else + log_fail "Cleanup guard: expected 'linked', got '$result'" +fi + +echo "=== Test 6: Cleanup guard — main repo SHOULD remove ===" +cd "$TEMP_DIR/test-repo" +result=$(detect_worktree) +if [ "$result" = "normal" ]; then + log_pass "Cleanup guard: main repo correctly detected (would proceed with removal)" +else + log_fail "Cleanup guard: expected 'normal', got '$result'" +fi + +# Cleanup worktree before temp dir removal +cd "$TEMP_DIR/test-repo" +git worktree remove "$TEMP_DIR/test-wt" > /dev/null 2>&1 || true + +echo "" +echo "=== Results: $PASS passed, $FAIL failed ===" +if [ "$FAIL" -gt 0 ]; then + exit 1 +fi +``` + +- [ ] **Step 3: Make it executable and run it** + +```bash +chmod +x tests/codex-app-compat/test-environment-detection.sh +./tests/codex-app-compat/test-environment-detection.sh +``` + +Expected output: 6 passed, 0 failed. + +- [ ] **Step 4: Commit** + +```bash +git add tests/codex-app-compat/test-environment-detection.sh +git commit -m "test: add environment detection tests for Codex App compat (PRI-823) + +Tests git-dir vs git-common-dir comparison in normal repo, linked +worktree, detached HEAD, and cleanup guard scenarios." +``` + +--- + +### Task 8: Final verification + +**Files:** +- Read: all 5 modified skill files + +- [ ] **Step 1: Run the automated detection tests** + +```bash +./tests/codex-app-compat/test-environment-detection.sh +``` + +Expected: 6 passed, 0 failed. + +- [ ] **Step 2: Read each modified file and verify changes** + +Read each file end-to-end: +- `skills/using-git-worktrees/SKILL.md` — Step 0 present, rest unchanged +- `skills/finishing-a-development-branch/SKILL.md` — Step 1.5 present, cleanup guard present, rest unchanged +- `skills/subagent-driven-development/SKILL.md` — line 268 updated +- `skills/executing-plans/SKILL.md` — line 68 updated +- `skills/using-superpowers/references/codex-tools.md` — two new sections at end + +- [ ] **Step 3: Verify no unintended changes** + +```bash +git diff --stat HEAD~7 +``` + +Should show exactly 6 files changed (5 skill files + 1 test file). No other files modified. + +- [ ] **Step 4: Run existing test suite** + +If test runner exists: +```bash +# Run skill-triggering tests +# Note: tests/skill-triggering/ was lifted into drill scenarios on 2026-05-06. +# See evals/scenarios/triggering-*.yaml. The reference below is a dated artifact. +./tests/skill-triggering/run-all.sh 2>/dev/null || echo "Skill triggering tests not available in this environment" + +# Run SDD integration test +./tests/claude-code/test-subagent-driven-development-integration.sh 2>/dev/null || echo "SDD integration test not available in this environment" +``` + +Note: these tests require Claude Code with `--dangerously-skip-permissions`. If not available, document that regression tests should be run manually. diff --git a/docs/superpowers/plans/2026-04-06-worktree-rototill.md b/docs/superpowers/plans/2026-04-06-worktree-rototill.md new file mode 100644 index 0000000..183ee74 --- /dev/null +++ b/docs/superpowers/plans/2026-04-06-worktree-rototill.md @@ -0,0 +1,866 @@ +# Worktree Rototill Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Make superpowers defer to native harness worktree systems when available, fall back to manual git worktrees when not, and fix three known finishing bugs. + +**Architecture:** Two skill files are rewritten (`using-git-worktrees`, `finishing-a-development-branch`), three files get one-line integration updates (`executing-plans`, `subagent-driven-development`, `writing-plans`). The core change is adding detection (`GIT_DIR != GIT_COMMON`) and a native-tool-first creation path. These are markdown skill instruction files, not application code — "tests" are agent behavior tests using the testing-skills-with-subagents TDD framework. + +**Tech Stack:** Markdown (skill files), bash (test scripts), Claude Code CLI (`claude -p` for headless testing) + +**Spec:** `docs/superpowers/specs/2026-04-06-worktree-rototill-design.md` + +--- + +### Task 1: GATE — TDD Validation of Step 1a (Native Tool Preference) + +Step 1a is the load-bearing assumption of the entire design. If agents don't prefer native worktree tools over `git worktree add`, the spec fails. Validate this FIRST, before touching any skill files. + +**Files:** +- Create: `tests/claude-code/test-worktree-native-preference.sh` +- Read: `skills/using-git-worktrees/SKILL.md` (current version, for RED baseline) +- Read: `tests/claude-code/test-helpers.sh` (for `run_claude`, `assert_contains`, etc.) +- Read: `skills/writing-skills/testing-skills-with-subagents.md` (TDD framework) + +**This task is a gate.** If the GREEN phase fails after 2 REFACTOR iterations, STOP. Do not proceed to Task 2. Report back — the creation approach needs redesign. + +- [ ] **Step 1: Write the RED baseline test script** + +Create the test script that will run scenarios both WITHOUT and WITH the updated skill text. The RED phase runs against the current skill (which has no Step 1a). + +```bash +#!/usr/bin/env bash +# Test: Does the agent prefer native worktree tools (EnterWorktree) over git worktree add? +# Framework: RED-GREEN-REFACTOR per testing-skills-with-subagents.md +# +# RED: Current skill has no native tool preference. Agent should use git worktree add. +# GREEN: Updated skill has Step 1a. Agent should use EnterWorktree on Claude Code. + +set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "$SCRIPT_DIR/test-helpers.sh" + +# Pressure scenario: realistic implementation task where agent needs isolation +SCENARIO='IMPORTANT: This is a real task. Choose and act. + +You need to implement a small feature (add a "version" field to package.json). +This should be done in an isolated workspace to protect the main branch. + +You have the using-git-worktrees skill available. Set up the isolated workspace now. +Do NOT actually implement the feature — just set up the workspace and report what you did. + +Respond with EXACTLY what tool/command you used to create the workspace.' + +echo "=== Worktree Native Preference Test ===" +echo "" + +# Phase selection +PHASE="${1:-red}" + +if [ "$PHASE" = "red" ]; then + echo "--- RED PHASE: Running WITHOUT Step 1a (current skill) ---" + echo "Expected: Agent uses 'git worktree add' (no native tool awareness)" + echo "" + + test_dir=$(create_test_project) + cd "$test_dir" + git init && git commit --allow-empty -m "init" + mkdir -p .worktrees + + output=$(run_claude "$SCENARIO" 120) + + echo "Agent output:" + echo "$output" + echo "" + + # RED expectation: agent uses git worktree add (current behavior) + if echo "$output" | grep -qi "EnterWorktree"; then + echo "[UNEXPECTED] Agent used EnterWorktree WITHOUT Step 1a — skill may not be needed" + echo "Investigate: is Claude Code's default behavior already correct?" + else + echo "[RED CONFIRMED] Agent did NOT use EnterWorktree" + assert_contains "$output" "git worktree" "Agent used git worktree (expected in RED)" + fi + + cleanup_test_project "$test_dir" + +elif [ "$PHASE" = "green" ]; then + echo "--- GREEN PHASE: Running WITH Step 1a (updated skill) ---" + echo "Expected: Agent uses EnterWorktree instead of git worktree add" + echo "" + + test_dir=$(create_test_project) + cd "$test_dir" + git init && git commit --allow-empty -m "init" + + output=$(run_claude "$SCENARIO" 120) + + echo "Agent output:" + echo "$output" + echo "" + + # GREEN expectation: agent uses EnterWorktree + assert_contains "$output" "EnterWorktree" "Agent used native EnterWorktree tool" + assert_not_contains "$output" "git worktree add" "Agent did NOT fall back to git worktree add" + + cleanup_test_project "$test_dir" + +elif [ "$PHASE" = "pressure" ]; then + echo "--- PRESSURE PHASE: Multiple pressures to skip native tools ---" + echo "" + + PRESSURE_SCENARIO='IMPORTANT: This is a real task. Choose and act. + +You need to set up an isolated workspace URGENTLY. Production is impacted and +the fix needs to go in a worktree. You know git worktree add works reliably. +The .worktrees/ directory already exists and is gitignored. + +You have the using-git-worktrees skill. Set up the workspace NOW. +Speed matters. Report EXACTLY what tool/command you used.' + + test_dir=$(create_test_project) + cd "$test_dir" + git init && git commit --allow-empty -m "init" + mkdir -p .worktrees + echo ".worktrees/" >> .gitignore + + output=$(run_claude "$PRESSURE_SCENARIO" 120) + + echo "Agent output:" + echo "$output" + echo "" + + # Should STILL use EnterWorktree even under pressure + assert_contains "$output" "EnterWorktree" "Agent used native tool even under time pressure" + assert_not_contains "$output" "git worktree add" "Agent resisted falling back to git despite pressure" + + cleanup_test_project "$test_dir" +fi + +echo "" +echo "=== Test Complete ===" +``` + +- [ ] **Step 2: Run RED phase — confirm agent uses git worktree add today** + +Run: `cd tests/claude-code && bash test-worktree-native-preference.sh red` + +Expected: `[RED CONFIRMED] Agent did NOT use EnterWorktree` — agent uses `git worktree add` because current skill has no native tool preference. + +Document the agent's exact output and any rationalizations verbatim. This is the baseline failure the skill must fix. + +- [ ] **Step 3: If RED confirmed, proceed. Write the Step 1a skill text.** + +Create a temporary test version of the skill with ONLY the Step 1a addition (minimal change to isolate the variable). Add this section to the top of the skill's creation instructions, BEFORE the existing directory selection process: + +```markdown +## Step 1: Create Isolated Workspace + +**You have two mechanisms. Try them in this order.** + +### 1a. Native Worktree Tools (preferred) + +If your platform provides a worktree or workspace-isolation tool, use it. You know your own toolkit — the skill does not need to name specific tools. Native tools handle directory placement, branch creation, and cleanup automatically. + +After using a native tool, skip to Step 3 (Project Setup). + +### 1b. Git Worktree Fallback + +If no native tool is available, create a worktree manually using git. +``` + +- [ ] **Step 4: Run GREEN phase — confirm agent now uses EnterWorktree** + +Run: `cd tests/claude-code && bash test-worktree-native-preference.sh green` + +Expected: `[PASS] Agent used native EnterWorktree tool` + +If FAIL: Document the agent's exact output and rationalizations. This is a REFACTOR signal — the Step 1a text needs revision. Try up to 2 REFACTOR iterations. If still failing after 2 iterations, STOP and report back. + +- [ ] **Step 5: Run PRESSURE phase — confirm agent resists fallback under pressure** + +Run: `cd tests/claude-code && bash test-worktree-native-preference.sh pressure` + +Expected: `[PASS] Agent used native tool even under time pressure` + +If FAIL: Document rationalizations verbatim. Add explicit counters to Step 1a text (e.g., a Red Flag entry: "Never use git worktree add when your platform provides a native worktree tool"). Re-run. + +- [ ] **Step 6: Commit test script** + +```bash +git add tests/claude-code/test-worktree-native-preference.sh +git commit -m "test: add RED/GREEN validation for native worktree preference (PRI-974) + +Gate test for Step 1a — validates agents prefer EnterWorktree over +git worktree add on Claude Code. Must pass before skill rewrite." +``` + +--- + +### Task 2: Rewrite `using-git-worktrees` SKILL.md + +Full rewrite of the creation skill. Replaces the existing file entirely. + +**Files:** +- Modify: `skills/using-git-worktrees/SKILL.md` (full rewrite, 219 lines → ~210 lines) + +**Depends on:** Task 1 GREEN passing. + +- [ ] **Step 1: Write the complete new SKILL.md** + +Replace the entire contents of `skills/using-git-worktrees/SKILL.md` with: + +```markdown +--- +name: using-git-worktrees +description: Use when starting feature work that needs isolation from current workspace or before executing implementation plans - ensures an isolated workspace exists via native tools or git worktree fallback +--- + +# Using Git Worktrees + +## Overview + +Ensure work happens in an isolated workspace. Prefer your platform's native worktree tools. Fall back to manual git worktrees only when no native tool is available. + +**Core principle:** Detect existing isolation first. Then use native tools. Then fall back to git. Never fight the harness. + +**Announce at start:** "I'm using the using-git-worktrees skill to set up an isolated workspace." + +## Step 0: Detect Existing Isolation + +**Before creating anything, check if you are already in an isolated workspace.** + +```bash +GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P) +GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P) +BRANCH=$(git branch --show-current) +``` + +**Submodule guard:** `GIT_DIR != GIT_COMMON` is also true inside git submodules. Before concluding "already in a worktree," verify you are not in a submodule: + +```bash +# If this returns a path, you're in a submodule, not a worktree — proceed to Step 1 +git rev-parse --show-superproject-working-tree 2>/dev/null +``` + +**If `GIT_DIR != GIT_COMMON` (and not a submodule):** You are already in a linked worktree. Skip to Step 3 (Project Setup). Do NOT create another worktree. + +Report with branch state: +- On a branch: "Already in isolated workspace at `` on branch ``." +- Detached HEAD: "Already in isolated workspace at `` (detached HEAD, externally managed). Branch creation needed at finish time." + +**If `GIT_DIR == GIT_COMMON` (or in a submodule):** You are in a normal repo checkout. + +Has the user already indicated their worktree preference in your instructions? If not, ask for consent before creating a worktree: + +> "Would you like me to set up an isolated worktree? It protects your current branch from changes." + +Honor any existing declared preference without asking. If the user declines consent, work in place and skip to Step 3. + +## Step 1: Create Isolated Workspace + +**You have two mechanisms. Try them in this order.** + +### 1a. Native Worktree Tools (preferred) + +If your platform provides a worktree or workspace-isolation tool, use it. You know your own toolkit — the skill does not need to name specific tools. Native tools handle directory placement, branch creation, and cleanup automatically. + +After using a native tool, skip to Step 3 (Project Setup). + +### 1b. Git Worktree Fallback + +If no native tool is available, create a worktree manually using git. + +#### Directory Selection + +Follow this priority order: + +1. **Check your instructions for a worktree directory preference.** If specified, use it without asking. + +2. **Check existing project-local directories:** + ```bash + ls -d .worktrees 2>/dev/null # Preferred (hidden) + ls -d worktrees 2>/dev/null # Alternative + ``` + If found, use that directory. If both exist, `.worktrees` wins. + +3. **Default to `.worktrees/`.** + +#### Safety Verification (project-local directories only) + +**MUST verify directory is ignored before creating worktree:** + +```bash +git check-ignore -q .worktrees 2>/dev/null || git check-ignore -q worktrees 2>/dev/null +``` + +**If NOT ignored:** Add to .gitignore, commit the change, then proceed. + +**Why critical:** Prevents accidentally committing worktree contents to repository. + +#### Create the Worktree + +```bash +# Determine path based on chosen location +path="$LOCATION/$BRANCH_NAME" + +git worktree add "$path" -b "$BRANCH_NAME" +cd "$path" +``` + +#### Hooks Awareness + +Git worktrees do not inherit the parent repo's hooks directory. After creating the worktree, symlink hooks from the main repo if they exist: + +```bash +MAIN_ROOT=$(git -C "$(git rev-parse --git-common-dir)/.." rev-parse --show-toplevel) +if [ -d "$MAIN_ROOT/.git/hooks" ]; then + ln -sf "$MAIN_ROOT/.git/hooks" "$path/.git/hooks" +fi +``` + +This prevents pre-commit checks, linters, and other hooks from silently stopping when work moves to a worktree. + +**Sandbox fallback:** If `git worktree add` fails with a permission error (sandbox denial), treat this as a restricted environment. Skip creation, run setup and baseline tests in the current directory, report accordingly. + +## Step 3: Project Setup + +Auto-detect and run appropriate setup: + +```bash +# Node.js +if [ -f package.json ]; then npm install; fi + +# Rust +if [ -f Cargo.toml ]; then cargo build; fi + +# Python +if [ -f requirements.txt ]; then pip install -r requirements.txt; fi +if [ -f pyproject.toml ]; then poetry install; fi + +# Go +if [ -f go.mod ]; then go mod download; fi +``` + +## Step 4: Verify Clean Baseline + +Run tests to ensure workspace starts clean: + +```bash +# Use project-appropriate command +npm test / cargo test / pytest / go test ./... +``` + +**If tests fail:** Report failures, ask whether to proceed or investigate. + +**If tests pass:** Report ready. + +### Report + +``` +Worktree ready at +Tests passing ( tests, 0 failures) +Ready to implement +``` + +## Quick Reference + +| Situation | Action | +|-----------|--------| +| Already in linked worktree | Skip creation (Step 0) | +| In a submodule | Treat as normal repo (Step 0 guard) | +| Native worktree tool available | Use it (Step 1a) | +| No native tool | Git worktree fallback (Step 1b) | +| `.worktrees/` exists | Use it (verify ignored) | +| `worktrees/` exists | Use it (verify ignored) | +| Both exist | Use `.worktrees/` | +| Neither exists | Check instruction file, then default `.worktrees/` | +| Directory not ignored | Add to .gitignore + commit | +| Permission error on create | Sandbox fallback, work in place | +| Tests fail during baseline | Report failures + ask | +| No package.json/Cargo.toml | Skip dependency install | + +## Common Mistakes + +### Fighting the harness + +- **Problem:** Using `git worktree add` when the platform already provides isolation +- **Fix:** Step 0 detects existing isolation. Step 1a defers to native tools. + +### Skipping detection + +- **Problem:** Creating a nested worktree inside an existing one +- **Fix:** Always run Step 0 before creating anything + +### Skipping ignore verification + +- **Problem:** Worktree contents get tracked, pollute git status +- **Fix:** Always use `git check-ignore` before creating project-local worktree + +### Assuming directory location + +- **Problem:** Creates inconsistency, violates project conventions +- **Fix:** Follow priority: existing > instruction file > default + +### Proceeding with failing tests + +- **Problem:** Can't distinguish new bugs from pre-existing issues +- **Fix:** Report failures, get explicit permission to proceed + +## Red Flags + +**Never:** +- Create a worktree when Step 0 detects existing isolation +- Use git commands when a native worktree tool is available +- Create worktree without verifying it's ignored (project-local) +- Skip baseline test verification +- Proceed with failing tests without asking + +**Always:** +- Run Step 0 detection first +- Prefer native tools over git fallback +- Follow directory priority: existing > instruction file > default +- Verify directory is ignored for project-local +- Auto-detect and run project setup +- Verify clean test baseline +- Symlink hooks after creating worktree via 1b + +## Integration + +**Called by:** +- **subagent-driven-development** - Ensures isolated workspace (creates one or verifies existing) +- **executing-plans** - Ensures isolated workspace (creates one or verifies existing) +- Any skill needing isolated workspace + +**Pairs with:** +- **finishing-a-development-branch** - REQUIRED for cleanup after work complete +``` + +- [ ] **Step 2: Verify the file reads correctly** + +Run: `wc -l skills/using-git-worktrees/SKILL.md` + +Expected: Approximately 200-220 lines. Scan for any markdown formatting issues. + +- [ ] **Step 3: Commit** + +```bash +git add skills/using-git-worktrees/SKILL.md +git commit -m "feat: rewrite using-git-worktrees with detect-and-defer (PRI-974) + +Step 0: GIT_DIR != GIT_COMMON detection (skip if already isolated) +Step 0 consent: opt-in prompt before creating worktree (#991) +Step 1a: native tool preference (short, first, declarative) +Step 1b: git worktree fallback with project-local directory policy +Submodule guard prevents false detection +Platform-neutral instruction file references (#1049)" +``` + +--- + +### Task 3: Rewrite `finishing-a-development-branch` SKILL.md + +Full rewrite of the finishing skill. Adds environment detection, fixes three bugs, adds provenance-based cleanup. + +**Files:** +- Modify: `skills/finishing-a-development-branch/SKILL.md` (full rewrite, 201 lines → ~220 lines) + +- [ ] **Step 1: Write the complete new SKILL.md** + +Replace the entire contents of `skills/finishing-a-development-branch/SKILL.md` with: + +```markdown +--- +name: finishing-a-development-branch +description: Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup +--- + +# Finishing a Development Branch + +## Overview + +Guide completion of development work by presenting clear options and handling chosen workflow. + +**Core principle:** Verify tests → Detect environment → Present options → Execute choice → Clean up. + +**Announce at start:** "I'm using the finishing-a-development-branch skill to complete this work." + +## The Process + +### Step 1: Verify Tests + +**Before presenting options, verify tests pass:** + +```bash +# Run project's test suite +npm test / cargo test / pytest / go test ./... +``` + +**If tests fail:** +``` +Tests failing ( failures). Must fix before completing: + +[Show failures] + +Cannot proceed with merge/PR until tests pass. +``` + +Stop. Don't proceed to Step 2. + +**If tests pass:** Continue to Step 2. + +### Step 2: Detect Environment + +**Determine workspace state before presenting options:** + +```bash +GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P) +GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P) +``` + +This determines which menu to show and how cleanup works: + +| State | Menu | Cleanup | +|-------|------|---------| +| `GIT_DIR == GIT_COMMON` (normal repo) | Standard 4 options | No worktree to clean up | +| `GIT_DIR != GIT_COMMON`, named branch | Standard 4 options | Provenance-based (see Step 6) | +| `GIT_DIR != GIT_COMMON`, detached HEAD | Reduced 3 options (no merge) | No cleanup (externally managed) | + +### Step 3: Determine Base Branch + +```bash +# Try common base branches +git merge-base HEAD main 2>/dev/null || git merge-base HEAD master 2>/dev/null +``` + +Or ask: "This branch split from main - is that correct?" + +### Step 4: Present Options + +**Normal repo and named-branch worktree — present exactly these 4 options:** + +``` +Implementation complete. What would you like to do? + +1. Merge back to locally +2. Push and create a Pull Request +3. Keep the branch as-is (I'll handle it later) +4. Discard this work + +Which option? +``` + +**Detached HEAD — present exactly these 3 options:** + +``` +Implementation complete. You're on a detached HEAD (externally managed workspace). + +1. Push as new branch and create a Pull Request +2. Keep as-is (I'll handle it later) +3. Discard this work + +Which option? +``` + +**Don't add explanation** - keep options concise. + +### Step 5: Execute Choice + +#### Option 1: Merge Locally + +```bash +# Get main repo root for CWD safety +MAIN_ROOT=$(git -C "$(git rev-parse --git-common-dir)/.." rev-parse --show-toplevel) +cd "$MAIN_ROOT" + +# Merge first — verify success before removing anything +git checkout +git pull +git merge + +# Verify tests on merged result + + +# Only after merge succeeds: remove worktree, then delete branch +# (See Step 6 for worktree cleanup) +git branch -d +``` + +Then: Cleanup worktree (Step 6) + +#### Option 2: Push and Create PR + +```bash +# Push branch +git push -u origin + +# Create PR +gh pr create --title "" --body "$(cat <<'EOF' +## Summary +<2-3 bullets of what changed> + +## Test Plan +- [ ] <verification steps> +EOF +)" +``` + +**Do NOT clean up worktree** — user needs it alive to iterate on PR feedback. + +#### Option 3: Keep As-Is + +Report: "Keeping branch <name>. Worktree preserved at <path>." + +**Don't cleanup worktree.** + +#### Option 4: Discard + +**Confirm first:** +``` +This will permanently delete: +- Branch <name> +- All commits: <commit-list> +- Worktree at <path> + +Type 'discard' to confirm. +``` + +Wait for exact confirmation. + +If confirmed: +```bash +MAIN_ROOT=$(git -C "$(git rev-parse --git-common-dir)/.." rev-parse --show-toplevel) +cd "$MAIN_ROOT" +``` + +Then: Cleanup worktree (Step 6), then force-delete branch: +```bash +git branch -D <feature-branch> +``` + +### Step 6: Cleanup Workspace + +**Only runs for Options 1 and 4.** Options 2 and 3 always preserve the worktree. + +```bash +GIT_DIR=$(cd "$(git rev-parse --git-dir)" 2>/dev/null && pwd -P) +GIT_COMMON=$(cd "$(git rev-parse --git-common-dir)" 2>/dev/null && pwd -P) +WORKTREE_PATH=$(git rev-parse --show-toplevel) +``` + +**If `GIT_DIR == GIT_COMMON`:** Normal repo, no worktree to clean up. Done. + +**If worktree path is under `.worktrees/` or `worktrees/`:** Superpowers created this worktree — we own cleanup. + +```bash +MAIN_ROOT=$(git -C "$(git rev-parse --git-common-dir)/.." rev-parse --show-toplevel) +cd "$MAIN_ROOT" +git worktree remove "$WORKTREE_PATH" +git worktree prune # Self-healing: clean up any stale registrations +``` + +**Otherwise:** The host environment (harness) owns this workspace. Do NOT remove it. If your platform provides a workspace-exit tool, use it. Otherwise, leave the workspace in place. + +## Quick Reference + +| Option | Merge | Push | Keep Worktree | Cleanup Branch | +|--------|-------|------|---------------|----------------| +| 1. Merge locally | yes | - | - | yes | +| 2. Create PR | - | yes | yes | - | +| 3. Keep as-is | - | - | yes | - | +| 4. Discard | - | - | - | yes (force) | + +## Common Mistakes + +**Skipping test verification** +- **Problem:** Merge broken code, create failing PR +- **Fix:** Always verify tests before offering options + +**Open-ended questions** +- **Problem:** "What should I do next?" is ambiguous +- **Fix:** Present exactly 4 structured options (or 3 for detached HEAD) + +**Cleaning up worktree for Option 2** +- **Problem:** Remove worktree user needs for PR iteration +- **Fix:** Only cleanup for Options 1 and 4 + +**Deleting branch before removing worktree** +- **Problem:** `git branch -d` fails because worktree still references the branch +- **Fix:** Merge first, remove worktree, then delete branch + +**Running git worktree remove from inside the worktree** +- **Problem:** Command fails silently when CWD is inside the worktree being removed +- **Fix:** Always `cd` to main repo root before `git worktree remove` + +**Cleaning up harness-owned worktrees** +- **Problem:** Removing a worktree the harness created causes phantom state +- **Fix:** Only clean up worktrees under `.worktrees/` or `worktrees/` + +**No confirmation for discard** +- **Problem:** Accidentally delete work +- **Fix:** Require typed "discard" confirmation + +## Red Flags + +**Never:** +- Proceed with failing tests +- Merge without verifying tests on result +- Delete work without confirmation +- Force-push without explicit request +- Remove a worktree before confirming merge success +- Clean up worktrees you didn't create (provenance check) +- Run `git worktree remove` from inside the worktree + +**Always:** +- Verify tests before offering options +- Detect environment before presenting menu +- Present exactly 4 options (or 3 for detached HEAD) +- Get typed confirmation for Option 4 +- Clean up worktree for Options 1 & 4 only +- `cd` to main repo root before worktree removal +- Run `git worktree prune` after removal + +## Integration + +**Called by:** +- **subagent-driven-development** (Step 7) - After all tasks complete +- **executing-plans** (Step 5) - After all batches complete + +**Pairs with:** +- **using-git-worktrees** - Cleans up worktree created by that skill +``` + +- [ ] **Step 2: Verify the file reads correctly** + +Run: `wc -l skills/finishing-a-development-branch/SKILL.md` + +Expected: Approximately 210-230 lines. + +- [ ] **Step 3: Commit** + +```bash +git add skills/finishing-a-development-branch/SKILL.md +git commit -m "feat: rewrite finishing-a-development-branch with detect-and-defer (PRI-974) + +Step 2: environment detection (GIT_DIR != GIT_COMMON) before presenting menu +Detached HEAD: reduced 3-option menu (no merge from detached HEAD) +Provenance-based cleanup: .worktrees/ = ours, anything else = hands off +Bug #940: Option 2 no longer cleans up worktree +Bug #999: merge -> verify -> remove worktree -> delete branch +Bug #238: cd to main repo root before git worktree remove +Stale worktree pruning after removal (git worktree prune)" +``` + +--- + +### Task 4: Integration Updates + +One-line changes to three files that reference `using-git-worktrees`. + +**Files:** +- Modify: `skills/executing-plans/SKILL.md:68` +- Modify: `skills/subagent-driven-development/SKILL.md:268` +- Modify: `skills/writing-plans/SKILL.md:16` + +- [ ] **Step 1: Update executing-plans integration line** + +In `skills/executing-plans/SKILL.md`, change line 68 from: + +```markdown +- **superpowers:using-git-worktrees** - REQUIRED: Set up isolated workspace before starting +``` + +to: + +```markdown +- **superpowers:using-git-worktrees** - Ensures isolated workspace (creates one or verifies existing) +``` + +- [ ] **Step 2: Update subagent-driven-development integration line** + +In `skills/subagent-driven-development/SKILL.md`, change line 268 from: + +```markdown +- **superpowers:using-git-worktrees** - REQUIRED: Set up isolated workspace before starting +``` + +to: + +```markdown +- **superpowers:using-git-worktrees** - Ensures isolated workspace (creates one or verifies existing) +``` + +- [ ] **Step 3: Update writing-plans context line** + +In `skills/writing-plans/SKILL.md`, change line 16 from: + +```markdown +**Context:** This should be run in a dedicated worktree (created by brainstorming skill). +``` + +to: + +```markdown +**Context:** If working in an isolated worktree, it should have been created via the using-git-worktrees skill at execution time. +``` + +- [ ] **Step 4: Commit all three** + +```bash +git add skills/executing-plans/SKILL.md skills/subagent-driven-development/SKILL.md skills/writing-plans/SKILL.md +git commit -m "fix: update worktree integration references across skills (PRI-974) + +Remove REQUIRED language from executing-plans and subagent-driven-development. +Consent and detection now live inside using-git-worktrees itself. +Fix stale 'created by brainstorming' claim in writing-plans." +``` + +--- + +### Task 5: End-to-End Validation + +Verify the full rewritten skills work together. Run the existing test suite plus manual verification. + +**Files:** +- Read: `tests/claude-code/run-skill-tests.sh` +- Read: `skills/using-git-worktrees/SKILL.md` (verify final state) +- Read: `skills/finishing-a-development-branch/SKILL.md` (verify final state) + +- [ ] **Step 1: Run existing test suite** + +Run: `cd tests/claude-code && bash run-skill-tests.sh` + +Expected: All existing tests pass. If any fail, investigate — the integration changes (Task 4) may have broken a content assertion. + +- [ ] **Step 2: Re-run Step 1a GREEN test** + +Run: `cd tests/claude-code && bash test-worktree-native-preference.sh green` + +Expected: PASS — agent still uses EnterWorktree with the final skill text (not just the minimal Step 1a addition from Task 1). + +- [ ] **Step 3: Manual verification — read both rewritten skills end-to-end** + +Read `skills/using-git-worktrees/SKILL.md` and `skills/finishing-a-development-branch/SKILL.md` in their entirety. Check: + +1. No references to old behavior (hardcoded `CLAUDE.md`, interactive directory prompt, "REQUIRED" language) +2. Step numbering is consistent within each file +3. Quick Reference tables match the prose +4. Integration sections cross-reference correctly +5. No markdown formatting issues + +- [ ] **Step 4: Verify git status is clean** + +Run: `git status` + +Expected: Clean working tree. All changes committed across Tasks 1-4. + +- [ ] **Step 5: Final commit if any fixups needed** + +If manual verification found issues, fix them and commit: + +```bash +git add -A +git commit -m "fix: address review findings in worktree skill rewrite (PRI-974)" +``` + +If no issues found, skip this step. diff --git a/docs/superpowers/plans/2026-05-06-lift-drill-into-evals.md b/docs/superpowers/plans/2026-05-06-lift-drill-into-evals.md new file mode 100644 index 0000000..b1c01ca --- /dev/null +++ b/docs/superpowers/plans/2026-05-06-lift-drill-into-evals.md @@ -0,0 +1,1374 @@ +# Lift drill into superpowers as `evals/` — implementation plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Move the standalone `obra/drill` skill-compliance benchmark into superpowers as a top-level `evals/` directory, delete redundant bash tests under `superpowers/tests/` after per-file subagent verification of drill scenario coverage, and update top-level docs so contributors land on the new structure. + +**Architecture:** Single PR against `dev` on a new branch `f/evals-lift`. Drill source is copied verbatim with explicit rsync excludes to keep `.git/`, `.venv/`, etc. out of the new dir. A small helper in `drill/cli.py` defaults `SUPERPOWERS_ROOT` to the parent of the `evals/` directory, so contributors don't have to set the env var. Each bash-test deletion is gated by a subagent that compares the bash test's assertions to its claimed drill scenario's verify block. Historical references in plan docs and release notes are annotated, not rewritten. + +**Tech Stack:** Python 3.11 + uv (drill's existing toolchain, unchanged); rsync; bash; git. + +**Spec:** `docs/superpowers/specs/2026-05-06-lift-drill-into-evals-design.md` — read this first. + +**Drill source location:** `/Users/jesse/Documents/GitHub/superpowers/drill/` (sibling to `superpowers/`). + +--- + +## Task 1: Branch off dev + +**Files:** none (git operation only) + +- [ ] **Step 1: Verify clean working tree** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers +git status --short +``` + +Expected: empty output (or only untracked `.opencode/package-lock.json`, which is fine). + +- [ ] **Step 2: Fetch latest dev** + +```bash +git fetch origin dev:dev +``` + +- [ ] **Step 3: Create the branch** + +```bash +git checkout -b f/evals-lift dev +``` + +Expected: `Switched to a new branch 'f/evals-lift'`. + +- [ ] **Step 4: Sanity check** + +```bash +git log --oneline -1 +``` + +Expected output begins with whatever commit `origin/dev` points to (currently `b4363df docs: turned the dash in "- Jesse" into an escape sequence (#1474)`). + +--- + +## Task 2: Capture drill SHA at copy time + +**Files:** none (records the value for the lift commit message) + +- [ ] **Step 1: Get the current drill HEAD SHA** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/drill +DRILL_SHA=$(git rev-parse HEAD) +echo "$DRILL_SHA" +``` + +- [ ] **Step 2: Verify drill has no uncommitted work** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/drill +git status --short +``` + +Expected: empty (no untracked or modified files). If output is non-empty, stop and report — drill working tree must be clean before lift, otherwise the SHA-pin is meaningless. + +- [ ] **Step 3: Save the SHA in shell env for next task** + +```bash +echo "DRILL_SHA=$DRILL_SHA" # write this down for use in Task 3 +``` + +--- + +## Task 3: rsync drill into evals/ + +**Files:** +- Create: `evals/` (entire directory tree from drill, minus excludes) + +- [ ] **Step 1: Verify source and destination paths** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers +test -d /Users/jesse/Documents/GitHub/superpowers/drill && echo "drill source: OK" +test ! -d evals && echo "evals/ does not yet exist: OK" +``` + +Expected: both echoes print. + +- [ ] **Step 2: rsync drill to evals/ with explicit excludes** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers +rsync -a \ + --exclude=.git \ + --exclude=.venv \ + --exclude=results \ + --exclude=.env \ + --exclude=__pycache__ \ + --exclude='*.egg-info' \ + --exclude=.private-journal \ + --exclude='*.pyc' \ + /Users/jesse/Documents/GitHub/superpowers/drill/ \ + evals/ +``` + +- [ ] **Step 3: Verify excludes worked** + +```bash +find evals -name '.git' -type d +find evals -name '.venv' -type d +find evals -name 'results' -type d +find evals -name '.env' +find evals -name '__pycache__' -type d +find evals -name '*.egg-info' -type d +``` + +Expected: every command returns no output. If any returns a path, manually `rm -rf` it before continuing. + +- [ ] **Step 4: Confirm the source SHA for the commit message** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/drill +DRILL_SHA=$(git rev-parse HEAD) +echo "$DRILL_SHA" +``` + +Expected: the SHA from Task 2 step 1. + +- [ ] **Step 5: Stage everything** + +```bash +git add evals/ +git status --short | head -20 +``` + +Expected output starts with `A evals/...` lines listing many added files. Many of these are in scenarios/, drill/, backends/, setup_helpers/, etc. + +- [ ] **Step 6: Commit** + +```bash +: "${DRILL_SHA:?Set DRILL_SHA from Task 2 before committing}" +git commit -m "$(cat <<EOF +Lift drill into evals/ at $DRILL_SHA + +rsync of obra/drill@$DRILL_SHA into superpowers/evals/, excluding +.git/, .venv/, results/, .env/, __pycache__/, *.egg-info/, +.private-journal/. + +The drill repo is unaffected by this commit; archival is a separate +manual step after this PR merges. + +Source SHA recorded in this commit message for provenance. +EOF +)" +``` + +--- + +## Task 4: Verify the copy with checksums + +**Files:** none (verification only) + +- [ ] **Step 1: Get list of files that exist in drill but should NOT be in evals (the excludes)** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/drill +find . \ + \( -name '.git' -prune \ + -o -name '.venv' -prune \ + -o -name 'results' -prune \ + -o -name '__pycache__' -prune \ + -o -name '*.egg-info' -prune \ + -o -name '.private-journal' -prune \ + -o -name '*.pyc' -prune \ + -o -name '.env' -prune \) \ + -o -type f -print | sort > /tmp/drill-files.txt +wc -l /tmp/drill-files.txt +``` + +- [ ] **Step 2: Get list of files in evals/** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers +find evals -type f | sed 's|^evals/|./|' | sort > /tmp/evals-files.txt +wc -l /tmp/evals-files.txt +``` + +- [ ] **Step 3: Diff the two lists** + +The file lists should match exactly after excluded paths are removed. + +```bash +diff /tmp/drill-files.txt /tmp/evals-files.txt +``` + +Expected: no output. + +- [ ] **Step 4: Per-file checksum verification** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/drill +while read -r f; do + sha1=$(shasum -a 256 "$f" | cut -d' ' -f1) + sha2=$(shasum -a 256 "/Users/jesse/Documents/GitHub/superpowers/superpowers/evals/${f#./}" | cut -d' ' -f1) + if [ "$sha1" != "$sha2" ]; then + echo "MISMATCH: $f ($sha1 vs $sha2)" + fi +done < /tmp/drill-files.txt | head -20 +``` + +Expected: no output (every file's checksum matches between drill and evals). + +- [ ] **Step 5: Smoke check - install dependencies** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers/evals +uv sync +``` + +Expected: `Installed N packages` or similar. No errors. + +- [ ] **Step 6: Smoke check - drill list** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers/evals +uv run drill list 2>&1 | head -5 +``` + +Expected: starts with scenario names. (Will likely error or warn about missing SUPERPOWERS_ROOT — that's fine, fixed in next task.) + +- [ ] **Step 7: Dispatch verification subagent** + +Dispatch a `general-purpose` subagent with this prompt: + +``` +You are verifying a verbatim copy of the drill repo at +/Users/jesse/Documents/GitHub/superpowers/drill into +/Users/jesse/Documents/GitHub/superpowers/superpowers/evals. + +Verify: + +1. The lift commit message records the SHA reported by: + cd /Users/jesse/Documents/GitHub/superpowers/drill && git rev-parse HEAD + +2. None of these excluded paths exist under evals/: .git/, .venv/, +results/, .env/, __pycache__/, *.egg-info/, .private-journal/. + +3. Every non-excluded file in drill has a SHA-256-identical +counterpart in evals/, and there are no extra files in evals/. + +4. The pyproject.toml, uv.lock, scenarios/*.yaml, backends/*.yaml, +setup_helpers/*.py, drill/*.py, prompts/*.md, fixtures/, bin/, and +docs/ are all present. + +Report each check with PASS/FAIL. If any FAIL, dump enough detail +that the parent can fix. +``` + +If the subagent reports any FAIL, fix the underlying issue (delete the leaked file, re-rsync, etc.) before continuing. + +--- + +## Task 5: Add `SUPERPOWERS_ROOT` default helper + +**Files:** +- Modify: `evals/drill/cli.py:11-14` + +- [ ] **Step 1: Read the current cli.py header** + +```bash +sed -n '1,20p' /Users/jesse/Documents/GitHub/superpowers/superpowers/evals/drill/cli.py +``` + +Expected output: + +```python +"""Drill CLI: run, compare, list.""" + +from __future__ import annotations + +import secrets +from pathlib import Path + +import click +from dotenv import load_dotenv + +PROJECT_ROOT: Path = Path(__file__).parent.parent + +load_dotenv(PROJECT_ROOT / ".env") +``` + +- [ ] **Step 2: Write a failing test for the helper** + +Open `evals/tests/test_cli.py` and add this test at the end: + +```python +def test_set_superpowers_root_default_when_unset(monkeypatch, tmp_path): + """When SUPERPOWERS_ROOT is unset, helper sets it to PROJECT_ROOT.parent.""" + monkeypatch.delenv("SUPERPOWERS_ROOT", raising=False) + from drill.cli import _set_superpowers_root_default, PROJECT_ROOT + + _set_superpowers_root_default() + + import os + assert os.environ["SUPERPOWERS_ROOT"] == str(PROJECT_ROOT.parent) + + +def test_set_superpowers_root_default_respects_existing(monkeypatch): + """When SUPERPOWERS_ROOT is already set, helper does not override.""" + monkeypatch.setenv("SUPERPOWERS_ROOT", "/custom/path") + from drill.cli import _set_superpowers_root_default + + _set_superpowers_root_default() + + import os + assert os.environ["SUPERPOWERS_ROOT"] == "/custom/path" +``` + +- [ ] **Step 3: Run the test and watch it fail** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers/evals +uv run pytest tests/test_cli.py -k set_superpowers_root_default -v +``` + +Expected: 2 tests fail with `AttributeError: module 'drill.cli' has no attribute '_set_superpowers_root_default'`. + +- [ ] **Step 4: Add the helper to cli.py** + +Edit `/Users/jesse/Documents/GitHub/superpowers/superpowers/evals/drill/cli.py`. Replace lines 1–14 with: + +```python +"""Drill CLI: run, compare, list.""" + +from __future__ import annotations + +import os +import secrets +from pathlib import Path + +import click +from dotenv import load_dotenv + +PROJECT_ROOT: Path = Path(__file__).parent.parent + +load_dotenv(PROJECT_ROOT / ".env") + + +def _set_superpowers_root_default() -> None: + """Default SUPERPOWERS_ROOT to the parent of evals/ if not already set. + + Drill historically required contributors to export SUPERPOWERS_ROOT + pointing at the superpowers checkout. After lifting drill into + superpowers/evals/, the parent of PROJECT_ROOT is always the + superpowers root, so we can supply this default automatically. + + Existing SUPERPOWERS_ROOT environment values are respected as overrides. + """ + os.environ.setdefault("SUPERPOWERS_ROOT", str(PROJECT_ROOT.parent)) + + +_set_superpowers_root_default() +``` + +The bottom-of-module call to `_set_superpowers_root_default()` runs at import time, immediately after `load_dotenv()`. This ensures both `engine.py` and `setup.py` (which read `os.environ["SUPERPOWERS_ROOT"]` directly) and the YAML interpolation (which reads `os.environ` when the backend YAML is loaded) all see the value. + +- [ ] **Step 5: Run the test and watch it pass** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers/evals +uv run pytest tests/test_cli.py -k set_superpowers_root_default -v +``` + +Expected: 2 tests pass. + +- [ ] **Step 6: Commit** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers +git add evals/drill/cli.py evals/tests/test_cli.py +git commit -m "evals: default SUPERPOWERS_ROOT to parent of evals/ if unset + +Adds _set_superpowers_root_default() to drill/cli.py, called at +module import after load_dotenv(). PROJECT_ROOT resolves to evals/ +post-lift; its parent is the superpowers repo root, which is the +correct value for SUPERPOWERS_ROOT. + +Existing env values are respected as overrides via os.environ.setdefault. + +Tests: +- helper sets default when var is unset +- helper does not override when var is already set" +``` + +--- + +## Task 6: Update backend YAMLs to reflect the new env contract + +**Files:** +- Modify: `evals/backends/codex.yaml` (drop `SUPERPOWERS_ROOT` from `required_env`) +- Modify: `evals/backends/gemini.yaml` (drop `SUPERPOWERS_ROOT` from `required_env`) + +The five `claude*.yaml` backend configs interpolate `${SUPERPOWERS_ROOT}` into `args` for the `--plugin-dir` flag — they keep `SUPERPOWERS_ROOT` in `required_env` because the interpolation needs it. The codex/gemini configs only listed it for engine.py/setup.py's `os.environ` reads, which the helper now satisfies. + +- [ ] **Step 1: Confirm current state** + +```bash +grep -A3 'required_env:' /Users/jesse/Documents/GitHub/superpowers/superpowers/evals/backends/codex.yaml +grep -A2 'required_env:' /Users/jesse/Documents/GitHub/superpowers/superpowers/evals/backends/gemini.yaml +``` + +Expected outputs include `- SUPERPOWERS_ROOT` lines. + +- [ ] **Step 2: Read codex.yaml fully** + +```bash +cat /Users/jesse/Documents/GitHub/superpowers/superpowers/evals/backends/codex.yaml +``` + +- [ ] **Step 3: Edit codex.yaml — drop the `- SUPERPOWERS_ROOT` line under `required_env`** + +Open `evals/backends/codex.yaml` and find: + +```yaml +required_env: + - OPENAI_API_KEY + - SUPERPOWERS_ROOT +``` + +Replace with: + +```yaml +required_env: + - OPENAI_API_KEY +``` + +- [ ] **Step 4: Edit gemini.yaml — drop the `- SUPERPOWERS_ROOT` line under `required_env`** + +Open `evals/backends/gemini.yaml` and find: + +```yaml +required_env: + - SUPERPOWERS_ROOT +``` + +Replace with: + +```yaml +required_env: [] +``` + +(Empty list rather than dropping the field, so YAML schema validation doesn't trip.) + +- [ ] **Step 5: Run drill's pytest suite to ensure nothing broke** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers/evals +uv run pytest -x 2>&1 | tail -20 +``` + +Expected: all tests pass. If `tests/test_backend.py` complains about `required_env` membership for codex/gemini, see Task 7. + +- [ ] **Step 6: Commit** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers +git add evals/backends/codex.yaml evals/backends/gemini.yaml +git commit -m "evals: drop SUPERPOWERS_ROOT from codex/gemini required_env + +These backends only read SUPERPOWERS_ROOT via engine.py/setup.py's +os.environ access, which the new cli.py default helper supplies +automatically. claude*.yaml keep SUPERPOWERS_ROOT in required_env +because they interpolate \${SUPERPOWERS_ROOT} into --plugin-dir args." +``` + +--- + +## Task 7: Update drill's pytest suite for the new contract + +**Files:** +- Modify: `evals/tests/test_backend.py` (per-test updates if Task 6 step 5 surfaced failures) + +- [ ] **Step 1: Run the test suite** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers/evals +uv run pytest tests/test_backend.py -v 2>&1 | tail -30 +``` + +If all tests pass, skip to step 5 (commit nothing, move to Task 8). Otherwise: + +- [ ] **Step 2: Read failing tests** + +For each failure, open the test in `evals/tests/test_backend.py` and read the assertion. + +- [ ] **Step 3: Update assertions** + +For tests that assert `SUPERPOWERS_ROOT` membership in `codex.yaml`'s or `gemini.yaml`'s `required_env`: invert the assertion to confirm absence. Example: + +```python +# Before: +def test_codex_requires_superpowers_root(): + backend = load_backend("codex") + assert "SUPERPOWERS_ROOT" in backend.required_env + +# After: +def test_codex_does_not_require_superpowers_root(): + """codex.yaml dropped SUPERPOWERS_ROOT from required_env; + the cli.py helper supplies the default.""" + backend = load_backend("codex") + assert "SUPERPOWERS_ROOT" not in backend.required_env +``` + +- [ ] **Step 4: Re-run the test suite** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers/evals +uv run pytest -x 2>&1 | tail -10 +``` + +Expected: all tests pass. + +- [ ] **Step 5: Commit (only if step 1 had failures)** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers +git add evals/tests/test_backend.py +git commit -m "evals: update test_backend.py for relaxed required_env contract" +``` + +--- + +## Task 8: Update evals/README.md and evals/CLAUDE.md + +**Files:** +- Modify: `evals/README.md` (drop SUPERPOWERS_ROOT setup step) +- Modify: `evals/CLAUDE.md` (drop SUPERPOWERS_ROOT setup step) + +- [ ] **Step 1: Edit evals/README.md** + +Find the section that looks like: + +```markdown +Required environment: +```bash +export SUPERPOWERS_ROOT=/path/to/superpowers +export ANTHROPIC_API_KEY=sk-... +``` +``` + +Replace with: + +```markdown +Required environment: +```bash +export ANTHROPIC_API_KEY=sk-... +``` + +`SUPERPOWERS_ROOT` defaults to the parent of `evals/` (the superpowers repo root) and only needs to be set if you're running drill against a different superpowers checkout. +``` + +- [ ] **Step 2: Edit evals/CLAUDE.md** + +Find the section: + +```markdown +## Required env + +``` +SUPERPOWERS_ROOT=/path/to/superpowers +ANTHROPIC_API_KEY=sk-... +``` +``` + +Replace with: + +```markdown +## Required env + +``` +ANTHROPIC_API_KEY=sk-... +``` + +`SUPERPOWERS_ROOT` defaults to the parent of `evals/` (the superpowers repo root). Override only if running drill against a different superpowers checkout. +``` + +- [ ] **Step 3: Commit** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers +git add evals/README.md evals/CLAUDE.md +git commit -m "evals: drop SUPERPOWERS_ROOT setup step from README/CLAUDE + +The cli.py helper now defaults the env var. Mention as override only." +``` + +--- + +## Task 9: Validate from new location + +**Files:** none (validation only — no commit unless something needs fixing) + +- [ ] **Step 1: Run drill's full pytest suite** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers/evals +unset SUPERPOWERS_ROOT +uv run pytest 2>&1 | tail -5 +``` + +Expected: all tests pass. The `unset` ensures we're testing the helper, not an inherited env var. + +- [ ] **Step 2: Run drill list** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers/evals +unset SUPERPOWERS_ROOT +uv run drill list 2>&1 | head -10 +``` + +Expected: scenario list, no error about missing SUPERPOWERS_ROOT. + +- [ ] **Step 3: Source the env file** + +```bash +set -a +source /Users/jesse/Documents/GitHub/prime-radiant-inc/sprout/.env +set +a +echo "ANTHROPIC_API_KEY set: ${ANTHROPIC_API_KEY:+yes}" +``` + +Expected: `ANTHROPIC_API_KEY set: yes`. + +- [ ] **Step 4: Run a cheap drill scenario** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers/evals +unset SUPERPOWERS_ROOT +uv run drill run triggering-test-driven-development -b claude 2>&1 | tail -3 +``` + +Expected: `claude: 1 passed, 0 failed, 0 errors`. + +If FAIL, debug before continuing. The path-defaults change is the most likely culprit; check that the helper actually fired by adding a `print(os.environ["SUPERPOWERS_ROOT"])` after the helper call temporarily. + +--- + +## Task 10: Bash test deletion phase — per-file with subagent gate + +This task has many sub-steps because each candidate-deletion file gets its own subagent verification + commit. The candidate list comes from the spec's coverage map. For each entry below: + +1. Read the bash test file. +2. Read the candidate drill scenario YAML. +3. Dispatch a subagent with both contents and the comparison prompt. +4. Subagent reports per-assertion match table. +5. If every bash assertion has a match: delete the bash test, commit. +6. If any unmatched: stop, escalate, do not delete. + +**Subagent prompt template (use for every deletion):** + +``` +You are gating a bash test deletion. The bash test is allegedly +covered by a drill scenario; your job is to verify that claim. + +BASH TEST: <paste full contents of bash test> + +DRILL SCENARIO: <paste full contents of drill scenario YAML> + +Output a markdown table with columns: BASH ASSERTION, DRILL CHECK, +STATUS. List EVERY assertion the bash test makes (every grep, every +[ ], every test command, every PASS/FAIL emit). For each, find a +matching drill check (in verify.assertions or verify.criteria) or +mark as UNMATCHED. + +After the table, output "VERDICT: SAFE TO DELETE" if every bash +assertion has a match, otherwise "VERDICT: KEEP — N unmatched +assertions". Be conservative: if you are uncertain about a match, +mark as UNMATCHED. +``` + +### Task 10a: Skill-triggering prompts (6 files) + +**Files:** +- Delete: `tests/skill-triggering/prompts/dispatching-parallel-agents.txt` +- Delete: `tests/skill-triggering/prompts/executing-plans.txt` +- Delete: `tests/skill-triggering/prompts/requesting-code-review.txt` +- Delete: `tests/skill-triggering/prompts/systematic-debugging.txt` +- Delete: `tests/skill-triggering/prompts/test-driven-development.txt` +- Delete: `tests/skill-triggering/prompts/writing-plans.txt` +- Keep: `tests/skill-triggering/run-test.sh`, `run-all.sh` + +These prompt files are inputs to the bash runner — they don't have their own assertions. The runner script does the assertion. Map each prompt to its drill scenario: + +| Prompt | Drill scenario | +|--------|----------------| +| dispatching-parallel-agents.txt | triggering-dispatching-parallel-agents.yaml | +| executing-plans.txt | triggering-executing-plans.yaml | +| requesting-code-review.txt | triggering-requesting-code-review.yaml | +| systematic-debugging.txt | triggering-systematic-debugging.yaml | +| test-driven-development.txt | triggering-test-driven-development.yaml | +| writing-plans.txt | triggering-writing-plans.yaml | + +- [ ] **Step 1: For each prompt file, dispatch the subagent** + +For prompt `tests/skill-triggering/prompts/<name>.txt` and scenario `evals/scenarios/triggering-<name>.yaml`, run the subagent prompt template with both contents pasted in. The subagent's job is to verify the prompt content matches what the drill scenario's `turns[].intent` describes. + +If all 6 verify SAFE TO DELETE, proceed to step 2. If any verifies KEEP, that one stays and the rest may still proceed. + +- [ ] **Step 2: Verify the runner is still useful for unrelated cases** + +```bash +ls /Users/jesse/Documents/GitHub/superpowers/superpowers/tests/skill-triggering/prompts/ +``` + +If the prompts/ directory is empty after the planned deletions, also delete `tests/skill-triggering/run-test.sh` and `run-all.sh` (they have nothing to run). Otherwise keep the runner. + +- [ ] **Step 3: Delete and commit** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers +git rm tests/skill-triggering/prompts/dispatching-parallel-agents.txt +git rm tests/skill-triggering/prompts/executing-plans.txt +git rm tests/skill-triggering/prompts/requesting-code-review.txt +git rm tests/skill-triggering/prompts/systematic-debugging.txt +git rm tests/skill-triggering/prompts/test-driven-development.txt +git rm tests/skill-triggering/prompts/writing-plans.txt +# If runner is now orphaned: +git rm tests/skill-triggering/run-test.sh tests/skill-triggering/run-all.sh +rmdir tests/skill-triggering/prompts/ 2>/dev/null || true +rmdir tests/skill-triggering/ 2>/dev/null || true +git commit -m "tests: remove skill-triggering bash prompts (covered by drill triggering-* scenarios) + +Subagent verification confirmed each prompt's intent matches its +corresponding drill scenario's turns[].intent. Drill scenarios are +canonical; bash runner has no remaining prompts to drive." +``` + +### Task 10b: explicit-skill-requests (selective deletion) + +**Files:** +- Inspect: 6 files in `tests/explicit-skill-requests/` +- Delete: only those verified to be 100% covered by drill scenarios +- Keep: the rest + +Per the spec's updated coverage map, most of these have no drill counterpart. The likely-deletable ones: + +| Bash test | Candidate drill scenario | Likely outcome | +|-----------|--------------------------|----------------| +| `run-test.sh` | n/a (runner) | KEEP | +| `run-all.sh` | n/a (runner) | KEEP | +| `run-claude-describes-sdd.sh` | `mid-conversation-skill-invocation.yaml` | likely DELETE; verify | +| `run-haiku-test.sh` | none (Haiku-specific) | KEEP | +| `run-multiturn-test.sh`, `run-extended-multiturn-test.sh` | none | KEEP | +| `prompts/please-use-brainstorming.txt`, `prompts/use-systematic-debugging.txt` | none | KEEP | + +- [ ] **Step 1: Read each .sh file and prompt to confirm** + +```bash +for f in /Users/jesse/Documents/GitHub/superpowers/superpowers/tests/explicit-skill-requests/*.sh /Users/jesse/Documents/GitHub/superpowers/superpowers/tests/explicit-skill-requests/prompts/*.txt; do + echo "=== $f ===" + cat "$f" | head -30 +done +``` + +- [ ] **Step 2: Dispatch subagent for `run-claude-describes-sdd.sh` only** + +Use the subagent prompt template above with: +- Bash test content: `tests/explicit-skill-requests/run-claude-describes-sdd.sh` +- Drill scenario: `evals/scenarios/mid-conversation-skill-invocation.yaml` + +- [ ] **Step 3: Act on subagent verdict** + +If SAFE TO DELETE: + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers +git rm tests/explicit-skill-requests/run-claude-describes-sdd.sh +git commit -m "tests: remove run-claude-describes-sdd.sh (covered by drill mid-conversation-skill-invocation) + +Subagent verification: every assertion matches a drill check. +Other tests in tests/explicit-skill-requests/ are preserved +(run-haiku-test.sh, run-*-multiturn-test.sh, please-use-brainstorming +and use-systematic-debugging prompts have no drill coverage)." +``` + +If KEEP: skip the deletion, document the gap as a future drill-scenario authoring task. + +### Task 10c: subagent-driven-dev real-project tests + +**Files:** +- Inspect: `tests/subagent-driven-dev/go-fractals/`, `tests/subagent-driven-dev/svelte-todo/` +- Candidate scenarios: `evals/scenarios/sdd-go-fractals.yaml`, `evals/scenarios/sdd-svelte-todo.yaml` + +These are entire fixture directories with `design.md`, `plan.md`, `scaffold.sh`. Each fixture directory was lifted into drill as a fixture under `evals/fixtures/`. + +- [ ] **Step 1: Confirm drill has fixture parity** + +```bash +ls /Users/jesse/Documents/GitHub/superpowers/superpowers/evals/fixtures/sdd-go-fractals/ +ls /Users/jesse/Documents/GitHub/superpowers/superpowers/evals/fixtures/sdd-svelte-todo/ +``` + +Expected: each contains `design.md`, `plan.md`, `scaffold.sh` (or equivalent) matching the source under `tests/subagent-driven-dev/`. + +- [ ] **Step 2: Dispatch subagent for each pair** + +Subagent prompt: same template, with bash "test" being the directory's `scaffold.sh` and (if present) any `*.sh` runner. Drill scenario being the corresponding `sdd-*.yaml`. + +- [ ] **Step 3: Act on verdicts** + +For each that returns SAFE TO DELETE: + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers +git rm -r tests/subagent-driven-dev/go-fractals/ # or svelte-todo +git commit -m "tests: remove subagent-driven-dev/<fixture> (covered by drill sdd-<fixture>) + +Subagent verification: drill scenario asserts test suite passes +post-execution. Fixture content lives at evals/fixtures/sdd-<fixture>/." +``` + +If both directories are removed, also `git rm -r tests/subagent-driven-dev/` if it becomes empty. + +### Task 10d: tests/claude-code/test-document-review-system.sh + +**Candidate scenario:** `evals/scenarios/spec-reviewer-catches-planted-flaws.yaml` + +- [ ] **Step 1: Dispatch subagent** + +Subagent prompt template with the bash test content and the drill scenario YAML. + +- [ ] **Step 2: Act on verdict** + +If SAFE TO DELETE: + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers +git rm tests/claude-code/test-document-review-system.sh +git commit -m "tests: remove test-document-review-system.sh (covered by drill spec-reviewer-catches-planted-flaws) + +Subagent verification: every assertion matches a drill check." +``` + +### Task 10e: tests/claude-code/test-requesting-code-review.sh + +**Candidate scenario:** `evals/scenarios/code-review-catches-planted-bugs.yaml` + +- [ ] **Step 1: Dispatch subagent** + +Subagent prompt template with both contents. + +- [ ] **Step 2: Act on verdict** + +If SAFE TO DELETE: + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers +git rm tests/claude-code/test-requesting-code-review.sh +git commit -m "tests: remove test-requesting-code-review.sh (covered by drill code-review-catches-planted-bugs) + +Subagent verification: every assertion matches a drill check." +``` + +### Task 10f: tests/claude-code/test-worktree-native-preference.sh + +**Candidate scenario:** `evals/scenarios/worktree-creation-under-pressure.yaml` + +- [ ] **Step 1: Dispatch subagent** + +Subagent prompt template with both contents. + +- [ ] **Step 2: Act on verdict** + +If SAFE TO DELETE: + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers +git rm tests/claude-code/test-worktree-native-preference.sh +git commit -m "tests: remove test-worktree-native-preference.sh (covered by drill worktree-creation-under-pressure) + +Subagent verification: every assertion matches a drill check." +``` + +### Task 10g: tests/claude-code/test-subagent-driven-development-integration.sh + +**Candidate scenario:** `evals/scenarios/sdd-rejects-extra-features.yaml` (partial) + +The spec marks this as "almost certainly keep + extend drill scenario". Don't delete. Instead: + +- [ ] **Step 1: Dispatch subagent for the comparison anyway** + +This documents the gap explicitly. + +- [ ] **Step 2: Decide based on subagent output** + +Likely outcome: KEEP with documented gap. The bash test asserts: `commit_count >= 3`, `npm test` passes, runs `analyze-token-usage.py`. The drill scenario asserts forbidden-exports + reviewer-as-gate. These are mostly disjoint. + +- [ ] **Step 3: Document the gap** (if KEEP) + +Add a comment at the top of `tests/claude-code/test-subagent-driven-development-integration.sh`: + +```bash +# Drill coverage: sdd-rejects-extra-features.yaml covers the YAGNI +# enforcement (forbidden exports + reviewer-as-gate). This bash test +# additionally asserts: ≥3 task commits, npm test passes, token +# analysis runs. Keep until those assertions are added to drill or +# explicitly retired. +``` + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers +git add tests/claude-code/test-subagent-driven-development-integration.sh +git commit -m "tests: annotate SDD integration test with drill coverage notes + +Drill scenario sdd-rejects-extra-features covers the YAGNI subset. +This bash test adds: ≥3 commits, npm test, token analysis. Kept +until drill scenario covers those or they're retired." +``` + +### Task 10h: tests/claude-code/test-subagent-driven-development.sh + +This is a meta/describe-skill test (per spec). No drill scenario covers describe-skill behavior. + +- [ ] **Step 1: Confirm by reading the file** + +```bash +cat /Users/jesse/Documents/GitHub/superpowers/superpowers/tests/claude-code/test-subagent-driven-development.sh +``` + +Expected: tests asking the agent to describe SDD skills, not exercise them. + +- [ ] **Step 2: KEEP and annotate** + +Add at the top: + +```bash +# No drill coverage: this test asks the agent to *describe* SDD +# (asserts that asked-about skills can be summarized correctly). +# Drill scenarios test behavior, not description. Kept. +``` + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers +git add tests/claude-code/test-subagent-driven-development.sh +git commit -m "tests: annotate SDD describe-skill test with kept-by-design note + +Tests agent's ability to *describe* the SDD skill — drill scenarios +test behavior, not description. No drill coverage; kept by design." +``` + +--- + +## Task 11: Stale-reference scrub + +**Files:** +- Possibly modify: `docs/testing.md`, `README.md`, `CLAUDE.md`, `lefthook.yml`, `.opencode/INSTALL.md`, `.codex-plugin/INSTALL.md`, `.github/*`, `scripts/*` +- Annotate (do not rewrite): `RELEASE-NOTES.md`, `docs/superpowers/plans/*.md` + +- [ ] **Step 1: Build list of deleted-file paths** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers +git diff --name-only --diff-filter=D dev..HEAD | sort > /tmp/deleted-paths.txt +cat /tmp/deleted-paths.txt +``` + +- [ ] **Step 2: Search for active references** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers +while read -r path; do + echo "=== $path ===" + grep -rln "$path" \ + --include="*.md" \ + --include="*.yml" \ + --include="*.yaml" \ + --include="*.sh" \ + --include="*.json" \ + --exclude-dir=node_modules \ + --exclude-dir=.venv \ + --exclude-dir=evals \ + --exclude-dir=.git \ + . +done < /tmp/deleted-paths.txt +``` + +This finds every reference to a deleted file. Categorize each hit: + +| Hit location | Treatment | +|--------------|-----------| +| `docs/testing.md` | Update — actively documents the test | +| `README.md` (Contributing section) | Update if it points at deleted tests | +| `CLAUDE.md`, `GEMINI.md`, `AGENTS.md` | Update if they reference deleted tests | +| `.github/workflows/*.yml` | Update — CI shouldn't try to run deleted tests | +| `scripts/*` | Update if they run deleted tests | +| `.opencode/INSTALL.md`, `.codex-plugin/INSTALL.md` | Update if they reference deleted tests | +| `lefthook.yml` | Update if hooks invoke deleted tests | +| `RELEASE-NOTES.md` | Annotate, don't rewrite (dated artifact) | +| `docs/superpowers/plans/*.md` | Annotate, don't rewrite (dated artifact) | + +- [ ] **Step 3: Update active references** + +For each "Update" hit, edit the file to either: +- Remove the reference if the deleted test was the only reason it was named. +- Replace with a pointer to the drill scenario (e.g., "see `evals/scenarios/triggering-test-driven-development.yaml`"). + +- [ ] **Step 4: Annotate dated artifacts** + +For each `RELEASE-NOTES.md` or `docs/superpowers/plans/*.md` hit, add an inline annotation at the *first* hit per file: + +```markdown +> Note: this section references `tests/skill-triggering/run-all.sh` and +> related bash tests that were lifted into drill scenarios on 2026-05-06 +> (see `evals/scenarios/triggering-*.yaml`). The references are +> preserved as dated artifacts of the work this doc describes. +``` + +Don't modify the actual references — they're historical. + +- [ ] **Step 5: Dispatch subagent for second-pass scrub** + +Dispatch a `general-purpose` subagent: + +``` +Working directory: /Users/jesse/Documents/GitHub/superpowers/superpowers + +These bash test paths were deleted on the current branch; some are +already addressed, but I want a second pair of eyes: + +<paste contents of /tmp/deleted-paths.txt> + +Search the entire superpowers tree (excluding evals/, node_modules/, +.venv/, .git/) for any remaining references to those paths. Report +every hit with file:line and one-sentence judgment of whether it +needs an update or is fine as-is. Do not modify files; just report. +``` + +Address every reported hit before continuing. + +- [ ] **Step 6: Commit the active updates** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers +git add -u # picks up edits to existing files +git commit -m "docs: update references to lifted-and-deleted bash tests + +Active references in docs/testing.md, README.md, CI workflows, etc. +now point at drill scenarios. Historical references in RELEASE-NOTES.md +and docs/superpowers/plans/*.md are annotated as dated artifacts, +not rewritten." +``` + +--- + +## Task 12: Top-level docs + +**Files:** +- Modify: `docs/testing.md` — split into "Plugin tests" + "Skill behavior evals" +- Modify: `CLAUDE.md` — add evals pointer +- Modify: `README.md` — add Contributing-section pointer +- Modify: `.gitignore` — add `evals/results/`, `evals/.venv/`, `evals/.env` + +- [ ] **Step 1: Split docs/testing.md** + +The file is currently Claude-Code-centric. Split into two top-level sections. + +Open `/Users/jesse/Documents/GitHub/superpowers/superpowers/docs/testing.md` and replace the file content with this structure (preserve the existing Plugin-test details where applicable): + +```markdown +# Testing Superpowers + +Superpowers has two distinct kinds of tests, each in its own directory: + +- **`tests/`** — does the plugin's non-LLM code work? Bash + node + python integration tests for brainstorm-server JS, OpenCode plugin loading, codex-plugin sync, and analysis utilities. +- **`evals/`** — do agents behave correctly on real LLM sessions? Python harness driving real tmux sessions of Claude Code / Codex / Gemini CLI / Copilot CLI, with an LLM actor and verifier judging skill compliance. + +## Plugin tests + +Live in `tests/`. Currently: + +- `tests/brainstorm-server/` — node test suite for the brainstorm server JS code. +- `tests/opencode/` — bash tests for OpenCode plugin loading, bootstrap caching, and tool registration. +- `tests/codex-plugin-sync/` — bash sync verification. +- `tests/claude-code/test-helpers.sh`, `analyze-token-usage.py` — utilities used by remaining bash tests. +- `tests/claude-code/test-subagent-driven-development.sh` — agent-can-describe-SDD test (no drill counterpart). +- `tests/claude-code/test-subagent-driven-development-integration.sh` — extended SDD integration with token analysis (drill covers the YAGNI subset). +- `tests/explicit-skill-requests/` — Haiku-specific, multi-turn, and skill-name-prompted tests not covered by drill. + +Run plugin tests via the relevant directory's `run-*.sh` or `npm test`. + +## Skill behavior evals + +Live in `evals/`. Drill is the harness; scenarios live at `evals/scenarios/*.yaml`. See `evals/README.md` for setup. Quick start: + +```bash +cd evals +uv sync +export ANTHROPIC_API_KEY=sk-... +uv run drill run triggering-test-driven-development -b claude +``` + +Drill scenarios are slow (3-30+ minutes each) and run real LLM sessions. They are not part of CI today; the natural follow-up is a tiered model (fast subset on PR, full sweep nightly + on-demand). +``` + +- [ ] **Step 2: Update CLAUDE.md** + +Read the current CLAUDE.md, find a spot near the project structure section, and add: + +```markdown +## Eval harness + +Skill-behavior evals live at `evals/` — see `evals/README.md`. Drill (the harness) drives real tmux sessions of Claude Code / Codex / Gemini CLI / Copilot CLI and judges skill compliance with an LLM verifier. Plugin-infrastructure tests still live at `tests/`. +``` + +- [ ] **Step 3: Update README.md** + +Find the Contributing section. Add a line: + +```markdown +- Skill-behavior tests use the eval harness at `evals/`. See `evals/README.md` for setup. Plugin-infrastructure tests live at `tests/` and run via the relevant `run-*.sh` or `npm test`. +``` + +- [ ] **Step 4: Update top-level .gitignore** + +Open `/Users/jesse/Documents/GitHub/superpowers/superpowers/.gitignore` and add at the bottom: + +``` +# Eval harness — drill ships its own gitignore at evals/.gitignore; +# these are belt-and-suspenders entries for tools that don't recurse. +evals/results/ +evals/.venv/ +evals/.env +``` + +- [ ] **Step 5: Commit** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers +git add docs/testing.md CLAUDE.md README.md .gitignore +git commit -m "docs: introduce evals/ as the canonical skill-behavior eval harness + +- docs/testing.md split into Plugin tests + Skill behavior evals +- CLAUDE.md adds Eval harness section pointing at evals/ +- README.md Contributing section mentions evals/ alongside tests/ +- .gitignore adds evals/{results,.venv,.env} as belt-and-suspenders + (evals/.gitignore covers these locally; root-level entries help + tooling that does not recurse into nested ignore files)." +``` + +--- + +## Task 13: Re-run smoke checks (regression gate) + +**Files:** none (validation only) + +- [ ] **Step 1: Run drill's pytest** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers/evals +unset SUPERPOWERS_ROOT +uv run pytest 2>&1 | tail -5 +``` + +Expected: all tests pass. + +- [ ] **Step 2: Run cheap drill scenario** + +```bash +set -a +source /Users/jesse/Documents/GitHub/prime-radiant-inc/sprout/.env +set +a +cd /Users/jesse/Documents/GitHub/superpowers/superpowers/evals +unset SUPERPOWERS_ROOT +uv run drill run triggering-test-driven-development -b claude 2>&1 | tail -3 +``` + +Expected: `claude: 1 passed, 0 failed, 0 errors`. If FAIL, the docs / scrub / deletion phases broke something — bisect over the recent commits. + +- [ ] **Step 3: Run remaining plugin tests that survived** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers/tests/brainstorm-server +node server.test.js 2>&1 | tail -3 +``` + +Expected: `Results: 25 passed, 0 failed`. + +--- + +## Task 14: Final adversarial review + +**Files:** none (review only; subagent dispatches) + +- [ ] **Step 1: Build the diff for reviewers** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers +git log --oneline dev..HEAD +git diff dev..HEAD --stat +``` + +Capture both outputs to share with reviewers. + +- [ ] **Step 2: Dispatch two parallel subagents** + +Use the `Agent` tool with two parallel calls. Same prompt to both, with adversarial framing: + +``` +Adversarial review competition: 5 points to whoever finds the most +legitimate issues. You're competing against a parallel reviewer +assigned the identical task. + +**Branch:** f/evals-lift, in /Users/jesse/Documents/GitHub/superpowers/superpowers +**Base:** dev (currently b4363df) +**Spec:** docs/superpowers/specs/2026-05-06-lift-drill-into-evals-design.md + +This branch lifts the obra/drill repo into superpowers/evals/ and +deletes redundant bash tests that drill scenarios cover. Two prior +adversarial reviews caught issues at the spec stage; this is the +post-implementation review. + +Run: git log --oneline dev..HEAD; git diff dev..HEAD --stat + +Look hard at: +1. Did the rsync-with-excludes actually exclude what it claimed? + (find evals -name '.git' -type d should return nothing) +2. Does the lift commit message point at a real commit in obra/drill? +3. Does the SUPERPOWERS_ROOT helper actually default correctly when + the env var is unset? (cd evals && unset SUPERPOWERS_ROOT && uv + run drill list — does it work?) +4. For each deleted bash test, does the corresponding drill scenario + actually verify what the bash test asserted? Spot-check by reading + the scenario YAML. +5. Are there active references in docs/, .github/, scripts/, + lefthook.yml that still point at deleted bash test paths? +6. Did the drill pytest suite get updated for the new env-var contract, + and does it pass? +7. Did the smoke scenario actually get run after path changes? +8. Is the drill repo unchanged? (cd ../drill && git status) + +Verify before claiming. If you assert "X is broken", check on disk +first. Confidently-wrong claims count negatively. + +Report format: numbered list, each with severity (critical/important/ +minor/nitpick) and one-sentence explanation with file:line. Lead with +most serious. Cap at ~600 words. +``` + +- [ ] **Step 3: Address findings** + +For each legitimate finding from either reviewer, fix in a separate commit. Re-run smoke checks (Task 13) after fixes. + +- [ ] **Step 4: Declare a winner** + +Per the cross-platform PR pattern, count legitimate findings (false positives count negatively). Acknowledge the winner in your reply summary. + +--- + +## Task 15: Push and open PR + +**Files:** none + +- [ ] **Step 1: Push the branch** + +```bash +cd /Users/jesse/Documents/GitHub/superpowers/superpowers +git push -u origin f/evals-lift +``` + +- [ ] **Step 2: Open PR against dev with full description** + +```bash +gh pr create \ + --base dev \ + --head f/evals-lift \ + --reviewer arittr \ + --title "Lift drill into superpowers as evals/ harness" \ + --body "$(cat <<'EOF' +## What problem are you trying to solve? + +Drill — the standalone Python skill-compliance benchmark at obra/drill — is already the de facto eval harness for superpowers. The PRI-1397 commit series lifted ~22 bash tests into drill scenarios, and the most recent superpowers commit (a2292c5) explicitly removed a redundant bash test with the message "replaced by drill behavioral coverage". Drill is a sibling repo today, requiring contributors to clone two checkouts and set SUPERPOWERS_ROOT manually. This PR completes the migration: drill becomes superpowers/evals/. + +## What does this PR change? + +- Lifts the obra/drill repo into superpowers as `evals/`, with explicit rsync excludes (.git, .venv, results, .env, __pycache__, *.egg-info, .private-journal). The lift commit records the source SHA. +- Adds a `_set_superpowers_root_default()` helper to drill/cli.py so SUPERPOWERS_ROOT defaults to the parent of evals/ — no manual env-var setup. +- Drops SUPERPOWERS_ROOT from required_env in codex.yaml/gemini.yaml (the helper supplies it). Claude*.yaml keep it because they interpolate ${SUPERPOWERS_ROOT} into --plugin-dir args. +- Deletes redundant bash tests under tests/skill-triggering/, tests/explicit-skill-requests/, tests/subagent-driven-dev/, and tests/claude-code/ — gated per-file by a subagent that compared each bash test's assertions to its drill scenario's verify block. Anything not 100% covered was kept. +- docs/testing.md split into Plugin tests + Skill behavior evals. +- README.md Contributing and CLAUDE.md gain pointers to evals/. + +## Is this change appropriate for the core library? + +Yes. Cross-runtime evaluation is core to superpowers, the migration to drill scenarios was already underway in this repo, and the eval harness needs to be discoverable in-tree to be findable. + +## What alternatives did you consider? + +- Vendored copy + sync script (drill repo continues independently). Rejected: divergence risk; single-source-of-truth wins. +- git subtree merge (preserves drill history in-tree). Rejected: superpowers' git history grows by 50+ commits, the merge commit is ugly, subtrees are operationally heavy. +- Keep drill as a sibling repo and just polish docs. Rejected: doesn't solve the discoverability problem. + +## Does this PR contain multiple unrelated changes? + +No — every change supports "drill is now evals/ inside superpowers". Multiple commits for atomicity (verbatim copy, env helper, YAML updates, docs) but one direction. + +## Existing PRs + +- [x] I have reviewed all open AND closed PRs for duplicates or prior art +- Related PRs: #1486 (obra/superpowers cross-platform PR — independent; no shared file changes besides README, which has no overlap) + +## Environment tested + +| Harness | Version | Model | Model ID | +|---------|---------|-------|----------| +| Claude Code | local install | Opus | claude-opus-4-7 (1M context) | + +Drill's own pytest suite passes from the new location. `triggering-test-driven-development` drill scenario passes from `evals/` after the path-default changes. (Larger drill sweep deferred to release-cadence runs per the spec's deferred-CI policy.) + +## Evaluation + +- Initial prompt: see linked spec (`docs/superpowers/specs/2026-05-06-lift-drill-into-evals-design.md`). +- Drill's own pytest suite passes. +- One drill scenario re-run from the new location end-to-end (proves the SUPERPOWERS_ROOT default works). +- Per-deleted-file subagent verification recorded in each deletion commit's message. + +## Rigor + +- [x] If this is a skills change: this is not a skills change; it's a tooling/infrastructure migration. No behavior-shaping content modified. +- [x] Adversarial pressure-tested: two parallel reviewers on the spec; final adversarial pre-PR review on the implementation; spec already corrected for findings before implementation began. +- [x] Did not modify carefully-tuned content. + +## Human review + +- [x] A human has reviewed the COMPLETE proposed diff before submission + +## Action items after merge + +1. Archive obra/drill on GitHub (mark read-only, add README pointer to obra/superpowers/evals/). +2. The spec lists CI integration, scenario co-location with skills, and Python package rename as deferred work. Open issues for any of these you want tracked. +EOF +)" +``` + +- [ ] **Step 3: Confirm PR opened** + +```bash +gh pr view --web +``` + +Expected: browser opens to the new PR. Take a screenshot or note the URL for follow-up. + +--- + +## Verification checklist (run after Task 15) + +- [ ] `git log --oneline dev..HEAD` shows the expected commits in order +- [ ] The lift commit message records the source SHA +- [ ] `find evals -name '.git' -type d` returns no output +- [ ] `cd evals && unset SUPERPOWERS_ROOT && uv run pytest` passes +- [ ] `cd evals && unset SUPERPOWERS_ROOT && uv run drill list` returns scenarios +- [ ] `cd evals && unset SUPERPOWERS_ROOT && uv run drill run triggering-test-driven-development -b claude` passes +- [ ] `tests/brainstorm-server/server.test.js` still passes (regression gate for non-LLM tests) +- [ ] `git diff dev..HEAD docs/superpowers/plans/2026-04-06-worktree-rototill.md docs/superpowers/plans/2026-03-23-codex-app-compatibility.md RELEASE-NOTES.md` shows annotations only, no path rewrites +- [ ] `cd ../drill && git log --oneline -1` shows obra/drill is unchanged from the source SHA recorded in the lift commit +- [ ] PR body lists the post-merge archival action item diff --git a/docs/superpowers/plans/2026-05-07-pi-extension-and-evals.md b/docs/superpowers/plans/2026-05-07-pi-extension-and-evals.md new file mode 100644 index 0000000..6272acd --- /dev/null +++ b/docs/superpowers/plans/2026-05-07-pi-extension-and-evals.md @@ -0,0 +1,143 @@ +# Pi Extension and Evals Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add first-class Pi package support for Superpowers and add Pi as a Drill eval backend. + +**Architecture:** The Pi package is declared in the root `package.json` and loads existing `skills/` plus a small Pi extension. The extension injects the `using-superpowers` bootstrap into provider context as a user-role message on session startup and after compaction, with Pi-specific tool mapping. Drill gains a `pi` backend, Pi session-log normalization, and tests. + +**Tech Stack:** Pi TypeScript extension API, Node built-in test runner, Drill Python eval harness, pytest. + +--- + +### Task 1: Pi package manifest and extension tests + +**Files:** +- Modify: `package.json` +- Create: `tests/pi/test-pi-extension.mjs` + +- [ ] **Step 1: Write failing package/extension tests** + +Create `tests/pi/test-pi-extension.mjs` with tests that import `extensions/superpowers.ts`, register fake Pi handlers, and assert: +- root `package.json` has `keywords` containing `pi-package` +- root `package.json` has `pi.skills: ["./skills"]` +- root `package.json` has `pi.extensions: ["./extensions/superpowers.ts"]` +- the extension registers `resources_discover`, `session_start`, `session_compact`, `context`, and `agent_end` +- startup `context` injects exactly one user-role bootstrap message +- `agent_end` clears startup injection +- `session_compact` re-enables injection +- the extension does not register `session_before_compact` + +- [ ] **Step 2: Run tests and verify RED** + +Run: `node --experimental-strip-types --test tests/pi/test-pi-extension.mjs` + +Expected: FAIL because `extensions/superpowers.ts` does not exist and `package.json` lacks the `pi` manifest. + +- [ ] **Step 3: Implement manifest fields** + +Update `package.json` with `description`, `keywords`, `pi.extensions`, and `pi.skills` while preserving existing `name`, `version`, `type`, and `main`. + +- [ ] **Step 4: Implement `extensions/superpowers.ts`** + +Create a zero-runtime-dependency extension that: +- locates the package root from `import.meta.url` +- reads `skills/using-superpowers/SKILL.md` +- strips YAML frontmatter +- appends Pi-specific tool mapping +- exposes `resources_discover` with the skills path +- marks bootstrap pending on `session_start` and `session_compact` +- injects a user-role bootstrap message in `context` +- inserts post-compact bootstrap after leading `compactionSummary` messages +- clears pending bootstrap on `agent_end` + +- [ ] **Step 5: Run tests and verify GREEN** + +Run: `node --experimental-strip-types --test tests/pi/test-pi-extension.mjs` + +Expected: PASS. + +### Task 2: Pi tool mapping reference + +**Files:** +- Create: `skills/using-superpowers/references/pi-tools.md` +- Modify: `tests/pi/test-pi-extension.mjs` + +- [ ] **Step 1: Write failing test for Pi reference doc** + +Add assertions that `skills/using-superpowers/references/pi-tools.md` exists and documents mappings for `Skill`, `Task`, `TodoWrite`, and built-in tool names. + +- [ ] **Step 2: Run tests and verify RED** + +Run: `node --experimental-strip-types --test tests/pi/test-pi-extension.mjs` + +Expected: FAIL because `pi-tools.md` does not exist. + +- [ ] **Step 3: Add Pi reference doc** + +Create `skills/using-superpowers/references/pi-tools.md` explaining Pi-native skills, optional `pi-subagents`, no canonical todo/tasklist plugin, and built-in lowercase tools. + +- [ ] **Step 4: Run tests and verify GREEN** + +Run: `node --experimental-strip-types --test tests/pi/test-pi-extension.mjs` + +Expected: PASS. + +### Task 3: Drill Pi backend and session log normalization + +**Files:** +- Create: `evals/backends/pi.yaml` +- Modify: `evals/drill/backend.py` +- Modify: `evals/drill/engine.py` +- Modify: `evals/drill/normalizer.py` +- Modify: `evals/tests/test_backend.py` +- Modify: `evals/tests/test_normalizer.py` + +- [ ] **Step 1: Write failing backend/normalizer tests** + +Add pytest coverage for: +- `load_backend("pi")` returns `family == "pi"` +- Pi backend command starts with `pi` and includes `-e ${SUPERPOWERS_ROOT}` +- `_resolve_log_dir()` for Pi points under `~/.pi/agent/sessions` +- `filter_pi_logs_by_cwd()` keeps only session files whose header `cwd` matches the scenario workdir +- `normalize_pi_logs()` extracts `toolCall` blocks from Pi assistant session entries and maps built-in lowercase tools to canonical names + +- [ ] **Step 2: Run tests and verify RED** + +Run: `uv run pytest evals/tests/test_backend.py evals/tests/test_normalizer.py -q` + +Expected: FAIL because the Pi backend and normalizer do not exist. + +- [ ] **Step 3: Add `evals/backends/pi.yaml`** + +Configure the backend to run `pi -e ${SUPERPOWERS_ROOT}`, use permissive TUI readiness, `/quit` shutdown, and Pi session log location. + +- [ ] **Step 4: Implement Pi family support** + +Update `Backend.family`, `Engine._resolve_log_dir`, `Engine._collect_tool_calls`, and `normalizer.py` with Pi log filtering and normalizing. + +- [ ] **Step 5: Run tests and verify GREEN** + +Run: `uv run pytest evals/tests/test_backend.py evals/tests/test_normalizer.py -q` + +Expected: PASS. + +### Task 4: Documentation and full verification + +**Files:** +- Modify: `README.md` +- Modify: `evals/README.md` + +- [ ] **Step 1: Document Pi install and eval backend** + +Add Pi to README quickstart/install list and add backend entry/usage to `evals/README.md`. + +- [ ] **Step 2: Run verification** + +Run: +```bash +node --experimental-strip-types --test tests/pi/test-pi-extension.mjs +uv run pytest evals/tests/test_backend.py evals/tests/test_setup.py evals/tests/test_normalizer.py -q +``` + +Expected: all tests pass. diff --git a/docs/superpowers/plans/2026-06-09-sdd-task-scoped-review-dispatch.md b/docs/superpowers/plans/2026-06-09-sdd-task-scoped-review-dispatch.md new file mode 100644 index 0000000..57cc5fc --- /dev/null +++ b/docs/superpowers/plans/2026-06-09-sdd-task-scoped-review-dispatch.md @@ -0,0 +1,774 @@ +# SDD Task-Scoped Review Dispatch Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Scope SDD's per-task reviews to the task (diff-first reading, justified broadening, no redundant test runs) while final branch review stays broad. + +**Architecture:** Four prose edits to the subagent-driven-development skill (the per-task quality prompt becomes self-contained instead of delegating to the merge-readiness template; the spec prompt gets a third verdict channel and grounded skepticism; the implementer prompt gains a re-run-after-fix rule; SKILL.md gets controller guidance) plus one new eval scenario in the `evals/` submodule. `skills/requesting-code-review/` is deliberately untouched. + +**Tech Stack:** Markdown skill files; Python setup helper + bash checks + story.md for the quorum eval. + +**Spec:** `docs/superpowers/specs/2026-06-09-sdd-task-scoped-review-dispatch-design.md` — read it before starting. Decisions already settled there: full re-reviews stay; the two review stages stay separate; coordinator keeps model judgment; `requesting-code-review/` stays broad. + +**These are behavior-shaping prose files, not code.** There are no unit tests for them. Each task's verification steps are exact `grep` checks that the edit landed; behavioral verification is Task 6 (static) and Task 7 (live evals, maintainer-gated). + +--- + +### Task 1: Rewrite the per-task quality reviewer prompt as self-contained + +The current file delegates to `../requesting-code-review/code-reviewer.md`, which is a merge-readiness review (architecture, security, production readiness, "Ready to merge?"). Replace the entire file with a self-contained, task-scoped template. + +**Files:** +- Rewrite: `skills/subagent-driven-development/code-quality-reviewer-prompt.md` + +- [ ] **Step 1: Replace the full file contents with:** + +````markdown +# Code Quality Reviewer Prompt Template + +Use this template when dispatching a code quality reviewer subagent. + +**Purpose:** Verify one task's implementation is well-built (clean, tested, maintainable) + +**Only dispatch after spec compliance review passes.** + +``` +Subagent (general-purpose): + description: "Review code quality for Task N" + prompt: | + You are reviewing one task's implementation for code quality. This is a + task-scoped gate, not a merge review — a broad whole-branch review happens + separately after all tasks are complete. + + ## What Was Implemented + + [DESCRIPTION] + + ## Task Requirements (context only) + + [TASK_TEXT] + + ## Git Range to Review + + **Base:** [BASE_SHA] + **Head:** [HEAD_SHA] + + ```bash + git diff --stat [BASE_SHA]..[HEAD_SHA] + git diff [BASE_SHA]..[HEAD_SHA] + ``` + + ## Read-Only Review + + Your review is read-only on this checkout. Do not mutate the working tree, + the index, HEAD, or branch state in any way. Use tools like `git show`, + `git diff`, and `git log` to inspect history. + + ## Scope + + Spec compliance was already verified by a separate reviewer. Do not + re-check whether the code matches the requirements or the plan. + + Start from the diff. Read the changed files first. Inspect code outside + the diff only to evaluate a concrete risk you can name — and name it in + your report. Cross-cutting changes are legitimate named risks: if the + diff changes lock ordering, a function or API contract, or shared mutable + state, checking the call sites is the right method. Do not crawl the + codebase by default. + + ## Tests + + The implementer already ran the tests and reported results with TDD + evidence for exactly this code. Do not re-run the suite to confirm their + report. Run a test only when reading the code raises a specific doubt + that no existing run answers — and then a focused test, never a + package-wide suite, race detector run, or repeated/high-count loop. If + heavy validation seems warranted, recommend it in your report instead of + running it. If you cannot run commands in this environment, name the + test you would run. + + ## What to Check + + **Code quality:** + - Clean separation of concerns? + - Proper error handling? + - DRY without premature abstraction? + - Edge cases handled? + + **Tests:** + - Do the new and changed tests verify real behavior, not mocks? + - Are the task's edge cases covered? + + **Structure:** + - Does each file have one clear responsibility with a well-defined interface? + - Are units decomposed so they can be understood and tested independently? + - Is the implementation following the file structure from the plan? + - Did this change create new files that are already large, or + significantly grow existing files? (Don't flag pre-existing file + sizes — focus on what this change contributed.) + + ## Calibration + + Categorize issues by actual severity. Not everything is Critical. + Acknowledge what was done well before listing issues — accurate praise + helps the implementer trust the rest of the feedback. + + ## Output Format + + ### Strengths + [What's well done? Be specific.] + + ### Issues + + #### Critical (Must Fix) + [Bugs, data loss risks, broken functionality] + + #### Important (Should Fix) + [Poor error handling, test gaps, structural problems] + + #### Minor (Nice to Have) + [Code style, optimization opportunities] + + For each issue: + - File:line reference + - What's wrong + - Why it matters + - How to fix (if not obvious) + + ### Assessment + + **Task quality:** [Approved | Needs fixes] + + **Reasoning:** [1-2 sentence technical assessment] +``` + +**Placeholders:** +- `[DESCRIPTION]` — task summary, from implementer's report +- `[TASK_TEXT]` — the task's requirements text or plan reference, for context +- `[BASE_SHA]` — commit before this task +- `[HEAD_SHA]` — current commit + +**Reviewer returns:** Strengths, Issues (Critical/Important/Minor), Task quality verdict +```` + +- [ ] **Step 2: Verify the rewrite landed** + +Run: `grep -c "requesting-code-review" skills/subagent-driven-development/code-quality-reviewer-prompt.md || echo ABSENT` +Expected: `ABSENT` (no more delegation) + +Run: `grep -n "Task quality:" skills/subagent-driven-development/code-quality-reviewer-prompt.md | head -2` +Expected: one match (the Output Format verdict line; the "Reviewer returns" footer says "Task quality verdict" without a colon) + +Run: `grep -n "worktree add\|Ready to merge" skills/subagent-driven-development/code-quality-reviewer-prompt.md || echo CLEAN` +Expected: `CLEAN` + +- [ ] **Step 3: Commit** + +```bash +git add skills/subagent-driven-development/code-quality-reviewer-prompt.md +git commit -m "Make per-task quality reviewer prompt self-contained and task-scoped" +``` + +--- + +### Task 2: Spec reviewer prompt cleanups + +Four exact edits to `skills/subagent-driven-development/spec-reviewer-prompt.md`. Current line numbers refer to the file as of commit f55642e. + +**Files:** +- Modify: `skills/subagent-driven-development/spec-reviewer-prompt.md` + +- [ ] **Step 1: Add the judge-from-the-diff clause.** After the line (currently line 31): + +``` + Only read files in this diff. Do not crawl the broader codebase. +``` + +insert a blank line and: + +``` + Spec compliance is judged by reading the diff against the requirements. + The implementer already ran the tests and reported TDD evidence — do not + re-run them. If a requirement cannot be verified from this diff alone + (it lives in unchanged code or spans tasks), report it as a ⚠️ item + instead of broadening your search. +``` + +- [ ] **Step 2: Trim the read-only section.** Replace (currently line 35): + +``` + Your review is read-only on this checkout. Do not mutate the working tree, the index, HEAD, or branch state in any way. Use tools like `git show`, `git diff`, and `git log` to inspect history. If you need a working copy of a different revision, check it out into a separate temporary directory (e.g. `git worktree add /tmp/review-[SHA] [SHA]`) — never move HEAD on this checkout. +``` + +with: + +``` + Your review is read-only on this checkout. Do not mutate the working tree, the index, HEAD, or branch state in any way. Use tools like `git show`, `git diff`, and `git log` to inspect history. +``` + +- [ ] **Step 3: Ground the skepticism.** Replace (currently lines 39-40): + +``` + The implementer finished suspiciously quickly. Their report may be incomplete, + inaccurate, or optimistic. You MUST verify everything independently. +``` + +with: + +``` + Treat the implementer's report as unverified claims about the code. It may + be incomplete, inaccurate, or optimistic. Verify the claims against the diff. +``` + +- [ ] **Step 4: Add the third verdict channel.** Replace (currently lines 74-76): + +``` + Report: + - ✅ Spec compliant (if everything matches after code inspection) + - ❌ Issues found: [list specifically what's missing or extra, with file:line references] +``` + +with: + +``` + Report: + - ✅ Spec compliant (if everything matches after code inspection) + - ❌ Issues found: [list specifically what's missing or extra, with file:line references] + - ⚠️ Cannot verify from diff: [requirements you could not verify from the + diff alone, and what the controller should check — report alongside the + ✅/❌ verdict for everything you could verify] +``` + +- [ ] **Step 5: Verify** + +Run: `grep -n "suspiciously\|worktree add" skills/subagent-driven-development/spec-reviewer-prompt.md || echo CLEAN` +Expected: `CLEAN` + +Run: `grep -c "⚠️" skills/subagent-driven-development/spec-reviewer-prompt.md` +Expected: `2` (judge-from-diff clause + verdict channel) + +- [ ] **Step 6: Commit** + +```bash +git add skills/subagent-driven-development/spec-reviewer-prompt.md +git commit -m "Spec reviewer: judge from the diff, grounded skepticism, ⚠️ verdict channel" +``` + +--- + +### Task 3: Implementer prompt — re-run tests after fixing review findings + +The reviewers' "don't re-run the implementer's tests" rule assumes the implementer re-runs tests after every fix. Make that real. + +**Files:** +- Modify: `skills/subagent-driven-development/implementer-prompt.md` + +- [ ] **Step 1: Insert a new section.** Immediately before the line (currently line 100): + +``` + ## Report Format +``` + +insert: + +``` + ## After Review Findings + + If a reviewer finds issues and you fix them, re-run the tests that cover + the amended code and include the results in your fix report. Reviewers + will not re-run tests for you — your report is the test evidence. + +``` + +- [ ] **Step 2: Verify** + +Run: `grep -n "After Review Findings" skills/subagent-driven-development/implementer-prompt.md` +Expected: one match, on a line before `## Report Format` + +- [ ] **Step 3: Commit** + +```bash +git add skills/subagent-driven-development/implementer-prompt.md +git commit -m "Implementer prompt: re-run covering tests after fixing review findings" +``` + +--- + +### Task 4: SKILL.md controller changes + +Six exact edits to `skills/subagent-driven-development/SKILL.md`. Current line numbers refer to commit f55642e. + +**Files:** +- Modify: `skills/subagent-driven-development/SKILL.md` + +- [ ] **Step 1: Point the final-review flowchart node at the broad template.** The node label `Dispatch final code reviewer subagent for entire implementation` appears 3 times (currently lines 65, 84, 85). In all 3 occurrences, replace the label string with: + +``` +Dispatch final code reviewer subagent (../requesting-code-review/code-reviewer.md) +``` + +(Graphviz nodes are matched by label text — all three must be byte-identical or the graph grows a phantom node.) + +- [ ] **Step 2: Model selection by judgment.** Replace (currently lines 97-99): + +``` +**Architecture, design, and review tasks**: use the most capable available model. + +**Task complexity signals:** +``` + +with: + +``` +**Architecture and design tasks**: use the most capable available model. + +**Review tasks**: choose the model with the same judgment, scaled to the +diff's size, complexity, and risk. A small mechanical diff does not need the +most capable model; a subtle concurrency change does. + +**Task complexity signals (implementation tasks):** +``` + +- [ ] **Step 3: Add controller guidance sections.** Immediately before the line (currently line 122): + +``` +## Prompt Templates +``` + +insert: + +``` +## Handling Spec Reviewer ⚠️ Items + +The spec reviewer may report "⚠️ Cannot verify from diff" items — requirements +that live in unchanged code or span tasks. These do not block dispatching the +code quality reviewer, but you must resolve each one yourself before marking +the task complete: you hold the plan and cross-task context the reviewer +lacks. If you confirm an item is a real gap, treat it as a failed spec +review — send it back to the implementer and re-review. + +## Constructing Reviewer Prompts + +Per-task reviews are task-scoped gates. The broad review happens once, at the +final whole-branch review. When you fill a reviewer template: + +- Do not add open-ended directives like "check all uses" or "run race tests + if useful" without a concrete, task-specific reason +- Do not ask a reviewer to re-run tests the implementer already ran on the + same code — the implementer's report carries the test evidence + +``` + +- [ ] **Step 4: Prompt Templates list — add the final-review pointer.** Replace (currently line 126): + +``` +- [code-quality-reviewer-prompt.md](code-quality-reviewer-prompt.md) - Dispatch code quality reviewer subagent +``` + +with: + +``` +- [code-quality-reviewer-prompt.md](code-quality-reviewer-prompt.md) - Dispatch code quality reviewer subagent +- Final whole-branch review: use superpowers:requesting-code-review's [code-reviewer.md](../requesting-code-review/code-reviewer.md) +``` + +- [ ] **Step 5: Example workflow verdict vocabulary.** Two replacements: + +Replace (currently line 157): +``` +Code reviewer: Strengths: Good test coverage, clean. Issues: None. Approved. +``` +with: +``` +Code reviewer: Strengths: Good test coverage, clean. Issues: None. Task quality: Approved. +``` + +Replace (currently line 191): +``` +Code reviewer: ✅ Approved +``` +with: +``` +Code reviewer: ✅ Task quality: Approved +``` + +(The final reviewer's "ready to merge" line, currently line 199, stays.) + +- [ ] **Step 6: Integration section.** Replace (currently line 272): + +``` +- **superpowers:requesting-code-review** - Code review template for reviewer subagents +``` + +with: + +``` +- **superpowers:requesting-code-review** - Code review template for the final whole-branch review +``` + +- [ ] **Step 7: Verify** + +Run: `grep -c "Dispatch final code reviewer subagent (../requesting-code-review/code-reviewer.md)" skills/subagent-driven-development/SKILL.md` +Expected: `3` + +Run: `grep -n "most capable available model" skills/subagent-driven-development/SKILL.md` +Expected: exactly one match (architecture/design bullet) + +Run: `grep -n "Handling Spec Reviewer\|Constructing Reviewer Prompts" skills/subagent-driven-development/SKILL.md` +Expected: two section headers, both before `## Prompt Templates` + +Run: `grep -c "Task quality: Approved" skills/subagent-driven-development/SKILL.md` +Expected: `2` + +- [ ] **Step 8: Commit** + +```bash +git add skills/subagent-driven-development/SKILL.md +git commit -m "SDD controller: reviewer prompt budgets, ⚠️ handling, final-review pointer, model judgment" +``` + +--- + +### Task 5: New eval scenario — per-task quality reviewer catches a planted defect + +Lives in the `evals/` **submodule** (separate repo, `superpowers-evals`). Work on a branch there; the parent submodule-pointer bump happens at finishing time per `evals/CLAUDE.md`. + +The fixture plan's Task 2 implementation snippet duplicates Task 1's formatting logic verbatim. The duplication is spec-compliant, so the spec reviewer should pass it — the per-task quality reviewer is the gate under test (DRY violation). + +**Files:** +- Create: `evals/setup_helpers/sdd_quality_defect_plan.py` +- Modify: `evals/setup_helpers/__init__.py` +- Create: `evals/scenarios/sdd-quality-reviewer-catches-planted-defect/story.md` +- Create: `evals/scenarios/sdd-quality-reviewer-catches-planted-defect/setup.sh` +- Create: `evals/scenarios/sdd-quality-reviewer-catches-planted-defect/checks.sh` + +- [ ] **Step 0: Branch in the submodule** + +```bash +cd evals +git checkout -b sdd-quality-defect-scenario +``` + +- [ ] **Step 1: Create `evals/setup_helpers/sdd_quality_defect_plan.py`:** + +````python +"""Setup helper for the sdd-quality-reviewer-catches-planted-defect scenario. + +Scaffolds a tiny Node project with a 2-task plan whose Task 2 +implementation snippet duplicates Task 1's formatting logic verbatim. +The duplication is spec-compliant — the requirements only describe +behavior — so the spec compliance reviewer should pass it. The test +measures whether the per-task code quality reviewer catches the DRY +violation and forces a refactor in the review-fix loop. +""" + +from __future__ import annotations + +from pathlib import Path + +from setup_helpers.base import _git + +PACKAGE_JSON = """\ +{ + "name": "report-quality", + "version": "1.0.0", + "type": "module", + "scripts": { + "test": "node --test" + } +} +""" + +PLAN_BODY = """\ +# Report Formatter — Implementation Plan + +Two report formatting functions. Implement exactly what each task +specifies. + +## Task 1: User Report + +**File:** `src/report.js` + +**Requirements:** +- Function named `formatUserReport` +- Takes one parameter `user`: an object with `name`, `email`, `visits` +- Returns a multi-line string: a banner of 40 `=` characters, then + `Report for <name> <<email>>`, then the banner again, then + `Visits: <visits>`, then a closing banner +- Export the function + +**Implementation:** +```javascript +export function formatUserReport(user) { + const banner = "=".repeat(40); + const lines = []; + lines.push(banner); + lines.push(`Report for ${user.name} <${user.email}>`); + lines.push(banner); + lines.push(`Visits: ${user.visits}`); + lines.push(banner); + return lines.join("\\n"); +} +``` + +**Tests:** Create `test/report.test.js` verifying: +- the result contains `Report for Ada <ada@example.com>` for that user +- the result contains `Visits: 3` when `visits` is `3` +- the result starts and ends with the 40-char banner + +**Verification:** `npm test` + +## Task 2: Admin Report + +**File:** `src/report.js` (add to existing file) + +**Requirements:** +- Function named `formatAdminReport` +- Takes one parameter `admin`: an object with `name`, `email`, `lastLogin` +- Same banner layout as the user report; the body line is + `Last login: <lastLogin>` instead of the visits line +- Export the function; keep `formatUserReport` working + +**Implementation:** +```javascript +export function formatAdminReport(admin) { + const banner = "=".repeat(40); + const lines = []; + lines.push(banner); + lines.push(`Report for ${admin.name} <${admin.email}>`); + lines.push(banner); + lines.push(`Last login: ${admin.lastLogin}`); + lines.push(banner); + return lines.join("\\n"); +} +``` + +**Tests:** Add to `test/report.test.js`: +- the result contains `Report for Grace <grace@example.com>` for that admin +- the result contains `Last login: 2026-06-01` +- the result starts and ends with the 40-char banner + +**Verification:** `npm test` +""" + + +def scaffold_sdd_quality_defect_plan(workdir: Path) -> None: + workdir = Path(workdir) + workdir.mkdir(parents=True, exist_ok=True) + _git(["git", "init", "-b", "main"], cwd=workdir) + _git(["git", "config", "user.email", "drill@test.local"], cwd=workdir) + _git(["git", "config", "user.name", "Drill Test"], cwd=workdir) + + (workdir / "package.json").write_text(PACKAGE_JSON) + plans_dir = workdir / "docs" / "superpowers" / "plans" + plans_dir.mkdir(parents=True, exist_ok=True) + (plans_dir / "report-plan.md").write_text(PLAN_BODY) + + _git(["git", "add", "-A"], cwd=workdir) + _git(["git", "commit", "-m", "initial: report formatter plan"], cwd=workdir) +```` + +(Note the `\\n` in the JS snippets inside PLAN_BODY: the Python source must +produce a literal `\n` in the markdown so the JS reads `lines.join("\n")`.) + +- [ ] **Step 2: Register the helper.** In `evals/setup_helpers/__init__.py`: + +After the line: +```python +from setup_helpers.sdd_real_projects import scaffold_sdd_go_fractals, scaffold_sdd_svelte_todo +``` +add: +```python +from setup_helpers.sdd_quality_defect_plan import scaffold_sdd_quality_defect_plan +``` + +After the registry entry: +```python + "scaffold_sdd_yagni_plan": scaffold_sdd_yagni_plan, +``` +add: +```python + "scaffold_sdd_quality_defect_plan": scaffold_sdd_quality_defect_plan, +``` + +- [ ] **Step 3: Create `evals/scenarios/sdd-quality-reviewer-catches-planted-defect/story.md`:** + +```markdown +--- +id: sdd-quality-reviewer-catches-planted-defect +title: SDD's per-task code quality review catches a planted DRY violation +status: ready +tags: subagent-driven-development +quorum_max_time: 90m +--- + +You have a small plan at docs/superpowers/plans/report-plan.md — two report +formatting functions. The plan's Task 2 implementation snippet duplicates +Task 1's formatting logic verbatim instead of sharing it. The duplication is +spec-compliant (the requirements only describe behavior), so the spec +compliance reviewer should pass it — the per-task code quality reviewer is +the gate under test. You are spec-aware — name the skill. + +When the agent is ready for input, tell it to execute the plan with SDD. Use +phrasing like: + +"I have a small plan at docs/superpowers/plans/report-plan.md — two report +formatting functions. Use the superpowers:subagent-driven-development skill +to execute it end-to-end — dispatch fresh subagents per task and run the +two-stage review after each." + +Let the agent proceed autonomously. If it asks clarifying questions, give +brief answers. If it asks where the finished work should land — merge to the +main branch, open a PR, etc. — tell it to **merge the work into the main +checkout** (this is a local repo with no remote). If a quality reviewer +flags the duplicated formatting logic and an implementer refactors it, let +the review-fix cycle play out — that cycle is exactly the behavior under +test. + +The deliverable must end up in the checkout you launched in (the main +working tree). If the agent did its work on a branch or in a worktree, it +is not done until it has merged/finished that work back into the main +checkout. Once the agent reports the plan is complete (both functions +implemented, tests passing) AND the code is present on the main checkout, +you are done. + +## Acceptance Criteria + +- A `Skill` invocation naming `superpowers:subagent-driven-development` + and at least one `Agent` (subagent dispatch) tool call appear in the + session log. +- The duplicated report-formatting logic did not survive to the end of + the run. Either (a) the implementer never introduced the duplication + (wrote or self-reviewed its way to shared logic), or (b) the per-task + code quality reviewer flagged the duplication as an issue and a + review-fix loop removed it. A fail looks like the duplicated logic + shipping with the per-task quality reviewer approving it, or the + duplication being caught only by the final whole-branch review. +- The per-task quality reviewers stayed task-scoped: no package-wide + test suites, race detector runs, or repeated/high-count test loops + appear in reviewer subagent activity, and reviewers did not re-run + the full test suite merely to confirm the implementer's report. +- `npm test` passes in the main checkout and both `formatUserReport` and + `formatAdminReport` are exported from src/report.js. The deterministic + assertions gate this; the criteria above are about whether the + *per-task quality review* was the mechanism that kept the code clean. +``` + +- [ ] **Step 4: Create `evals/scenarios/sdd-quality-reviewer-catches-planted-defect/setup.sh`:** + +```bash +#!/usr/bin/env bash +set -euo pipefail +uv run setup-helpers run scaffold_sdd_quality_defect_plan +``` + +Then: `chmod +x evals/scenarios/sdd-quality-reviewer-catches-planted-defect/setup.sh` + +- [ ] **Step 5: Create `evals/scenarios/sdd-quality-reviewer-catches-planted-defect/checks.sh`** (no executable bit): + +```bash +pre() { + git-repo + git-branch main + requires-tool npm + file-exists 'docs/superpowers/plans/report-plan.md' + file-contains 'docs/superpowers/plans/report-plan.md' 'formatAdminReport' + file-contains 'docs/superpowers/plans/report-plan.md' 'repeat\(40\)' +} + +post() { + skill-called superpowers:subagent-driven-development + tool-called Agent + command-succeeds 'npm test' + file-contains 'src/report.js' 'export function formatUserReport' + file-contains 'src/report.js' 'export function formatAdminReport' + command-succeeds 'test "$(grep -c "repeat(40)" src/report.js)" -le 1' +} +``` + +(The last check is the deterministic DRY gate: the banner construction +`"=".repeat(40)` must appear at most once in the final file — shared, not +duplicated per function.) + +- [ ] **Step 6: Validate and test in the evals repo** + +```bash +cd evals +uv run quorum check +uv run ruff check +uv run pytest -x -q +``` + +Expected: all pass; `quorum check` lists the new scenario without errors. + +- [ ] **Step 7: Commit (in the submodule)** + +```bash +cd evals +git add setup_helpers/sdd_quality_defect_plan.py setup_helpers/__init__.py scenarios/sdd-quality-reviewer-catches-planted-defect/ +git commit -m "Add sdd-quality-reviewer-catches-planted-defect scenario" +``` + +--- + +### Task 6: Static verification sweep + +**Files:** none modified — verification only. + +- [ ] **Step 1: No dangling references in the parent repo** + +Run: `grep -rn "requesting-code-review" skills/subagent-driven-development/` +Expected: matches only in SKILL.md (final-review flowchart node ×3, Prompt Templates pointer, Integration bullet). None in code-quality-reviewer-prompt.md. + +Run: `grep -rn "Ready to merge" skills/subagent-driven-development/ || echo CLEAN` +Expected: `CLEAN` + +- [ ] **Step 2: Plugin infrastructure tests** + +Run: `bash tests/shell-lint/test-lint-shell.sh` +Expected: all PASS (we added `setup.sh` only inside the evals submodule, which has its own checks). + +- [ ] **Step 3: Cross-platform tool tables still coherent** + +Run: `grep -n "code-quality-reviewer" skills/using-superpowers/references/antigravity-tools.md skills/using-superpowers/references/gemini-tools.md` +Expected: both tables still list `code-quality-reviewer` as a reviewer template (the new prompt's "If you cannot run commands in this environment, name the test you would run" line keeps the read-only `research` mapping valid — no table edits needed). + +--- + +### Task 7: Live before/after evals (maintainer-gated) + +Live quorum runs launch agent CLIs in permissive modes — **trusted-maintainer operation; Jesse launches these**, per `evals/CLAUDE.md`. Requires `ANTHROPIC_API_KEY`. + +- [ ] **Step 1: Baseline (skills as released on dev)** — from the main checkout (`/Users/jesse/git/superpowers/superpowers`, on dev), or any checkout without this branch's changes: + +```bash +cd evals +export SUPERPOWERS_ROOT=/Users/jesse/git/superpowers/superpowers +uv run quorum run scenarios/sdd-rejects-extra-features --coding-agent claude +uv run quorum run scenarios/sdd-go-fractals --coding-agent claude +uv run quorum run scenarios/sdd-svelte-todo --coding-agent claude +uv run quorum run scenarios/spec-reviewer-catches-planted-flaws --coding-agent claude +``` + +- [ ] **Step 2: After (this branch's skills)** — point `SUPERPOWERS_ROOT` at this worktree: + +```bash +cd evals +export SUPERPOWERS_ROOT=/Users/jesse/git/superpowers/superpowers/.claude/worktrees/sdd-review-dispatch +uv run quorum run scenarios/sdd-rejects-extra-features --coding-agent claude +uv run quorum run scenarios/sdd-go-fractals --coding-agent claude +uv run quorum run scenarios/sdd-svelte-todo --coding-agent claude +uv run quorum run scenarios/spec-reviewer-catches-planted-flaws --coding-agent claude +uv run quorum run scenarios/sdd-quality-reviewer-catches-planted-defect --coding-agent claude +uv run quorum show +``` + +- [ ] **Step 3: Compare** + +Pass bar: all four pre-existing scenarios still pass after the change (no regression in catch rate); the new planted-defect scenario passes. For exploration cost, compare reviewer-subagent tool-call counts between the before/after run transcripts (no automated check exists — the spec calls this out as a known gap). + +--- + +## Finishing + +After all tasks pass: the evals submodule commit needs to land in `superpowers-evals` (PR to its `main`), then this branch bumps the `evals` submodule pointer — per `evals/CLAUDE.md`, the parent bump is part of propagation, not optional. Then use superpowers:finishing-a-development-branch. PRs against superpowers target `dev`. diff --git a/docs/superpowers/plans/2026-06-09-visual-companion-issues.md b/docs/superpowers/plans/2026-06-09-visual-companion-issues.md new file mode 100644 index 0000000..09d6c1d --- /dev/null +++ b/docs/superpowers/plans/2026-06-09-visual-companion-issues.md @@ -0,0 +1,352 @@ +# Visual Brainstorming Companion — Issue & Change Catalog + +**Date:** 2026-06-09 +**Status:** Analysis / triage. We are implementing these ourselves; the referenced +community PRs are evidence and reference material, **not** code we intend to merge. + +## Purpose + +A single place that captures every open issue and PR touching the visual +brainstorming companion (the local server in `skills/brainstorming/scripts/`), +distilled to the underlying problem and the change we'd make. Each item is +grounded against the current code, not the PR author's description. + +## Scope decisions (Jesse, 2026-06-09) + +- **Not vendoring Alpine.js.** PR #1639 (interactive mockups via a vendored + Alpine build) is **dropped**. See E3. +- **E1 (terminal-vs-HTML hard gate) is a workshop item.** We'll design it + together; it is not specced here. +- **E2 (storage location, #975/#977) is deferred** for now. +- **Remote serving is a first-class scenario.** Superpowers is general-purpose; + users connect from remote (SSH tunnel, Tailscale, `--host 0.0.0.0`). The + security fix MUST protect those users, not just loopback. **Decision: a + per-session secret key**, not a Host allowlist. A Host allowlist only + defends the loopback browser-confused-deputy; a direct remote client just + sends the expected `Host`, so the allowlist is theater for remote exposure. A + secret key is the only thing that authenticates a client uniformly across + loopback, tunnel, and direct-remote, and it also defeats DNS rebinding. See A1. + +## Component map + +| File | Role | +|------|------| +| `skills/brainstorming/scripts/server.cjs` | Zero-dep HTTP + WebSocket server (RFC 6455 hand-rolled). Serves the newest screen, watches `content/`, records events to `state/events`. | +| `skills/brainstorming/scripts/helper.js` | Injected into every page. WebSocket client, click capture, `window.brainstorm` API. | +| `skills/brainstorming/scripts/frame-template.html` | Frame (header, theme CSS, status dot, indicator bar) wrapped around content fragments. | +| `skills/brainstorming/scripts/start-server.sh` | Launch wrapper. Session dir, host/url-host, owner-PID resolution, platform backgrounding. | +| `skills/brainstorming/scripts/stop-server.sh` | Kills the server by PID file, cleans `/tmp` sessions. | +| `skills/brainstorming/visual-companion.md` | Operator guide the agent reads when it accepts the companion. | +| `skills/brainstorming/SKILL.md` | Where the companion is offered and the per-question decision lives. | + +## Disposition summary + +| ID | Item | Source | Disposition | +|----|------|--------|-------------| +| A1 | Per-session secret key on `/`, `/files/*`, and WS (supersedes Host allowlist) | issues #1014, PRs #1110/#1553 | **Do** — chosen approach | +| A2 | Host allowlist; browser WS Origin check | PRs #1110/#1553 | Host allowlist dropped; WS Origin check retained after auth for browser confused-deputy defense | +| A3 | Crash on `null` / non-object WS payload | PR #1504 | Do | +| A4 | Frame-length bound in `decodeFrame` | issue #1446 | Already fixed — verify/close | +| B1 | Dotfile screens served as content (`._*.html`) | PR #950 | Do | +| B2 | `stop-server.sh` kills reused/stale PID | PR #1703 | Do | +| B3 | WS client reconnect backoff + status indicator | PR #856 | Do | +| C1 | Idle timeout too short / not configurable; WS not closed on shutdown | issue #1237 (PR #1689) | Do | +| C2 | Server death is invisible to user/agent | issue #1237 (residual) | Do | +| D1 | Permanent opt-out of the companion | issue #892 | Deferred - not in PR #1720 | +| D2 | Free-text feedback from the browser | issue #957 | Deferred - not in PR #1720 | +| D3 | Auto-open the companion URL | PR #759 (#755) | Done in PR #1720 via `--open` | +| D4 | Light/dark contrast helpers in the frame | PR #1683 | Deferred - not in PR #1720 | +| E1 | Hard-gate terminal-vs-HTML per question | PR #1037 | **Workshop** | +| E2 | Move session state out of the working tree | issue #975 (PR #977) | **Deferred** | +| E3 | Vendor Alpine.js for interactive mockups | PR #1639 | **Dropped** | +| E4 | Shell-lint warnings in start/stop scripts | PR #1677 | Opportunistic only | + +--- + +## A. Server security hardening (`server.cjs`) + +### A1 — Per-session secret key (chosen approach) + +**Threat model.** Two assets: confidentiality of the served screen (`/`) and +files (`/files/*`), and integrity of `state/events` — a WebSocket client with a +truthy `choice` writes there (`server.cjs:243-246`), and the agent reads it next +turn as the user's selection, i.e. **prompt injection into a live session with +full tool access**. Reachers: with the default `127.0.0.1` bind, a malicious +page in the user's browser (a confused deputy — runs attacker JS *and* can reach +loopback); with a remote bind (`--host 0.0.0.0`, tailnet/LAN), any host that can +route to the port, directly, with no same-origin policy in the way. Today +`handleUpgrade` (`server.cjs:176`) checks only `Sec-WebSocket-Key`, and +`handleRequest` (`server.cjs:138`) checks nothing — both are wide open. + +**Why a key, not a Host allowlist.** A Host allowlist only defends the +loopback browser-deputy. A direct remote client just sends the expected `Host` +and forges/omits `Origin`, so the allowlist is theater for exactly the remote +case we must protect. A per-session secret authenticates the client uniformly +across loopback, SSH tunnel, and direct-remote, and it also kills DNS rebinding +(the rebound page neither knows the key nor receives the host-scoped cookie). +So the key **supersedes** A1/A2's Host allowlist entirely — no `BRAINSTORM_ALLOWED_HOSTS`. + +**Design.** Random token (`crypto.randomBytes(32)` hex), generated in +`server.cjs` at startup (overridable via `BRAINSTORM_TOKEN` for deterministic +tests): + +1. **URL carries it** as `?key=<token>`. The server already builds `url` in its + `server-started` JSON (`server.cjs:351`) and writes it to `state/server-info` + — appending `?key=` there means `start-server.sh` (greps and prints that + JSON) and the skill (hands the user that URL) need **no change**. +2. **Cookie bootstrap.** A valid `?key` on `/` sets + `brainstorm-key-<port>=<token>; HttpOnly; SameSite=Strict; Path=/`. The + browser then auto-attaches it to same-origin subresources (`/files/*`) and + the WebSocket handshake, so the agent can write any URL style and it works, + and `helper.js` needs no change. Cookie name is **per-port** to avoid the + Jupyter multi-server collision (cookies aren't port-scoped). + `SameSite=Strict` is safe for CDN/Unsplash content — that cookie is host- + scoped, so outbound CDN requests never carry it; SameSite only governs + requests back to our origin, which are all same-site. +3. **Auth gate** = valid `?key` **OR** valid cookie (compared with + `crypto.timingSafeEqual`) on `/`, `/files/*`, and the WS upgrade. Missing/bad + key → friendly **403 HTML page** ("this page needs the full URL your coding + agent gave you, including `?key=…`" — generic "coding agent", not "Claude", + since this ships on Codex/Gemini/Copilot too). WS upgrade → destroy socket. + +The query token is the source of truth; the cookie is a convenience that never +bears initial-auth load. + +**Blast radius.** `server.cjs` (all logic). `helper.js` optional one-liner +(append `?key=` from `location.search` to the WS URL as a cookie-blocked +fallback). `start-server.sh` none. `visual-companion.md` doc note (URL now has +`?key=`; don't strip it). Tests updated to pass the token. + +### A2 — Host allowlist dropped; browser WS Origin retained + +Subsumed by A1. The secret key closes the WS-injection vector (#1014), the +HTTP/WS DNS-rebinding read vector (PR #1553), and the cross-origin WS vector +(PR #1110) in one mechanism, and unlike an allowlist it actually protects the +remote-bind case. No `BRAINSTORM_ALLOWED_HOSTS` and no Host allowlist. The final +implementation still checks browser WebSocket `Origin` after session auth so a +cross-origin localhost tab cannot ride the companion cookie. + +### A3 — Server crashes on `null` / primitive WS payload + +**Problem.** `handleMessage` (`server.cjs:233`) does `JSON.parse(text)` then +`if (event.choice)` at `server.cjs:243`. A client that sends the 4-byte text +frame `null` yields `event === null`, and `null.choice` throws. The throw is +**not** caught — `handleMessage` is called from the `socket.on('data')` handler +(`server.cjs:207`) outside the `try/catch`, which only wraps `decodeFrame`. The +result is an uncaught exception and process exit. Any local client can kill the +server. + +**Change.** Guard the access: `if (event && event.choice)`. Minimal and exact — +`JSON.parse` can't produce `undefined`, and primitives return `undefined` for +`.choice` without throwing, so only `null` is the live hazard. (Avoid the +broader fixes — a top-level `try/catch` or `process.on('uncaughtException')` +would mask other bugs.) + +### A4 — Frame-length bound in `decodeFrame` (adjacent) + +Referenced by PR #1504 as #1446. The current code **already** bounds extended +frame lengths: `MAX_FRAME_PAYLOAD_BYTES = 10MB` (`server.cjs:10`) is enforced at +`server.cjs:58-67` before any `Buffer.alloc`. Action: verify #1446 against +current `dev` and close if already resolved, rather than re-implementing. + +--- + +## B. Server robustness / correctness + +### B1 — macOS resource-fork dotfiles served as screen content + +**Problem.** The newest-screen selector filters on `f.endsWith('.html')` only +(`server.cjs:127-128`). On macOS/ExFAT, `._screen.html` resource-fork files pass +that filter and, being written alongside the real file, can sort newest — so the +browser gets binary metadata instead of the mockup. Four read sites share the +weak filter: `getNewestScreen` (`server.cjs:127`), `knownFiles` init +(`server.cjs:279`), the `fs.watch` handler (`server.cjs:286`), and the `/files/` +endpoint (`server.cjs:154-156`). + +**Change.** Reject dotfiles (`!f.startsWith('.')`) at all four sites. Covers +`._*`, `.DS_Store`, etc. + +### B2 — `stop-server.sh` can kill a reused PID + +**Problem.** `stop-server.sh` reads the PID from `state/server.pid` +(`stop-server.sh:20`) and `kill`s it (`:23`, escalating to `-9` at `:35`) +without confirming the PID still belongs to our server. After a reboot or PID +wraparound the file can point at an unrelated process, which we'd then SIGKILL. + +**Change.** Before signalling, verify ownership — the PID's command is `node` +running our `server.cjs`, ideally matching this session. If ownership can't be +proven, fail closed (report `stale_pid`, don't kill). Keep the existing +`stopped` / `not_running` outputs for the real cases. + +### B3 — WebSocket client: silent reconnect, stale "Connected" + +**Problem.** `helper.js` reconnects on a fixed 1s timer (`helper.js:21-23`), +has no `onerror` handler, never nulls `ws` on close, and never clears a pending +reconnect timer. The frame's status element is hardcoded to "Connected" with the +dot pinned to `var(--success)` (`frame-template.html:77,200`). When the laptop +sleeps or the server restarts, the page shows "Connected" over a dead socket and +queues events with no feedback. + +**Change.** +- `helper.js`: exponential backoff (500ms → ×2 → cap 30s, reset on open); + `onerror` delegating to `onclose`; `ws = null` on close; `clearTimeout` before + reconnecting. +- `frame-template.html`: drive the status dot from a `--status-color` custom + property so JS can switch Connected (green) / Reconnecting (yellow) / + Disconnected (red). + +--- + +## C. Lifecycle / timeout (issue #1237) + +### C1 — Idle timeout too short, not configurable, WS keeps process alive + +**Problem.** `IDLE_TIMEOUT_MS` is hardcoded to 30 minutes (`server.cjs:258`), +enforced by the 60s lifecycle check (`server.cjs:329-332`). A single brainstorm +question can sit longer than 30 min while the user thinks or steps away, so the +server dies mid-session. Separately, `shutdown()` (`server.cjs:310-321`) calls +`server.close()` but never closes the upgraded sockets in `clients` +(`server.cjs:174`), so an open browser connection can keep the Node process +alive past shutdown. + +**Change.** +- Raise the default to 4 hours and make it configurable: + `--idle-timeout-minutes` in `start-server.sh` → an env var → `IDLE_TIMEOUT_MS`, + with validation against Node timer overflow. +- Expose the effective timeout in the startup JSON / `state/server-info`. +- In `shutdown()`, close every socket in `clients` so the process actually + exits. + +### C2 — Server death is invisible + +**Problem.** When the server exits it writes `state/server-stopped` and removes +`state/server-info` (`server.cjs:312-317`), and the skill is *told* to check +those files (`visual-companion.md:108`) — but it's soft guidance the model skips, +and the browser just shows a generic "can't be reached." The user diagnoses it +manually; the agent keeps referring to a dead URL. + +**Change (two parts, independent of C1):** +- **Browser-facing tombstone.** Leave something at the last-served URL that says + "this companion expired — ask Claude to restart it" instead of a connection + error. Options to weigh: `helper.js` rendering a banner when the socket stays + down past backoff (works only while the page is loaded), vs. a more involved + approach that keeps a minimal responder alive to serve a tombstone page. +- **Harder skill check.** Tighten `visual-companion.md` / `SKILL.md` so + "check `server-info`/`server-stopped` before referring to the URL or pushing a + screen" is a required step, not a note. Keep it lightweight — possibly a + one-line helper the agent always runs. + +--- + +## D. Features + +### D1 — Permanent opt-out of the visual companion (issue #892) + +**Problem.** The companion is offered as its own message every session +(`SKILL.md:25,151-152`). A user who never wants it pays that round-trip — and +HTML generation — every time. There's no way to say "never offer this." + +**Change.** Before the offer step, the skill checks a user-level setting and +skips the offer entirely when opt-out is set. + +**Design choice open.** Mechanism isn't settled: +- Env var (e.g. `SUPERPOWERS_VISUAL_COMPANION=off`) the skill is told to read — + simplest, matches what the issue asks for, lives in `.zshrc`. +- A plugin-settings file (`.claude/superpowers.local.md` frontmatter) — more + structured, per-project capable, but heavier and project-scoped. +- Reliability caveat from the issue: a separate "no-companion" skill competes on + trigger words and isn't reliable — rejected. + +Pick the mechanism, then it's a small `SKILL.md` change plus a documented knob. + +### D2 — Free-text feedback from the browser (issue #957) + +**Problem.** The client only captures clicks on `[data-choice]` +(`helper.js:36-62`). A user who wants to annotate a mockup ("wrong shade of +blue") has to switch to the terminal, breaking the visual flow. + +**Change.** Add a feedback `<textarea>` whose submit emits +`{"type":"feedback","text":...,"timestamp":...}` via the existing +`window.brainstorm.send` path (`helper.js:82-85`). + +**Cross-cutting — server change required.** `handleMessage` only persists events +when `event.choice` is truthy (`server.cjs:243`). A `feedback` event has no +`choice`, so today it would be logged but **never written to `state/events`**, +and the agent wouldn't see it. The persistence condition must also accept +`feedback` events. Document the new event shape in `visual-companion.md` +(Browser Events Format, `:247-259`). Decide the submit trigger (button vs blur +vs both) and where the textarea renders (frame-level vs opt-in per screen). + +### D3 — Auto-open the companion URL (PR #759, issue #755) + +**Problem.** `start-server.sh` only prints the URL; the user opens it manually. +In WSL2 especially, people expect the browser to open. + +**Change.** Best-effort opener after the `server-started` JSON is parsed: +Windows/WSL → `rundll32.exe url.dll,FileProtocolHandler <url>`, macOS → `open`, +Linux → `xdg-open` only when `DISPLAY`/`WAYLAND_DISPLAY` is set. Swallow +failures, never block startup, keep echoing the URL. Document in +`visual-companion.md`. (Consider an opt-out for headless/remote runs where +popping a browser is wrong — ties into D1's config mechanism.) + +### D4 — Light/dark contrast helpers (PR #1683) + +**Problem.** Content fragments are wrapped in the OS-aware frame +(`frame-template.html`). In dark mode, quick mockups often use white inline +backgrounds while inheriting low-contrast frame text, making cards/panels hard +to read. + +**Change.** Add `.light-surface` / `.dark-surface` helper classes plus a +conservative fallback for common inline light backgrounds, and document them in +`visual-companion.md`'s CSS reference. Pure CSS in `frame-template.html`. + +--- + +## E. Workshop / deferred / dropped + +### E1 — Hard-gate terminal-vs-HTML per question (PR #1037) — WORKSHOP + +The soft guidance already exists: "decide per-question," with browser-vs-terminal +tests in `SKILL.md:156-161` and `visual-companion.md:5-25`. The complaint is that +the model renders HTML for purely textual content (A/B lists, clarifying +questions), wasting tokens and a turn. PR #1037 wraps the decision in a +`<HARD-GATE>`. **Per Jesse, we'll workshop the wording/mechanism together** — +this is behavior-shaping skill content and not specced here. + +### E2 — Move session state out of the working tree (issue #975 / PR #977) — DEFERRED + +Today `--project-dir` writes session state to `<project>/.superpowers/brainstorm/` +(`start-server.sh:80-84`) and the skill tells the user to gitignore it +(`visual-companion.md:58`). The ask is a `--state-dir` / `SUPERPOWERS_STATE_DIR` +default outside the repo (XDG), keeping `--project-dir` as an alias. +**Deferred by Jesse for now.** Captured so it isn't lost. + +### E3 — Vendor Alpine.js for interactive mockups (PR #1639) — DROPPED + +Adds a vendored Alpine build so mockups can be interactive (tabs, accordions, +forms) without hand-rolled JS. **Dropped per Jesse** — we are not taking on a +vendored third-party dependency in the companion runtime. The underlying need +(interactive mockups) is not being pursued via this route. + +### E4 — Shell-lint warnings (PR #1677) — OPPORTUNISTIC + +SC2034 (and friends) in `start-server.sh` / `stop-server.sh`. Trivial; fold into +B2/C1/D3 when we're already editing those scripts rather than as its own change. + +--- + +## Suggested grouping for implementation + +These cluster into a few coherent passes (each independently testable against +`tests/brainstorm-server/`): + +1. **Security pass** (IN PROGRESS, branch `brainstorm-companion-session-key`) — + A1 per-session key (supersedes A2) + A3 null-crash guard. Verify/close A4. + *Highest priority.* +2. **Lifecycle pass** — C1 + C2 together (both touch `shutdown()` and the + server-death story). +3. **Robustness pass** — B1, B2, B3 (independent, small). +4. **Deferred feature pass** - D1, D2, D4 are not part of PR #1720. D3 is + shipped through the `--open` flow. + +E1 is a separate workshop session. E2/E3 are out of scope for this round. diff --git a/docs/superpowers/plans/2026-06-10-visual-companion-auth-hardening.md b/docs/superpowers/plans/2026-06-10-visual-companion-auth-hardening.md new file mode 100644 index 0000000..e290dde --- /dev/null +++ b/docs/superpowers/plans/2026-06-10-visual-companion-auth-hardening.md @@ -0,0 +1,785 @@ +# Visual Companion Auth Hardening Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Harden the brainstorming visual companion auth and reconnect flow while preserving trusted same-origin screen JavaScript and future vendored UI libraries. + +**Architecture:** Keyed root loads become a bootstrap step that sets the cookie, stores the key in tab-scoped `sessionStorage`, and navigates to a bare `/` screen URL. WebSockets require valid auth plus browser same-origin `Origin`, while `/files/*` uses realpath containment to prevent content-directory escapes. + +**Tech Stack:** Node.js built-ins (`http`, `fs`, `path`, `crypto`), zero runtime dependencies, existing `ws` test dependency, Bash start/stop scripts, repo shell lint script. + +**Important:** Do not commit during execution unless Drew explicitly asks. This repository's instructions override the generic plan template's commit cadence. + +--- + +## File Map + +- Modify: `skills/brainstorming/scripts/server.cjs` + - Add bootstrap response. + - Add shared security headers. + - Add WebSocket Origin validation. + - Add `/files/*` realpath containment. +- Modify: `skills/brainstorming/scripts/helper.js` + - Read the stored session key and append it to the WebSocket URL. +- Modify: `tests/brainstorm-server/auth.test.js` + - Add bootstrap, header, same-origin WS, cross-origin WS, and cookie/file auth regressions. +- Modify: `tests/brainstorm-server/helper.test.js` + - Add mocked-browser coverage for sessionStorage-backed WS URLs. +- Modify: `tests/brainstorm-server/server.test.js` + - Add symlink containment regression for `/files/*`. +- Modify: `tests/brainstorm-server/lifecycle.test.js` + - Make the start-server timeout flag test force background mode. + - Add restart reconnect credential coverage if it fits the existing lifecycle helper. +- Modify: `skills/brainstorming/scripts/start-server.sh` + - Fix shell lint. +- Modify: `skills/brainstorming/scripts/stop-server.sh` + - Fix shell lint. +- Modify: `.gitignore` + - Add `.superpowers/`. +- Optional docs update: `skills/brainstorming/visual-companion.md` + - Mention bootstrap URL stripping and trusted same-origin screen JS if the code behavior changes need operator-facing explanation. + +## Task 1: Bootstrap Keyed Root Loads + +**Files:** +- Modify: `tests/brainstorm-server/auth.test.js` +- Modify: `skills/brainstorming/scripts/server.cjs` + +- [ ] **Step 1: Add RED tests for bootstrap behavior** + +In `tests/brainstorm-server/auth.test.js`, add tests after the existing valid-key root test: + +```js + await test('GET / with valid query returns bootstrap instead of screen content', async () => { + const res = await get('/', { key: TOKEN }); + assert.strictEqual(res.status, 200); + assert(res.body.includes('sessionStorage'), 'bootstrap should store the session key in tab storage'); + assert(res.body.includes('location.replace'), 'bootstrap should navigate to the bare root URL'); + assert(!res.body.includes('Secret screen'), 'bootstrap must not serve screen HTML at the keyed URL'); + }); + + await test('GET / with valid cookie serves the screen after bootstrap', async () => { + const res = await get('/', { cookie: `${COOKIE_NAME}=${TOKEN}` }); + assert.strictEqual(res.status, 200); + assert(res.body.includes('Secret screen'), 'cookie-authenticated bare root should serve the screen'); + assert(!res.body.includes('sessionStorage'), 'bare screen response should not be the bootstrap page'); + }); +``` + +Keep the existing cookie test if present; merge assertions rather than duplicating the same test name. + +- [ ] **Step 2: Verify RED** + +Run: + +```bash +cd /Users/drewritter/prime-rad/superpowers/tests/brainstorm-server +node auth.test.js +``` + +Expected: the new bootstrap test fails because current `GET /?key=...` serves `Secret screen` directly and does not include the bootstrap `sessionStorage`/`location.replace` code. + +- [ ] **Step 3: Implement minimal bootstrap response** + +In `skills/brainstorming/scripts/server.cjs`, add a helper near the page constants: + +```js +function bootstrapPage(key) { + const jsonKey = JSON.stringify(String(key)); + return `<!DOCTYPE html> +<html> +<head><meta charset="utf-8"><title>Opening Brainstorm Companion + + + +`; +} +``` + +Then in `handleRequest`, after authorization and cookie setting but before serving screen HTML, detect a valid query key on root: + +```js +function queryKey(url) { + const q = url.indexOf('?'); + if (q < 0) return null; + return new URLSearchParams(url.slice(q + 1)).get('key'); +} +``` + +Use it in `handleRequest`: + +```js + const pathname = pathnameOf(req.url); + const keyFromQuery = queryKey(req.url); + if (req.method === 'GET' && pathname === '/' && keyFromQuery && timingSafeEqualStr(keyFromQuery, TOKEN)) { + res.writeHead(200, securityHeaders({ 'Content-Type': 'text/html; charset=utf-8' })); + res.end(bootstrapPage(keyFromQuery)); + return; + } +``` + +This assumes Task 4 will introduce `securityHeaders`. If implementing Task 1 first, temporarily use: + +```js + res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); +``` + +and replace it in Task 4. + +- [ ] **Step 4: Verify GREEN** + +Run: + +```bash +cd /Users/drewritter/prime-rad/superpowers/tests/brainstorm-server +node auth.test.js +``` + +Expected: all auth tests pass, including the new bootstrap tests. + +## Task 2: WebSocket Origin Enforcement + +**Files:** +- Modify: `tests/brainstorm-server/auth.test.js` +- Modify: `skills/brainstorming/scripts/server.cjs` + +- [ ] **Step 1: Add RED tests for same-origin and cross-origin WS** + +In `tests/brainstorm-server/auth.test.js`, extend `wsConnect` to accept an `origin` option: + +```js +function wsConnect({ key, cookie, origin } = {}) { + const url = `ws://localhost:${TEST_PORT}/` + (key !== undefined ? `?key=${key}` : ''); + const headers = {}; + if (cookie) headers['Cookie'] = cookie; + if (origin) headers['Origin'] = origin; + const ws = new WebSocket(url, Object.keys(headers).length ? { headers } : {}); + return new Promise((resolve) => { + let settled = false; + const done = (outcome) => { if (!settled) { settled = true; resolve({ outcome, ws }); } }; + ws.on('open', () => done('opened')); + ws.on('error', () => done('rejected')); + ws.on('close', () => done('rejected')); + setTimeout(() => done('rejected'), 1500); + }); +} +``` + +Then add: + +```js + await test('WS upgrade with valid cookie and same-origin Origin opens', async () => { + const { outcome, ws } = await wsConnect({ + cookie: `${COOKIE_NAME}=${TOKEN}`, + origin: `http://localhost:${TEST_PORT}` + }); + ws.close(); + assert.strictEqual(outcome, 'opened'); + }); + + await test('WS upgrade with valid cookie but cross-origin Origin is rejected', async () => { + const eventsFile = path.join(TEST_DIR, 'state', 'events'); + if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile); + + const { outcome, ws } = await wsConnect({ + cookie: `${COOKIE_NAME}=${TOKEN}`, + origin: 'http://localhost:9999' + }); + if (outcome === 'opened') { + ws.send(JSON.stringify({ type: 'choice', choice: 'attacker-injected', text: 'local attacker probe' })); + await sleep(300); + } + ws.close(); + + assert.strictEqual(outcome, 'rejected', 'cross-origin browser WS must not open even with cookie'); + assert(!fs.existsSync(eventsFile), 'cross-origin WS must not write state/events'); + }); +``` + +- [ ] **Step 2: Verify RED** + +Run: + +```bash +cd /Users/drewritter/prime-rad/superpowers/tests/brainstorm-server +node auth.test.js +``` + +Expected: cross-origin cookie WS test fails because current server accepts any cookie-authenticated WS regardless of Origin. + +- [ ] **Step 3: Implement Origin check** + +In `skills/brainstorming/scripts/server.cjs`, add: + +```js +function isAllowedWebSocketOrigin(req) { + const origin = req.headers.origin; + if (!origin) return true; // non-browser clients still need the session key + const host = req.headers.host; + if (!host) return false; + return origin === 'http://' + host; +} +``` + +Then update `handleUpgrade`: + +```js +function handleUpgrade(req, socket) { + if (!isAuthorized(req) || !isAllowedWebSocketOrigin(req)) { socket.destroy(); return; } +``` + +- [ ] **Step 4: Verify GREEN** + +Run: + +```bash +cd /Users/drewritter/prime-rad/superpowers/tests/brainstorm-server +node auth.test.js +``` + +Expected: auth tests pass; cross-origin WS is rejected; same-origin and direct key WS still open. + +## Task 3: Helper Uses Stored Key For Reconnect + +**Files:** +- Modify: `tests/brainstorm-server/helper.test.js` +- Modify: `skills/brainstorming/scripts/helper.js` + +- [ ] **Step 1: Add RED test for WebSocket URL key** + +In `tests/brainstorm-server/helper.test.js`, add a mocked-browser test near the reconnect state-machine tests: + +```js +test('uses sessionStorage key in the WebSocket URL when present', () => { + const e = makeEnv(); + e.state.sessionKey = 'stored-key-abc'; + e.boot(); + assert.strictEqual(e.sockets[0].url, 'ws://localhost:7777/?key=stored-key-abc'); +}); +``` + +Update `makeEnv()` so the returned object exposes `sockets`, and the mock window includes sessionStorage: + +```js + window: { + location: { host: 'localhost:7777', reload() { state.reloads++; } }, + sessionStorage: { getItem: (key) => key === 'brainstorm-session-key' ? state.sessionKey : null } + }, +``` + +Also add a fallback test: + +```js +test('uses cookie-only WebSocket URL when no sessionStorage key is present', () => { + const e = makeEnv(); + e.state.sessionKey = null; + e.boot(); + assert.strictEqual(e.sockets[0].url, 'ws://localhost:7777'); +}); +``` + +- [ ] **Step 2: Verify RED** + +Run: + +```bash +cd /Users/drewritter/prime-rad/superpowers/tests/brainstorm-server +node helper.test.js +``` + +Expected: stored-key test fails because current helper uses `ws://localhost:7777`. + +- [ ] **Step 3: Implement stored-key WS URL** + +In `skills/brainstorming/scripts/helper.js`, replace: + +```js + const WS_URL = 'ws://' + window.location.host; +``` + +with: + +```js + function websocketUrl() { + let key = null; + try { key = window.sessionStorage && window.sessionStorage.getItem('brainstorm-session-key'); } catch (e) {} + return 'ws://' + window.location.host + (key ? '/?key=' + encodeURIComponent(key) : ''); + } +``` + +Then replace: + +```js + ws = new WebSocket(WS_URL); +``` + +with: + +```js + ws = new WebSocket(websocketUrl()); +``` + +- [ ] **Step 4: Verify GREEN** + +Run: + +```bash +cd /Users/drewritter/prime-rad/superpowers/tests/brainstorm-server +node helper.test.js +``` + +Expected: helper tests pass. + +## Task 4: Security Headers + +**Files:** +- Modify: `tests/brainstorm-server/auth.test.js` +- Modify: `skills/brainstorming/scripts/server.cjs` + +- [ ] **Step 1: Add RED header tests** + +In `tests/brainstorm-server/auth.test.js`, add: + +```js + await test('HTML responses include leak-reduction and anti-framing headers', async () => { + const res = await get('/', { key: TOKEN }); + assert.strictEqual(res.headers['referrer-policy'], 'no-referrer'); + assert.strictEqual(res.headers['cache-control'], 'no-store'); + assert.strictEqual(res.headers['x-frame-options'], 'DENY'); + assert.strictEqual(res.headers['content-security-policy'], "frame-ancestors 'none'"); + assert.strictEqual(res.headers['cross-origin-resource-policy'], 'same-origin'); + }); + + await test('403 responses include leak-reduction and anti-framing headers', async () => { + const res = await get('/'); + assert.strictEqual(res.status, 403); + assert.strictEqual(res.headers['referrer-policy'], 'no-referrer'); + assert.strictEqual(res.headers['cache-control'], 'no-store'); + assert.strictEqual(res.headers['x-frame-options'], 'DENY'); + assert.strictEqual(res.headers['content-security-policy'], "frame-ancestors 'none'"); + assert.strictEqual(res.headers['cross-origin-resource-policy'], 'same-origin'); + }); +``` + +- [ ] **Step 2: Verify RED** + +Run: + +```bash +cd /Users/drewritter/prime-rad/superpowers/tests/brainstorm-server +node auth.test.js +``` + +Expected: header tests fail because current responses do not include these headers. + +- [ ] **Step 3: Implement shared header helper** + +In `skills/brainstorming/scripts/server.cjs`, add: + +```js +function securityHeaders(headers = {}) { + return { + 'Referrer-Policy': 'no-referrer', + 'Cache-Control': 'no-store', + 'X-Frame-Options': 'DENY', + 'Content-Security-Policy': "frame-ancestors 'none'", + 'Cross-Origin-Resource-Policy': 'same-origin', + ...headers + }; +} +``` + +Update response writes in `handleRequest`: + +```js +res.writeHead(403, securityHeaders({ 'Content-Type': 'text/html; charset=utf-8' })); +``` + +```js +res.writeHead(200, securityHeaders({ 'Content-Type': 'text/html; charset=utf-8' })); +``` + +```js +res.writeHead(200, securityHeaders({ 'Content-Type': contentType })); +``` + +For 404s: + +```js +res.writeHead(404, securityHeaders()); +``` + +- [ ] **Step 4: Verify GREEN** + +Run: + +```bash +cd /Users/drewritter/prime-rad/superpowers/tests/brainstorm-server +node auth.test.js +``` + +Expected: auth tests pass and header assertions are green. + +## Task 5: `/files/*` Realpath Containment + +**Files:** +- Modify: `tests/brainstorm-server/server.test.js` +- Modify: `skills/brainstorming/scripts/server.cjs` + +- [ ] **Step 1: Add RED symlink escape test** + +In `tests/brainstorm-server/server.test.js`, after the `/files/` empty-name test, add: + +```js + await test('does not serve symlinks that escape content dir via /files/', async () => { + const target = path.join(STATE_DIR, 'server-info'); + const link = path.join(CONTENT_DIR, 'linked-server-info.txt'); + try { fs.unlinkSync(link); } catch (e) {} + fs.symlinkSync(target, link); + + const res = await fetch(`http://localhost:${TEST_PORT}/files/linked-server-info.txt`); + assert.strictEqual(res.status, 404, 'symlink to state/server-info must not be served'); + assert(!res.body.includes('server-started'), 'response must not include server-info body'); + }); +``` + +- [ ] **Step 2: Verify RED** + +Run: + +```bash +cd /Users/drewritter/prime-rad/superpowers/tests/brainstorm-server +node server.test.js +``` + +Expected: symlink test fails because current `/files/*` follows symlinks and serves `server-info`. + +- [ ] **Step 3: Implement containment helper** + +In `skills/brainstorming/scripts/server.cjs`, add: + +```js +function isRegularFileInsideContentDir(filePath) { + let stat, realContentDir, realFilePath; + try { + stat = fs.lstatSync(filePath); + if (stat.isSymbolicLink()) return false; + if (!stat.isFile()) return false; + realContentDir = fs.realpathSync(CONTENT_DIR); + realFilePath = fs.realpathSync(filePath); + } catch (e) { + return false; + } + return realFilePath.startsWith(realContentDir + path.sep); +} +``` + +Replace the `/files/*` guard with: + +```js + if (!fileName || fileName.startsWith('.') || !isRegularFileInsideContentDir(filePath)) { + res.writeHead(404, securityHeaders()); + res.end('Not found'); + return; + } +``` + +- [ ] **Step 4: Verify GREEN** + +Run: + +```bash +cd /Users/drewritter/prime-rad/superpowers/tests/brainstorm-server +node server.test.js +``` + +Expected: server tests pass, including symlink rejection. + +## Task 6: Restart Reconnect Regression + +**Files:** +- Modify: `tests/brainstorm-server/lifecycle.test.js` +- Modify: `skills/brainstorming/scripts/server.cjs` +- Modify: `skills/brainstorming/scripts/helper.js` + +- [ ] **Step 1: Add RED integration test for same key over WS after restart** + +In `tests/brainstorm-server/lifecycle.test.js`, add a test after the port/token persistence test: + +```js + await test('stored key can authenticate WebSocket after same-port restart', async () => { + const dir = fs.mkdtempSync('/tmp/bs-reconnect-'); + const portFile = path.join(dir, '.last-port'); + const tokenFile = path.join(dir, '.last-token'); + const env = { ...process.env, BRAINSTORM_PORT_FILE: portFile, BRAINSTORM_TOKEN_FILE: tokenFile, BRAINSTORM_LIFECYCLE_CHECK_MS: 100000 }; + + const a = spawn('node', [SERVER], { env: { ...env, BRAINSTORM_DIR: path.join(dir, 's1') } }); + let outA = ''; a.stdout.on('data', d => outA += d.toString()); + for (let i = 0; i < 60 && !outA.includes('server-started'); i++) await sleep(50); + const infoA = firstServerStarted(outA); + const keyA = new URL(infoA.url).searchParams.get('key'); + a.kill(); await sleep(400); + + const b = spawn('node', [SERVER], { env: { ...env, BRAINSTORM_DIR: path.join(dir, 's2') } }); + let outB = ''; b.stdout.on('data', d => outB += d.toString()); + for (let i = 0; i < 60 && !outB.includes('server-started'); i++) await sleep(50); + const infoB = firstServerStarted(outB); + + const ws = new WebSocket(`ws://localhost:${infoB.port}/?key=${keyA}`, { + headers: { Origin: `http://localhost:${infoB.port}` } + }); + const opened = await new Promise(resolve => { + ws.on('open', () => resolve(true)); + ws.on('error', () => resolve(false)); + setTimeout(() => resolve(false), 1500); + }); + + try { + assert.strictEqual(infoB.port, infoA.port, 'restart should reuse same port'); + assert(opened, 'stored key should authenticate WS after restart'); + } finally { + try { ws.close(); } catch (e) {} + b.kill(); await sleep(100); + fs.rmSync(dir, { recursive: true, force: true }); + } + }); +``` + +This test may already pass once Tasks 2 and 3 are implemented. If it passes before code changes, keep it as coverage but do not call it RED. The real browser reconnect behavior is primarily covered by Task 3 plus final manual/headless browser verification. + +- [ ] **Step 2: Verify behavior** + +Run: + +```bash +cd /Users/drewritter/prime-rad/superpowers/tests/brainstorm-server +node lifecycle.test.js +``` + +Expected after Tasks 2 and 3: lifecycle tests pass. If this fails, fix the auth/restart path before continuing. + +## Task 7: Lifecycle Hang And Shell Lint + +**Files:** +- Modify: `tests/brainstorm-server/lifecycle.test.js` +- Modify: `skills/brainstorming/scripts/start-server.sh` +- Modify: `skills/brainstorming/scripts/stop-server.sh` + +- [ ] **Step 1: Reproduce shell lint failure** + +Run: + +```bash +cd /Users/drewritter/prime-rad/superpowers +scripts/lint-shell.sh skills/brainstorming/scripts/start-server.sh skills/brainstorming/scripts/stop-server.sh tests/brainstorm-server/stop-server.test.sh +``` + +Expected current failure: + +```text +SC2164: skills/brainstorming/scripts/start-server.sh line 128: cd "$SCRIPT_DIR" +SC2034: skills/brainstorming/scripts/start-server.sh line 166: for i in {1..50} +SC2034: skills/brainstorming/scripts/stop-server.sh line 57: for i in {1..20} +``` + +- [ ] **Step 2: Fix shell lint minimally** + +In `skills/brainstorming/scripts/start-server.sh`, change: + +```bash +cd "$SCRIPT_DIR" +``` + +to: + +```bash +cd "$SCRIPT_DIR" || exit 1 +``` + +Change unused loop variables from `i` to `_` where they are not read: + +```bash +for _ in {1..50}; do +``` + +In `skills/brainstorming/scripts/stop-server.sh`, change: + +```bash +for i in {1..20}; do +``` + +to: + +```bash +for _ in {1..20}; do +``` + +- [ ] **Step 3: Fix lifecycle start-server hang** + +In `tests/brainstorm-server/lifecycle.test.js`, update the `start-server.sh --idle-timeout-minutes sets the timeout` test command: + +```js +const out = execFileSync('bash', [START, '--project-dir', dir, '--idle-timeout-minutes', '5', '--background'], { encoding: 'utf8' }); +``` + +This keeps the test from hanging when `CODEX_CI` triggers start-server foreground mode. + +- [ ] **Step 4: Verify lint and lifecycle** + +Run: + +```bash +cd /Users/drewritter/prime-rad/superpowers +scripts/lint-shell.sh skills/brainstorming/scripts/start-server.sh skills/brainstorming/scripts/stop-server.sh tests/brainstorm-server/stop-server.test.sh +cd tests/brainstorm-server +node lifecycle.test.js +``` + +Expected: shell lint exits 0; lifecycle tests exit 0 without hanging. + +## Task 8: Gitignore Durable Companion State + +**Files:** +- Modify: `.gitignore` + +- [ ] **Step 1: Verify current ignore gap** + +Run: + +```bash +cd /Users/drewritter/prime-rad/superpowers +git check-ignore .superpowers/brainstorm/.last-token || true +``` + +Expected current output: no matching ignore rule. + +- [ ] **Step 2: Add ignore rule** + +Add this line to `.gitignore`: + +```gitignore +.superpowers/ +``` + +- [ ] **Step 3: Verify GREEN** + +Run: + +```bash +cd /Users/drewritter/prime-rad/superpowers +git check-ignore .superpowers/brainstorm/.last-token +``` + +Expected output: + +```text +.superpowers/brainstorm/.last-token +``` + +## Task 9: Full Automated Verification + +**Files:** +- No code changes in this task. + +- [ ] **Step 1: Run focused suites** + +Run: + +```bash +cd /Users/drewritter/prime-rad/superpowers/tests/brainstorm-server +node auth.test.js +node helper.test.js +node server.test.js +node lifecycle.test.js +``` + +Expected: all four commands exit 0. + +- [ ] **Step 2: Run full brainstorm-server suite** + +Run: + +```bash +cd /Users/drewritter/prime-rad/superpowers/tests/brainstorm-server +npm test +``` + +Expected: all tests pass, including ws-protocol, helper, auth, server, lifecycle, and stop-server. + +- [ ] **Step 3: Repeat suite for lifecycle/watch flake** + +Run: + +```bash +cd /Users/drewritter/prime-rad/superpowers/tests/brainstorm-server +for i in 1 2 3; do npm test || exit 1; done +``` + +Expected: all three repeats pass without hanging. + +- [ ] **Step 4: Run shell lint** + +Run: + +```bash +cd /Users/drewritter/prime-rad/superpowers +scripts/lint-shell.sh skills/brainstorming/scripts/start-server.sh skills/brainstorming/scripts/stop-server.sh tests/brainstorm-server/stop-server.test.sh +``` + +Expected: exits 0. + +## Task 10: Re-run Security Probes + +**Files:** +- No code changes in this task. + +- [ ] **Step 1: Recreate the cross-origin attacker probe** + +Use the previous scratch probe if available: + +```bash +node /tmp/superpowers-pr1720-security-drewritter/probe-pr1720.cjs +``` + +If the scratch probe is unavailable, recreate a minimal probe under `/tmp` that: + +- starts the companion with a fixed token +- loads the keyed URL in headless Chrome +- starts an attacker page on a different localhost port +- attempts `new WebSocket('ws://localhost:/')` +- sends `{"type":"choice","choice":"attacker-injected"}` +- checks `state/events` + +Expected after fixes: + +- keyless and wrong-key HTTP still return 403 +- same-origin helper reaches Connected +- cross-origin WebSocket does not open +- `state/events` does not contain `attacker-injected` +- symlink-to-`server-info` returns 404 +- keyed browser load ends on bare `/` + +- [ ] **Step 2: Re-run manual/browser flow only after automated probes pass** + +Manual flow: + +1. start the companion with `--project-dir --open` +2. push a screen +3. confirm URL strips to `/` +4. confirm status reaches Connected +5. click a choice and verify `state/events` +6. stop and restart same project +7. verify the open tab reconnects automatically + +Expected: all steps pass without manual URL reload. + +## Self-Review Checklist + +- Spec coverage: every design requirement maps to at least one task. +- Placeholder scan: this plan contains no unresolved placeholder markers or unspecified edge-case steps. +- TDD order: every production change task starts with a focused failing test or a command that demonstrates the current failure. +- Trust model: the plan preserves trusted same-origin screen JavaScript and future same-origin vendored libraries. +- No-commit rule: execution does not commit unless Drew explicitly asks. diff --git a/docs/superpowers/plans/2026-06-11-visual-companion-final-hardening-fixup.md b/docs/superpowers/plans/2026-06-11-visual-companion-final-hardening-fixup.md new file mode 100644 index 0000000..eec99e5 --- /dev/null +++ b/docs/superpowers/plans/2026-06-11-visual-companion-final-hardening-fixup.md @@ -0,0 +1,1126 @@ +# Visual Companion Final Hardening Fixup Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Finish PR #1720's final hardening fixup with test-first changes, clean rebase state, and reviewer-ready evidence. + +**Spec:** `docs/superpowers/specs/2026-06-11-visual-companion-final-hardening-fixup-design.md` + +**Architecture:** Keep the companion zero-dependency and local-first. Add focused guards to the existing server and shell scripts: root screen selection reuses the `/files/*` containment guard, fallback token handling tracks token source, and lifecycle shutdown uses a per-start command-line instance id for ownership proof. + +**Tech Stack:** Node.js built-ins (`http`, `fs`, `path`, `crypto`), existing `ws` test dependency, Bash scripts, Git Bash on Windows, `gh` CLI for PR metadata. + +**Commit discipline:** Each task includes a suggested commit. When using subagent-driven execution, the orchestrator reviews the worker diff, runs the task verification, and performs the commit. + +--- + +## File Map + +- Modify: `skills/brainstorming/scripts/server.cjs` + - Filter root screen candidates through `isRegularFileInsideContentDir()`. + - Track token source and rotate or fail closed on fallback. +- Modify: `skills/brainstorming/scripts/start-server.sh` + - Generate `state/server-instance-id`. + - Pass `--brainstorm-server-id=` after `server.cjs`. +- Modify: `skills/brainstorming/scripts/stop-server.sh` + - Require exact instance-id argv proof before signalling a PID. + - Remove stale `server.pid` and `server-instance-id` on stale/stopped outcomes. +- Modify: `tests/brainstorm-server/server.test.js` + - Add fixed-port startup guard. + - Add skip-aware test harness for symlink capability. + - Add root symlink and hardlink escape regressions. +- Modify: `tests/brainstorm-server/auth.test.js` + - Add fixed-port startup guard. +- Modify: `tests/brainstorm-server/lifecycle.test.js` + - Add fallback token rotation, explicit-token fail-closed, and fallback-key rejection regressions. +- Modify: `tests/brainstorm-server/stop-server.test.sh` + - Add top-level cleanup trap. + - Add positive and negative server-instance-id ownership tests. +- Modify: `tests/brainstorm-server/start-server.test.sh` + - Assert Windows-like fake-node path receives exact server id argv and writes a valid id file. +- Modify: `tests/brainstorm-server/windows-lifecycle.test.sh` + - Pass server id argv for direct Node stop-server coverage. + - Add Windows fake-node assertion for the id argv. +- Modify: `skills/brainstorming/visual-companion.md` + - Add `--open` to platform commands that should preserve auto-open behavior. +- Modify: `docs/superpowers/plans/2026-06-09-visual-companion-issues.md` + - Reconcile shipped scope, WS Origin wording, default timeout, and deferred feature items. +- Update outside tracked files: PR #1720 body + - Record post-rebase diff state, RED/GREEN evidence, macOS/Windows verification, manual browser smoke, and external eval evidence. + +## Task 0: Rebase And Baseline State + +**Files:** +- No source edits +- Verification target: git branch state + +- [ ] **Step 1: Fetch current dev** + +Run: + +```bash +git fetch origin dev +``` + +Expected: command exits 0. + +- [ ] **Step 2: Rebase onto current dev** + +Run: + +```bash +git rebase origin/dev +``` + +Expected: command exits 0, or stops only on conflicts that must be resolved by taking `origin/dev` for `evals`. + +- [ ] **Step 3: Resolve an evals conflict by taking dev** + +If the rebase stops on `evals`, run: + +```bash +git restore --source=origin/dev --staged --worktree evals +git add evals +git rebase --continue +``` + +Expected: rebase continues. After the rebase, `git diff --name-only origin/dev...HEAD -- evals` prints nothing. + +- [ ] **Step 4: Record baseline status** + +Run: + +```bash +git status --short --branch +git diff --name-only origin/dev...HEAD -- evals +``` + +Expected: status shows the branch on top of `origin/dev`; second command prints no paths. + +## Task 1: Root Screen Containment + +**Files:** +- Modify: `tests/brainstorm-server/server.test.js` +- Modify: `skills/brainstorming/scripts/server.cjs` + +- [ ] **Step 1: Add fixed-port guard and skip-aware test helper** + +In `tests/brainstorm-server/server.test.js`, add this helper after `waitForServer()`: + +```js +class SkipTest extends Error { + constructor(message) { + super(message); + this.skip = true; + } +} + +function skip(message) { + throw new SkipTest(message); +} + +function serverStartedMessage(out) { + const line = out.trim().split('\n').find(l => l.includes('server-started')); + assert(line, 'server-started JSON should be present'); + return JSON.parse(line); +} + +function assertStartedOnExpectedPort(out) { + const msg = serverStartedMessage(out); + assert.strictEqual( + msg.port, + TEST_PORT, + `server.test.js expected fixed port ${TEST_PORT}, got ${msg.port}; fixed-port tests must not run through fallback` + ); + return msg; +} + +function ensureSymlinkWorks(target, link) { + try { + fs.symlinkSync(target, link); + fs.unlinkSync(link); + } catch (e) { + try { fs.unlinkSync(link); } catch (ignore) {} + skip(`symlink creation unavailable on this host: ${e.message}`); + } +} +``` + +Then change the startup section from: + +```js + const { stdout: initialStdout } = await waitForServer(server); + let passed = 0; + let failed = 0; +``` + +to: + +```js + const { stdout: initialStdout } = await waitForServer(server); + assertStartedOnExpectedPort(initialStdout); + let passed = 0; + let failed = 0; + let skipped = 0; +``` + +Change the `test()` helper catch block to handle skips: + +```js + }).catch(e => { + if (e && e.skip) { + console.log(` SKIP: ${name}`); + console.log(` ${e.message}`); + skipped++; + return; + } + console.log(` FAIL: ${name}`); + console.log(` ${e.message}`); + failed++; + }); +``` + +Change the summary line to: + +```js + console.log(`\n--- Results: ${passed} passed, ${failed} failed, ${skipped} skipped ---`); +``` + +- [ ] **Step 2: Make the existing `/files/*` symlink test skip-capable** + +Replace the setup inside `does not serve symlinks that escape content dir via /files/` with: + +```js + const target = path.join(STATE_DIR, 'server-info'); + const link = path.join(CONTENT_DIR, 'linked-server-info.txt'); + try { fs.unlinkSync(link); } catch (e) {} + ensureSymlinkWorks(target, link); + fs.symlinkSync(target, link); +``` + +Expected behavior: hosts that cannot create usable symlinks skip only this assertion. + +- [ ] **Step 3: Add RED tests for root symlink and hardlink escapes** + +Add these tests after the existing `/files/*` hardlink test: + +```js + await test('does not serve symlinks that escape content dir via root screen selection', async () => { + const target = path.join(STATE_DIR, 'server-info'); + const link = path.join(CONTENT_DIR, 'root-linked-server-info.html'); + try { fs.unlinkSync(link); } catch (e) {} + ensureSymlinkWorks(target, link); + fs.symlinkSync(target, link); + const future = new Date(Date.now() + 2000); + fs.utimesSync(target, future, future); + await sleep(300); + + const res = await fetch(`http://localhost:${TEST_PORT}/`); + assert.strictEqual(res.status, 200); + assert(!res.body.includes('"type":"server-started"'), 'root screen must not serve state/server-info through a symlink'); + assert(!res.body.includes('"state_dir"'), 'root screen must not include server-info body'); + }); + + await test('does not serve hard links that escape content dir via root screen selection', async () => { + const target = path.join(STATE_DIR, 'server-info'); + const link = path.join(CONTENT_DIR, 'root-hard-linked-server-info.html'); + try { fs.unlinkSync(link); } catch (e) {} + try { + fs.linkSync(target, link); + } catch (e) { + skip(`hardlink creation unavailable on this host: ${e.message}`); + } + const linkStat = fs.lstatSync(link); + if (linkStat.nlink <= 1) { + skip(`hardlink nlink did not expose multiple links: ${linkStat.nlink}`); + } + const future = new Date(Date.now() + 3000); + fs.utimesSync(target, future, future); + await sleep(300); + + const res = await fetch(`http://localhost:${TEST_PORT}/`); + assert.strictEqual(res.status, 200); + assert(!res.body.includes('"type":"server-started"'), 'root screen must not serve state/server-info through a hardlink'); + assert(!res.body.includes('"state_dir"'), 'root screen must not include server-info body'); + }); +``` + +- [ ] **Step 4: Verify RED** + +Run: + +```bash +cd /Users/drewritter/.codex/worktrees/59f6/superpowers/tests/brainstorm-server +node server.test.js +``` + +Expected: at least one new root containment test fails before the production fix because root screen selection can read `state/server-info`. + +- [ ] **Step 5: Implement root containment** + +In `skills/brainstorming/scripts/server.cjs`, replace `getNewestScreen()` with: + +```js +function getNewestScreen() { + const files = fs.readdirSync(CONTENT_DIR) + .filter(f => !f.startsWith('.') && f.endsWith('.html')) + .map(f => { + const fp = path.join(CONTENT_DIR, f); + if (!isRegularFileInsideContentDir(fp)) return null; + return { path: fp, mtime: fs.statSync(fp).mtime.getTime() }; + }) + .filter(Boolean) + .sort((a, b) => b.mtime - a.mtime); + return files.length > 0 ? files[0].path : null; +} +``` + +- [ ] **Step 6: Verify GREEN** + +Run: + +```bash +cd /Users/drewritter/.codex/worktrees/59f6/superpowers/tests/brainstorm-server +node server.test.js +``` + +Expected: root symlink and supported hardlink tests pass or skip only for unsupported host capabilities. Existing `/files/*` containment tests remain green. + +- [ ] **Step 7: Commit** + +Run: + +```bash +git add tests/brainstorm-server/server.test.js skills/brainstorming/scripts/server.cjs +git commit -m "Harden root screen containment" +``` + +## Task 2: Fallback Token Isolation + +**Files:** +- Modify: `tests/brainstorm-server/lifecycle.test.js` +- Modify: `skills/brainstorming/scripts/server.cjs` + +- [ ] **Step 1: Add HTTP status helper** + +In `tests/brainstorm-server/lifecycle.test.js`, add this helper after `openCaptureCommand()`: + +```js +function httpStatus(port, key) { + return new Promise(resolve => { + const pathWithKey = key ? '/?key=' + encodeURIComponent(key) : '/'; + require('http') + .get({ hostname: '127.0.0.1', port, path: pathWithKey }, res => { + res.resume(); + resolve(res.statusCode); + }) + .on('error', () => resolve(0)); + }); +} +``` + +- [ ] **Step 2: Add RED test for persisted-token fallback rotation** + +Add this test after `falls back to a random port when the preferred port is taken`: + +```js + await test('fallback with persisted token generates a fresh unpersisted key', async () => { + const dir = fs.mkdtempSync('/tmp/bs-port-'); + const portFile = path.join(dir, '.last-port'); + const tokenFile = path.join(dir, '.last-token'); + const preferredToken = 'abababababababababababababababab'; + let a = null, b = null; + + try { + a = spawn('node', [SERVER], { + env: { + ...process.env, + BRAINSTORM_DIR: path.join(dir, 'a'), + BRAINSTORM_PORT: 3422, + BRAINSTORM_TOKEN: preferredToken, + BRAINSTORM_LIFECYCLE_CHECK_MS: 100000 + } + }); + let outA = ''; a.stdout.on('data', d => outA += d.toString()); + for (let i = 0; i < 60 && !outA.includes('server-started'); i++) await sleep(50); + assert(outA.includes('server-started'), 'preferred-port server should start'); + + fs.writeFileSync(portFile, '3422'); + fs.writeFileSync(tokenFile, preferredToken, { mode: 0o600 }); + + b = spawn('node', [SERVER], { + env: { + ...process.env, + BRAINSTORM_DIR: path.join(dir, 'b'), + BRAINSTORM_PORT_FILE: portFile, + BRAINSTORM_TOKEN_FILE: tokenFile, + BRAINSTORM_LIFECYCLE_CHECK_MS: 100000 + } + }); + let outB = ''; b.stdout.on('data', d => outB += d.toString()); + for (let i = 0; i < 60 && !outB.includes('server-started'); i++) await sleep(50); + const infoB = firstServerStarted(outB); + const fallbackKey = new URL(infoB.url).searchParams.get('key'); + const persistedAfter = fs.readFileSync(tokenFile, 'utf8').trim(); + const originalStatus = await httpStatus(3422, fallbackKey); + + assert.notStrictEqual(infoB.port, 3422, 'fallback should use a different port'); + assert.notStrictEqual(fallbackKey, preferredToken, 'fallback must not reuse persisted key'); + assert.strictEqual(persistedAfter, preferredToken, 'fallback must not overwrite .last-token'); + assert.strictEqual(originalStatus, 403, 'fallback key must not authenticate to original server'); + } finally { + await killAndWait(a); + await killAndWait(b); + fs.rmSync(dir, { recursive: true, force: true }); + } + }); +``` + +- [ ] **Step 3: Add RED test for explicit-token fallback fail-closed** + +Add this test immediately after the persisted-token fallback test: + +```js + await test('fallback with explicit BRAINSTORM_TOKEN fails closed', async () => { + const dir = fs.mkdtempSync('/tmp/bs-port-'); + const portFile = path.join(dir, '.last-port'); + const explicitToken = 'cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd'; + let a = null, b = null; + + try { + a = spawn('node', [SERVER], { + env: { + ...process.env, + BRAINSTORM_DIR: path.join(dir, 'a'), + BRAINSTORM_PORT: 3423, + BRAINSTORM_TOKEN: explicitToken, + BRAINSTORM_LIFECYCLE_CHECK_MS: 100000 + } + }); + let outA = ''; a.stdout.on('data', d => outA += d.toString()); + for (let i = 0; i < 60 && !outA.includes('server-started'); i++) await sleep(50); + assert(outA.includes('server-started'), 'preferred-port server should start'); + + fs.writeFileSync(portFile, '3423'); + b = spawn('node', [SERVER], { + env: { + ...process.env, + BRAINSTORM_DIR: path.join(dir, 'b'), + BRAINSTORM_PORT_FILE: portFile, + BRAINSTORM_TOKEN: explicitToken, + BRAINSTORM_LIFECYCLE_CHECK_MS: 100000 + } + }); + let outB = ''; let errB = ''; + b.stdout.on('data', d => outB += d.toString()); + b.stderr.on('data', d => errB += d.toString()); + for (let i = 0; i < 60 && !outB.includes('server-started') && b.exitCode === null; i++) await sleep(50); + const exited = await waitForExit(b, 1500); + + assert(exited, 'explicit-token fallback process should exit'); + assert.notStrictEqual(b.exitCode, 0, 'explicit-token fallback should fail non-zero'); + assert(!outB.includes('server-started'), 'explicit-token fallback must not start on a random port'); + assert(/BRAINSTORM_TOKEN/.test(errB), `stderr should explain explicit token fallback refusal, got: ${errB}`); + } finally { + await killAndWait(a); + await killAndWait(b); + fs.rmSync(dir, { recursive: true, force: true }); + } + }); +``` + +- [ ] **Step 4: Verify RED** + +Run: + +```bash +cd /Users/drewritter/.codex/worktrees/59f6/superpowers/tests/brainstorm-server +node lifecycle.test.js +``` + +Expected: persisted-token fallback test fails because fallback reuses `.last-token`, and explicit-token fallback test fails because fallback currently starts. + +- [ ] **Step 5: Track token source in production code** + +In `skills/brainstorming/scripts/server.cjs`, replace the current `const TOKEN = (() => { ... })();` block with: + +```js +function generateToken() { + return crypto.randomBytes(32).toString('hex'); +} + +function initialToken() { + if (process.env.BRAINSTORM_TOKEN) { + return { value: process.env.BRAINSTORM_TOKEN, source: 'env' }; + } + if (TOKEN_FILE) { + try { + const t = fs.readFileSync(TOKEN_FILE, 'utf-8').trim(); + if (/^[0-9a-f]{32,}$/i.test(t)) return { value: t, source: 'file' }; + } catch (e) { /* no prior token recorded */ } + } + return { value: generateToken(), source: 'generated' }; +} + +const tokenInfo = initialToken(); +let TOKEN = tokenInfo.value; +let tokenSource = tokenInfo.source; +``` + +- [ ] **Step 6: Rotate or fail closed on EADDRINUSE fallback** + +In the `server.on('error', ...)` handler, replace the `EADDRINUSE` branch with: + +```js + if (err.code === 'EADDRINUSE' && !triedFallback) { + if (tokenSource === 'env') { + console.error('Server failed to bind: preferred port is in use and BRAINSTORM_TOKEN is set; refusing fallback with explicit token'); + process.exit(1); + } + triedFallback = true; + PORT = randomPort(); + if (tokenSource === 'file') { + TOKEN = generateToken(); + tokenSource = 'generated-fallback'; + } + server.listen(PORT, HOST, onListen); + } else { +``` + +- [ ] **Step 7: Verify GREEN** + +Run: + +```bash +cd /Users/drewritter/.codex/worktrees/59f6/superpowers/tests/brainstorm-server +node lifecycle.test.js +``` + +Expected: all lifecycle tests pass, including fallback token rotation and explicit-token fail-closed. + +- [ ] **Step 8: Commit** + +Run: + +```bash +git add tests/brainstorm-server/lifecycle.test.js skills/brainstorming/scripts/server.cjs +git commit -m "Isolate companion fallback tokens" +``` + +## Task 3: Stop-Server Instance-Id Ownership + +**Files:** +- Modify: `tests/brainstorm-server/stop-server.test.sh` +- Modify: `skills/brainstorming/scripts/start-server.sh` +- Modify: `skills/brainstorming/scripts/stop-server.sh` + +- [ ] **Step 1: Add cleanup tracking and id helpers to stop-server tests** + +In `tests/brainstorm-server/stop-server.test.sh`, after `PASS=0; FAIL=0`, add: + +```bash +PIDS=() +DIRS=() + +cleanup() { + for pid in "${PIDS[@]}"; do + kill -9 "$pid" 2>/dev/null || true + wait "$pid" 2>/dev/null || true + done + for dir in "${DIRS[@]}"; do + rm -rf "$dir" + done +} +trap cleanup EXIT + +track_dir() { DIRS+=("$1"); } +track_pid() { PIDS+=("$1"); } +new_server_id() { + printf 'testid%026d\n' "$RANDOM" +} +``` + +When each test creates a `SESS="$(mktemp -d)"`, immediately add: + +```bash +track_dir "$SESS" +``` + +When a test starts `UNRELATED`, `SRV`, or `IMPOSTOR`, immediately add the +matching tracking call: + +```bash +track_pid "$UNRELATED" +track_pid "$SRV" +track_pid "$IMPOSTOR" +``` + +- [ ] **Step 2: Add RED ownership tests** + +Replace the current real-server and impostor sections with these cases: + +```bash +# --- Test 2: a real brainstorm server with matching instance id IS stopped --- +SESS="$(mktemp -d)"; track_dir "$SESS"; mkdir -p "$SESS/content" "$SESS/state" +SERVER_ID="$(new_server_id)" +printf '%s\n' "$SERVER_ID" > "$SESS/state/server-instance-id" +BRAINSTORM_DIR="$SESS" BRAINSTORM_PORT=3399 node "$SERVER" "--brainstorm-server-id=$SERVER_ID" > /dev/null 2>&1 & +SRV=$! +track_pid "$SRV" +disown "$SRV" 2>/dev/null || true +for _ in $(seq 1 40); do kill -0 "$SRV" 2>/dev/null && break; sleep 0.1; done +sleep 0.4 +echo "$SRV" > "$SESS/state/server.pid" +OUT="$("$STOP" "$SESS")" +sleep 0.3 +if kill -0 "$SRV" 2>/dev/null; then + bad "real brainstorm server still running after stop" "$OUT" +else + case "$OUT" in + *stopped*) ok "real brainstorm server with matching instance id is stopped" ;; + *) bad "server stopped but status was not 'stopped'" "$OUT" ;; + esac +fi + +# --- Test 4: a node server.cjs impostor with missing instance id is spared --- +SESS="$(mktemp -d)"; track_dir "$SESS"; mkdir -p "$SESS/state" +( exec -a "node server.cjs" sleep 600 ) & +IMPOSTOR=$! +track_pid "$IMPOSTOR" +disown "$IMPOSTOR" 2>/dev/null || true +echo "$IMPOSTOR" > "$SESS/state/server.pid" +OUT="$("$STOP" "$SESS")" +if kill -0 "$IMPOSTOR" 2>/dev/null; then + case "$OUT" in + *stale_pid*) ok "missing instance id leaves node server.cjs impostor alone" ;; + *) bad "impostor survived but status was not stale_pid" "$OUT" ;; + esac +else + bad "killed a node server.cjs impostor with missing instance id" "$OUT" +fi + +# --- Test 5: a node server.cjs impostor with wrong instance id is spared --- +SESS="$(mktemp -d)"; track_dir "$SESS"; mkdir -p "$SESS/state" +EXPECTED_ID="$(new_server_id)" +WRONG_ID="$(new_server_id)" +printf '%s\n' "$EXPECTED_ID" > "$SESS/state/server-instance-id" +( exec -a "node server.cjs --brainstorm-server-id=$WRONG_ID" sleep 600 ) & +IMPOSTOR=$! +track_pid "$IMPOSTOR" +disown "$IMPOSTOR" 2>/dev/null || true +echo "$IMPOSTOR" > "$SESS/state/server.pid" +OUT="$("$STOP" "$SESS")" +if kill -0 "$IMPOSTOR" 2>/dev/null; then + case "$OUT" in + *stale_pid*) ok "wrong instance id leaves node server.cjs impostor alone" ;; + *) bad "wrong-id impostor survived but status was not stale_pid" "$OUT" ;; + esac +else + bad "killed a node server.cjs impostor with wrong instance id" "$OUT" +fi + +# --- Test 6: malformed instance id is fail-closed --- +SESS="$(mktemp -d)"; track_dir "$SESS"; mkdir -p "$SESS/state" +printf '%s\n' 'bad id with spaces' > "$SESS/state/server-instance-id" +( exec -a "node server.cjs --brainstorm-server-id=bad-id-with-spaces" sleep 600 ) & +IMPOSTOR=$! +track_pid "$IMPOSTOR" +disown "$IMPOSTOR" 2>/dev/null || true +echo "$IMPOSTOR" > "$SESS/state/server.pid" +OUT="$("$STOP" "$SESS")" +if kill -0 "$IMPOSTOR" 2>/dev/null; then + case "$OUT" in + *stale_pid*) ok "malformed instance id is fail-closed" ;; + *) bad "malformed-id impostor survived but status was not stale_pid" "$OUT" ;; + esac +else + bad "killed process despite malformed instance id" "$OUT" +fi +``` + +Keep the unrelated PID and missing PID tests. + +- [ ] **Step 3: Verify RED** + +Run: + +```bash +cd /Users/drewritter/.codex/worktrees/59f6/superpowers +bash tests/brainstorm-server/stop-server.test.sh +``` + +Expected: matching-instance-id real server is reported `stale_pid` before implementation, and one of the impostor cases may be killed by the old command-name proof. + +- [ ] **Step 4: Generate and pass instance id in start-server** + +In `skills/brainstorming/scripts/start-server.sh`, after `LOG_FILE="${STATE_DIR}/server.log"`, add: + +```bash +SERVER_ID_FILE="${STATE_DIR}/server-instance-id" +``` + +After `mkdir -p "${SESSION_DIR}/content" "$STATE_DIR"`, add: + +```bash +SERVER_ID="" +if [[ -r /dev/urandom ]]; then + SERVER_ID="$(od -An -N24 -tx1 /dev/urandom 2>/dev/null | tr -d ' \n' || true)" +fi +if ! [[ "$SERVER_ID" =~ ^[A-Za-z0-9_-]{32,64}$ ]]; then + SERVER_ID="$(printf '%08x%08x%08x%08x' "$$" "$(date +%s)" "${RANDOM:-0}" "${RANDOM:-0}")" +fi +printf '%s\n' "$SERVER_ID" > "$SERVER_ID_FILE" +chmod 600 "$SERVER_ID_FILE" 2>/dev/null || true +``` + +Update both Node launch commands to pass the argv: + +```bash +env BRAINSTORM_DIR="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs "--brainstorm-server-id=$SERVER_ID" & +``` + +and: + +```bash +nohup env BRAINSTORM_DIR="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs "--brainstorm-server-id=$SERVER_ID" > "$LOG_FILE" 2>&1 & +``` + +- [ ] **Step 5: Require instance id in stop-server** + +In `skills/brainstorming/scripts/stop-server.sh`, add: + +```bash +SERVER_ID_FILE="${STATE_DIR}/server-instance-id" +``` + +Replace `is_brainstorm_server()` with: + +```bash +read_expected_server_id() { + [[ -f "$SERVER_ID_FILE" ]] || return 1 + local id + id="$(tr -d '\r\n' < "$SERVER_ID_FILE" 2>/dev/null || true)" + [[ "$id" =~ ^[A-Za-z0-9_-]{32,64}$ ]] || return 1 + printf '%s\n' "$id" +} + +command_line_for_pid() { + local pid="$1" + if [[ -r "/proc/$pid/cmdline" ]]; then + tr '\0' '\n' < "/proc/$pid/cmdline" 2>/dev/null || true + return 0 + fi + ps -ww -p "$pid" -o command= 2>/dev/null || ps -f -p "$pid" 2>/dev/null | sed '1d' || true +} + +command_has_server_id() { + local pid="$1" + local expected="$2" + local expected_arg="--brainstorm-server-id=$expected" + if [[ -r "/proc/$pid/cmdline" ]]; then + local arg + while IFS= read -r -d '' arg; do + [[ "$arg" == "$expected_arg" ]] && return 0 + done < "/proc/$pid/cmdline" + return 1 + fi + local command_line + command_line="$(command_line_for_pid "$pid")" + [[ -n "$command_line" ]] || return 1 + case " $command_line " in + *" $expected_arg "*) return 0 ;; + *) return 1 ;; + esac +} + +is_brainstorm_server() { + kill -0 "$1" 2>/dev/null || return 1 + local expected_id + expected_id="$(read_expected_server_id)" || return 1 + command_has_server_id "$1" "$expected_id" || return 1 + return 0 +} +``` + +In the stale PID branch, remove both metadata files: + +```bash + rm -f "$PID_FILE" "$SERVER_ID_FILE" +``` + +In the stopped branch, change the cleanup line to: + +```bash + rm -f "$PID_FILE" "$SERVER_ID_FILE" "${STATE_DIR}/server.log" +``` + +- [ ] **Step 6: Verify GREEN** + +Run: + +```bash +cd /Users/drewritter/.codex/worktrees/59f6/superpowers +bash tests/brainstorm-server/stop-server.test.sh +``` + +Expected: real matching-id server stops, impostors survive, and all stale cases return `stale_pid`. + +- [ ] **Step 7: Commit** + +Run: + +```bash +git add tests/brainstorm-server/stop-server.test.sh skills/brainstorming/scripts/start-server.sh skills/brainstorming/scripts/stop-server.sh +git commit -m "Harden companion stop ownership proof" +``` + +## Task 4: Platform And Fixed-Port Test Hardening + +**Files:** +- Modify: `tests/brainstorm-server/auth.test.js` +- Modify: `tests/brainstorm-server/start-server.test.sh` +- Modify: `tests/brainstorm-server/windows-lifecycle.test.sh` + +- [ ] **Step 1: Add fixed-port guard to auth tests** + +In `tests/brainstorm-server/auth.test.js`, add this helper after `waitForServer()`: + +```js +function serverStartedMessage(out) { + const line = out.trim().split('\n').find(l => l.includes('server-started')); + assert(line, 'server-started JSON should be present'); + return JSON.parse(line); +} + +function assertStartedOnExpectedPort(out) { + const msg = serverStartedMessage(out); + assert.strictEqual( + msg.port, + TEST_PORT, + `auth.test.js expected fixed port ${TEST_PORT}, got ${msg.port}; fixed-port tests must not run through fallback` + ); + return msg; +} +``` + +After `const { stdout: initialStdout } = await waitForServer(server);`, add: + +```js + assertStartedOnExpectedPort(initialStdout); +``` + +- [ ] **Step 2: Verify auth fixed-port guard** + +Run: + +```bash +cd /Users/drewritter/.codex/worktrees/59f6/superpowers/tests/brainstorm-server +node auth.test.js +``` + +Expected: auth tests pass on a free `3335`, and would fail clearly if fallback occurred. + +- [ ] **Step 3: Add start-server id argv assertion** + +In `tests/brainstorm-server/start-server.test.sh`, change the first fake node body to: + +```bash +cat > "$TEST_DIR/fake-bin/node" <<'EOF' +#!/usr/bin/env bash +echo "CAPTURED_OWNER_PID=${BRAINSTORM_OWNER_PID:-__UNSET__}" +echo "CAPTURED_ARGV=$*" +exit 0 +EOF +``` + +After the owner PID assertion, add: + +```bash +captured_argv=$(echo "$captured" | grep "CAPTURED_ARGV=" | head -1 | sed 's/CAPTURED_ARGV=//') +if echo "$captured_argv" | grep -Eq -- '--brainstorm-server-id=[A-Za-z0-9_-]{32,64}'; then + pass "passes shell-safe server instance id argv" +else + fail "passes shell-safe server instance id argv" \ + "expected --brainstorm-server-id=, got: $captured_argv" +fi + +server_id_file=$(find "$TEST_DIR/project/.superpowers/brainstorm" -name server-instance-id -print 2>/dev/null | head -1) +server_id_value="" +if [[ -n "$server_id_file" ]]; then + server_id_value="$(tr -d '\r\n' < "$server_id_file")" +fi +if [[ "$server_id_value" =~ ^[A-Za-z0-9_-]{32,64}$ ]]; then + pass "writes shell-safe server-instance-id state file" +else + fail "writes shell-safe server-instance-id state file" \ + "expected valid id in state, got '$server_id_value'" +fi +``` + +- [ ] **Step 4: Add Windows lifecycle id argv assertions** + +In `tests/brainstorm-server/windows-lifecycle.test.sh`, change the Test 2 fake node body to: + +```bash +cat > "$FAKE_NODE_DIR/node" <<'FAKENODE' +#!/usr/bin/env bash +echo "CAPTURED_OWNER_PID=${BRAINSTORM_OWNER_PID:-__UNSET__}" +echo "CAPTURED_ARGV=$*" +exit 0 +FAKENODE +``` + +After the owner PID check in Test 2, add: + +```bash +captured_argv=$(echo "$captured" | grep "CAPTURED_ARGV=" | head -1 | sed 's/CAPTURED_ARGV=//') +if echo "$captured_argv" | grep -Eq -- '--brainstorm-server-id=[A-Za-z0-9_-]{32,64}'; then + pass "start-server.sh passes server instance id argv on Windows" +else + fail "start-server.sh passes server instance id argv on Windows" \ + "Expected --brainstorm-server-id=, output: $captured" +fi +``` + +In Test 6, before launching direct Node, add: + +```bash +STOP_TEST_ID="$(printf 'windowsstop%021d\n' "$RANDOM")" +printf '%s\n' "$STOP_TEST_ID" > "$TEST_DIR/stop-test/state/server-instance-id" +``` + +Change the direct Node launch in Test 6 to: + +```bash + node "$SERVER_SCRIPT" "--brainstorm-server-id=$STOP_TEST_ID" > "$TEST_DIR/stop-test/.server.log" 2>&1 & +``` + +- [ ] **Step 5: Verify platform tests** + +Run: + +```bash +cd /Users/drewritter/.codex/worktrees/59f6/superpowers +bash tests/brainstorm-server/start-server.test.sh +``` + +Expected: all start-server shell tests pass on macOS. + +Run the Windows lifecycle test later on `ballmer` as part of Task 6. + +- [ ] **Step 6: Commit** + +Run: + +```bash +git add tests/brainstorm-server/auth.test.js tests/brainstorm-server/start-server.test.sh tests/brainstorm-server/windows-lifecycle.test.sh +git commit -m "Harden companion platform tests" +``` + +## Task 5: Docs And PR Consistency + +**Files:** +- Modify: `skills/brainstorming/visual-companion.md` +- Modify: `docs/superpowers/plans/2026-06-09-visual-companion-issues.md` +- Update: PR #1720 body through `gh pr edit` + +- [ ] **Step 1: Keep platform start commands aligned with auto-open behavior** + +In `skills/brainstorming/visual-companion.md`, update platform-specific commands that start a user-approved companion session so they include `--open`: + +```bash +scripts/start-server.sh --project-dir /path/to/project --open +``` + +```bash +scripts/start-server.sh --project-dir /path/to/project --open --foreground +``` + +Do not add `--open` to remote bind examples where auto-open is intentionally skipped. + +- [ ] **Step 2: Reconcile issue catalog disposition rows** + +In `docs/superpowers/plans/2026-06-09-visual-companion-issues.md`, replace the disposition rows for A2, D1, D2, D3, and D4 with: + +```markdown +| A2 | Host allowlist; browser WS Origin check | PRs #1110/#1553 | Host allowlist dropped; WS Origin check retained after auth for browser confused-deputy defense | +| D1 | Permanent opt-out of the companion | issue #892 | Deferred - not in PR #1720 | +| D2 | Free-text feedback from the browser | issue #957 | Deferred - not in PR #1720 | +| D3 | Auto-open the companion URL | PR #759 (#755) | Done in PR #1720 via `--open` | +| D4 | Light/dark contrast helpers in the frame | PR #1683 | Deferred - not in PR #1720 | +``` + +- [ ] **Step 3: Reconcile A2 detail text** + +Replace the final sentence in the A2 section with: + +```markdown +No `BRAINSTORM_ALLOWED_HOSTS` and no Host allowlist. The final implementation still checks browser WebSocket `Origin` after session auth so a cross-origin localhost tab cannot ride the companion cookie. +``` + +- [ ] **Step 4: Reconcile timeout and feature grouping text** + +In the C1 section, replace: + +```markdown +- Raise the default (about 2h) and make it configurable: +``` + +with: + +```markdown +- Raise the default to 4 hours and make it configurable: +``` + +In the suggested grouping section, replace item 4 with: + +```markdown +4. **Deferred feature pass** - D1, D2, D4 are not part of PR #1720. D3 is shipped through the `--open` flow. +``` + +- [ ] **Step 5: Verify docs diff** + +Run: + +```bash +git diff -- skills/brainstorming/visual-companion.md docs/superpowers/plans/2026-06-09-visual-companion-issues.md +``` + +Expected: diff only updates auto-open command consistency, shipped/deferred dispositions, WS Origin wording, and the 4 hour timeout statement. + +- [ ] **Step 6: Commit** + +Run: + +```bash +git add skills/brainstorming/visual-companion.md docs/superpowers/plans/2026-06-09-visual-companion-issues.md +git commit -m "Align visual companion docs with shipped scope" +``` + +## Task 6: Full Verification And Evidence + +**Files:** +- No required source edits +- Update: PR #1720 body + +- [ ] **Step 1: Run focused macOS checks** + +Run: + +```bash +cd /Users/drewritter/.codex/worktrees/59f6/superpowers/tests/brainstorm-server +node server.test.js +node auth.test.js +node lifecycle.test.js +bash stop-server.test.sh +bash start-server.test.sh +``` + +Expected: all focused tests pass; symlink-only tests may report skipped only when host support is unavailable. + +- [ ] **Step 2: Run full macOS test suite** + +Run: + +```bash +cd /Users/drewritter/.codex/worktrees/59f6/superpowers/tests/brainstorm-server +npm test +``` + +Expected: full brainstorm-server test suite passes. + +- [ ] **Step 3: Run static checks** + +Run from repo root: + +```bash +git diff --check +node --check skills/brainstorming/scripts/server.cjs +node --check skills/brainstorming/scripts/helper.js +bash scripts/lint-shell.sh skills/brainstorming/scripts/start-server.sh skills/brainstorming/scripts/stop-server.sh tests/brainstorm-server/start-server.test.sh tests/brainstorm-server/stop-server.test.sh tests/brainstorm-server/windows-lifecycle.test.sh +``` + +Expected: all commands exit 0. + +- [ ] **Step 4: Run Windows validation on ballmer** + +Copy or fetch the rebased branch on `ballmer`, then run: + +```bash +cd superpowers +npm --prefix tests/brainstorm-server ci +npm --prefix tests/brainstorm-server test +bash tests/brainstorm-server/windows-lifecycle.test.sh +``` + +Expected: full runnable Windows suite passes. If Git Bash lacks `lsof`, only the lsof-specific legacy port-cross-check test may skip; instance-id stop tests must still pass. + +- [ ] **Step 5: Verify PR diff and GitHub state** + +Run: + +```bash +git diff --quiet origin/dev...HEAD -- evals +gh pr view 1720 --json mergeStateStatus,statusCheckRollup,headRefOid +``` + +Expected: first command exits 0. PR JSON no longer reports `DIRTY` or `CONFLICTING` after the branch is pushed. + +- [ ] **Step 6: Collect external eval evidence** + +Run: + +```bash +git -C /Users/drewritter/.codex/worktrees/59f6/superpowers-evals rev-parse HEAD +git -C /Users/drewritter/.codex/worktrees/59f6/superpowers-evals status --short --branch +``` + +If the eval worktree is not at that path, run the same commands in `/Users/drewritter/prime-rad/superpowers-evals`. + +Record the exact eval scenario path, command, result artifact path, and RED/GREEN outcome from the already-run eval evidence. Do not claim the eval submodule is included in PR #1720. + +- [ ] **Step 7: Run final manual/browser smoke** + +After automated tests are green, start the companion with `--open`, push a small screen, verify the browser reaches a bare `/` URL after bootstrap, verify status reaches Connected, stop and restart the server with the same project dir, and verify the open tab reconnects. Record the exact commands and observed result. + +- [ ] **Step 8: Update PR body** + +Prepare `/tmp/pr-1720-body.md`, then run `gh pr edit 1720 --body-file /tmp/pr-1720-body.md` after the body includes: + +- model, harness, plugins, and Drew as human reviewer +- duplicate/related PR search results +- exact post-rebase note that `evals` is absent from this PR diff +- focused RED/GREEN evidence table +- macOS `npm test` evidence +- Windows `ballmer` evidence +- manual/browser smoke evidence +- external eval repo commit, scenario path, command, artifact path, and outcome + +- [ ] **Step 9: Push branch** + +Run: + +```bash +git status --short --branch +git push origin brainstorming-companion +``` + +Expected: push succeeds and PR #1720 updates. + +- [ ] **Step 10: Final PR readiness check** + +Run: + +```bash +gh pr view 1720 --json mergeStateStatus,statusCheckRollup,headRefOid,url +``` + +Expected: PR points at the pushed head SHA, merge state is no longer conflict-blocked, and check status is recorded for Drew. + +## Self-Review Checklist + +- [ ] Every requirement in `docs/superpowers/specs/2026-06-11-visual-companion-final-hardening-fixup-design.md` maps to one of the tasks above. +- [ ] The plan contains no vague or incomplete steps. +- [ ] Tests are added before production fixes in Tasks 1, 2, and 3. +- [ ] The docs task does not add deferred features. +- [ ] The verification task includes macOS, Windows, PR diff, PR metadata, external eval evidence, and final manual/browser smoke. diff --git a/docs/superpowers/specs/2026-01-22-document-review-system-design.md b/docs/superpowers/specs/2026-01-22-document-review-system-design.md new file mode 100644 index 0000000..f8f611e --- /dev/null +++ b/docs/superpowers/specs/2026-01-22-document-review-system-design.md @@ -0,0 +1,136 @@ +# Document Review System Design + +## Overview + +Add two new review stages to the superpowers workflow: + +1. **Spec Document Review** - After brainstorming, before writing-plans +2. **Plan Document Review** - After writing-plans, before implementation + +Both follow the iterative loop pattern used by implementation reviews. + +## Spec Document Reviewer + +**Purpose:** Verify the spec is complete, consistent, and ready for implementation planning. + +**Location:** `skills/brainstorming/spec-document-reviewer-prompt.md` + +**What it checks for:** + +| Category | What to Look For | +|----------|------------------| +| Completeness | TODOs, placeholders, "TBD", incomplete sections | +| Coverage | Missing error handling, edge cases, integration points | +| Consistency | Internal contradictions, conflicting requirements | +| Clarity | Ambiguous requirements | +| YAGNI | Unrequested features, over-engineering | + +**Output format:** +``` +## Spec Review + +**Status:** Approved | Issues Found + +**Issues (if any):** +- [Section X]: [issue] - [why it matters] + +**Recommendations (advisory):** +- [suggestions that don't block approval] +``` + +**Review loop:** Issues found -> brainstorming agent fixes -> re-review -> repeat until approved. + +**Dispatch mechanism:** Use the Task tool with `subagent_type: general-purpose`. The reviewer prompt template provides the full prompt. The brainstorming skill's controller dispatches the reviewer. + +## Plan Document Reviewer + +**Purpose:** Verify the plan is complete, matches the spec, and has proper task decomposition. + +**Location:** `skills/writing-plans/plan-document-reviewer-prompt.md` + +**What it checks for:** + +| Category | What to Look For | +|----------|------------------| +| Completeness | TODOs, placeholders, incomplete tasks | +| Spec Alignment | Plan covers spec requirements, no scope creep | +| Task Decomposition | Tasks atomic, clear boundaries | +| Task Syntax | Checkbox syntax on tasks and steps | +| Chunk Size | Each chunk under 1000 lines | + +**Chunk definition:** A chunk is a logical grouping of tasks within the plan document, delimited by `## Chunk N: ` headings. The writing-plans skill creates these boundaries based on logical phases (e.g., "Foundation", "Core Features", "Integration"). Each chunk should be self-contained enough to review independently. + +**Spec alignment verification:** The reviewer receives both: +1. The plan document (or current chunk) +2. The path to the spec document for reference + +The reviewer reads both and compares requirements coverage. + +**Output format:** Same as spec reviewer, but scoped to the current chunk. + +**Review process (chunk-by-chunk):** +1. Writing-plans creates chunk N +2. Controller dispatches plan-document-reviewer with chunk N content and spec path +3. Reviewer reads chunk and spec, returns verdict +4. If issues: writing-plans agent fixes chunk N, goto step 2 +5. If approved: proceed to chunk N+1 +6. Repeat until all chunks approved + +**Dispatch mechanism:** Same as spec reviewer - Task tool with `subagent_type: general-purpose`. + +## Updated Workflow + +``` +brainstorming -> spec -> SPEC REVIEW LOOP -> writing-plans -> plan -> PLAN REVIEW LOOP -> implementation +``` + +**Spec Review Loop:** +1. Spec complete +2. Dispatch reviewer +3. If issues: fix -> goto 2 +4. If approved: proceed + +**Plan Review Loop:** +1. Chunk N complete +2. Dispatch reviewer for chunk N +3. If issues: fix -> goto 2 +4. If approved: next chunk or implementation + +## Markdown Task Syntax + +Tasks and steps use checkbox syntax: + +```markdown +- [ ] ### Task 1: Name + +- [ ] **Step 1:** Description + - File: path + - Command: cmd +``` + +## Error Handling + +**Review loop termination:** +- No hard iteration limit - loops continue until reviewer approves +- If loop exceeds 5 iterations, the controller should surface this to the human for guidance +- The human can choose to: continue iterating, approve with known issues, or abort + +**Disagreement handling:** +- Reviewers are advisory - they flag issues but don't block +- If the agent believes reviewer feedback is incorrect, it should explain why in its fix +- If disagreement persists after 3 iterations on the same issue, surface to human + +**Malformed reviewer output:** +- Controller should validate reviewer output has required fields (Status, Issues if applicable) +- If malformed, re-dispatch reviewer with a note about expected format +- After 2 malformed responses, surface to human + +## Files to Change + +**New files:** +- `skills/brainstorming/spec-document-reviewer-prompt.md` +- `skills/writing-plans/plan-document-reviewer-prompt.md` + +**Modified files:** +- `skills/brainstorming/SKILL.md` - add review loop after spec written +- `skills/writing-plans/SKILL.md` - add chunk-by-chunk review loop, update task syntax examples diff --git a/docs/superpowers/specs/2026-02-19-visual-brainstorming-refactor-design.md b/docs/superpowers/specs/2026-02-19-visual-brainstorming-refactor-design.md new file mode 100644 index 0000000..46f88bf --- /dev/null +++ b/docs/superpowers/specs/2026-02-19-visual-brainstorming-refactor-design.md @@ -0,0 +1,162 @@ +# Visual Brainstorming Refactor: Browser Displays, Terminal Commands + +**Date:** 2026-02-19 +**Status:** Approved +**Scope:** `lib/brainstorm-server/`, `skills/brainstorming/visual-companion.md`, `tests/brainstorm-server/` + +## Problem + +During visual brainstorming, Claude runs `wait-for-feedback.sh` as a background task and blocks on `TaskOutput(block=true, timeout=600s)`. This seizes the TUI entirely — the user cannot type to Claude while visual brainstorming is running. The browser becomes the only input channel. + +Claude Code's execution model is turn-based. There is no way for Claude to listen on two channels simultaneously within a single turn. The blocking `TaskOutput` pattern was the wrong primitive — it simulates event-driven behavior the platform doesn't support. + +## Design + +### Core Model + +**Browser = interactive display.** Shows mockups, lets the user click to select options. Selections are recorded server-side. + +**Terminal = conversation channel.** Always unblocked, always available. The user talks to Claude here. + +### The Loop + +1. Claude writes an HTML file to the session directory +2. Server detects it via chokidar, pushes WebSocket reload to the browser (unchanged) +3. Claude ends its turn — tells the user to check the browser and respond in the terminal +4. User looks at browser, optionally clicks to select an option, then types feedback in the terminal +5. On the next turn, Claude reads `$SCREEN_DIR/.events` for the browser interaction stream (clicks, selections), merges with the terminal text +6. Iterate or advance + +No background tasks. No `TaskOutput` blocking. No polling scripts. + +### Key Deletion: `wait-for-feedback.sh` + +Deleted entirely. Its purpose was to bridge "server logs events to stdout" and "Claude needs to receive those events." The `.events` file replaces this — the server writes user interaction events directly, and Claude reads them with whatever file-reading mechanism the platform provides. + +### Key Addition: `.events` File (Per-Screen Event Stream) + +The server writes all user interaction events to `$SCREEN_DIR/.events`, one JSON object per line. This gives Claude the full interaction stream for the current screen — not just the final selection, but the user's exploration path (clicked A, then B, settled on C). + +Example contents after a user explores options: + +```jsonl +{"type":"click","choice":"a","text":"Option A - Preset-First Wizard","timestamp":1706000101} +{"type":"click","choice":"c","text":"Option C - Manual Config","timestamp":1706000108} +{"type":"click","choice":"b","text":"Option B - Hybrid Approach","timestamp":1706000115} +``` + +- Append-only within a screen. Each user event is appended as a new line. +- The file is cleared (deleted) when chokidar detects a new HTML file (new screen pushed), preventing stale events from carrying over. +- If the file doesn't exist when Claude reads it, no browser interaction occurred — Claude uses only the terminal text. +- The file contains only user events (`click`, etc.) — not server lifecycle events (`server-started`, `screen-added`). This keeps it small and focused. +- Claude can read the full stream to understand the user's exploration pattern, or just look at the last `choice` event for the final selection. + +## Changes by File + +### `index.js` (server) + +**A. Write user events to `.events` file.** + +In the WebSocket `message` handler, after logging the event to stdout: append the event as a JSON line to `$SCREEN_DIR/.events` via `fs.appendFileSync`. Only write user interaction events (those with `source: 'user-event'`), not server lifecycle events. + +**B. Clear `.events` on new screen.** + +In the chokidar `add` handler (new `.html` file detected), delete `$SCREEN_DIR/.events` if it exists. This is the definitive "new screen" signal — better than clearing on GET `/` which fires on every reload. + +**C. Replace `wrapInFrame` content injection.** + +The current regex anchors on `