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
package/src/text.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Normalizes Windows CRLF line endings to LF. */
|
|
2
|
+
export function normalizeLineEndings(text: string): string {
|
|
3
|
+
return text.replace(/\r\n/g, "\n");
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/** Removes a single outer markdown code fence wrapper when present. */
|
|
7
|
+
export function stripMarkdownCodeFence(text: string): string {
|
|
8
|
+
const trimmed = text.trim();
|
|
9
|
+
if (!trimmed.startsWith("```")) {
|
|
10
|
+
return trimmed;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const lines = trimmed.split("\n");
|
|
14
|
+
if (lines.length >= 3 && lines[0]!.startsWith("```") && lines[lines.length - 1] === "```") {
|
|
15
|
+
return lines.slice(1, -1).join("\n").trim();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return trimmed;
|
|
19
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { TSchema } from "@sinclair/typebox";
|
|
2
|
+
import type { AgentSession, AgentSessionOptions, ExecOptions, ExecResult } from "./platform/types.js";
|
|
3
|
+
|
|
1
4
|
// src/types.ts — Shared type definitions for supipowers
|
|
2
5
|
|
|
3
6
|
/** Task complexity level */
|
|
@@ -35,12 +38,14 @@ export interface Notification {
|
|
|
35
38
|
detail?: string;
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
/**
|
|
39
|
-
export
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
/** Canonical quality gate identifiers */
|
|
42
|
+
export type CommandGateId = "lint" | "typecheck" | "format" | "test-suite" | "build";
|
|
43
|
+
export type GateId = "lsp-diagnostics" | CommandGateId;
|
|
44
|
+
|
|
45
|
+
export type ConfigScope = "project" | "global";
|
|
46
|
+
|
|
47
|
+
/** Aggregate gate execution status */
|
|
48
|
+
export type GateStatus = "passed" | "failed" | "skipped" | "blocked";
|
|
44
49
|
|
|
45
50
|
/** A single issue from a quality gate */
|
|
46
51
|
export interface GateIssue {
|
|
@@ -48,23 +53,315 @@ export interface GateIssue {
|
|
|
48
53
|
message: string;
|
|
49
54
|
file?: string;
|
|
50
55
|
line?: number;
|
|
56
|
+
detail?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** A single quality gate result */
|
|
60
|
+
export interface GateResult {
|
|
61
|
+
gate: GateId;
|
|
62
|
+
status: GateStatus;
|
|
63
|
+
summary: string;
|
|
64
|
+
issues: GateIssue[];
|
|
65
|
+
metadata?: Record<string, unknown>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Aggregate quality-gate summary */
|
|
69
|
+
export interface GateSummary {
|
|
70
|
+
passed: number;
|
|
71
|
+
failed: number;
|
|
72
|
+
skipped: number;
|
|
73
|
+
blocked: number;
|
|
51
74
|
}
|
|
52
75
|
|
|
53
76
|
/** Review report */
|
|
54
77
|
export interface ReviewReport {
|
|
55
|
-
profile: string;
|
|
56
78
|
timestamp: string;
|
|
79
|
+
selectedGates: GateId[];
|
|
57
80
|
gates: GateResult[];
|
|
58
|
-
|
|
81
|
+
summary: GateSummary;
|
|
82
|
+
overallStatus: Exclude<GateStatus, "skipped">;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** AI review pipeline levels */
|
|
86
|
+
export type ReviewLevel = "quick" | "deep" | "multi-agent";
|
|
87
|
+
|
|
88
|
+
/** Scope selection modes for /supi:review */
|
|
89
|
+
export type ReviewScopeMode = "pull-request" | "uncommitted" | "commit" | "custom";
|
|
90
|
+
|
|
91
|
+
/** Structured review output status */
|
|
92
|
+
export type ReviewOutputStatus = "passed" | "failed" | "blocked";
|
|
93
|
+
|
|
94
|
+
/** Review finding severity */
|
|
95
|
+
export type ReviewFindingSeverity = "error" | "warning" | "info";
|
|
96
|
+
|
|
97
|
+
/** Prioritization tier for review findings */
|
|
98
|
+
export type ReviewFindingPriority = "P0" | "P1" | "P2" | "P3";
|
|
99
|
+
|
|
100
|
+
/** Validation verdict for a review finding */
|
|
101
|
+
export type ReviewValidationVerdict = "confirmed" | "rejected" | "uncertain";
|
|
102
|
+
|
|
103
|
+
/** Review session lifecycle status */
|
|
104
|
+
export type ReviewSessionStatus = "running" | "completed" | "blocked" | "cancelled";
|
|
105
|
+
|
|
106
|
+
/** User decision after reviewing consolidated/current findings */
|
|
107
|
+
export type ReviewPostConsolidationAction =
|
|
108
|
+
| "fix-now"
|
|
109
|
+
| "document-only"
|
|
110
|
+
| "discuss-before-fixing";
|
|
111
|
+
|
|
112
|
+
/** File-level diff summary within a review scope */
|
|
113
|
+
export interface ReviewScopeFile {
|
|
114
|
+
path: string;
|
|
115
|
+
additions: number;
|
|
116
|
+
deletions: number;
|
|
117
|
+
diff: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Aggregate statistics for a review scope */
|
|
121
|
+
export interface ReviewScopeStats {
|
|
122
|
+
filesChanged: number;
|
|
123
|
+
excludedFiles: number;
|
|
124
|
+
additions: number;
|
|
125
|
+
deletions: number;
|
|
59
126
|
}
|
|
60
127
|
|
|
128
|
+
/** Review scope selected by the command pipeline */
|
|
129
|
+
export interface ReviewScope {
|
|
130
|
+
mode: ReviewScopeMode;
|
|
131
|
+
description: string;
|
|
132
|
+
diff: string;
|
|
133
|
+
files: ReviewScopeFile[];
|
|
134
|
+
stats: ReviewScopeStats;
|
|
135
|
+
baseBranch?: string;
|
|
136
|
+
commit?: string;
|
|
137
|
+
customInstructions?: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Per-finding validation metadata */
|
|
141
|
+
export interface ReviewFindingValidation {
|
|
142
|
+
verdict: ReviewValidationVerdict;
|
|
143
|
+
reasoning: string;
|
|
144
|
+
validatedBy: string;
|
|
145
|
+
validatedAt: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Canonical review finding produced by AI reviewers */
|
|
149
|
+
export interface ReviewFinding {
|
|
150
|
+
id: string;
|
|
151
|
+
title: string;
|
|
152
|
+
severity: ReviewFindingSeverity;
|
|
153
|
+
priority: ReviewFindingPriority;
|
|
154
|
+
confidence: number;
|
|
155
|
+
file: string | null;
|
|
156
|
+
lineStart: number | null;
|
|
157
|
+
lineEnd: number | null;
|
|
158
|
+
body: string;
|
|
159
|
+
suggestion: string | null;
|
|
160
|
+
agent?: string;
|
|
161
|
+
validation?: ReviewFindingValidation;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Structured output returned by review agents */
|
|
165
|
+
export interface ReviewOutput {
|
|
166
|
+
findings: ReviewFinding[];
|
|
167
|
+
summary: string;
|
|
168
|
+
status: ReviewOutputStatus;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Config entry for one review agent */
|
|
172
|
+
export interface ReviewAgentConfig {
|
|
173
|
+
name: string;
|
|
174
|
+
enabled: boolean;
|
|
175
|
+
data: string;
|
|
176
|
+
model: string | null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Top-level review agent pipeline config */
|
|
180
|
+
export interface ReviewAgentsConfig {
|
|
181
|
+
agents: ReviewAgentConfig[];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Loaded review agent definition from markdown frontmatter + prompt body */
|
|
185
|
+
export interface ReviewAgentDefinition {
|
|
186
|
+
name: string;
|
|
187
|
+
description: string;
|
|
188
|
+
focus: string | null;
|
|
189
|
+
prompt: string;
|
|
190
|
+
filePath: string;
|
|
191
|
+
}
|
|
192
|
+
/** Review agent definition combined with pipeline config */
|
|
193
|
+
export interface ConfiguredReviewAgent extends ReviewAgentDefinition {
|
|
194
|
+
enabled: boolean;
|
|
195
|
+
data: string;
|
|
196
|
+
model: string | null;
|
|
197
|
+
scope?: "global" | "project";
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
/** Persisted summary for one review iteration */
|
|
203
|
+
export interface ReviewIterationSummary {
|
|
204
|
+
iteration: number;
|
|
205
|
+
findings: number;
|
|
206
|
+
status: ReviewOutputStatus;
|
|
207
|
+
file: string;
|
|
208
|
+
createdAt: string;
|
|
209
|
+
}
|
|
210
|
+
/** Aggregate status of an auto-fix pass */
|
|
211
|
+
export type ReviewFixOutputStatus = "applied" | "partial" | "skipped" | "blocked";
|
|
212
|
+
|
|
213
|
+
/** Structured result returned by the review fixer */
|
|
214
|
+
export interface ReviewFixOutput {
|
|
215
|
+
fixes: ReviewFixRecord[];
|
|
216
|
+
summary: string;
|
|
217
|
+
status: ReviewFixOutputStatus;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
/** Persisted auto-fix attempt */
|
|
223
|
+
export interface ReviewFixRecord {
|
|
224
|
+
findingIds: string[];
|
|
225
|
+
file: string | null;
|
|
226
|
+
status: "applied" | "skipped" | "failed";
|
|
227
|
+
summary: string;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Paths to persisted review session artifacts */
|
|
231
|
+
export interface ReviewSessionArtifacts {
|
|
232
|
+
scope: string;
|
|
233
|
+
iterationsDir: string;
|
|
234
|
+
agentsDir: string;
|
|
235
|
+
rawFindings?: string;
|
|
236
|
+
validatedFindings?: string;
|
|
237
|
+
consolidatedFindings?: string;
|
|
238
|
+
findingsReport?: string;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Persisted /supi:review session metadata */
|
|
242
|
+
export interface ReviewSession {
|
|
243
|
+
id: string;
|
|
244
|
+
createdAt: string;
|
|
245
|
+
updatedAt: string;
|
|
246
|
+
level: ReviewLevel;
|
|
247
|
+
status: ReviewSessionStatus;
|
|
248
|
+
scope: ReviewScope;
|
|
249
|
+
validateFindings: boolean;
|
|
250
|
+
consolidate: boolean;
|
|
251
|
+
postConsolidationAction: ReviewPostConsolidationAction | null;
|
|
252
|
+
maxIterations: number;
|
|
253
|
+
currentIteration: number;
|
|
254
|
+
iterations: ReviewIterationSummary[];
|
|
255
|
+
fixes: ReviewFixRecord[];
|
|
256
|
+
artifacts: ReviewSessionArtifacts;
|
|
257
|
+
agents: string[];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
/** Config for the lsp-diagnostics gate */
|
|
262
|
+
export interface LspDiagnosticsGateConfig {
|
|
263
|
+
enabled: boolean;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Shared config for command-driven gates. */
|
|
267
|
+
export type CommandGateConfig =
|
|
268
|
+
| { enabled: false; command?: string | null }
|
|
269
|
+
| { enabled: true; command: string };
|
|
270
|
+
|
|
271
|
+
export type LintGateConfig = CommandGateConfig;
|
|
272
|
+
export type TypecheckGateConfig = CommandGateConfig;
|
|
273
|
+
export type FormatGateConfig = CommandGateConfig;
|
|
274
|
+
export type TestSuiteGateConfig = CommandGateConfig;
|
|
275
|
+
export type BuildGateConfig = CommandGateConfig;
|
|
276
|
+
|
|
277
|
+
/** Canonical quality gate config map */
|
|
278
|
+
export interface QualityGatesConfig {
|
|
279
|
+
"lsp-diagnostics"?: LspDiagnosticsGateConfig;
|
|
280
|
+
"lint"?: LintGateConfig;
|
|
281
|
+
"typecheck"?: TypecheckGateConfig;
|
|
282
|
+
"format"?: FormatGateConfig;
|
|
283
|
+
"test-suite"?: TestSuiteGateConfig;
|
|
284
|
+
"build"?: BuildGateConfig;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** Gate filter options provided by commands */
|
|
288
|
+
export interface GateFilters {
|
|
289
|
+
only?: GateId[];
|
|
290
|
+
skip?: GateId[];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Project facts used by gate setup/detection */
|
|
294
|
+
export interface ProjectFacts {
|
|
295
|
+
cwd: string;
|
|
296
|
+
packageScripts: Record<string, string>;
|
|
297
|
+
lockfiles: string[];
|
|
298
|
+
activeTools: string[];
|
|
299
|
+
existingGates: QualityGatesConfig;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Recommendation returned by gate auto-detection */
|
|
303
|
+
export interface GateDetectionResult<TConfig = unknown> {
|
|
304
|
+
suggestedConfig: TConfig | null;
|
|
305
|
+
confidence: "high" | "medium" | "low";
|
|
306
|
+
reason: string;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** Shared runtime context passed to each gate */
|
|
310
|
+
export interface GateExecutionContext {
|
|
311
|
+
cwd: string;
|
|
312
|
+
changedFiles: string[];
|
|
313
|
+
scopeFiles: string[];
|
|
314
|
+
fileScope: "changed-files" | "all-files";
|
|
315
|
+
exec: (cmd: string, args: string[], opts?: ExecOptions) => Promise<ExecResult>;
|
|
316
|
+
execShell: (command: string, opts?: ExecOptions) => Promise<ExecResult>;
|
|
317
|
+
getLspDiagnostics: (
|
|
318
|
+
scopeFiles: string[],
|
|
319
|
+
fileScope: GateExecutionContext["fileScope"]
|
|
320
|
+
) => Promise<GateIssue[]>;
|
|
321
|
+
createAgentSession: (
|
|
322
|
+
opts: Pick<AgentSessionOptions, "cwd" | "model" | "thinkingLevel">
|
|
323
|
+
) => Promise<AgentSession>;
|
|
324
|
+
activeTools: string[];
|
|
325
|
+
reviewModel?: Pick<ResolvedModel, "model" | "thinkingLevel">;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** Registered quality gate contract */
|
|
329
|
+
export interface GateDefinition<TConfig> {
|
|
330
|
+
id: GateId;
|
|
331
|
+
description: string;
|
|
332
|
+
configSchema: TSchema;
|
|
333
|
+
detect(projectFacts: ProjectFacts): GateDetectionResult<TConfig> | null;
|
|
334
|
+
run(context: GateExecutionContext, config: TConfig): Promise<GateResult>;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** A proposed quality-gate configuration */
|
|
338
|
+
export interface SetupProposal {
|
|
339
|
+
gates: QualityGatesConfig;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/** Result of running the setup flow */
|
|
343
|
+
export type SetupGatesResult =
|
|
344
|
+
| { status: "proposed"; proposal: SetupProposal }
|
|
345
|
+
| { status: "invalid"; proposal: SetupProposal; errors: string[] }
|
|
346
|
+
| { status: "cancelled"; proposal?: SetupProposal; errors?: string[] };
|
|
347
|
+
|
|
61
348
|
// ── Release types ──────────────────────────────────────────
|
|
62
349
|
|
|
63
350
|
/** Semantic version bump type */
|
|
64
351
|
export type BumpType = "major" | "minor" | "patch";
|
|
65
352
|
|
|
66
|
-
/** Release channel target */
|
|
67
|
-
export type ReleaseChannel =
|
|
353
|
+
/** Release channel target (built-in IDs or user-defined custom channel IDs) */
|
|
354
|
+
export type ReleaseChannel = string;
|
|
355
|
+
|
|
356
|
+
/** User-defined custom release channel configuration */
|
|
357
|
+
export interface CustomChannelConfig {
|
|
358
|
+
/** Display name shown in the channel selection UI */
|
|
359
|
+
label: string;
|
|
360
|
+
/** Shell command template with ${tag}, ${version}, ${changelog} placeholders */
|
|
361
|
+
publishCommand: string;
|
|
362
|
+
/** Shell command to detect availability — exit code 0 means available. When omitted, always available. */
|
|
363
|
+
detectCommand?: string;
|
|
364
|
+
}
|
|
68
365
|
|
|
69
366
|
/** A single parsed commit entry */
|
|
70
367
|
export interface CommitEntry {
|
|
@@ -97,7 +394,7 @@ export interface ReleaseResult {
|
|
|
97
394
|
|
|
98
395
|
/** Context-mode integration settings */
|
|
99
396
|
export interface ContextModeConfig {
|
|
100
|
-
/** Master toggle for all context-mode integration (default: true) */
|
|
397
|
+
/** Master toggle for all supi-context-mode integration (default: true) */
|
|
101
398
|
enabled: boolean;
|
|
102
399
|
/** Byte threshold above which tool results are compressed (default: 4096) */
|
|
103
400
|
compressionThreshold: number;
|
|
@@ -170,33 +467,46 @@ export interface ResolvedModel {
|
|
|
170
467
|
/** Config shape */
|
|
171
468
|
export interface SupipowersConfig {
|
|
172
469
|
version: string;
|
|
173
|
-
|
|
470
|
+
quality: {
|
|
471
|
+
gates: QualityGatesConfig;
|
|
472
|
+
};
|
|
174
473
|
lsp: {
|
|
175
474
|
setupGuide: boolean;
|
|
176
475
|
};
|
|
177
|
-
notifications: {
|
|
178
|
-
verbosity: "quiet" | "normal" | "verbose";
|
|
179
|
-
};
|
|
180
476
|
qa: {
|
|
181
477
|
framework: string | null;
|
|
182
|
-
command: string | null;
|
|
183
478
|
e2e: boolean;
|
|
184
479
|
};
|
|
185
480
|
release: {
|
|
186
481
|
channels: ReleaseChannel[];
|
|
482
|
+
/** Tag format template. Use ${version} as placeholder. Default: "v${version}" */
|
|
483
|
+
tagFormat: string;
|
|
484
|
+
/** User-defined custom release channels keyed by channel ID */
|
|
485
|
+
customChannels: Record<string, CustomChannelConfig>;
|
|
187
486
|
};
|
|
188
487
|
contextMode: ContextModeConfig;
|
|
189
488
|
mcp: McpManagementConfig;
|
|
190
489
|
}
|
|
191
490
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
491
|
+
|
|
492
|
+
/** Persisted state for /supi:generate docs — tracks which docs are monitored and the last checked commit */
|
|
493
|
+
export interface DocDriftState {
|
|
494
|
+
trackedFiles: string[];
|
|
495
|
+
lastCommit: string | null;
|
|
496
|
+
lastRunAt: string | null;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/** A single drift finding from a sub-agent doc review */
|
|
500
|
+
export interface DriftFinding {
|
|
501
|
+
file: string;
|
|
502
|
+
description: string;
|
|
503
|
+
severity: "info" | "warning" | "error";
|
|
504
|
+
relatedFiles?: string[];
|
|
202
505
|
}
|
|
506
|
+
|
|
507
|
+
/** Result of a headless doc-drift check (multi-agent pipeline) */
|
|
508
|
+
export interface DriftCheckResult {
|
|
509
|
+
drifted: boolean;
|
|
510
|
+
summary: string;
|
|
511
|
+
findings: DriftFinding[];
|
|
512
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns the directory of the calling module, correctly resolved on all
|
|
6
|
+
* platforms.
|
|
7
|
+
*
|
|
8
|
+
* The naive alternative — `path.dirname(new URL(import.meta.url).pathname)` —
|
|
9
|
+
* is broken on Windows: `URL.pathname` for a `file:///C:/foo/bar.ts` URL
|
|
10
|
+
* yields `/C:/foo/bar.ts` (leading slash before the drive letter), which
|
|
11
|
+
* `path.dirname` on Windows converts to a backslash path. Those backslashes,
|
|
12
|
+
* when later embedded in a bash command, are consumed as escape sequences and
|
|
13
|
+
* silently corrupt the path.
|
|
14
|
+
*
|
|
15
|
+
* `fileURLToPath` handles the Windows case correctly, stripping the leading
|
|
16
|
+
* slash and returning a proper `C:\foo\bar.ts` path.
|
|
17
|
+
*
|
|
18
|
+
* @param importMetaUrl Pass `import.meta.url` from the caller.
|
|
19
|
+
*/
|
|
20
|
+
export function moduleDir(importMetaUrl: string): string {
|
|
21
|
+
return path.dirname(fileURLToPath(importMetaUrl));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Normalizes path separators to forward slashes so the path is safe to embed
|
|
26
|
+
* in bash commands or shell-script arguments on Windows.
|
|
27
|
+
*
|
|
28
|
+
* Git Bash (and WSL) on Windows interpret `\` as an escape character, so a
|
|
29
|
+
* native Windows path like `C:\Users\foo\bar` silently becomes `C:Usersfoobar`
|
|
30
|
+
* when passed as a bash argument. Forward slashes are accepted by both Git
|
|
31
|
+
* Bash and Node.js file APIs on Windows, so this conversion is safe to apply
|
|
32
|
+
* universally.
|
|
33
|
+
*
|
|
34
|
+
* Use on every path that will be passed to `platform.exec("bash", [...])` or
|
|
35
|
+
* embedded as a literal in an AI prompt that contains bash commands.
|
|
36
|
+
*/
|
|
37
|
+
export function toBashPath(p: string): string {
|
|
38
|
+
return p.replace(/\\/g, "/");
|
|
39
|
+
}
|
package/src/visual/companion.ts
CHANGED
|
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import type { VisualServerInfo, VisualEvent } from "./types.js";
|
|
5
|
+
import { normalizeLineEndings } from "../text.js";
|
|
5
6
|
import type { PlatformPaths } from "../platform/types.js";
|
|
6
7
|
|
|
7
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -32,7 +33,7 @@ export function readEvents(sessionDir: string): VisualEvent[] {
|
|
|
32
33
|
const eventsFile = path.join(sessionDir, ".events");
|
|
33
34
|
if (!fs.existsSync(eventsFile)) return [];
|
|
34
35
|
|
|
35
|
-
const content = fs.readFileSync(eventsFile, "utf-8");
|
|
36
|
+
const content = normalizeLineEndings(fs.readFileSync(eventsFile, "utf-8"));
|
|
36
37
|
const events: VisualEvent[] = [];
|
|
37
38
|
|
|
38
39
|
for (const line of content.split("\n")) {
|
|
@@ -60,9 +61,10 @@ export function getScriptsDir(): string {
|
|
|
60
61
|
return path.join(__dirname, "scripts");
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
/** Parse server info from
|
|
64
|
+
/** Parse server info from launcher output or server logs. */
|
|
64
65
|
export function parseServerInfo(stdout: string): VisualServerInfo | null {
|
|
65
|
-
|
|
66
|
+
const normalizedStdout = normalizeLineEndings(stdout);
|
|
67
|
+
for (const line of normalizedStdout.split("\n")) {
|
|
66
68
|
if (!line.trim()) continue;
|
|
67
69
|
try {
|
|
68
70
|
const data = JSON.parse(line);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { getScriptsDir, readServerInfo } from "./companion.js";
|
|
4
|
+
import { stopVisualServer } from "./stop-server.js";
|
|
5
|
+
import type { VisualServerInfo } from "./types.js";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_HOST = "127.0.0.1";
|
|
8
|
+
const START_TIMEOUT_MS = 5_000;
|
|
9
|
+
const POLL_INTERVAL_MS = 100;
|
|
10
|
+
|
|
11
|
+
interface StartServerOptions {
|
|
12
|
+
sessionDir: string;
|
|
13
|
+
host?: string;
|
|
14
|
+
urlHost?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Start the visual companion server and wait for its connection info. */
|
|
18
|
+
export async function startVisualServer(opts: StartServerOptions): Promise<VisualServerInfo | null> {
|
|
19
|
+
const host = opts.host ?? DEFAULT_HOST;
|
|
20
|
+
const urlHost = opts.urlHost ?? deriveUrlHost(host);
|
|
21
|
+
const scriptsDir = getScriptsDir();
|
|
22
|
+
const pidFile = path.join(opts.sessionDir, ".server.pid");
|
|
23
|
+
|
|
24
|
+
fs.mkdirSync(opts.sessionDir, { recursive: true });
|
|
25
|
+
stopVisualServer(opts.sessionDir);
|
|
26
|
+
cleanupStartupArtifacts(opts.sessionDir);
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const subprocess = Bun.spawn(["node", "index.js"], {
|
|
30
|
+
cwd: scriptsDir,
|
|
31
|
+
env: {
|
|
32
|
+
...process.env,
|
|
33
|
+
SUPI_VISUAL_DIR: opts.sessionDir,
|
|
34
|
+
SUPI_VISUAL_HOST: host,
|
|
35
|
+
SUPI_VISUAL_URL_HOST: urlHost,
|
|
36
|
+
},
|
|
37
|
+
stdin: "ignore",
|
|
38
|
+
stdout: "ignore",
|
|
39
|
+
stderr: "ignore",
|
|
40
|
+
detached: true,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
subprocess.unref();
|
|
44
|
+
fs.writeFileSync(pidFile, String(subprocess.pid));
|
|
45
|
+
|
|
46
|
+
const serverInfo = await pollForServerStart(opts.sessionDir, subprocess.pid, START_TIMEOUT_MS);
|
|
47
|
+
if (serverInfo) {
|
|
48
|
+
return serverInfo;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
stopVisualServer(opts.sessionDir);
|
|
52
|
+
// stopVisualServer may leave artifacts when kill returns a non-ESRCH error;
|
|
53
|
+
// force-clean so the caller never inherits stale state.
|
|
54
|
+
fs.rmSync(pidFile, { force: true });
|
|
55
|
+
cleanupStartupArtifacts(opts.sessionDir);
|
|
56
|
+
return null;
|
|
57
|
+
} catch {
|
|
58
|
+
fs.rmSync(pidFile, { force: true });
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function deriveUrlHost(host: string): string {
|
|
64
|
+
return host === "127.0.0.1" || host === "localhost" ? "localhost" : host;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function cleanupStartupArtifacts(sessionDir: string): void {
|
|
68
|
+
fs.rmSync(path.join(sessionDir, ".server-info"), { force: true });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function pollForServerStart(
|
|
72
|
+
sessionDir: string,
|
|
73
|
+
pid: number,
|
|
74
|
+
timeoutMs: number,
|
|
75
|
+
): Promise<VisualServerInfo | null> {
|
|
76
|
+
const deadline = Date.now() + timeoutMs;
|
|
77
|
+
|
|
78
|
+
while (Date.now() < deadline) {
|
|
79
|
+
const serverInfo = readServerInfo(sessionDir);
|
|
80
|
+
if (serverInfo) {
|
|
81
|
+
return isProcessAlive(pid) ? serverInfo : null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!isProcessAlive(pid)) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
await Bun.sleep(POLL_INTERVAL_MS);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isProcessAlive(pid: number): boolean {
|
|
95
|
+
try {
|
|
96
|
+
process.kill(pid, 0);
|
|
97
|
+
return true;
|
|
98
|
+
} catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
export function stopVisualServer(
|
|
5
|
+
sessionDir: string,
|
|
6
|
+
): { status: "stopped" | "not_running" | "failed" } {
|
|
7
|
+
const pidFile = path.join(sessionDir, ".server.pid");
|
|
8
|
+
|
|
9
|
+
if (!fs.existsSync(pidFile)) {
|
|
10
|
+
return { status: "not_running" };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const raw = fs.readFileSync(pidFile, "utf-8").trim();
|
|
14
|
+
const pid = parseInt(raw, 10);
|
|
15
|
+
|
|
16
|
+
if (isNaN(pid)) {
|
|
17
|
+
// Corrupt PID file — clean up and treat as not running
|
|
18
|
+
cleanupServerArtifacts(sessionDir);
|
|
19
|
+
return { status: "not_running" };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
process.kill(pid);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
26
|
+
if (code !== "ESRCH") {
|
|
27
|
+
return { status: "failed" };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
cleanupServerArtifacts(sessionDir);
|
|
32
|
+
return { status: "stopped" };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function cleanupServerArtifacts(sessionDir: string): void {
|
|
36
|
+
fs.rmSync(path.join(sessionDir, ".server.pid"), { force: true });
|
|
37
|
+
fs.rmSync(path.join(sessionDir, ".server.log"), { force: true });
|
|
38
|
+
fs.rmSync(path.join(sessionDir, ".server-info"), { force: true });
|
|
39
|
+
}
|