supipowers 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +1 -1
  2. package/bin/ctx-mode-wrapper.mjs +3 -3
  3. package/package.json +8 -22
  4. package/skills/planning/SKILL.md +2 -4
  5. package/skills/release/SKILL.md +5 -2
  6. package/src/bootstrap.ts +16 -33
  7. package/src/commands/commit.ts +44 -0
  8. package/src/commands/config.ts +0 -27
  9. package/src/commands/context.ts +117 -0
  10. package/src/commands/doctor.ts +1 -2
  11. package/src/commands/fix-pr.ts +16 -42
  12. package/src/commands/mcp.ts +121 -3
  13. package/src/commands/plan.ts +5 -1
  14. package/src/commands/release.ts +327 -32
  15. package/src/commands/review.ts +1 -1
  16. package/src/commands/status.ts +9 -26
  17. package/src/commands/supi.ts +2 -5
  18. package/src/config/defaults.ts +0 -19
  19. package/src/config/model-config.ts +0 -19
  20. package/src/context/analyzer.ts +316 -0
  21. package/src/deps/registry.ts +2 -4
  22. package/src/fix-pr/bot-detector.ts +41 -0
  23. package/src/fix-pr/scripts/fetch-pr-comments.sh +2 -2
  24. package/src/fix-pr/types.ts +1 -0
  25. package/src/git/commit-msg-hook.ts +21 -0
  26. package/src/git/commit-msg.ts +53 -0
  27. package/src/git/commit.ts +659 -0
  28. package/src/git/conventions.ts +215 -0
  29. package/src/git/status.ts +27 -0
  30. package/src/global.d.ts +5 -0
  31. package/src/index.ts +1 -7
  32. package/src/mcp/config.ts +80 -1
  33. package/src/mcp/docs.ts +1 -1
  34. package/src/mcp/types.ts +14 -0
  35. package/src/planning/approval-flow.ts +166 -0
  36. package/src/planning/plan-writer-prompt.ts +6 -5
  37. package/src/planning/prompt-builder.ts +4 -3
  38. package/src/platform/detect.ts +4 -6
  39. package/src/platform/omp.ts +15 -15
  40. package/src/platform/test-utils.ts +17 -17
  41. package/src/platform/types.ts +6 -6
  42. package/src/release/changelog.ts +30 -20
  43. package/src/release/commit-types.ts +35 -0
  44. package/src/release/executor.ts +63 -25
  45. package/src/release/prompt.ts +1 -1
  46. package/src/release/version.ts +27 -0
  47. package/src/storage/plans.ts +2 -12
  48. package/src/types.ts +7 -55
  49. package/src/commands/run.ts +0 -511
  50. package/src/orchestrator/agent-grid.ts +0 -439
  51. package/src/orchestrator/agent-prompts.ts +0 -290
  52. package/src/orchestrator/batch-scheduler.ts +0 -59
  53. package/src/orchestrator/conflict-resolver.ts +0 -39
  54. package/src/orchestrator/dispatcher.ts +0 -666
  55. package/src/orchestrator/progress-renderer.ts +0 -281
  56. package/src/orchestrator/prompts.ts +0 -149
  57. package/src/orchestrator/result-collector.ts +0 -72
  58. package/src/orchestrator/run-progress.ts +0 -95
  59. package/src/platform/pi.ts +0 -61
  60. package/src/storage/runs.ts +0 -126
  61. package/src/types/bun-sqlite.d.ts +0 -24
package/README.md CHANGED
@@ -283,7 +283,7 @@ bun run test:watch # watch mode
283
283
  bun run build # emit to dist/
