supipowers 0.4.0 → 0.6.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 (57) hide show
  1. package/package.json +3 -3
  2. package/skills/context-mode/SKILL.md +38 -0
  3. package/skills/qa-strategy/SKILL.md +103 -21
  4. package/src/commands/config.ts +23 -2
  5. package/src/commands/fix-pr.ts +1 -1
  6. package/src/commands/plan.ts +1 -1
  7. package/src/commands/qa.ts +232 -148
  8. package/src/commands/release.ts +1 -1
  9. package/src/commands/review.ts +1 -1
  10. package/src/commands/run.ts +9 -4
  11. package/src/commands/supi.ts +1 -1
  12. package/src/config/defaults.ts +11 -0
  13. package/src/config/schema.ts +11 -0
  14. package/src/context-mode/compressor.ts +200 -0
  15. package/src/context-mode/detector.ts +43 -0
  16. package/src/context-mode/event-extractor.ts +170 -0
  17. package/src/context-mode/event-store.ts +168 -0
  18. package/src/context-mode/hooks.ts +176 -0
  19. package/src/context-mode/installer.ts +71 -0
  20. package/src/context-mode/snapshot-builder.ts +127 -0
  21. package/src/discipline/debugging.ts +7 -7
  22. package/src/discipline/receiving-review.ts +5 -5
  23. package/src/discipline/tdd.ts +2 -2
  24. package/src/discipline/verification.ts +9 -9
  25. package/src/git/base-branch.ts +30 -0
  26. package/src/git/branch-finish.ts +12 -3
  27. package/src/git/sanitize.ts +19 -0
  28. package/src/git/worktree.ts +38 -11
  29. package/src/index.ts +8 -1
  30. package/src/orchestrator/agent-prompts.ts +15 -7
  31. package/src/orchestrator/conflict-resolver.ts +3 -2
  32. package/src/orchestrator/dispatcher.ts +76 -21
  33. package/src/orchestrator/prompts.ts +46 -6
  34. package/src/planning/plan-reviewer.ts +1 -1
  35. package/src/planning/plan-writer-prompt.ts +6 -9
  36. package/src/planning/prompt-builder.ts +17 -16
  37. package/src/planning/spec-reviewer.ts +2 -2
  38. package/src/qa/config.ts +43 -0
  39. package/src/qa/matrix.ts +84 -0
  40. package/src/qa/prompt-builder.ts +212 -0
  41. package/src/qa/scripts/detect-app-type.sh +68 -0
  42. package/src/qa/scripts/discover-routes.sh +143 -0
  43. package/src/qa/scripts/ensure-playwright.sh +38 -0
  44. package/src/qa/scripts/run-e2e-tests.sh +99 -0
  45. package/src/qa/scripts/start-dev-server.sh +46 -0
  46. package/src/qa/scripts/stop-dev-server.sh +36 -0
  47. package/src/qa/session.ts +39 -55
  48. package/src/qa/types.ts +97 -0
  49. package/src/storage/qa-sessions.ts +9 -9
  50. package/src/types.ts +22 -70
  51. package/src/qa/detector.ts +0 -61
  52. package/src/qa/phases/discovery.ts +0 -34
  53. package/src/qa/phases/execution.ts +0 -65
  54. package/src/qa/phases/matrix.ts +0 -41
  55. package/src/qa/phases/reporting.ts +0 -71
  56. package/src/qa/report.ts +0 -22
  57. package/src/qa/runner.ts +0 -46
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env bash
2
+ # Run playwright tests and produce a compact JSON summary.
3
+ # Usage: run-e2e-tests.sh <test_dir> <base_url> [test_filter]
4
+ # Output: Compact JSON summary on stdout
5
+ set -euo pipefail
6
+
7
+ TEST_DIR="$1"
8
+ BASE_URL="$2"
9
+ TEST_FILTER="${3:-}"
10
+ RESULTS_DIR="${TEST_DIR}/../results"
11
+ SCREENSHOTS_DIR="${TEST_DIR}/../screenshots"
12
+
13
+ mkdir -p "$RESULTS_DIR" "$SCREENSHOTS_DIR"
14
+
15
+ # Build playwright command
16
+ PW_ARGS=(
17
+ test
18
+ "$TEST_DIR"
19
+ --reporter=json
20
+ --output="$RESULTS_DIR"
21
+ )
22
+
23
+ if [ -n "$TEST_FILTER" ]; then
24
+ PW_ARGS+=(--grep "$TEST_FILTER")
25
+ fi
26
+
27
+ # Run playwright, capture JSON output
28
+ RAW_OUTPUT="$RESULTS_DIR/raw-results.json"
29
+ set +e
30
+ BASE_URL="$BASE_URL" npx playwright "${PW_ARGS[@]}" > "$RAW_OUTPUT" 2>/dev/null
31
+ PW_EXIT=$?
32
+ set -e
33
+
34
+ # If no JSON output was produced, create a minimal error report
35
+ if [ ! -s "$RAW_OUTPUT" ]; then
36
+ cat <<EOF
37
+ {"total": 0, "passed": 0, "failed": 0, "skipped": 0, "duration": 0, "failures": [], "error": "Playwright produced no output (exit code: $PW_EXIT)"}
38
+ EOF
39
+ exit 0
40
+ fi
41
+
42
+ # Parse the JSON output into compact summary using node (more reliable than jq)
43
+ node -e "
44
+ const fs = require('fs');
45
+ const raw = JSON.parse(fs.readFileSync('$RAW_OUTPUT', 'utf-8'));
46
+
47
+ const suites = raw.suites || [];
48
+ const results = [];
49
+
50
+ function collectTests(suite, parentTitle) {
51
+ const title = parentTitle ? parentTitle + ' > ' + suite.title : suite.title;
52
+ for (const spec of (suite.specs || [])) {
53
+ for (const test of (spec.tests || [])) {
54
+ for (const result of (test.results || [])) {
55
+ results.push({
56
+ test: title + ' > ' + spec.title,
57
+ file: spec.file + (spec.line ? ':' + spec.line : ''),
58
+ status: result.status,
59
+ duration: result.duration || 0,
60
+ error: result.error?.message || null,
61
+ });
62
+ }
63
+ }
64
+ }
65
+ for (const child of (suite.suites || [])) {
66
+ collectTests(child, title);
67
+ }
68
+ }
69
+
70
+ for (const suite of suites) {
71
+ collectTests(suite, '');
72
+ }
73
+
74
+ const passed = results.filter(r => r.status === 'passed').length;
75
+ const failed = results.filter(r => r.status === 'failed' || r.status === 'timedOut').length;
76
+ const skipped = results.filter(r => r.status === 'skipped').length;
77
+ const totalDuration = results.reduce((sum, r) => sum + r.duration, 0);
78
+
79
+ const failures = results
80
+ .filter(r => r.status === 'failed' || r.status === 'timedOut')
81
+ .map(r => ({
82
+ test: r.test,
83
+ file: r.file,
84
+ error: r.error || 'Unknown error',
85
+ }));
86
+
87
+ const summary = {
88
+ total: results.length,
89
+ passed,
90
+ failed,
91
+ skipped,
92
+ duration: totalDuration,
93
+ failures,
94
+ };
95
+
96
+ console.log(JSON.stringify(summary));
97
+ " 2>/dev/null || cat <<EOF
98
+ {"total": 0, "passed": 0, "failed": 0, "skipped": 0, "duration": 0, "failures": [], "error": "Failed to parse playwright output"}
99
+ EOF
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env bash
2
+ # Start the dev server in the background and wait for it to be ready.
3
+ # Usage: start-dev-server.sh <cwd> <dev_command> <port> <timeout_seconds> <session_dir>
4
+ # Output: JSON on stdout
5
+ set -euo pipefail
6
+
7
+ CWD="$1"
8
+ DEV_COMMAND="$2"
9
+ PORT="$3"
10
+ TIMEOUT="${4:-60}"
11
+ SESSION_DIR="${5:-.}"
12
+
13
+ cd "$CWD"
14
+
15
+ # Check if port is already in use
16
+ if curl -s -o /dev/null -w "%{http_code}" "http://localhost:$PORT" 2>/dev/null | grep -qE '^[0-9]'; then
17
+ echo "{\"pid\": null, \"url\": \"http://localhost:$PORT\", \"ready\": true, \"note\": \"Server already running\"}"
18
+ exit 0
19
+ fi
20
+
21
+ # Start dev server in background
22
+ eval "$DEV_COMMAND" > "$SESSION_DIR/dev-server.log" 2>&1 &
23
+ PID=$!
24
+ echo "$PID" > "$SESSION_DIR/dev-server.pid"
25
+
26
+ # Wait for server to be ready
27
+ for i in $(seq 1 "$TIMEOUT"); do
28
+ # Check if process is still alive
29
+ if ! kill -0 "$PID" 2>/dev/null; then
30
+ echo "{\"pid\": $PID, \"url\": \"http://localhost:$PORT\", \"ready\": false, \"error\": \"Server process exited\"}"
31
+ exit 1
32
+ fi
33
+
34
+ # Check if port responds
35
+ if curl -s -o /dev/null "http://localhost:$PORT" 2>/dev/null; then
36
+ echo "{\"pid\": $PID, \"url\": \"http://localhost:$PORT\", \"ready\": true}"
37
+ exit 0
38
+ fi
39
+
40
+ sleep 1
41
+ done
42
+
43
+ # Timeout — kill the server
44
+ kill "$PID" 2>/dev/null || true
45
+ echo "{\"pid\": $PID, \"url\": \"http://localhost:$PORT\", \"ready\": false, \"error\": \"Timeout after ${TIMEOUT}s\"}"
46
+ exit 1
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bash
2
+ # Stop the dev server started by start-dev-server.sh.
3
+ # Usage: stop-dev-server.sh <session_dir>
4
+ set -euo pipefail
5
+
6
+ SESSION_DIR="${1:-.}"
7
+ PID_FILE="$SESSION_DIR/dev-server.pid"
8
+
9
+ if [ ! -f "$PID_FILE" ]; then
10
+ echo '{"stopped": false, "error": "No PID file found"}'
11
+ exit 0
12
+ fi
13
+
14
+ PID=$(cat "$PID_FILE")
15
+
16
+ if [ -z "$PID" ]; then
17
+ echo '{"stopped": false, "error": "Empty PID file"}'
18
+ exit 0
19
+ fi
20
+
21
+ # Kill the process and its children
22
+ if kill -0 "$PID" 2>/dev/null; then
23
+ # Kill process group if possible
24
+ kill -- -"$PID" 2>/dev/null || kill "$PID" 2>/dev/null || true
25
+ # Wait briefly for cleanup
26
+ sleep 1
27
+ # Force kill if still alive
28
+ if kill -0 "$PID" 2>/dev/null; then
29
+ kill -9 "$PID" 2>/dev/null || true
30
+ fi
31
+ rm -f "$PID_FILE"
32
+ echo "{\"stopped\": true, \"pid\": $PID}"
33
+ else
34
+ rm -f "$PID_FILE"
35
+ echo "{\"stopped\": true, \"pid\": $PID, \"note\": \"Process was already dead\"}"
36
+ fi
package/src/qa/session.ts CHANGED
@@ -1,37 +1,55 @@
1
- import type { QaPhase, QaPhaseStatus, QaSessionLedger, QaTestCase, QaTestResult } from "../types.js";
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import type { E2ePhase, E2ePhaseStatus, E2eSessionLedger, E2eQaConfig } from "./types.js";
2
4
  import { generateSessionId, createSession, updateSession } from "../storage/qa-sessions.js";
