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.
Files changed (137) hide show
  1. package/README.md +118 -56
  2. package/bin/install.ts +48 -128
  3. package/package.json +11 -3
  4. package/skills/code-review/SKILL.md +137 -40
  5. package/skills/context-mode/SKILL.md +67 -56
  6. package/skills/creating-supi-agents/SKILL.md +204 -0
  7. package/skills/debugging/SKILL.md +86 -40
  8. package/skills/fix-pr/SKILL.md +96 -65
  9. package/skills/planning/SKILL.md +103 -46
  10. package/skills/qa-strategy/SKILL.md +68 -46
  11. package/skills/receiving-code-review/SKILL.md +60 -53
  12. package/skills/release/SKILL.md +111 -39
  13. package/skills/tdd/SKILL.md +118 -67
  14. package/skills/verification/SKILL.md +71 -37
  15. package/src/bootstrap.ts +27 -5
  16. package/src/commands/agents.ts +249 -0
  17. package/src/commands/ai-review.ts +1113 -0
  18. package/src/commands/config.ts +224 -95
  19. package/src/commands/doctor.ts +19 -13
  20. package/src/commands/fix-pr.ts +8 -11
  21. package/src/commands/generate.ts +200 -0
  22. package/src/commands/model-picker.ts +5 -15
  23. package/src/commands/model.ts +4 -5
  24. package/src/commands/optimize-context.ts +202 -0
  25. package/src/commands/plan.ts +148 -92
  26. package/src/commands/qa.ts +14 -23
  27. package/src/commands/release.ts +504 -275
  28. package/src/commands/review.ts +643 -86
  29. package/src/commands/status.ts +44 -17
  30. package/src/commands/supi.ts +69 -41
  31. package/src/commands/update.ts +57 -2
  32. package/src/config/defaults.ts +6 -39
  33. package/src/config/loader.ts +388 -40
  34. package/src/config/model-resolver.ts +26 -22
  35. package/src/config/schema.ts +113 -48
  36. package/src/context/analyzer.ts +61 -2
  37. package/src/context/optimizer.ts +199 -0
  38. package/src/context-mode/compressor.ts +14 -11
  39. package/src/context-mode/detector.ts +16 -54
  40. package/src/context-mode/event-extractor.ts +45 -16
  41. package/src/context-mode/event-store.ts +225 -16
  42. package/src/context-mode/hooks.ts +195 -22
  43. package/src/context-mode/knowledge/chunker.ts +235 -0
  44. package/src/context-mode/knowledge/store.ts +187 -0
  45. package/src/context-mode/routing.ts +12 -23
  46. package/src/context-mode/sandbox/executor.ts +183 -0
  47. package/src/context-mode/sandbox/runners.ts +40 -0
  48. package/src/context-mode/snapshot-builder.ts +243 -7
  49. package/src/context-mode/tools.ts +440 -0
  50. package/src/context-mode/web/fetcher.ts +117 -0
  51. package/src/context-mode/web/html-to-md.ts +293 -0
  52. package/src/debug/logger.ts +107 -0
  53. package/src/deps/registry.ts +0 -20
  54. package/src/docs/drift.ts +454 -0
  55. package/src/fix-pr/fetch-comments.ts +66 -0
  56. package/src/git/commit-msg.ts +2 -1
  57. package/src/git/commit.ts +123 -141
  58. package/src/git/conventions.ts +2 -2
  59. package/src/git/status.ts +4 -1
  60. package/src/lsp/bridge.ts +138 -12
  61. package/src/planning/approval-flow.ts +125 -19
  62. package/src/planning/plan-writer-prompt.ts +4 -11
  63. package/src/planning/planning-ask-tool.ts +81 -0
  64. package/src/planning/prompt-builder.ts +9 -169
  65. package/src/planning/system-prompt.ts +290 -0
  66. package/src/platform/omp.ts +50 -4
  67. package/src/platform/progress.ts +182 -0
  68. package/src/platform/test-utils.ts +4 -1
  69. package/src/platform/tui-colors.ts +30 -0
  70. package/src/platform/types.ts +1 -0
  71. package/src/qa/detect-app-type.ts +102 -0
  72. package/src/qa/discover-routes.ts +353 -0
  73. package/src/quality/ai-session.ts +96 -0
  74. package/src/quality/ai-setup.ts +86 -0
  75. package/src/quality/gates/ai-review.ts +129 -0
  76. package/src/quality/gates/build.ts +8 -0
  77. package/src/quality/gates/command.ts +150 -0
  78. package/src/quality/gates/format.ts +28 -0
  79. package/src/quality/gates/lint.ts +22 -0
  80. package/src/quality/gates/lsp-diagnostics.ts +84 -0
  81. package/src/quality/gates/test-suite.ts +8 -0
  82. package/src/quality/gates/typecheck.ts +22 -0
  83. package/src/quality/registry.ts +25 -0
  84. package/src/quality/review-gates.ts +33 -0
  85. package/src/quality/runner.ts +268 -0
  86. package/src/quality/schemas.ts +48 -0
  87. package/src/quality/setup.ts +227 -0
  88. package/src/release/changelog.ts +7 -3
  89. package/src/release/channels/custom.ts +43 -0
  90. package/src/release/channels/gitea.ts +35 -0
  91. package/src/release/channels/github.ts +35 -0
  92. package/src/release/channels/gitlab.ts +35 -0
  93. package/src/release/channels/registry.ts +52 -0
  94. package/src/release/channels/types.ts +27 -0
  95. package/src/release/detector.ts +10 -63
  96. package/src/release/executor.ts +61 -51
  97. package/src/release/prompt.ts +38 -38
  98. package/src/release/version.ts +129 -10
  99. package/src/review/agent-loader.ts +331 -0
  100. package/src/review/consolidator.ts +180 -0
  101. package/src/review/default-agents/correctness.md +72 -0
  102. package/src/review/default-agents/maintainability.md +64 -0
  103. package/src/review/default-agents/security.md +67 -0
  104. package/src/review/fixer.ts +219 -0
  105. package/src/review/multi-agent-runner.ts +135 -0
  106. package/src/review/output.ts +147 -0
  107. package/src/review/prompts/agent-review-wrapper.md +36 -0
  108. package/src/review/prompts/fix-findings.md +32 -0
  109. package/src/review/prompts/fix-output-schema.md +18 -0
  110. package/src/review/prompts/invalid-output-retry.md +22 -0
  111. package/src/review/prompts/output-instructions.md +14 -0
  112. package/src/review/prompts/review-output-schema.md +38 -0
  113. package/src/review/prompts/single-review.md +53 -0
  114. package/src/review/prompts/validation-review.md +30 -0
  115. package/src/review/runner.ts +128 -0
  116. package/src/review/scope.ts +353 -0
  117. package/src/review/template.ts +15 -0
  118. package/src/review/types.ts +296 -0
  119. package/src/review/validator.ts +160 -0
  120. package/src/storage/plans.ts +5 -3
  121. package/src/storage/reports.ts +50 -7
  122. package/src/storage/review-sessions.ts +117 -0
  123. package/src/text.ts +19 -0
  124. package/src/types.ts +336 -26
  125. package/src/utils/paths.ts +39 -0
  126. package/src/visual/companion.ts +5 -3
  127. package/src/visual/start-server.ts +101 -0
  128. package/src/visual/stop-server.ts +39 -0
  129. package/bin/ctx-mode-wrapper.mjs +0 -66
  130. package/src/config/profiles.ts +0 -64
  131. package/src/context-mode/installer.ts +0 -38
  132. package/src/quality/ai-review-gate.ts +0 -43
  133. package/src/quality/gate-runner.ts +0 -67
  134. package/src/quality/lsp-gate.ts +0 -24
  135. package/src/quality/test-gate.ts +0 -39
  136. package/src/visual/scripts/start-server.sh +0 -98
  137. 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
- // ANSI helpers — we cannot rely on the Theme object passed by ctx.ui.custom()
29
- // because its shape is internal to OMP and not part of the public API.
30
- const RESET = "\x1b[0m";
31
- const BOLD = "\x1b[1m";
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
  }
@@ -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(24)} ${"thinking".padEnd(10)} source`];
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(24)} ${thinkingDisplay.padEnd(10)} ${sourceDisplay}`,
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
- const slashIndex = choice.indexOf("/");
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
+ }