284
284
  ```
285
285
 
286
- The test suite mirrors the `src/` structure under `tests/`. Tests use Vitest with global imports and inline mocks (no module-level `vi.mock` calls). Filesystem tests use temp directories created in `beforeEach`.
286
+ The test suite mirrors the `src/` structure under `tests/`. Tests use Bun's built-in test runner (`bun:test`) with inline mocks via `mock()`. Filesystem tests use temp directories created in `beforeEach`.
287
287
 
288
288
  > [!NOTE]
289
289
  > Supipowers works with both Pi and OMP. The platform abstraction in `src/platform/` normalizes API differences (event names, tool registration, message delivery) so you can develop against either agent.
@@ -6,7 +6,7 @@
6
6
  //
7
7
  // Setting CLAUDE_PROJECT_DIR causes start.mjs to also write a CLAUDE.md
8
8
  // in the project root. This is harmless (OMP doesn't read it) but the
9
- // user should gitignore it. We also write .omp/SYSTEM.md with routing
9
+ // user should gitignore it. We also write .omp/APPEND_SYSTEM.md with routing
10
10
  // rules since that's what OMP actually loads as system prompt.
11
11
 
12
12
  import { resolve, join, dirname } from "node:path";
@@ -32,7 +32,7 @@ if (!startMjs) {
32
32
  process.exit(1);
33
33
  }
34
34
 
35
- // Write .omp/SYSTEM.md with routing rules (idempotent — only if marker absent)
35
+ // Write .omp/APPEND_SYSTEM.md with routing rules (idempotent — only if marker absent)
36
36
  const ROUTING_MARKER = "# context-mode — MANDATORY routing rules";
37
37
  try {
38
38
  // Find SKILL.md from multiple possible locations
@@ -47,7 +47,7 @@ try {
47
47
 
48
48
  if (skillContent && skillContent.includes(ROUTING_MARKER)) {
49
49
  const ompDir = join(projectDir, ".omp");
50
- const systemMdPath = join(ompDir, "SYSTEM.md");
50
+ const systemMdPath = join(ompDir, "APPEND_SYSTEM.md");
51
51
 
52
52
  if (!existsSync(ompDir)) mkdirSync(ompDir, { recursive: true });
53
53
 
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "supipowers",
3
- "version": "1.1.0",
4
- "description": "Workflow extension for Pi and OMP coding agents.",
3
+ "version": "1.2.0",
4
+ "description": "Workflow extension for OMP coding agents.",
5
5
  "type": "module",
6
6
  "scripts": {
7
- "test": "vitest run",
7
+ "test": "bun test tests/",
8
8
  "typecheck": "tsc --noEmit",
9
- "test:watch": "vitest",
10
- "build": "tsc -p tsconfig.build.json"
9
+ "test:watch": "bun test --watch tests/",
10
+ "build": "tsc -p tsconfig.build.json",
11
+ "prepare": "git config core.hooksPath hooks || true"
11
12
  },
12
13
  "keywords": [
13
- "pi-extension",
14
14
  "omp-extension",
15
15
  "workflow",
16
16
  "agent",
@@ -27,14 +27,6 @@
27
27
  "README.md",
28
28
  "LICENSE"
29
29
  ],
30
- "pi": {
31
- "extensions": [
32
- "./src/index.ts"
33
- ],
34
- "skills": [
35
- "./skills"
36
- ]
37
- },
38
30
  "omp": {
39
31
  "extensions": [
40
32
  "./src/index.ts"
@@ -47,14 +39,10 @@
47
39
  "@clack/prompts": "^0.10.0"
48
40
  },
49
41
  "peerDependencies": {
50
- "@mariozechner/pi-coding-agent": "*",
51
42
  "@oh-my-pi/pi-coding-agent": "*",
52
43
  "@sinclair/typebox": "*"
53
44
  },
54
45
  "peerDependenciesMeta": {
55
- "@mariozechner/pi-coding-agent": {
56
- "optional": true
57
- },
58
46
  "@oh-my-pi/pi-coding-agent": {
59
47
  "optional": true
60
48
  },
@@ -67,9 +55,7 @@
67
55
  "@oh-my-pi/pi-tui": "latest",
68
56
  "@sinclair/typebox": "^0.34.48",
69
57
  "@types/node": "^22.0.0",
70
- "better-sqlite3": "^12.8.0",
71
- "@types/better-sqlite3": "^7.6.13",
72
- "typescript": "^5.9.3",
73
- "vitest": "^4.0.0"
58
+ "bun-types": "^1.3.11",
59
+ "typescript": "^5.9.3"
74
60
  }
75
61
  }
@@ -74,7 +74,7 @@ Wait for their response. Only proceed once approved.
74
74
 
75
75
  Break into bite-sized tasks (2-5 minutes each). Each task must have:
76
76
 
77
- - Name with parallelism: `[parallel-safe]` or `[sequential: depends on N]`
77
+ - Name
78
78
  - **files**: Exact paths the agent will touch
79
79
  - **criteria**: Acceptance criteria (testable)
80
80
  - **complexity**: `small` | `medium` | `large`
@@ -97,7 +97,7 @@ tags: [<relevant>, <tags>]
97
97
 
98
98
  ## Tasks
99
99
 
100
- ### 1. <Task name> [parallel-safe]
100
+ ### 1. <Task name>
101
101
  - **files**: src/path/to/file.ts
102
102
  - **criteria**: <what success looks like>
103
103
  - **complexity**: small
@@ -112,8 +112,6 @@ tags: [<relevant>, <tags>]
112
112
  ## Principles
113
113
 
114
114
  - Each task should be completable in 2-5 minutes
115
- - Tasks that touch different files are parallel-safe
116
- - Tasks that depend on others' output are sequential
117
115
  - Include test files in the files list
118
116
  - Prefer small, focused tasks over large ones
119
117
  - DRY, YAGNI, TDD, frequent commits
@@ -22,10 +22,13 @@ If multiple commits touch the same feature or area, consolidate them into a sing
22
22
 
23
23
  ### Preserve section structure
24
24
 
25
- Maintain the three sections in this order:
25
+ Maintain the six sections in this order:
26
26
  1. **Breaking Changes** — anything that requires user action on upgrade
27
27
  2. **Features** — new capabilities
28
28
  3. **Fixes** — bugs resolved
29
+ 4. **Improvements** — refactors, performance gains, reverts
30
+ 5. **Maintenance** — chores, CI, build, tests, docs, style
31
+ 6. **Other** — non-conventional commits
29
32
 
30
33
  If a section has no entries, omit it entirely.
31
34
 
@@ -39,7 +42,7 @@ Every entry must retain its original commit hash(es) for traceability. Format: `
39
42
  - Do not invent features or fixes not represented in the provided commits