3
5
 
4
- const PHASE_ORDER: QaPhase[] = ["discovery", "matrix", "execution", "reporting"];
6
+ const PHASE_ORDER: E2ePhase[] = ["flow-discovery", "test-generation", "execution", "reporting"];
5
7
 
6
- /** Create a new QA session with all phases pending */
7
- export function createNewSession(cwd: string, framework: string): QaSessionLedger {
8
+ const PHASE_LABELS: Record<E2ePhase, string> = {
9
+ "flow-discovery": "Discovery",
10
+ "test-generation": "Generation",
11
+ "execution": "Execution",
12
+ "reporting": "Reporting",
13
+ };
14
+
15
+ /** Create a new E2E QA session with all phases pending */
16
+ export function createNewE2eSession(cwd: string, config: E2eQaConfig): E2eSessionLedger {
8
17
  const now = new Date().toISOString();
9
- const ledger: QaSessionLedger = {
18
+ const ledger: E2eSessionLedger = {
10
19
  id: generateSessionId(),
11
20
  createdAt: now,
12
21
  updatedAt: now,
13
- framework,
22
+ appType: config.app.type,
23
+ baseUrl: config.app.baseUrl,
14
24
  phases: {
15
- discovery: { status: "pending" },
16
- matrix: { status: "pending" },
17
- execution: { status: "pending" },
18
- reporting: { status: "pending" },
25
+ "flow-discovery": { status: "pending" },
26
+ "test-generation": { status: "pending" },
27
+ "execution": { status: "pending" },
28
+ "reporting": { status: "pending" },
19
29
  },
20
- tests: [],
21
- matrix: [],
30
+ flows: [],
22
31
  results: [],
32
+ regressions: [],
33
+ config,
23
34
  };
35
+
36
+ // Create session with subdirectories
24
37
  createSession(cwd, ledger);
38
+
39
+ const sessionDir = path.join(cwd, ".omp", "supipowers", "qa-sessions", ledger.id);
40
+ fs.mkdirSync(path.join(sessionDir, "tests"), { recursive: true });
41
+ fs.mkdirSync(path.join(sessionDir, "screenshots"), { recursive: true });
42
+
25
43
  return ledger;
26
44
  }
27
45
 
28
46
  /** Update a phase's status and timestamps, persist to disk */
29
- export function advancePhase(
47
+ export function advanceE2ePhase(
30
48
  cwd: string,
31
- ledger: QaSessionLedger,
32
- phase: QaPhase,
33
- status: QaPhaseStatus
34
- ): QaSessionLedger {
49
+ ledger: E2eSessionLedger,
50
+ phase: E2ePhase,
51
+ status: E2ePhaseStatus,
52
+ ): E2eSessionLedger {
35
53
  const now = new Date().toISOString();
36
54
  const record = { ...ledger.phases[phase] };
37
55
 
@@ -43,7 +61,7 @@ export function advancePhase(
43
61
  record.completedAt = now;
44
62
  }
45
63
 
46
- const updated: QaSessionLedger = {
64
+ const updated: E2eSessionLedger = {
47
65
  ...ledger,
48
66
  updatedAt: now,
49
67
  phases: { ...ledger.phases, [phase]: record },
@@ -52,42 +70,8 @@ export function advancePhase(
52
70
  return updated;
53
71
  }
54
72
 
55
- /** Merge new test results into the ledger, upserting by testId */
56
- export function mergeTestResults(
57
- ledger: QaSessionLedger,
58
- newResults: QaTestResult[]
59
- ): QaSessionLedger {
60
- const resultMap = new Map(ledger.results.map((r) => [r.testId, r]));
61
-
62
- for (const incoming of newResults) {
63
- const existing = resultMap.get(incoming.testId);
64
- if (existing) {
65
- resultMap.set(incoming.testId, {
66
- ...incoming,
67
- retryCount: existing.retryCount + 1,
68
- });
69
- } else {
70
- resultMap.set(incoming.testId, incoming);
71
- }
72
- }
73
-
74
- return {
75
- ...ledger,
76
- updatedAt: new Date().toISOString(),
77
- results: Array.from(resultMap.values()),
78
- };
79
- }
80
-
81
- /** Get test cases whose latest result is "fail" */
82
- export function getFailedTests(ledger: QaSessionLedger): QaTestCase[] {
83
- const failedIds = new Set(
84
- ledger.results.filter((r) => r.status === "fail").map((r) => r.testId)
85
- );
86
- return ledger.tests.filter((t) => failedIds.has(t.id));
87
- }
88
-
89
73
  /** Get the next phase that is not completed */
90
- export function getNextPhase(ledger: QaSessionLedger): QaPhase | null {
74
+ export function getNextE2ePhase(ledger: E2eSessionLedger): E2ePhase | null {
91
75
  for (const phase of PHASE_ORDER) {
92
76
  if (ledger.phases[phase].status !== "completed") return phase;
93
77
  }
@@ -95,9 +79,9 @@ export function getNextPhase(ledger: QaSessionLedger): QaPhase | null {
95
79
  }
96
80
 
97
81
  /** Format phase status for TUI display */
98
- export function getPhaseStatusLine(ledger: QaSessionLedger): string {
82
+ export function getE2ePhaseStatusLine(ledger: E2eSessionLedger): string {
99
83
  return PHASE_ORDER.map((phase) => {
100
- const label = phase.charAt(0).toUpperCase() + phase.slice(1);
84
+ const label = PHASE_LABELS[phase];
101
85
  const done = ledger.phases[phase].status === "completed";
102
86
  return done ? `[done] ${label}` : `[ ] ${label}`;
103
87
  }).join(" · ");
@@ -0,0 +1,97 @@
1
+ // ── App Detection ──────────────────────────────────────────────────
2
+
3
+ export type AppType = "nextjs-app" | "nextjs-pages" | "react-router" | "vite" | "express" | "generic";
4
+
5
+ export interface AppTypeInfo {
6
+ type: AppType;
7
+ devCommand: string;
8
+ port: number;
9
+ baseUrl: string;
10
+ }
11
+
12
+ // ── Configuration ──────────────────────────────────────────────────
13
+
14
+ export interface PlaywrightConfig {
15
+ browser: "chromium" | "firefox" | "webkit";
16
+ headless: boolean;
17
+ timeout: number;
18
+ }
19
+
20
+ export interface ExecutionConfig {
21
+ maxRetries: number;
22
+ maxFlows: number;
23
+ }
24
+
25
+ export interface E2eQaConfig {
26
+ app: AppTypeInfo;
27
+ playwright: PlaywrightConfig;
28
+ execution: ExecutionConfig;
29
+ }
30
+
31
+ // ── Persistent Flow Matrix ─────────────────────────────────────────
32
+
33
+ export interface E2eFlowRecord {
34
+ id: string;
35
+ name: string;
36
+ entryRoute: string;
37
+ steps: string[];
38
+ priority: "critical" | "high" | "medium" | "low";
39
+ lastStatus: "pass" | "fail" | "untested";
40
+ lastTestedAt: string | null;
41
+ lastError?: string;
42
+ addedAt: string;
43
+ removedAt?: string;
44
+ }
45
+
46
+ export interface E2eMatrix {
47
+ version: string;
48
+ updatedAt: string;
49
+ appType: string;
50
+ flows: E2eFlowRecord[];
51
+ }
52
+
53
+ // ── Session ────────────────────────────────────────────────────────
54
+
55
+ export type E2ePhase = "flow-discovery" | "test-generation" | "execution" | "reporting";
56
+ export type E2ePhaseStatus = "pending" | "running" | "completed" | "failed";
57
+
58
+ export interface E2eFlow {
59
+ id: string;
60
+ name: string;
61
+ entryRoute: string;
62
+ steps: string[];
63
+ priority: "critical" | "high" | "medium" | "low";
64
+ testFile?: string;
65
+ }
66
+
67
+ export interface E2eTestResult {
68
+ flowId: string;
69
+ testFile: string;
70
+ status: "pass" | "fail" | "skip";
71
+ duration?: number;
72
+ error?: string;
73
+ screenshot?: string;
74
+ retryCount: number;
75
+ }
76
+
77
+ export interface E2eRegression {
78
+ flowId: string;
79
+ flowName: string;
80
+ previousStatus: "pass";
81
+ currentStatus: "fail";
82
+ error: string;
83
+ resolution?: "bug" | "intentional-change" | "skipped";
84
+ }
85
+
86
+ export interface E2eSessionLedger {
87
+ id: string;
88
+ createdAt: string;
89
+ updatedAt: string;
90
+ appType: string;
91
+ baseUrl: string;
92
+ phases: Record<E2ePhase, { status: E2ePhaseStatus; startedAt?: string; completedAt?: string }>;
93
+ flows: E2eFlow[];
94
+ results: E2eTestResult[];
95
+ regressions: E2eRegression[];
96
+ config: E2eQaConfig;
97
+ }
@@ -1,6 +1,6 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import type { QaSessionLedger } from "../types.js";
3
+ import type { E2eSessionLedger } from "../qa/types.js";
4
4
 
5
5
  const SESSIONS_DIR = [".omp", "supipowers", "qa-sessions"];
6
6
 
@@ -8,7 +8,7 @@ function getSessionsDir(cwd: string): string {
8
8
  return path.join(cwd, ...SESSIONS_DIR);
9
9
  }
10
10
 
11
- function getSessionDir(cwd: string, sessionId: string): string {
11
+ export function getSessionDir(cwd: string, sessionId: string): string {
12
12
  return path.join(getSessionsDir(cwd), sessionId);
13
13
  }
14
14
 
@@ -22,17 +22,17 @@ export function generateSessionId(): string {
22
22
  }
23
23
 
24
24
  /** Create a new QA session */
25
- export function createSession(cwd: string, ledger: QaSessionLedger): void {
25
+ export function createSession(cwd: string, ledger: E2eSessionLedger): void {
26
26
  const sessionDir = getSessionDir(cwd, ledger.id);
27
27
  fs.mkdirSync(sessionDir, { recursive: true });
28
28
  fs.writeFileSync(
29
29
  path.join(sessionDir, "ledger.json"),
30
- JSON.stringify(ledger, null, 2) + "\n"
30
+ JSON.stringify(ledger, null, 2) + "\n",
31
31
  );
32
32
  }
33
33
 
34
34
  /** Load a QA session ledger */
35
- export function loadSession(cwd: string, sessionId: string): QaSessionLedger | null {
35
+ export function loadSession(cwd: string, sessionId: string): E2eSessionLedger | null {
36
36
  const filePath = path.join(getSessionDir(cwd, sessionId), "ledger.json");
37
37
  if (!fs.existsSync(filePath)) return null;
38
38
  try {
@@ -43,7 +43,7 @@ export function loadSession(cwd: string, sessionId: string): QaSessionLedger | n
43
43
  }
44
44
 
45
45
  /** Update a QA session ledger */
46
- export function updateSession(cwd: string, ledger: QaSessionLedger): void {
46
+ export function updateSession(cwd: string, ledger: E2eSessionLedger): void {
47
47
  const filePath = path.join(getSessionDir(cwd, ledger.id), "ledger.json");
48
48
  fs.writeFileSync(filePath, JSON.stringify(ledger, null, 2) + "\n");
49
49
  }
@@ -60,12 +60,12 @@ export function listSessions(cwd: string): string[] {
60
60
  }
61
61
 
62
62
  /** Find the latest session with incomplete phases */
63
- export function findActiveSession(cwd: string): QaSessionLedger | null {
63
+ export function findActiveSession(cwd: string): E2eSessionLedger | null {
64
64
  for (const sessionId of listSessions(cwd)) {
65
65
  const ledger = loadSession(cwd, sessionId);
66
66
  if (!ledger) continue;
67
67
  const allCompleted = Object.values(ledger.phases).every(
68
- (p) => p.status === "completed"
68
+ (p) => p.status === "completed",
69
69
  );
70
70
  if (!allCompleted) return ledger;
71
71
  }
@@ -73,7 +73,7 @@ export function findActiveSession(cwd: string): QaSessionLedger | null {
73
73
  }
74
74
 
75
75
  /** Find the latest session with failed test results */
76
- export function findSessionWithFailures(cwd: string): QaSessionLedger | null {
76
+ export function findSessionWithFailures(cwd: string): E2eSessionLedger | null {
77
77
  for (const sessionId of listSessions(cwd)) {
78
78
  const ledger = loadSession(cwd, sessionId);
79
79
  if (!ledger) continue;
package/src/types.ts CHANGED
@@ -99,6 +99,26 @@ export interface ReviewReport {
99
99
  passed: boolean;
100
100
  }
101
101
 
102
+ /** Context-mode integration settings */
103
+ export interface ContextModeConfig {
104
+ /** Master toggle for all context-mode integration (default: true) */
105
+ enabled: boolean;
106
+ /** Byte threshold above which tool results are compressed (default: 4096) */
107
+ compressionThreshold: number;
108
+ /** Block curl/wget/HTTP commands and redirect to ctx_fetch_and_index (default: true) */
109
+ blockHttpCommands: boolean;
110
+ /** Inject routing instructions into system prompt when ctx_* tools detected (default: true) */
111
+ routingInstructions: boolean;
112
+ /** Track events from tool results in SQLite (default: true) */
113
+ eventTracking: boolean;
114
+ /** Inject session knowledge into compaction summaries (default: true) */
115
+ compaction: boolean;
116
+ /** Use LLM calls for summarizing very large outputs (default: false) */
117
+ llmSummarization: boolean;
118
+ /** Byte threshold above which LLM summarization is used instead of structural compression (default: 16384) */
119
+ llmThreshold: number;
120
+ }
121
+
102
122
  /** Config shape */
103
123
  export interface SupipowersConfig {
104
124
  version: string;
@@ -118,80 +138,12 @@ export interface SupipowersConfig {
118
138
  qa: {
119
139
  framework: string | null;
120
140
  command: string | null;
141
+ e2e: boolean;
121
142
  };
122
143
  release: {
123
144
  pipeline: string | null;
124
145
  };
125
- }
126
-
127
- // ── QA Session Management ──────────────────────────────────────────
128
-
129
- /** QA pipeline phase */
130
- export type QaPhase = "discovery" | "matrix" | "execution" | "reporting";
131
-
132
- /** Phase completion status */
133
- export type QaPhaseStatus = "pending" | "running" | "completed" | "failed";
134
-
135
- /** Per-test result status */
136
- export type TestResultStatus = "pass" | "fail" | "skip";
137
-
138
- /** A discovered test case */
139
- export interface QaTestCase {
140
- id: string;
141
- filePath: string;
142
- testName: string;
143
- suiteName?: string;
144
- tags?: string[];
145
- }
146
-
147
- /** Traceability matrix entry — one requirement mapped to its tests */
148
- export interface QaMatrixEntry {
149
- requirement: string;
150
- testIds: string[];
151
- platforms?: string[];
152
- coverage: "full" | "partial" | "none";
153
- }
154
-
155
- /** Per-test execution result */
156
- export interface QaTestResult {
157
- testId: string;
158
- status: TestResultStatus;
159
- duration?: number;
160
- error?: string;
161
- retryCount: number;
162
- lastRunAt: string;
163
- }
164
-
165
- /** Report summary generated in the Reporting phase */
166
- export interface QaReportSummary {
167
- generatedAt: string;
168
- total: number;
169
- passed: number;
170
- failed: number;
171
- skipped: number;
172
- passRate: number;
173
- failedTests: { testId: string; testName: string; error?: string }[];
174
- coverageSummary?: string;
175
- }
176
-
177
- /** Phase record within a session */
178
- export interface QaPhaseRecord {
179
- status: QaPhaseStatus;
180
- startedAt?: string;
181
- completedAt?: string;
182
- }
183
-
184
- /** The full QA session ledger */
185
- export interface QaSessionLedger {
186
- id: string;
187
- createdAt: string;
188
- updatedAt: string;
189
- framework: string;
190
- phases: Record<QaPhase, QaPhaseRecord>;
191
- tests: QaTestCase[];
192
- matrix: QaMatrixEntry[];
193
- results: QaTestResult[];
194
- report?: QaReportSummary;
146
+ contextMode: ContextModeConfig;
195
147
  }
196
148
 
197
149
  /** Profile shape */
@@ -1,61 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import { updateConfig, loadConfig } from "../config/loader.js";
4
-
5
- export interface DetectedFramework {
6
- name: string;
7
- command: string;
8
- }
9
-
10
- const FRAMEWORK_SIGNATURES: { name: string; files: string[]; command: string }[] = [
11
- { name: "vitest", files: ["vitest.config.ts", "vitest.config.js", "vitest.config.mts"], command: "npx vitest run" },
12
- { name: "jest", files: ["jest.config.ts", "jest.config.js", "jest.config.mjs"], command: "npx jest" },
13
- { name: "mocha", files: [".mocharc.yml", ".mocharc.json", ".mocharc.js"], command: "npx mocha" },
14
- { name: "pytest", files: ["pytest.ini", "pyproject.toml", "conftest.py"], command: "pytest" },
15
- { name: "cargo-test", files: ["Cargo.toml"], command: "cargo test" },
16
- { name: "go-test", files: ["go.mod"], command: "go test ./..." },
17
- ];
18
-
19
- export function detectFramework(cwd: string): DetectedFramework | null {
20
- const pkgPath = path.join(cwd, "package.json");
21
- if (fs.existsSync(pkgPath)) {
22
- try {
23
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
24
- if (pkg.scripts?.test && pkg.scripts.test !== 'echo "Error: no test specified" && exit 1') {
25
- const testScript = pkg.scripts.test;
26
- for (const sig of FRAMEWORK_SIGNATURES) {
27
- if (testScript.includes(sig.name)) {
28
- return { name: sig.name, command: "npm test" };
29
- }
30
- }
31
- return { name: "npm-test", command: "npm test" };
32
- }
33
- } catch {
34
- // continue to file-based detection
35
- }
36
- }
37
-
38
- for (const sig of FRAMEWORK_SIGNATURES) {
39
- for (const file of sig.files) {
40
- if (fs.existsSync(path.join(cwd, file))) {
41
- return { name: sig.name, command: sig.command };
42
- }
43
- }
44
- }
45
-
46
- return null;
47
- }
48
-
49
- export function detectAndCache(cwd: string): DetectedFramework | null {
50
- const config = loadConfig(cwd);
51
-
52
- if (config.qa.framework && config.qa.command) {
53
- return { name: config.qa.framework, command: config.qa.command };
54
- }
55
-
56
- const detected = detectFramework(cwd);
57
- if (detected) {
58
- updateConfig(cwd, { qa: { framework: detected.name, command: detected.command } });
59
- }
60
- return detected;
61
- }