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
@@ -1,78 +1,204 @@
1
1
  import type { Platform, PlatformContext } from "../platform/types.js";
2
- import { loadConfig, updateConfig } from "../config/loader.js";
3
- import { listProfiles } from "../config/profiles.js";
4
- import { checkInstallation } from "../context-mode/installer.js";
2
+ import { DEFAULT_CONFIG } from "../config/defaults.js";
3
+ import { formatConfigErrors, inspectConfig, updateConfig } from "../config/loader.js";
4
+ import type { InspectionLoadResult } from "../config/schema.js";
5
5
  import type { SupipowersConfig } from "../types.js";
6
+ import { createWorkflowProgress } from "../platform/progress.js";
7
+ import {
8
+ interactivelySaveGateSetup,
9
+ setupGates,
10
+ summarizeEnabledGates,
11
+ type GateSetupMode,
12
+ type SetupGatesProgressEvent,
13
+ } from "../quality/setup.js";
6
14
 
7
15
  const FRAMEWORK_OPTIONS = [
8
- { value: "", label: "not set — auto-detect on first /supi:qa run", command: null },
9
- { value: "vitest", label: "vitest — npx vitest run", command: "npx vitest run" },
10
- { value: "jest", label: "jest — npx jest", command: "npx jest" },
11
- { value: "mocha", label: "mocha — npx mocha", command: "npx mocha" },
12
- { value: "pytest", label: "pytest — pytest", command: "pytest" },
13
- { value: "cargo-test", label: "cargo-test — cargo test", command: "cargo test" },
14
- { value: "go-test", label: "go-test — go test ./...", command: "go test ./..." },
15
- { value: "npm-test", label: "npm-test — npm test", command: "npm test" },
16
+ { value: "", label: "not set — auto-detect on first /supi:qa run" },
17
+ { value: "vitest", label: "vitest — npx vitest run" },
18
+ { value: "jest", label: "jest — npx jest" },
19
+ { value: "mocha", label: "mocha — npx mocha" },
20
+ { value: "pytest", label: "pytest — pytest" },
21
+ { value: "cargo-test", label: "cargo-test — cargo test" },
22
+ { value: "go-test", label: "go-test — go test ./..." },
23
+ { value: "npm-test", label: "npm-test — npm test" },
16
24
  ];
17
25
 
18
26
  const CHANNEL_OPTIONS = [
19
27
  { value: [] as string[], label: "not set — auto-detect on first /supi:release run" },
20
- { value: ["github", "npm"], label: "both — GitHub Release + npm publish" },
21
28
  { value: ["github"], label: "github — GitHub Release with gh CLI" },
22
- { value: ["npm"], label: "npm — npm publish to registry" },
23
29
  ];
24
30
 