40
43
  - Do not skip the confirmation step before executing commands
41
44
  - Do not modify the release commands provided — execute them exactly as given
42
- - Do not reorder sections (Breaking > Features > Fixes)
45
+ - Do not reorder sections (Breaking > Features > Fixes > Improvements > Maintenance > Other)
43
46
 
44
47
  ## Confirmation Flow
45
48
 
package/src/bootstrap.ts CHANGED
@@ -7,29 +7,28 @@ import { registerConfigCommand, handleConfig } from "./commands/config.js";
7
7
  import { registerStatusCommand, handleStatus } from "./commands/status.js";
8
8
  import { registerPlanCommand, getActiveVisualSessionDir, setActiveVisualSessionDir } from "./commands/plan.js";
9
9
  import { getScriptsDir } from "./visual/companion.js";
10
- import { registerRunCommand } from "./commands/run.js";
11
10
  import { registerReviewCommand } from "./commands/review.js";
12
11
  import { registerQaCommand } from "./commands/qa.js";
13
- import { registerReleaseCommand } from "./commands/release.js";
12
+ import { registerReleaseCommand, handleRelease } from "./commands/release.js";
14
13
  import { registerUpdateCommand, handleUpdate } from "./commands/update.js";
15
14
  import { registerDoctorCommand, handleDoctor } from "./commands/doctor.js";
16
15
  import { registerMcpCommand, handleMcp, handleMcpCli, parseCliArgs } from "./commands/mcp.js";
