supipowers 1.2.6 → 1.5.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 +118 -56
- package/bin/install.ts +48 -128
- package/package.json +11 -3
- package/skills/code-review/SKILL.md +137 -40
- package/skills/context-mode/SKILL.md +67 -56
- package/skills/creating-supi-agents/SKILL.md +204 -0
- package/skills/debugging/SKILL.md +86 -40
- package/skills/fix-pr/SKILL.md +96 -65
- package/skills/planning/SKILL.md +103 -46
- package/skills/qa-strategy/SKILL.md +68 -46
- package/skills/receiving-code-review/SKILL.md +60 -53
- package/skills/release/SKILL.md +111 -39
- package/skills/tdd/SKILL.md +118 -67
- package/skills/verification/SKILL.md +71 -37
- package/src/bootstrap.ts +27 -5
- package/src/commands/agents.ts +249 -0
- package/src/commands/ai-review.ts +1113 -0
- package/src/commands/config.ts +224 -95
- package/src/commands/doctor.ts +19 -13
- package/src/commands/fix-pr.ts +8 -11
- package/src/commands/generate.ts +200 -0
- package/src/commands/model-picker.ts +5 -15
- package/src/commands/model.ts +4 -5
- package/src/commands/optimize-context.ts +202 -0
- package/src/commands/plan.ts +148 -92
- package/src/commands/qa.ts +14 -23
- package/src/commands/release.ts +504 -275
- package/src/commands/review.ts +643 -86
- package/src/commands/status.ts +44 -17
- package/src/commands/supi.ts +69 -41
- package/src/commands/update.ts +57 -2
- package/src/config/defaults.ts +6 -39
- package/src/config/loader.ts +388 -40
- package/src/config/model-resolver.ts +26 -22
- package/src/config/schema.ts +113 -48
- package/src/context/analyzer.ts +61 -2
- package/src/context/optimizer.ts +199 -0
- package/src/context-mode/compressor.ts +14 -11
- package/src/context-mode/detector.ts +16 -54
- package/src/context-mode/event-extractor.ts +45 -16
- package/src/context-mode/event-store.ts +225 -16
- package/src/context-mode/hooks.ts +195 -22
- package/src/context-mode/knowledge/chunker.ts +235 -0
- package/src/context-mode/knowledge/store.ts +187 -0
- package/src/context-mode/routing.ts +12 -23
- package/src/context-mode/sandbox/executor.ts +183 -0
- package/src/context-mode/sandbox/runners.ts +40 -0
- package/src/context-mode/snapshot-builder.ts +243 -7
- package/src/context-mode/tools.ts +440 -0
- package/src/context-mode/web/fetcher.ts +117 -0
- package/src/context-mode/web/html-to-md.ts +293 -0
- package/src/debug/logger.ts +107 -0
- package/src/deps/registry.ts +0 -20
- package/src/docs/drift.ts +454 -0
- package/src/fix-pr/fetch-comments.ts +66 -0
- package/src/git/commit-msg.ts +2 -1
- package/src/git/commit.ts +123 -141
- package/src/git/conventions.ts +2 -2
- package/src/git/status.ts +4 -1
- package/src/lsp/bridge.ts +138 -12
- package/src/planning/approval-flow.ts +125 -19
- package/src/planning/plan-writer-prompt.ts +4 -11
- package/src/planning/planning-ask-tool.ts +81 -0
- package/src/planning/prompt-builder.ts +9 -169
- package/src/planning/system-prompt.ts +290 -0
- package/src/platform/omp.ts +50 -4
- package/src/platform/progress.ts +182 -0
- package/src/platform/test-utils.ts +4 -1
- package/src/platform/tui-colors.ts +30 -0
- package/src/platform/types.ts +1 -0
- package/src/qa/detect-app-type.ts +102 -0
- package/src/qa/discover-routes.ts +353 -0
- package/src/quality/ai-session.ts +96 -0
- package/src/quality/ai-setup.ts +86 -0
- package/src/quality/gates/ai-review.ts +129 -0
- package/src/quality/gates/build.ts +8 -0
- package/src/quality/gates/command.ts +150 -0
- package/src/quality/gates/format.ts +28 -0
- package/src/quality/gates/lint.ts +22 -0
- package/src/quality/gates/lsp-diagnostics.ts +84 -0
- package/src/quality/gates/test-suite.ts +8 -0
- package/src/quality/gates/typecheck.ts +22 -0
- package/src/quality/registry.ts +25 -0
- package/src/quality/review-gates.ts +33 -0
- package/src/quality/runner.ts +268 -0
- package/src/quality/schemas.ts +48 -0
- package/src/quality/setup.ts +227 -0
- package/src/release/changelog.ts +7 -3
- package/src/release/channels/custom.ts +43 -0
- package/src/release/channels/gitea.ts +35 -0
- package/src/release/channels/github.ts +35 -0
- package/src/release/channels/gitlab.ts +35 -0
- package/src/release/channels/registry.ts +52 -0
- package/src/release/channels/types.ts +27 -0
- package/src/release/detector.ts +10 -63
- package/src/release/executor.ts +61 -51
- package/src/release/prompt.ts +38 -38
- package/src/release/version.ts +129 -10
- package/src/review/agent-loader.ts +331 -0
- package/src/review/consolidator.ts +180 -0
- package/src/review/default-agents/correctness.md +72 -0
- package/src/review/default-agents/maintainability.md +64 -0
- package/src/review/default-agents/security.md +67 -0
- package/src/review/fixer.ts +219 -0
- package/src/review/multi-agent-runner.ts +135 -0
- package/src/review/output.ts +147 -0
- package/src/review/prompts/agent-review-wrapper.md +36 -0
- package/src/review/prompts/fix-findings.md +32 -0
- package/src/review/prompts/fix-output-schema.md +18 -0
- package/src/review/prompts/invalid-output-retry.md +22 -0
- package/src/review/prompts/output-instructions.md +14 -0
- package/src/review/prompts/review-output-schema.md +38 -0
- package/src/review/prompts/single-review.md +53 -0
- package/src/review/prompts/validation-review.md +30 -0
- package/src/review/runner.ts +128 -0
- package/src/review/scope.ts +353 -0
- package/src/review/template.ts +15 -0
- package/src/review/types.ts +296 -0
- package/src/review/validator.ts +160 -0
- package/src/storage/plans.ts +5 -3
- package/src/storage/reports.ts +50 -7
- package/src/storage/review-sessions.ts +117 -0
- package/src/text.ts +19 -0
- package/src/types.ts +336 -26
- package/src/utils/paths.ts +39 -0
- package/src/visual/companion.ts +5 -3
- package/src/visual/start-server.ts +101 -0
- package/src/visual/stop-server.ts +39 -0
- package/bin/ctx-mode-wrapper.mjs +0 -66
- package/src/config/profiles.ts +0 -64
- package/src/context-mode/installer.ts +0 -38
- package/src/quality/ai-review-gate.ts +0 -43
- package/src/quality/gate-runner.ts +0 -67
- package/src/quality/lsp-gate.ts +0 -24
- package/src/quality/test-gate.ts +0 -39
- package/src/visual/scripts/start-server.sh +0 -98
- package/src/visual/scripts/stop-server.sh +0 -21
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import type { Platform } from "../platform/types.js";
|
|
2
|
+
import type { DriftCheckResult } from "../types.js";
|
|
3
|
+
import { notifyInfo, notifyError, notifyWarning } from "../notifications/renderer.js";
|
|
4
|
+
import {
|
|
5
|
+
loadState,
|
|
6
|
+
saveState,
|
|
7
|
+
discoverDocFiles,
|
|
8
|
+
getHeadCommit,
|
|
9
|
+
checkDocDrift,
|
|
10
|
+
} from "../docs/drift.js";
|
|
11
|
+
|
|
12
|
+
// ── Multi-select UI ───────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
async function selectDocFiles(
|
|
15
|
+
ctx: any,
|
|
16
|
+
discovered: string[],
|
|
17
|
+
): Promise<string[]> {
|
|
18
|
+
const selected = new Set<string>();
|
|
19
|
+
|
|
20
|
+
while (true) {
|
|
21
|
+
const options = discovered.map(
|
|
22
|
+
(f) => `${selected.has(f) ? "◉" : "○"} ${f}`,
|
|
23
|
+
);
|
|
24
|
+
options.push("─── Add manually ───");
|
|
25
|
+
options.push("─── Done ───");
|
|
26
|
+
|
|
27
|
+
const choice = await ctx.ui.select("Track these docs", options, {
|
|
28
|
+
helpText: `${selected.size} selected · Toggle files · Done when ready`,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (choice === undefined || choice === null || choice === "─── Done ───") break;
|
|
32
|
+
|
|
33
|
+
if (choice === "─── Add manually ───") {
|
|
34
|
+
const filePath = await ctx.ui.input("File path (relative to project root)");
|
|
35
|
+
if (filePath) selected.add(filePath);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const file = choice.replace(/^[○◉] /, "");
|
|
40
|
+
if (selected.has(file)) {
|
|
41
|
+
selected.delete(file);
|
|
42
|
+
} else {
|
|
43
|
+
selected.add(file);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return [...selected];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Subcommand: docs ──────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
async function handleDocs(platform: Platform, ctx: any): Promise<void> {
|
|
53
|
+
if (!ctx.hasUI) {
|
|
54
|
+
notifyWarning(ctx, "Doc drift check requires interactive mode");
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const cwd = ctx.cwd;
|
|
59
|
+
const { paths } = platform;
|
|
60
|
+
const state = loadState(paths, cwd);
|
|
61
|
+
|
|
62
|
+
// First run: discover and select files, then steer main thread for full audit
|
|
63
|
+
if (state.trackedFiles.length === 0) {
|
|
64
|
+
const discovered = await discoverDocFiles(platform, cwd);
|
|
65
|
+
if (discovered.length === 0) {
|
|
66
|
+
notifyWarning(ctx, "No documentation files found in this repository");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const selected = await selectDocFiles(ctx, discovered);
|
|
71
|
+
if (selected.length === 0) {
|
|
72
|
+
notifyInfo(ctx, "No files selected \u2014 doc tracking not set up");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Persist tracked files but leave lastCommit null \u2014 updated after fix starts
|
|
77
|
+
saveState(paths, cwd, {
|
|
78
|
+
trackedFiles: selected,
|
|
79
|
+
lastCommit: null,
|
|
80
|
+
lastRunAt: new Date().toISOString(),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Steer the main thread to audit and fix docs directly
|
|
84
|
+
const docList = selected.map((f) => `- \`${f}\``).join("\n");
|
|
85
|
+
const prompt = [
|
|
86
|
+
`Check these documentation files for accuracy against the current codebase:`,
|
|
87
|
+
``,
|
|
88
|
+
docList,
|
|
89
|
+
``,
|
|
90
|
+
`<critical>`,
|
|
91
|
+
`You MUST read skill://create-readme before assessing documentation quality.`,
|
|
92
|
+
`</critical>`,
|
|
93
|
+
``,
|
|
94
|
+
`Read each documentation file and compare against the current codebase. Look for:`,
|
|
95
|
+
`1. **Factual inaccuracies**: wrong names, missing parameters, removed features, changed behavior, outdated examples`,
|
|
96
|
+
`2. **Missing documentation**: new commands, features, config options that exist in code but not in docs`,
|
|
97
|
+
`3. **Structural gaps**: important sections that should exist based on the codebase but don't`,
|
|
98
|
+
``,
|
|
99
|
+
`For each issue found, fix it directly in the documentation file. Do NOT output JSON or a report \u2014 just fix the files.`,
|
|
100
|
+
`If documentation is accurate, say so and make no changes.`,
|
|
101
|
+
``,
|
|
102
|
+
`Rules:`,
|
|
103
|
+
`- Only fix factual inaccuracies and add missing sections for undocumented features`,
|
|
104
|
+
`- Do NOT rewrite prose, improve wording, or restructure existing content`,
|
|
105
|
+
`- Preserve each document's existing formatting, tone, and conventions`,
|
|
106
|
+
].join("\n");
|
|
107
|
+
notifyInfo(ctx, "Starting full documentation audit", `${selected.length} file(s) selected`);
|
|
108
|
+
|
|
109
|
+
platform.sendMessage(
|
|
110
|
+
{
|
|
111
|
+
customType: "supi-generate-docs",
|
|
112
|
+
content: [{ type: "text", text: prompt }],
|
|
113
|
+
display: "none",
|
|
114
|
+
},
|
|
115
|
+
{ deliverAs: "steer", triggerTurn: true },
|
|
116
|
+
);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Subsequent run: headless sub-agent drift check
|
|
121
|
+
const result = await checkDocDrift(platform, cwd);
|
|
122
|
+
|
|
123
|
+
if (!result || !result.drifted) {
|
|
124
|
+
notifyInfo(ctx, "Docs are up to date", result?.summary ?? "No changes since last check");
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Build lightweight steer summary from findings
|
|
129
|
+
const steer = buildSteerSummary(result);
|
|
130
|
+
notifyInfo(ctx, "Documentation drift detected", `${result.findings.length} finding(s)`);
|
|
131
|
+
|
|
132
|
+
// Update state only now \u2014 user has seen findings and we\u2019re about to fix.
|
|
133
|
+
const head = await getHeadCommit(platform, cwd);
|
|
134
|
+
const currentState = loadState(paths, cwd);
|
|
135
|
+
saveState(paths, cwd, { ...currentState, lastCommit: head, lastRunAt: new Date().toISOString() });
|
|
136
|
+
|
|
137
|
+
platform.sendMessage(
|
|
138
|
+
{
|
|
139
|
+
customType: "supi-generate-docs",
|
|
140
|
+
content: [{ type: "text", text: steer }],
|
|
141
|
+
display: "none",
|
|
142
|
+
},
|
|
143
|
+
{ deliverAs: "steer", triggerTurn: true },
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function buildSteerSummary(result: DriftCheckResult): string {
|
|
148
|
+
const lines: string[] = ["Documentation drift detected. Please fix the following issues:", ""];
|
|
149
|
+
|
|
150
|
+
const byFile = new Map<string, typeof result.findings>();
|
|
151
|
+
for (const f of result.findings) {
|
|
152
|
+
if (!byFile.has(f.file)) byFile.set(f.file, []);
|
|
153
|
+
byFile.get(f.file)!.push(f);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
for (const [file, findings] of byFile) {
|
|
157
|
+
lines.push(`### \`${file}\``);
|
|
158
|
+
for (const f of findings) {
|
|
159
|
+
lines.push(`- [${f.severity}] ${f.description}`);
|
|
160
|
+
}
|
|
161
|
+
lines.push("");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
lines.push("Read each file, apply the fixes, and write the corrected content.");
|
|
165
|
+
return lines.join("\n");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ── Command registration ──────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
const SUBCOMMANDS = [
|
|
171
|
+
{ name: "docs", description: "Check documentation for drift from codebase" },
|
|
172
|
+
] as const;
|
|
173
|
+
|
|
174
|
+
export function registerGenerateCommand(platform: Platform): void {
|
|
175
|
+
platform.registerCommand("supi:generate", {
|
|
176
|
+
description: "Generation utilities — docs drift detection",
|
|
177
|
+
getArgumentCompletions(prefix: string) {
|
|
178
|
+
const lower = prefix.toLowerCase();
|
|
179
|
+
const matches = SUBCOMMANDS
|
|
180
|
+
.filter((s) => s.name.startsWith(lower))
|
|
181
|
+
.map((s) => ({ value: `${s.name} `, label: s.name, description: s.description }));
|
|
182
|
+
return matches.length > 0 ? matches : null;
|
|
183
|
+
},
|
|
184
|
+
async handler(args: string | undefined, ctx: any) {
|
|
185
|
+
const subcommand = args?.trim().split(/\s+/)[0] ?? "docs";
|
|
186
|
+
|
|
187
|
+
switch (subcommand) {
|
|
188
|
+
case "docs":
|
|
189
|
+
await handleDocs(platform, ctx);
|
|
190
|
+
break;
|
|
191
|
+
default:
|
|
192
|
+
notifyError(
|
|
193
|
+
ctx,
|
|
194
|
+
`Unknown subcommand "${subcommand}"`,
|
|
195
|
+
`Available: ${SUBCOMMANDS.map((s) => s.name).join(", ")}`,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
}
|
|
@@ -25,20 +25,10 @@ import {
|
|
|
25
25
|
type GeneratedProvider,
|
|
26
26
|
} from "@oh-my-pi/pi-ai";
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const DIM = "\x1b[2m";
|
|
33
|
-
const CYAN = "\x1b[36m";
|
|
34
|
-
const WHITE = "\x1b[37m";
|
|
35
|
-
const YELLOW = "\x1b[33m";
|
|
36
|
-
const INVERSE = "\x1b[7m";
|
|
37
|
-
|
|
38
|
-
const accent = (t: string) => `${CYAN}${BOLD}${t}${RESET}`;
|
|
39
|
-
const muted = (t: string) => `${DIM}${t}${RESET}`;
|
|
40
|
-
const bright = (t: string) => `${WHITE}${BOLD}${t}${RESET}`;
|
|
41
|
-
const warn = (t: string) => `${YELLOW}${t}${RESET}`;
|
|
28
|
+
import {
|
|
29
|
+
RESET, BOLD, DIM, CYAN, INVERSE,
|
|
30
|
+
accent, muted, bright, warn,
|
|
31
|
+
} from "../platform/tui-colors.js";
|
|
42
32
|
|
|
43
33
|
const ALL_TAB = "ALL";
|
|
44
34
|
const MAX_VISIBLE = 12;
|
|
@@ -232,7 +222,7 @@ export function createModelPicker(
|
|
|
232
222
|
// Enter → select current model
|
|
233
223
|
if (matchesKey(data, "enter")) {
|
|
234
224
|
if (filteredModels.length > 0) {
|
|
235
|
-
done(filteredModels[selectedIndex].id);
|
|
225
|
+
done(`${filteredModels[selectedIndex].provider}/${filteredModels[selectedIndex].id}`);
|
|
236
226
|
}
|
|
237
227
|
return;
|
|
238
228
|
}
|
package/src/commands/model.ts
CHANGED
|
@@ -39,7 +39,7 @@ function buildDashboard(
|
|
|
39
39
|
bridge: ModelPlatformBridge,
|
|
40
40
|
): string {
|
|
41
41
|
const config = loadModelConfig(paths, cwd);
|
|
42
|
-
const lines: string[] = ["\n Model Configuration\n", ` ${"action".padEnd(20)} ${"model".padEnd(
|
|
42
|
+
const lines: string[] = ["\n Model Configuration\n", ` ${"action".padEnd(20)} ${"model".padEnd(32)} ${"thinking".padEnd(10)} source`];
|
|
43
43
|
|
|
44
44
|
let lastCategory: "command" | "sub-agent" | null = null;
|
|
45
45
|
let lastParent: string | undefined = undefined;
|
|
@@ -60,7 +60,7 @@ function buildDashboard(
|
|
|
60
60
|
const sourceDisplay = formatSource(source);
|
|
61
61
|
|
|
62
62
|
lines.push(
|
|
63
|
-
` ${action.id.padEnd(20)} ${modelDisplay.padEnd(
|
|
63
|
+
` ${action.id.padEnd(20)} ${modelDisplay.padEnd(32)} ${thinkingDisplay.padEnd(10)} ${sourceDisplay}`,
|
|
64
64
|
);
|
|
65
65
|
|
|
66
66
|
lastCategory = action.category;
|
|
@@ -181,7 +181,7 @@ async function runModelTUI(platform: Platform, ctx: any): Promise<void> {
|
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
async function selectModelFromList(
|
|
184
|
+
export async function selectModelFromList(
|
|
185
185
|
ctx: any,
|
|
186
186
|
currentModel?: string,
|
|
187
187
|
): Promise<string | null> {
|
|
@@ -224,8 +224,7 @@ async function selectModelFromList(
|
|
|
224
224
|
);
|
|
225
225
|
|
|
226
226
|
if (!choice) return null;
|
|
227
|
-
|
|
228
|
-
return slashIndex >= 0 ? choice.slice(slashIndex + 1) : choice;
|
|
227
|
+
return choice;
|
|
229
228
|
}
|
|
230
229
|
|
|
231
230
|
async function selectScope(ctx: any): Promise<"global" | "project" | null> {
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { Platform, PlatformContext } from "../platform/types.js";
|
|
2
|
+
import {
|
|
3
|
+
parseSystemPrompt,
|
|
4
|
+
parseIndividualSkills,
|
|
5
|
+
} from "../context/analyzer.js";
|
|
6
|
+
import {
|
|
7
|
+
detectTechStack,
|
|
8
|
+
buildContextReport,
|
|
9
|
+
} from "../context/optimizer.js";
|
|
10
|
+
import type { ContextReport } from "../context/optimizer.js";
|
|
11
|
+
|
|
12
|
+
function formatTokens(n: number): string {
|
|
13
|
+
return n >= 1000 ? `${(n / 1000).toFixed(1)}K` : String(n);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function handleOptimizeContext(platform: Platform, ctx: PlatformContext): void {
|
|
17
|
+
void (async () => {
|
|
18
|
+
if (!ctx.hasUI) return;
|
|
19
|
+
|
|
20
|
+
// 1. Detect tech stack
|
|
21
|
+
const techStack = await detectTechStack(platform, ctx.cwd);
|
|
22
|
+
|
|
23
|
+
// 2. Get system prompt
|
|
24
|
+
let systemPrompt = "";
|
|
25
|
+
try {
|
|
26
|
+
systemPrompt = (ctx as any).getSystemPrompt?.() ?? "";
|
|
27
|
+
} catch {
|
|
28
|
+
// getSystemPrompt not available
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!systemPrompt) {
|
|
32
|
+
ctx.ui.notify("System prompt unavailable", "warning");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 3. Parse
|
|
37
|
+
const sections = parseSystemPrompt(systemPrompt);
|
|
38
|
+
const skills = parseIndividualSkills(systemPrompt);
|
|
39
|
+
|
|
40
|
+
// 4. Build raw report
|
|
41
|
+
const report = buildContextReport(sections, skills, techStack);
|
|
42
|
+
|
|
43
|
+
// 5. Show TUI
|
|
44
|
+
await showReport(platform, ctx, report);
|
|
45
|
+
})().catch((err) => {
|
|
46
|
+
ctx.ui.notify(`Optimize error: ${(err as Error).message}`, "error");
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function registerOptimizeContextCommand(platform: Platform): void {
|
|
51
|
+
platform.registerCommand("supi:optimize-context", {
|
|
52
|
+
description: "Analyze context usage and suggest token optimizations",
|
|
53
|
+
async handler(_args: string | undefined, ctx: any) {
|
|
54
|
+
handleOptimizeContext(platform, ctx);
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── Internal ──────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
async function showReport(
|
|
62
|
+
platform: Platform,
|
|
63
|
+
ctx: PlatformContext,
|
|
64
|
+
report: ContextReport,
|
|
65
|
+
): Promise<void> {
|
|
66
|
+
const techList = [
|
|
67
|
+
...report.techStack.languages,
|
|
68
|
+
...(report.techStack.runtime ? [report.techStack.runtime] : []),
|
|
69
|
+
...report.techStack.frameworks,
|
|
70
|
+
...report.techStack.tools,
|
|
71
|
+
].join(", ");
|
|
72
|
+
|
|
73
|
+
const lines: string[] = [
|
|
74
|
+
`Tech: ${techList || "unknown"}`,
|
|
75
|
+
`Current: ~${formatTokens(report.totalTokens)} tokens | Target: ~8.0K tokens`,
|
|
76
|
+
"",
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
// Skills breakdown
|
|
80
|
+
if (report.skills.length === 0) {
|
|
81
|
+
lines.push("No skills detected in system prompt.");
|
|
82
|
+
} else {
|
|
83
|
+
const totalSkillTokens = report.skills.reduce((sum, s) => sum + s.tokens, 0);
|
|
84
|
+
lines.push(`Skills (${report.skills.length} loaded, ~${formatTokens(totalSkillTokens)} tok total):`);
|
|
85
|
+
lines.push("");
|
|
86
|
+
|
|
87
|
+
const sorted = [...report.skills].sort((a, b) => b.tokens - a.tokens);
|
|
88
|
+
for (const s of sorted) {
|
|
89
|
+
lines.push(` ${s.name.padEnd(32)} ~${formatTokens(s.tokens)} tok`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Non-skill sections
|
|
94
|
+
if (report.sections.length > 0) {
|
|
95
|
+
lines.push("");
|
|
96
|
+
lines.push("Other sections:");
|
|
97
|
+
lines.push("");
|
|
98
|
+
|
|
99
|
+
for (const sec of report.sections) {
|
|
100
|
+
const tok = formatTokens(sec.tokens);
|
|
101
|
+
const note = sec.note ? ` (${sec.note})` : "";
|
|
102
|
+
lines.push(` ${sec.label.padEnd(28)} ~${tok} tok${note}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const message = lines.join("\n");
|
|
107
|
+
|
|
108
|
+
// confirm() may not be available on all platforms
|
|
109
|
+
const shouldOptimize = ctx.ui.confirm
|
|
110
|
+
? await ctx.ui.confirm("Context Optimization", message)
|
|
111
|
+
: (await ctx.ui.select("Context Optimization", [message, "▶ Optimize with AI", "Close"]))?.includes("Optimize");
|
|
112
|
+
|
|
113
|
+
if (!shouldOptimize) return;
|
|
114
|
+
|
|
115
|
+
platform.sendMessage(
|
|
116
|
+
{
|
|
117
|
+
customType: "optimize-context",
|
|
118
|
+
content: [{ type: "text", text: buildOptimizationPrompt(report) }],
|
|
119
|
+
display: "none",
|
|
120
|
+
},
|
|
121
|
+
{ deliverAs: "steer", triggerTurn: true },
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function buildOptimizationPrompt(report: ContextReport): string {
|
|
126
|
+
const lines: string[] = [];
|
|
127
|
+
|
|
128
|
+
const techList = [
|
|
129
|
+
...report.techStack.languages,
|
|
130
|
+
...(report.techStack.runtime ? [report.techStack.runtime] : []),
|
|
131
|
+
...report.techStack.frameworks,
|
|
132
|
+
...report.techStack.tools,
|
|
133
|
+
].join(", ");
|
|
134
|
+
|
|
135
|
+
lines.push("# Context Optimization Request");
|
|
136
|
+
lines.push("");
|
|
137
|
+
lines.push(`Current system prompt is **~${formatTokens(report.totalTokens)} tokens**. Target is **< 8K tokens**.`);
|
|
138
|
+
lines.push(`Project tech stack: **${techList || "unknown"}**`);
|
|
139
|
+
lines.push("");
|
|
140
|
+
|
|
141
|
+
// Skill inventory
|
|
142
|
+
if (report.skills.length > 0) {
|
|
143
|
+
lines.push("## Skills currently loaded");
|
|
144
|
+
lines.push("");
|
|
145
|
+
lines.push("| Skill | Tokens |");
|
|
146
|
+
lines.push("|-------|--------|");
|
|
147
|
+
const sorted = [...report.skills].sort((a, b) => b.tokens - a.tokens);
|
|
148
|
+
for (const s of sorted) {
|
|
149
|
+
lines.push(`| ${s.name} | ~${formatTokens(s.tokens)} |`);
|
|
150
|
+
}
|
|
151
|
+
lines.push("");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Section inventory
|
|
155
|
+
if (report.sections.length > 0) {
|
|
156
|
+
lines.push("## Other prompt sections");
|
|
157
|
+
lines.push("");
|
|
158
|
+
for (const sec of report.sections) {
|
|
159
|
+
const note = sec.note ? ` — ${sec.note}` : "";
|
|
160
|
+
lines.push(`- **${sec.label}**: ~${formatTokens(sec.tokens)} tok${note}`);
|
|
161
|
+
}
|
|
162
|
+
lines.push("");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
lines.push("## Your task");
|
|
166
|
+
lines.push("");
|
|
167
|
+
lines.push("Classify each loaded skill into one of these actions:");
|
|
168
|
+
lines.push("");
|
|
169
|
+
lines.push("| Action | Prompt cost | When to use |");
|
|
170
|
+
lines.push("|--------|-------------|-------------|");
|
|
171
|
+
lines.push("| **Keep as skill** | Full content every turn | Essential for this project, needed constantly |");
|
|
172
|
+
lines.push("| **Convert to rulebook rule** | Name + description only | Relevant but only needed on-demand (load via `rule://`) |");
|
|
173
|
+
lines.push("| **Convert to TTSR rule** | Zero | Behavioral enforcement — triggered by regex pattern in output stream |");
|
|
174
|
+
lines.push("| **Convert to slash command** | Zero | Interactive workflow the user invokes explicitly |");
|
|
175
|
+
lines.push("| **Disable** | Zero | Irrelevant to this project's tech stack |");
|
|
176
|
+
lines.push("");
|
|
177
|
+
lines.push("For each skill, consider:");
|
|
178
|
+
lines.push("1. Is it relevant to the detected tech stack? If not → **disable**.");
|
|
179
|
+
lines.push("2. Does it enforce a behavior pattern (debugging, TDD, verification)? → **TTSR** with a condition regex.");
|
|
180
|
+
lines.push("3. Is it reference material loaded for occasional lookups? → **rulebook** with a short description.");
|
|
181
|
+
lines.push("4. Is it an interactive workflow the user triggers explicitly? → **slash command**.");
|
|
182
|
+
lines.push("5. Is it essential context needed on every turn for this project? → **keep**.");
|
|
183
|
+
lines.push("");
|
|
184
|
+
lines.push("## Implementation");
|
|
185
|
+
lines.push("");
|
|
186
|
+
lines.push("After classifying, implement the changes:");
|
|
187
|
+
lines.push("");
|
|
188
|
+
lines.push("- **Rulebook**: Create `.omp/rules/<skill-name>.md` with YAML frontmatter `description: \"...\"` and condensed key content");
|
|
189
|
+
lines.push("- **TTSR**: Create `.omp/rules/<skill-name>.md` with YAML frontmatter `condition: \"regex_pattern\"`");
|
|
190
|
+
lines.push("- **Disable**: Note which skills to remove from the session configuration");
|
|
191
|
+
lines.push("- **Command**: Note which skills could become slash commands (but don't create them now)");
|
|
192
|
+
lines.push("");
|
|
193
|
+
lines.push("## Warnings");
|
|
194
|
+
lines.push("");
|
|
195
|
+
lines.push("- Do **NOT** delete files from `~/.omp/skills/` — only create project-local `.omp/rules/` files");
|
|
196
|
+
lines.push("- Rulebook and TTSR files go in `.omp/rules/` at the project root");
|
|
197
|
+
lines.push("- Preserve the original skill content's intent when condensing for rulebook rules");
|
|
198
|
+
lines.push("");
|
|
199
|
+
lines.push("Present your classification table and implementation plan first, then ask before executing.");
|
|
200
|
+
|
|
201
|
+
return lines.join("\n");
|
|
202
|
+
}
|