25
- interface SettingDef {
31
+ export interface SettingDef {
26
32
  label: string;
27
33
  key: string;
28
34
  helpText: string;
29
35
  type: "select" | "toggle";
30
36
  options?: string[];
31
- get: (config: SupipowersConfig) => string;
32
- set: (cwd: string, value: unknown) => void;
37
+ get: () => string;
38
+ set: (cwd: string, value: unknown) => Promise<string | null>;
33
39
  }
34
40
 
35
- function buildSettings(platform: Platform, cwd: string): SettingDef[] {
41
+ export interface ConfigCommandDependencies {
42
+ inspectConfig: typeof inspectConfig;
43
+ updateConfig: typeof updateConfig;
44
+ setupGates: typeof setupGates;
45
+ interactivelySaveGateSetup: typeof interactivelySaveGateSetup;
46
+ }
47
+
48
+ const CONFIG_COMMAND_DEPENDENCIES: ConfigCommandDependencies = {
49
+ inspectConfig,
50
+ updateConfig,
51
+ setupGates,
52
+ interactivelySaveGateSetup,
53
+ };
54
+
55
+ function currentConfig(inspection: InspectionLoadResult): SupipowersConfig {
56
+ return inspection.effectiveConfig ?? DEFAULT_CONFIG;
57
+ }
58
+
59
+ function describeInspection(inspection: InspectionLoadResult, config: SupipowersConfig): string {
60
+ if (inspection.parseErrors.length > 0 || inspection.validationErrors.length > 0) {
61
+ return `config error — ${formatConfigErrors(inspection).split("\n")[0]}`;
62
+ }
63
+
64
+ return summarizeEnabledGates(config.quality.gates);
65
+ }
66
+
67
+ function createQualityGateSetupProgress(ctx: PlatformContext, mode: GateSetupMode) {
68
+ if (mode !== "ai-assisted") {
69
+ return null;
70
+ }
71
+
72
+ const progress = createWorkflowProgress(ctx.ui, {
73
+ title: "quality gate setup",
74
+ statusKey: "supi-quality-gate-setup",
75
+ widgetKey: "supi-quality-gate-setup",
76
+ steps: [
77
+ { key: "collect", label: "Inspect project" },
78
+ { key: "ai", label: "AI analysis" },
79
+ ],
80
+ });
81
+
82
+ return {
83
+ handle(event: SetupGatesProgressEvent) {
84
+ switch (event.type) {
85
+ case "collecting-project-facts":
86
+ progress.activate("collect", "Reading scripts and tools");
87
+ return;
88
+ case "baseline-ready":
89
+ progress.complete("collect", "baseline ready");
90
+ return;
91
+ case "ai-analysis-started":
92
+ progress.activate("ai", "Analyzing project checks");
93
+ return;
94
+ case "ai-analysis-completed":
95
+ progress.complete("ai", "proposal ready");
96
+ return;
97
+ }
98
+ },
99
+ fail(message: string) {
100
+ if (progress.getStatus("ai") === "active") {
101
+ progress.fail("ai", message);
102
+ } else if (progress.getStatus("collect") === "active") {
103
+ progress.fail("collect", message);
104
+ }
105
+ },
106
+ dispose() {
107
+ progress.dispose();
108
+ },
109
+ };
110
+ }
111
+
112
+ function buildGateSetupDialogOptions(mode: GateSetupMode) {
113
+ return mode === "ai-assisted"
114
+ ? {
115
+ title: "AI-assisted quality gate proposal",
116
+ intro: "AI analyzed your project and suggested these gates.",
117
+ }
118
+ : {
119
+ intro: "Detected project checks and suggested these gates.",
120
+ };
121
+ }
122
+
123
+ export function buildSettings(
124
+ platform: Platform,
125
+ ctx: PlatformContext,
126
+ inspection: InspectionLoadResult,
127
+ deps: ConfigCommandDependencies = CONFIG_COMMAND_DEPENDENCIES,
128
+ ): SettingDef[] {
36
129
  const { paths } = platform;
130
+ const config = currentConfig(inspection);
131
+
37
132
  return [
38
133
  {
39
- label: "Default profile",
40
- key: "defaultProfile",
41
- helpText: "Review depth used when no flag is passed to /supi:review",
134
+ label: "Setup quality gates",
135
+ key: "quality.gates",
136
+ helpText: "Inspect the project and configure review gates",
42
137
  type: "select",
43
- options: listProfiles(paths, cwd),
44
- get: (c) => c.defaultProfile,
45
- set: (d, v) => updateConfig(paths, d, { defaultProfile: v }),
138
+ options: ["Run deterministic setup", "Run AI-assisted setup"],
139
+ get: () => describeInspection(inspection, config),
140
+ set: async (cwd, value) => {
141
+ const mode: GateSetupMode = String(value).includes("AI-assisted") ? "ai-assisted" : "deterministic";
142
+ const progress = createQualityGateSetupProgress(ctx, mode);
143
+
144
+ try {
145
+ const result = await deps.setupGates(platform, cwd, deps.inspectConfig(platform.paths, cwd), {
146
+ mode,
147
+ onProgress: (event) => progress?.handle(event),
148
+ });
149
+
150
+ if (result.status === "invalid") {
151
+ ctx.ui.notify(result.errors.join("\n"), "error");
152
+ return null;
153
+ }
154
+
155
+ if (result.status !== "proposed") {
156
+ return null;
157
+ }
158
+
159
+ progress?.dispose();
160
+ const saveResult = await deps.interactivelySaveGateSetup(
161
+ ctx,
162
+ paths,
163
+ cwd,
164
+ result.proposal,
165
+ buildGateSetupDialogOptions(mode),
166
+ );
167
+ return saveResult === "saved" ? "saved" : null;
168
+ } catch (error) {
169
+ progress?.fail((error as Error).message);
170
+ throw error;
171
+ } finally {
172
+ progress?.dispose();
173
+ }
174
+ },
46
175
  },
47
176
  {
48
177
  label: "LSP setup guide",
49
178
  key: "lsp.setupGuide",
50
179
  helpText: "Show LSP setup tips when no language server is active",
51
180
  type: "toggle",
52
- get: (c) => c.lsp.setupGuide ? "on" : "off",
53
- set: (d, v) => updateConfig(paths, d, { lsp: { setupGuide: v === "on" } }),
54
- },
55
- {
56
- label: "Notification verbosity",
57
- key: "notifications.verbosity",
58
- helpText: "How much detail supipowers shows in notifications",
59
- type: "select",
60
- options: ["quiet", "normal", "verbose"],
61
- get: (c) => c.notifications.verbosity,
62
- set: (d, v) => updateConfig(paths, d, { notifications: { verbosity: v } }),
181
+ get: () => (config.lsp.setupGuide ? "on" : "off"),
182
+ set: async (cwd, value) => {
183
+ deps.updateConfig(paths, cwd, { lsp: { setupGuide: value === "on" } });
184
+ return String(value);
185
+ },
63
186
  },
64
187
  {
65
188
  label: "QA framework",
66
189
  key: "qa.framework",
67
190
  helpText: "Test runner used by /supi:qa",
68
191
  type: "select",
69
- options: FRAMEWORK_OPTIONS.map((f) => f.label),
70
- get: (c) => c.qa.framework ?? "not set",
71
- set: (d, v) => {
72
- const chosen = FRAMEWORK_OPTIONS.find((f) => f.label === v);
73
- if (chosen) {
74
- updateConfig(paths, d, { qa: { framework: chosen.value || null, command: chosen.command } });
192
+ options: FRAMEWORK_OPTIONS.map((framework) => framework.label),
193
+ get: () => config.qa.framework ?? "not set",
194
+ set: async (cwd, value) => {
195
+ const chosen = FRAMEWORK_OPTIONS.find((framework) => framework.label === value);
196
+ if (!chosen) {
197
+ return null;
75
198
  }
199
+
200
+ deps.updateConfig(paths, cwd, { qa: { framework: chosen.value || null } });
201
+ return chosen.label.split(" — ")[0];
76
202
  },
77
203
  },
78
204
  {
@@ -80,50 +206,58 @@ function buildSettings(platform: Platform, cwd: string): SettingDef[] {
80
206
  key: "release.channels",
81
207
  helpText: "Where /supi:release publishes your project",
82
208
  type: "select",
83
- options: CHANNEL_OPTIONS.map((p) => p.label),
84
- get: (c) => c.release.channels.length > 0 ? c.release.channels.join(", ") : "not set",
85
- set: (d, v) => {
86
- const chosen = CHANNEL_OPTIONS.find((p) => p.label === v);
87
- if (chosen) {
88
- updateConfig(paths, d, { release: { channels: chosen.value } });
209
+ options: CHANNEL_OPTIONS.map((channel) => channel.label),
210
+ get: () =>
211
+ config.release.channels.length > 0 ? config.release.channels.join(", ") : "not set",
212
+ set: async (cwd, value) => {
213
+ const chosen = CHANNEL_OPTIONS.find((channel) => channel.label === value);
214
+ if (!chosen) {
215
+ return null;
89
216
  }
217
+
218
+ deps.updateConfig(paths, cwd, { release: { channels: chosen.value } });
219
+ return chosen.label.split(" — ")[0];
90
220
  },
91
221
  },
92
222
  ];
93
223
  }
94
224
 
95
- export function handleConfig(platform: Platform, ctx: PlatformContext): void {
225
+ export async function runConfigMenu(
226
+ platform: Platform,
227
+ ctx: PlatformContext,
228
+ deps: ConfigCommandDependencies = CONFIG_COMMAND_DEPENDENCIES,
229
+ ): Promise<void> {
96
230
  if (!ctx.hasUI) {
97
231
  ctx.ui.notify("Config UI requires interactive mode", "warning");
98
232
  return;
99
233
  }
100
234
 
101
- void (async () => {
102
- const settings = buildSettings(platform, ctx.cwd);
103
-
104
- while (true) {
105
- const config = loadConfig(platform.paths, ctx.cwd);
106
-
107
- const options = settings.map(
108
- (s) => `${s.label}: ${s.get(config)}`
109
- );
110
- options.push("Done");
235
+ while (true) {
236
+ const inspection = deps.inspectConfig(platform.paths, ctx.cwd);
237
+ const settings = buildSettings(platform, ctx, inspection, deps);
238
+ const options = settings.map((setting) => `${setting.label}: ${setting.get()}`);
239
+ options.push("Done");
111
240
 
112
- const choice = await ctx.ui.select(
113
- "Supipowers Settings",
114
- options,
115
- { helpText: "Select a setting to change · Esc to close" },
116
- );
241
+ const choice = await ctx.ui.select(
242
+ "Supipowers Settings",
243
+ options,
244
+ { helpText: "Select a setting to change · Esc to close" },
245
+ );
117
246
 
118
- if (choice === undefined || choice === null || choice === "Done") break;
247
+ if (choice === undefined || choice === null || choice === "Done") {
248
+ break;
249
+ }
119
250
 
120
- const index = options.indexOf(choice);
121
- const setting = settings[index];
122
- if (!setting) break;
251
+ const index = options.indexOf(choice);
252
+ const setting = settings[index];
253
+ if (!setting) {
254
+ break;
255
+ }
123
256
 
257
+ try {
124
258
  if (setting.type === "select" && setting.options) {
125
- const currentValue = setting.get(config);
126
- const currentIndex = setting.options.findIndex((o) => o.startsWith(currentValue));
259
+ const currentValue = setting.get();
260
+ const currentIndex = setting.options.findIndex((option) => option.startsWith(currentValue));
127
261
  const value = await ctx.ui.select(
128
262
  setting.label,
129
263
  setting.options,
@@ -132,39 +266,34 @@ export function handleConfig(platform: Platform, ctx: PlatformContext): void {
132
266
  helpText: setting.helpText,
133
267
  },
134
268
  );
269
+
135
270
  if (value !== undefined && value !== null) {
136
- setting.set(ctx.cwd, value);
137
- const display = value.split(" — ")[0];
138
- ctx.ui.notify(`${setting.label} → ${display}`, "info");
271
+ const display = await setting.set(ctx.cwd, value);
272
+ if (display) {
273
+ ctx.ui.notify(`${setting.label} → ${display}`, "info");
274
+ }
139
275
  }
140
276
  } else if (setting.type === "toggle") {
141
- const current = setting.get(config);
277
+ const current = setting.get();
142
278
  const newValue = current === "on" ? "off" : "on";
143
- setting.set(ctx.cwd, newValue);
144
- ctx.ui.notify(`${setting.label} → ${newValue}`, "info");
279
+ const display = await setting.set(ctx.cwd, newValue);
280
+ if (display) {
281
+ ctx.ui.notify(`${setting.label} → ${display}`, "info");
282
+ }
145
283
  }
284
+ } catch (error) {
285
+ ctx.ui.notify((error as Error).message, "error");
146
286
  }
147
- })();
148
-
149
- // Context-mode status (async, fire-and-forget)
150
- checkInstallation(
151
- (cmd: string, args: string[]) => platform.exec(cmd, args),
152
- platform.getActiveTools(),
153
- ).then((status) => {
154
- const lines = [
155
- "",
156
- "Context Mode:",
157
- ` CLI installed: ${status.cliInstalled ? "\u2713" + (status.version ? ` v${status.version}` : "") : "\u2717"}`,
158
- ` MCP configured: ${status.mcpConfigured ? "\u2713" : "\u2717"}`,
159
- ` Tools available: ${status.toolsAvailable ? "\u2713" : "\u2717"}`,
160
- ];
161
- if (!status.mcpConfigured && status.cliInstalled) {
162
- lines.push(` \u2192 Run \`${platform.name} mcp add context-mode\` to enable`);
163
- }
164
- ctx.ui.notify(lines.join("\n"), "info");
165
- }).catch(() => {
166
- // Silently ignore — context-mode status is optional
167
- });
287
+ }
288
+ }
289
+
290
+ export function handleConfig(platform: Platform, ctx: PlatformContext): void {
291
+ void runConfigMenu(platform, ctx, CONFIG_COMMAND_DEPENDENCIES);
292
+
293
+ ctx.ui.notify(
294
+ "\nContext Mode: built-in (native tools)",
295
+ "info",
296
+ );
168
297
  }
169
298
 
170
299
  export function registerConfigCommand(platform: Platform): void {
@@ -1,9 +1,11 @@
1
1
  import type { Platform, PlatformContext, PlatformCapabilities } from "../platform/types.js";
2
2
  import { existsSync, writeFileSync, unlinkSync } from "node:fs";
3
3
  import { join } from "node:path";
4
- import { loadConfig } from "../config/loader.js";
4
+ import { DEFAULT_CONFIG } from "../config/defaults.js";
5
+ import { formatConfigErrors, inspectConfig } from "../config/loader.js";
5
6
  import { detectContextMode } from "../context-mode/detector.js";
6
7
  import { isLspAvailable } from "../lsp/detector.js";
8
+ import { summarizeEnabledGates } from "../quality/setup.js";
7
9
  import { DEPENDENCIES } from "../deps/registry.js";
8
10
 
9
11
  export interface CheckResult {
@@ -39,7 +41,7 @@ export async function checkPlatform(platform: Platform): Promise<CheckResult> {
39
41
  const name = platform.name.toUpperCase();
40
42
  const presence = { ok: true, detail: `${name} detected` };
41
43
  try {
42
- const r = await platform.exec("echo", ["ok"]);
44
+ const r = await platform.exec("node", ["--version"]);
43
45
  return { name: "Platform", presence, functional: { ok: r.code === 0, detail: r.code === 0 ? "exec works" : "exec failed" } };
44
46
  } catch {
45
47
  return { name: "Platform", presence, functional: { ok: false, detail: "exec failed" } };
@@ -51,15 +53,14 @@ export async function checkConfig(platform: Platform, cwd: string): Promise<Chec
51
53
  const globalPath = platform.paths.global("config.json");
52
54
  const projectExists = existsSync(projectPath);
53
55
  const globalExists = existsSync(globalPath);
54
-
55
- // loadConfig never throws it falls back to DEFAULT_CONFIG via readJsonSafe
56
- const config = loadConfig(platform.paths, cwd);
56
+ const inspection = inspectConfig(platform.paths, cwd);
57
+ const config = inspection.effectiveConfig ?? DEFAULT_CONFIG;
57
58
 
58
59
  if (!projectExists && !globalExists) {
59
60
  return {
60
61
  name: "Config",
61
62
  presence: { ok: false, detail: "No config.json found (using defaults)" },
62
- functional: { ok: true, detail: `defaultProfile: ${config.defaultProfile}` },
63
+ functional: { ok: true, detail: `quality.gates: ${summarizeEnabledGates(config.quality.gates)}` },
63
64
  };
64
65
  }
65
66
 
@@ -67,10 +68,18 @@ export async function checkConfig(platform: Platform, cwd: string): Promise<Chec
67
68
  ? `${platform.paths.dotDir}/supipowers/config.json (project)`
68
69
  : `~/${platform.paths.dotDir}/supipowers/config.json (global)`;
69
70
 
71
+ if (inspection.parseErrors.length > 0 || inspection.validationErrors.length > 0) {
72
+ return {
73
+ name: "Config",
74
+ presence: { ok: true, detail: `Found ${foundPath}` },
75
+ functional: { ok: false, detail: formatConfigErrors(inspection) },
76
+ };
77
+ }
78
+
70
79
  return {
71
80
  name: "Config",
72
81
  presence: { ok: true, detail: `Found ${foundPath}` },
73
- functional: { ok: true, detail: `Parsed (defaultProfile: ${config.defaultProfile})` },
82
+ functional: { ok: true, detail: `quality.gates: ${summarizeEnabledGates(config.quality.gates)}` },
74
83
  };
75
84
  }
76
85
 
@@ -218,11 +227,8 @@ const CTX_TOOL_NAMES: Record<string, string> = {
218
227
  ctxFetchAndIndex: "ctx_fetch_and_index",
219
228
  };
220
229
 
221
- export function checkContextMode(activeTools: string[]): CheckResult {
222
- const status = detectContextMode(activeTools);
223
- if (!status.available) {
224
- return { name: "Context Mode", presence: { ok: false, detail: "No context-mode tools detected" } };
225
- }
230
+ export function checkContextMode(_activeTools: string[]): CheckResult {
231
+ const status = detectContextMode();
226
232
 
227
233
  const foundNames = Object.entries(status.tools)
228
234
  .filter(([, v]) => v)
@@ -230,7 +236,7 @@ export function checkContextMode(activeTools: string[]): CheckResult {
230
236
 
231
237
  return {
232
238
  name: "Context Mode",
233
- presence: { ok: true, detail: "Tools available" },
239
+ presence: { ok: true, detail: "Native tools (built-in)" },
234
240
  functional: { ok: true, detail: foundNames.join(", ") },
235
241
  };
236
242
  }
@@ -1,6 +1,7 @@
1
1
  import type { Platform } from "../platform/types.js";
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
+ import { moduleDir, toBashPath } from "../utils/paths.js";
4
5
  import { loadFixPrConfig, saveFixPrConfig } from "../fix-pr/config.js";
5
6
  import { buildFixPrOrchestratorPrompt } from "../fix-pr/prompt-builder.js";
6
7
  import type { FixPrConfig, CommentReplyPolicy } from "../fix-pr/types.js";
@@ -15,6 +16,7 @@ import { modelRegistry } from "../config/model-registry-instance.js";
15
16
  import { resolveModelForAction, createModelBridge, applyModelOverride } from "../config/model-resolver.js";
16
17
  import { loadModelConfig } from "../config/model-config.js";
17
18
  import { detectBotReviewers } from "../fix-pr/bot-detector.js";
19
+ import { fetchPrComments } from "../fix-pr/fetch-comments.js";
18
20
 
19
21
  modelRegistry.register({
20
22
  id: "fix-pr",
@@ -32,13 +34,13 @@ modelRegistry.register({
32
34
  });
33
35
 
34
36
  function getScriptsDir(): string {
35
- return path.join(path.dirname(new URL(import.meta.url).pathname), "..", "fix-pr", "scripts");
37
+ return toBashPath(path.join(moduleDir(import.meta.url), "..", "fix-pr", "scripts"));
36
38
  }
37
39
 
38
40
  function findSkillPath(skillName: string): string | null {
39
41
  const candidates = [
40
42
  path.join(process.cwd(), "skills", skillName, "SKILL.md"),
41
- path.join(path.dirname(new URL(import.meta.url).pathname), "..", "..", "skills", skillName, "SKILL.md"),
43
+ path.join(moduleDir(import.meta.url), "..", "..", "skills", skillName, "SKILL.md"),
42
44
  ];
43
45
  for (const p of candidates) {
44
46
  if (fs.existsSync(p)) return p;
@@ -138,19 +140,14 @@ export function registerFixPrCommand(platform: Platform): void {
138
140
  }
139
141
 
140
142
  // ── Step 4: Fetch initial comments ─────────────────────────────
141
- const sessionDir = getSessionDir(platform.paths, ctx.cwd, ledger.id);
143
+ const sessionDir = toBashPath(getSessionDir(platform.paths, ctx.cwd, ledger.id));
142
144
  const scriptsDir = getScriptsDir();
143
145
  const snapshotPath = path.join(sessionDir, "snapshots", `comments-${ledger.iteration}.jsonl`);
144
146
 
145
- const fetchResult = await platform.exec("bash", [
146
- path.join(scriptsDir, "fetch-pr-comments.sh"),
147
- repo,
148
- String(prNumber),
149
- snapshotPath,
150
- ], { cwd: ctx.cwd });
147
+ const fetchError = await fetchPrComments(platform, repo, prNumber, snapshotPath, ctx.cwd);
151
148
 
152
- if (fetchResult.code !== 0) {
153
- notifyError(ctx, "Failed to fetch PR comments", fetchResult.stderr);
149
+ if (fetchError) {
150
+ notifyError(ctx, "Failed to fetch PR comments", fetchError);
154
151
  return;
155
152
  }
156
153