17
16
  import { registerModelCommand, handleModel } from "./commands/model.js";
18
17
  import { executeManagerAction } from "./mcp/manager-tool.js";
19
18
  import { registerFixPrCommand } from "./commands/fix-pr.js";
19
+ import { registerContextCommand, handleContext } from "./commands/context.js";
20
+ import { registerCommitCommand, handleCommit } from "./commands/commit.js";
20
21
  import { loadConfig } from "./config/loader.js";
21
- import { migrateModelPreference } from "./config/model-config.js";
22
22
  import { registerContextModeHooks } from "./context-mode/hooks.js";
23
- import { registerProgressRenderer } from "./orchestrator/progress-renderer.js";
24
- import { activeRuns } from "./orchestrator/run-progress.js";
25
23
  import { loadMcpRegistry } from "./mcp/config.js";
26
24
  import { McpcClient } from "./mcp/mcpc.js";
27
25
  import { parseTags, computeActiveServers } from "./mcp/activation.js";
28
26
  import { initializeMcpServers, shutdownMcpServers } from "./mcp/lifecycle.js";
27
+ import { registerPlanApprovalHook } from "./planning/approval-flow.js";
29
28
 
30
29
  // TUI-only commands — intercepted at the input level to prevent
31
30
  // message submission and "Working..." indicator
