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.
- package/README.md +1 -1
- package/bin/ctx-mode-wrapper.mjs +3 -3
- package/package.json +8 -22
- package/skills/planning/SKILL.md +2 -4
- package/skills/release/SKILL.md +5 -2
- package/src/bootstrap.ts +16 -33
- package/src/commands/commit.ts +44 -0
- package/src/commands/config.ts +0 -27
- package/src/commands/context.ts +117 -0
- package/src/commands/doctor.ts +1 -2
- package/src/commands/fix-pr.ts +16 -42
- package/src/commands/mcp.ts +121 -3
- package/src/commands/plan.ts +5 -1
- package/src/commands/release.ts +327 -32
- package/src/commands/review.ts +1 -1
- package/src/commands/status.ts +9 -26
- package/src/commands/supi.ts +2 -5
- package/src/config/defaults.ts +0 -19
- package/src/config/model-config.ts +0 -19
- package/src/context/analyzer.ts +316 -0
- package/src/deps/registry.ts +2 -4
- package/src/fix-pr/bot-detector.ts +41 -0
- package/src/fix-pr/scripts/fetch-pr-comments.sh +2 -2
- package/src/fix-pr/types.ts +1 -0
- package/src/git/commit-msg-hook.ts +21 -0
- package/src/git/commit-msg.ts +53 -0
- package/src/git/commit.ts +659 -0
- package/src/git/conventions.ts +215 -0
- package/src/git/status.ts +27 -0
- package/src/global.d.ts +5 -0
- package/src/index.ts +1 -7
- package/src/mcp/config.ts +80 -1
- package/src/mcp/docs.ts +1 -1
- package/src/mcp/types.ts +14 -0
- package/src/planning/approval-flow.ts +166 -0
- package/src/planning/plan-writer-prompt.ts +6 -5
- package/src/planning/prompt-builder.ts +4 -3
- package/src/platform/detect.ts +4 -6
- package/src/platform/omp.ts +15 -15
- package/src/platform/test-utils.ts +17 -17
- package/src/platform/types.ts +6 -6
- package/src/release/changelog.ts +30 -20
- package/src/release/commit-types.ts +35 -0
- package/src/release/executor.ts +63 -25
- package/src/release/prompt.ts +1 -1
- package/src/release/version.ts +27 -0
- package/src/storage/plans.ts +2 -12
- package/src/types.ts +7 -55
- package/src/commands/run.ts +0 -511
- package/src/orchestrator/agent-grid.ts +0 -439
- package/src/orchestrator/agent-prompts.ts +0 -290
- package/src/orchestrator/batch-scheduler.ts +0 -59
- package/src/orchestrator/conflict-resolver.ts +0 -39
- package/src/orchestrator/dispatcher.ts +0 -666
- package/src/orchestrator/progress-renderer.ts +0 -281
- package/src/orchestrator/prompts.ts +0 -149
- package/src/orchestrator/result-collector.ts +0 -72
- package/src/orchestrator/run-progress.ts +0 -95
- package/src/platform/pi.ts +0 -61
- package/src/storage/runs.ts +0 -126
- 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
|
|
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.
|
package/bin/ctx-mode-wrapper.mjs
CHANGED
|
@@ -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/
|
|
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/
|
|
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, "
|
|
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.
|
|
4
|
-
"description": "Workflow extension for
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Workflow extension for OMP coding agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "
|
|
7
|
+
"test": "bun test tests/",
|
|
8
8
|
"typecheck": "tsc --noEmit",
|
|
9
|
-
"test:watch": "
|
|
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
|
-
"
|
|
71
|
-
"
|
|
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
|
}
|
package/skills/planning/SKILL.md
CHANGED
|
@@ -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
|
|
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>
|
|
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
|
package/skills/release/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
+
}
|
package/src/commands/config.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/commands/doctor.ts
CHANGED
|
@@ -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",
|
package/src/commands/fix-pr.ts
CHANGED
|
@@ -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,
|
|
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:
|
|
296
|
+
reviewer: { type: "none", triggerMethod: null },
|
|
323
297
|
commentPolicy,
|
|
324
298
|
loop: { delaySeconds, maxIterations },
|
|
325
299
|
models: {
|