32
- const TUI_COMMANDS: Record<string, (platform: Platform, ctx: any) => void> = {
31
+ const TUI_COMMANDS: Record<string, (platform: Platform, ctx: any, args?: string) => void> = {
33
32
  "supi": (platform, ctx) => handleSupi(platform, ctx),
34
33
  "supi:config": (platform, ctx) => handleConfig(platform, ctx),
35
34
  "supi:status": (platform, ctx) => handleStatus(platform, ctx),
@@ -37,6 +36,9 @@ const TUI_COMMANDS: Record<string, (platform: Platform, ctx: any) => void> = {
37
36
  "supi:doctor": (platform, ctx) => handleDoctor(platform, ctx),
38
37
  "supi:mcp": (platform, ctx) => handleMcp(platform, ctx),
39
38
  "supi:model": (platform, ctx) => handleModel(platform, ctx),
39
+ "supi:context": (platform, ctx) => handleContext(platform, ctx),
40
+ "supi:commit": (platform, ctx, args) => handleCommit(platform, ctx, args),
41
+ "supi:release": (platform, ctx, args) => handleRelease(platform, ctx, args),
40
42
  };
41
43
 
42
44
  let pendingTags: string[] = [];
@@ -57,7 +59,6 @@ export function bootstrap(platform: Platform): void {
57
59
  registerConfigCommand(platform);
58
60
  registerStatusCommand(platform);
59
61
  registerPlanCommand(platform);
60
- registerRunCommand(platform);
61
62
  registerReviewCommand(platform);
62
63
  registerQaCommand(platform);
63
64
  registerReleaseCommand(platform);
@@ -66,30 +67,13 @@ export function bootstrap(platform: Platform): void {
66
67
  registerDoctorCommand(platform);
67
68
  registerMcpCommand(platform);
68
69
  registerModelCommand(platform);
70
+ registerContextCommand(platform);
71
+ registerCommitCommand(platform);
72
+
73
+
74
+ // Register plan approval flow (agent_end hook for plan approval UI)
75
+ registerPlanApprovalHook(platform);
69
76
 
70
- // Register custom message renderers
71
- registerProgressRenderer(platform);
72
-
73
- // Wire ESC key to abort active runs via raw terminal input
74
- // The handleInput on InlineProgressComponent is not called by OMP's TUI
75
- // (custom message renderers are not the focused component), so we use
76
- // onTerminalInput which hooks into the TUI's input listener pipeline.
77
- platform.on("session_start", (_event: any, ctx: any) => {
78
- if (!ctx?.ui?.onTerminalInput) return;
79
- ctx.ui.onTerminalInput((data: string) => {
80
- // ESC key: \x1b not followed by [ (which would be an ANSI sequence)
81
- if (data !== "\x1b" && data !== "\x1b\x1b") return;
82
- // Find any active run and abort it
83
- for (const state of activeRuns.values()) {
84
- if (!state.aborted) {
85
- state.abort();
86
- ctx.ui.notify("Run interrupted — press ESC again to abort the agent turn", "warning");
87
- return { consume: true };
88
- }
89
- }
90
- return;
91
- });
92
- });
93
77
 
94
78
  // Intercept TUI-only commands at the input level — this runs BEFORE
95
79
  // message submission, so no chat message appears and no "Working..." indicator
@@ -114,7 +98,8 @@ export function bootstrap(platform: Platform): void {
114
98
  const handler = TUI_COMMANDS[commandName];
115
99
  if (!handler) return;
116
100
 
117
- handler(platform, ctx);
101
+ const args = spaceIndex === -1 ? undefined : text.slice(spaceIndex + 1);
102
+ handler(platform, ctx, args);
118
103
  return { action: "handled" };
119
104
  });
120
105
 
@@ -122,8 +107,6 @@ export function bootstrap(platform: Platform): void {
122
107
  const config = loadConfig(platform.paths, process.cwd());
123
108
  registerContextModeHooks(platform, config);
124
109
 
125
- // Migrate old modelPreference to model.json
126
- migrateModelPreference(platform.paths, process.cwd());
127
110
 
128
111
  // MCP per-turn activation
129
112
  platform.on("before_agent_start", async (event, ctx) => {
@@ -0,0 +1,44 @@
1
+ // src/commands/commit.ts — /supi:commit slash command
2
+ //
3
+ // TUI-only command — intercepted at the input level to prevent
4
+ // the "Working..." spinner. It never triggers the outer LLM session.
5
+
6
+ import type { Platform } from "../platform/types.js";
7
+ import type { PlatformContext } from "../platform/types.js";
8
+ import { analyzeAndCommit } from "../git/commit.js";
9
+
10
+ /**
11
+ * Register the command for autocomplete and /help listing.
12
+ * Actual execution goes through handleCommit via the TUI dispatch.
13
+ */
14
+ export function registerCommitCommand(platform: Platform): void {
15
+ platform.registerCommand("supi:commit", {
16
+ description: "AI-powered commit — analyzes changes and generates conventional commit messages",
17
+ async handler() {
18
+ // No-op: execution is handled by the TUI input interceptor.
19
+ // This registration exists only for autocomplete and /help.
20
+ },
21
+ });
22
+ }
23
+
24
+ /**
25
+ * TUI-only handler — called from the input event dispatcher in bootstrap.ts.
26
+ * Runs the full commit flow (git ops, agent analysis, UI approval, execution)
27
+ * without ever triggering the outer LLM session.
28
+ */
29
+ export function handleCommit(platform: Platform, ctx: PlatformContext, args?: string): void {
30
+ if (!ctx.hasUI) {
31
+ ctx.ui.notify("Commit requires interactive mode", "warning");
32
+ return;
33
+ }
34
+
35
+ void (async () => {
36
+ try {
37
+ await analyzeAndCommit(platform, ctx, {
38
+ userContext: args?.trim() || undefined,
39
+ });
40
+ } catch (err) {
41
+ ctx.ui.notify(`Commit error: ${(err as Error).message}`, "error");
42
+ }
43
+ })();
44
+ }
@@ -44,33 +44,6 @@ function buildSettings(platform: Platform, cwd: string): SettingDef[] {
44
44
  get: (c) => c.defaultProfile,
45
45
  set: (d, v) => updateConfig(paths, d, { defaultProfile: v }),
46
46
  },
47
- {
48
- label: "Max parallel agents",
49
- key: "orchestration.maxParallelAgents",
50
- helpText: "Sub-agents running concurrently in each /supi:run batch",
51
- type: "select",
52
- options: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"],
53
- get: (c) => String(c.orchestration.maxParallelAgents),
54
- set: (d, v) => updateConfig(paths, d, { orchestration: { maxParallelAgents: Number(v) } }),
55
- },
56
- {
57
- label: "Max fix retries",
58
- key: "orchestration.maxFixRetries",
59
- helpText: "Times a failed task is retried before marking it blocked",
60
- type: "select",
61
- options: ["0", "1", "2", "3", "4", "5"],
62
- get: (c) => String(c.orchestration.maxFixRetries),
63
- set: (d, v) => updateConfig(paths, d, { orchestration: { maxFixRetries: Number(v) } }),
64
- },
65
- {
66
- label: "Max nesting depth",
67
- key: "orchestration.maxNestingDepth",
68
- helpText: "How deep sub-agents can spawn other sub-agents",
69
- type: "select",
70
- options: ["0", "1", "2", "3", "4", "5"],
71
- get: (c) => String(c.orchestration.maxNestingDepth),
72
- set: (d, v) => updateConfig(paths, d, { orchestration: { maxNestingDepth: Number(v) } }),
73
- },
74
47
  {
75
48
  label: "LSP setup guide",
76
49
  key: "lsp.setupGuide",
@@ -0,0 +1,117 @@
1
+ import { writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { tmpdir } from "node:os";
4
+ import type { Platform, PlatformContext } from "../platform/types.js";
5
+ import {
6
+ parseSystemPrompt,
7
+ buildBreakdownItems,
8
+ formatSectionReport,
9
+ formatToolsReport,
10
+ } from "../context/analyzer.js";
11
+ import type { ContextUsage } from "../context/analyzer.js";
12
+
13
+ const REPORT_FILE = ".omp-context-breakdown.md";
14
+
15
+ export function handleContext(platform: Platform, ctx: PlatformContext): void {
16
+ void (async () => {
17
+ if (!ctx.hasUI) return;
18
+
19
+ // Gather data from OMP runtime
20
+ let usage: ContextUsage | null = null;
21
+ try {
22
+ const raw = (ctx as any).getContextUsage?.();
23
+ if (raw && typeof raw === "object") {
24
+ usage = {
25
+ tokens: typeof raw.tokens === "number" ? raw.tokens : null,
26
+ contextWindow: typeof raw.contextWindow === "number" ? raw.contextWindow : null,
27
+ percent: typeof raw.percent === "number" ? raw.percent : null,
28
+ };
29
+ }
30
+ } catch {
31
+ // getContextUsage not available — continue without
32
+ }
33
+
34
+ let systemPrompt = "";
35
+ try {
36
+ systemPrompt = (ctx as any).getSystemPrompt?.() ?? "";
37
+ } catch {
38
+ // getSystemPrompt not available — continue without
39
+ }
40
+
41
+ // If we have nothing to show, notify and bail
42
+ if (!usage && !systemPrompt) {
43
+ ctx.ui.notify("Context data unavailable", "warning");
44
+ return;
45
+ }
46
+
47
+ // Parse system prompt (may be empty)
48
+ const sections = systemPrompt ? parseSystemPrompt(systemPrompt) : [];
49
+ const activeTools = platform.getActiveTools();
50
+ const items = buildBreakdownItems(usage, sections, activeTools, !systemPrompt);
51
+ const lines = items.map(i => i.line);
52
+
53
+ while (true) {
54
+ const choice = await ctx.ui.select("Context Breakdown", lines, {
55
+ helpText: "Select to inspect, Esc to close",
56
+ });
57
+ if (!choice || choice.trim() === "Close") break;
58
+
59
+ const item = items.find(i => i.line === choice);
60
+ if (!item || (!item.section && !item.toolNames)) continue;
61
+
62
+ let report: string | null = null;
63
+ if (item.section) {
64
+ report = formatSectionReport(item.section);
65
+ } else if (item.toolNames) {
66
+ report = formatToolsReport(item.toolNames);
67
+ }
68
+
69
+ if (report) {
70
+ const filePath = writeReport(ctx.cwd, report);
71
+ await openInEditor(platform, filePath);
72
+ ctx.ui.notify(`Wrote ${REPORT_FILE}`, "info");
73
+ }
74
+ }
75
+ })().catch((err) => {
76
+ ctx.ui.notify(`Context error: ${(err as Error).message}`, "error");
77
+ });
78
+ }
79
+
80
+ export function registerContextCommand(platform: Platform): void {
81
+ platform.registerCommand("supi:context", {
82
+ description: "Show context window breakdown — what's consuming tokens",
83
+ async handler(_args: string | undefined, ctx: any) {
84
+ handleContext(platform, ctx);
85
+ },
86
+ });
87
+ }
88
+
89
+
90
+ /** Write report to project root, falling back to tmpdir on failure */
91
+ function writeReport(cwd: string, content: string): string {
92
+ const primary = join(cwd, REPORT_FILE);
93
+ try {
94
+ writeFileSync(primary, content, "utf-8");
95
+ return primary;
96
+ } catch {
97
+ const fallback = join(tmpdir(), REPORT_FILE);
98
+ writeFileSync(fallback, content, "utf-8");
99
+ return fallback;
100
+ }
101
+ }
102
+
103
+ /** Open a file in the user's preferred editor */
104
+ async function openInEditor(platform: Platform, filePath: string): Promise<void> {
105
+ const editor = process.env.VISUAL || process.env.EDITOR;
106
+ try {
107
+ if (editor) {
108
+ await platform.exec(editor, [filePath]);
109
+ } else {
110
+ const cmd = process.platform === "darwin" ? "open"
111
+ : process.platform === "win32" ? "start" : "xdg-open";
112
+ await platform.exec(cmd, [filePath]);
113
+ }
114
+ } catch {
115
+ // Editor open failed — non-fatal, file was still written
116
+ }
117
+ }
@@ -323,8 +323,7 @@ export function registerDoctorCommand(platform: Platform): void {
323
323
  });
324
324
  }
325
325
 
326
- const CAPABILITY_LABELS: Record<keyof PlatformCapabilities, string> = {
327
- agentSessions: "Sub-agent orchestration (/supi:run)",
326
+ const CAPABILITY_LABELS: Partial<Record<keyof PlatformCapabilities, string>> = {
328
327
  compactionHooks: "Context compression",
329
328
  customWidgets: "Progress widgets",
330
329
  registerTool: "Custom tool registration",
@@ -3,7 +3,7 @@ import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import { loadFixPrConfig, saveFixPrConfig, DEFAULT_FIX_PR_CONFIG } from "../fix-pr/config.js";
5
5
  import { buildFixPrOrchestratorPrompt } from "../fix-pr/prompt-builder.js";
6
- import type { FixPrConfig, ReviewerType, CommentReplyPolicy } from "../fix-pr/types.js";
6
+ import type { FixPrConfig, CommentReplyPolicy } from "../fix-pr/types.js";
7
7
  import {
8
8
  generateFixPrSessionId,
9
9
  createFixPrSession,
@@ -14,6 +14,7 @@ import { notifyInfo, notifyError, notifyWarning } from "../notifications/rendere
14
14
  import { modelRegistry } from "../config/model-registry-instance.js";
15
15
  import { resolveModelForAction, createModelBridge } from "../config/model-resolver.js";
16
16
  import { loadModelConfig } from "../config/model-config.js";
17
+ import { detectBotReviewers } from "../fix-pr/bot-detector.js";
17
18
 
18
19
  modelRegistry.register({
19
20
  id: "fix-pr",
@@ -155,6 +156,18 @@ export function registerFixPrCommand(platform: Platform): void {
155
156
 
156
157
  const commentCount = comments.split("\n").length;
157
158
 
159
+ // Auto-detect bot reviewers from comment data
160
+ const detectedBots = detectBotReviewers(comments);
161
+ if (detectedBots.length > 0) {
162
+ config = {
163
+ ...config,
164
+ reviewer: {
165
+ type: detectedBots[0].type,
166
+ triggerMethod: detectedBots[0].triggerMethod,
167
+ },
168
+ };
169
+ }
170
+
158
171
  // ── Step 5: Load skill ─────────────────────────────────────────
159
172
  let skillContent = "";
160
173
  const skillPath = findSkillPath("fix-pr");
@@ -180,7 +193,7 @@ export function registerFixPrCommand(platform: Platform): void {
180
193
  const modelConfig = loadModelConfig(platform.paths, ctx.cwd);
181
194
  const bridge = createModelBridge(platform);
182
195
  const resolved = resolveModelForAction("fix-pr", modelRegistry, modelConfig, bridge);
183
- if (resolved.source !== "main" && platform.setModel) {
196
+ if (resolved.source !== "main" && platform.setModel && resolved.model) {
184
197
  platform.setModel(resolved.model);
185
198
  }
186
199
 
@@ -200,18 +213,6 @@ export function registerFixPrCommand(platform: Platform): void {
200
213
 
201
214
  // ── Setup Wizard ───────────────────────────────────────────────────────
202
215
 
203
- const REVIEWER_OPTIONS = [
204
- "CodeRabbit",
205
- "GitHub Copilot",
206
- "Gemini Code Review",
207
- "None",
208
- ];
209
-
210
- const REVIEWER_DEFAULTS: Record<string, string> = {
211
- "CodeRabbit": "/review",
212
- "GitHub Copilot": "@copilot review",
213
- "Gemini Code Review": "/gemini review",
214
- };
215
216
 
216
217
  const POLICY_OPTIONS = [
217
218
  "Answer all comments",
@@ -239,33 +240,6 @@ const MODEL_TIER_OPTIONS = [
239
240
  ];
240
241
 
241
242
  async function runSetupWizard(ctx: any): Promise<FixPrConfig | null> {
242
- // 1. Automated reviewer
243
- const reviewerChoice = await ctx.ui.select(
244
- "Automated PR reviewer",
245
- REVIEWER_OPTIONS,
246
- { helpText: "Select your automated reviewer, if any" },
247
- );
248
- if (!reviewerChoice) return null;
249
-
250
- let reviewerType: ReviewerType = "none";
251
- let triggerMethod: string | null = null;
252
-
253
- if (reviewerChoice !== "None") {
254
- reviewerType = reviewerChoice.toLowerCase().replace(/ /g, "").replace("github", "") as ReviewerType;
255
- // Normalize to our type names
256
- if (reviewerChoice === "CodeRabbit") reviewerType = "coderabbit";
257
- else if (reviewerChoice === "GitHub Copilot") reviewerType = "copilot";
258
- else if (reviewerChoice === "Gemini Code Review") reviewerType = "gemini";
259
-
260
- const defaultTrigger = REVIEWER_DEFAULTS[reviewerChoice] || "";
261
- triggerMethod = await ctx.ui.input(
262
- "How to trigger re-review?",
263
- defaultTrigger,
264
- { helpText: `Default for ${reviewerChoice}: ${defaultTrigger}` },
265
- );
266
- if (triggerMethod === undefined) return null;
267
- if (!triggerMethod) triggerMethod = defaultTrigger;
268
- }
269
243
 
270
244
  // 2. Comment reply policy
271
245
  const policyChoice = await ctx.ui.select(
@@ -319,7 +293,7 @@ async function runSetupWizard(ctx: any): Promise<FixPrConfig | null> {
319
293
  if (!fixerTier) return null;
320
294
 
321
295
  const config: FixPrConfig = {
322
- reviewer: { type: reviewerType, triggerMethod },
296
+ reviewer: { type: "none", triggerMethod: null },
323
297
  commentPolicy,
324
298
  loop: { delaySeconds, maxIterations },
325
299
  models: {