sequant 2.1.2 → 2.3.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +73 -0
- package/dist/bin/cli.js +95 -9
- package/dist/src/commands/doctor.d.ts +25 -0
- package/dist/src/commands/doctor.js +36 -1
- package/dist/src/commands/init.d.ts +1 -0
- package/dist/src/commands/init.js +118 -0
- package/dist/src/commands/locks.d.ts +67 -0
- package/dist/src/commands/locks.js +290 -0
- package/dist/src/commands/merge.js +11 -0
- package/dist/src/commands/prompt.d.ts +39 -0
- package/dist/src/commands/prompt.js +179 -0
- package/dist/src/commands/run-display.d.ts +26 -0
- package/dist/src/commands/run-display.js +150 -0
- package/dist/src/commands/run-progress.d.ts +32 -0
- package/dist/src/commands/run-progress.js +76 -0
- package/dist/src/commands/run.js +83 -73
- package/dist/src/commands/stats.d.ts +2 -0
- package/dist/src/commands/stats.js +94 -8
- package/dist/src/commands/status.js +27 -1
- package/dist/src/commands/watch.d.ts +16 -0
- package/dist/src/commands/watch.js +147 -0
- package/dist/src/lib/ac-linter.d.ts +1 -1
- package/dist/src/lib/ac-linter.js +81 -0
- package/dist/src/lib/assess-collision-detect.d.ts +91 -0
- package/dist/src/lib/assess-collision-detect.js +217 -0
- package/dist/src/lib/assess-comment-parser.d.ts +59 -1
- package/dist/src/lib/assess-comment-parser.js +124 -2
- package/dist/src/lib/cli-ui/format.d.ts +19 -0
- package/dist/src/lib/cli-ui/format.js +34 -0
- package/dist/src/lib/cli-ui/run-renderer-types.d.ts +181 -0
- package/dist/src/lib/cli-ui/run-renderer-types.js +7 -0
- package/dist/src/lib/cli-ui/run-renderer.d.ts +239 -0
- package/dist/src/lib/cli-ui/run-renderer.js +1173 -0
- package/dist/src/lib/heuristics/behavior-rule-detector.d.ts +94 -0
- package/dist/src/lib/heuristics/behavior-rule-detector.js +467 -0
- package/dist/src/lib/locks/index.d.ts +7 -0
- package/dist/src/lib/locks/index.js +5 -0
- package/dist/src/lib/locks/lock-manager.d.ts +168 -0
- package/dist/src/lib/locks/lock-manager.js +433 -0
- package/dist/src/lib/locks/types.d.ts +59 -0
- package/dist/src/lib/locks/types.js +31 -0
- package/dist/src/lib/qa/markdown-only-ci.d.ts +46 -0
- package/dist/src/lib/qa/markdown-only-ci.js +74 -0
- package/dist/src/lib/relay/activation.d.ts +60 -0
- package/dist/src/lib/relay/activation.js +122 -0
- package/dist/src/lib/relay/archive.d.ts +34 -0
- package/dist/src/lib/relay/archive.js +106 -0
- package/dist/src/lib/relay/frame.d.ts +20 -0
- package/dist/src/lib/relay/frame.js +76 -0
- package/dist/src/lib/relay/index.d.ts +13 -0
- package/dist/src/lib/relay/index.js +13 -0
- package/dist/src/lib/relay/paths.d.ts +43 -0
- package/dist/src/lib/relay/paths.js +59 -0
- package/dist/src/lib/relay/pid.d.ts +34 -0
- package/dist/src/lib/relay/pid.js +72 -0
- package/dist/src/lib/relay/reader.d.ts +35 -0
- package/dist/src/lib/relay/reader.js +115 -0
- package/dist/src/lib/relay/types.d.ts +68 -0
- package/dist/src/lib/relay/types.js +76 -0
- package/dist/src/lib/relay/writer.d.ts +48 -0
- package/dist/src/lib/relay/writer.js +113 -0
- package/dist/src/lib/settings.d.ts +31 -1
- package/dist/src/lib/settings.js +18 -3
- package/dist/src/lib/skill-version.d.ts +19 -0
- package/dist/src/lib/skill-version.js +68 -0
- package/dist/src/lib/templates.d.ts +1 -0
- package/dist/src/lib/templates.js +1 -1
- package/dist/src/lib/version-check.d.ts +60 -5
- package/dist/src/lib/version-check.js +97 -9
- package/dist/src/lib/workflow/batch-executor.d.ts +20 -1
- package/dist/src/lib/workflow/batch-executor.js +249 -176
- package/dist/src/lib/workflow/config-resolver.js +4 -0
- package/dist/src/lib/workflow/heartbeat.d.ts +71 -0
- package/dist/src/lib/workflow/heartbeat.js +194 -0
- package/dist/src/lib/workflow/phase-executor.d.ts +88 -3
- package/dist/src/lib/workflow/phase-executor.js +276 -52
- package/dist/src/lib/workflow/phase-mapper.d.ts +3 -2
- package/dist/src/lib/workflow/phase-mapper.js +17 -20
- package/dist/src/lib/workflow/platforms/github.d.ts +1 -1
- package/dist/src/lib/workflow/platforms/github.js +20 -3
- package/dist/src/lib/workflow/pr-status.d.ts +18 -2
- package/dist/src/lib/workflow/pr-status.js +41 -9
- package/dist/src/lib/workflow/qa-stagnation.d.ts +117 -0
- package/dist/src/lib/workflow/qa-stagnation.js +179 -0
- package/dist/src/lib/workflow/run-orchestrator.d.ts +76 -0
- package/dist/src/lib/workflow/run-orchestrator.js +382 -29
- package/dist/src/lib/workflow/run-reflect.js +1 -1
- package/dist/src/lib/workflow/run-state.d.ts +71 -0
- package/dist/src/lib/workflow/run-state.js +14 -0
- package/dist/src/lib/workflow/state-cleanup.d.ts +13 -5
- package/dist/src/lib/workflow/state-cleanup.js +17 -5
- package/dist/src/lib/workflow/state-manager.d.ts +12 -1
- package/dist/src/lib/workflow/state-manager.js +37 -0
- package/dist/src/lib/workflow/state-schema.d.ts +62 -0
- package/dist/src/lib/workflow/state-schema.js +35 -1
- package/dist/src/lib/workflow/types.d.ts +74 -1
- package/dist/src/lib/workflow/worktree-manager.d.ts +12 -4
- package/dist/src/lib/workflow/worktree-manager.js +76 -17
- package/dist/src/mcp/tools/run.d.ts +44 -0
- package/dist/src/mcp/tools/run.js +104 -13
- package/dist/src/ui/tui/App.d.ts +14 -0
- package/dist/src/ui/tui/App.js +41 -0
- package/dist/src/ui/tui/ElapsedTimer.d.ts +10 -0
- package/dist/src/ui/tui/ElapsedTimer.js +31 -0
- package/dist/src/ui/tui/Header.d.ts +6 -0
- package/dist/src/ui/tui/Header.js +15 -0
- package/dist/src/ui/tui/IssueBox.d.ts +16 -0
- package/dist/src/ui/tui/IssueBox.js +68 -0
- package/dist/src/ui/tui/Spinner.d.ts +9 -0
- package/dist/src/ui/tui/Spinner.js +18 -0
- package/dist/src/ui/tui/index.d.ts +15 -0
- package/dist/src/ui/tui/index.js +29 -0
- package/dist/src/ui/tui/theme.d.ts +29 -0
- package/dist/src/ui/tui/theme.js +52 -0
- package/dist/src/ui/tui/truncate.d.ts +11 -0
- package/dist/src/ui/tui/truncate.js +31 -0
- package/package.json +10 -3
- package/templates/agents/sequant-explorer.md +1 -0
- package/templates/agents/sequant-qa-checker.md +2 -1
- package/templates/agents/sequant-testgen.md +1 -0
- package/templates/hooks/post-tool.sh +11 -0
- package/templates/hooks/pre-tool.sh +18 -9
- package/templates/hooks/relay-check.sh +107 -0
- package/templates/relay/frame.txt +11 -0
- package/templates/scripts/cleanup-worktree.sh +25 -3
- package/templates/scripts/new-feature.sh +6 -0
- package/templates/skills/_shared/references/behavior-rule-detection.md +205 -0
- package/templates/skills/_shared/references/subagent-types.md +21 -8
- package/templates/skills/assess/SKILL.md +261 -94
- package/templates/skills/assess/references/predicted-collision-detection.md +109 -0
- package/templates/skills/docs/SKILL.md +141 -22
- package/templates/skills/exec/SKILL.md +10 -49
- package/templates/skills/fullsolve/SKILL.md +80 -32
- package/templates/skills/loop/SKILL.md +28 -0
- package/templates/skills/merger/SKILL.md +621 -0
- package/templates/skills/qa/SKILL.md +746 -8
- package/templates/skills/qa/scripts/quality-checks.sh +47 -1
- package/templates/skills/setup/SKILL.md +6 -0
- package/templates/skills/spec/SKILL.md +217 -964
- package/templates/skills/spec/references/parallel-groups.md +7 -0
- package/templates/skills/spec/references/quality-checklist.md +75 -0
- package/templates/skills/spec/references/recommended-workflow.md +4 -2
- package/templates/skills/test/SKILL.md +0 -27
- package/templates/skills/testgen/SKILL.md +24 -44
|
@@ -71,6 +71,30 @@ function resolveLogDir() {
|
|
|
71
71
|
}
|
|
72
72
|
return projectPath;
|
|
73
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Find and parse a run log file by its exact runId suffix (#631).
|
|
76
|
+
*
|
|
77
|
+
* Avoids the recency-window heuristic in `readLatestRunLog`, which can
|
|
78
|
+
* return another concurrent run's log or a stale same-issue log when
|
|
79
|
+
* filesystem ordering doesn't favor the current run.
|
|
80
|
+
*
|
|
81
|
+
* Returns null on miss, parse failure, or I/O error (no new failure mode).
|
|
82
|
+
*/
|
|
83
|
+
export async function readRunLogById(runId) {
|
|
84
|
+
try {
|
|
85
|
+
const logDir = resolveLogDir();
|
|
86
|
+
const entries = await readdir(logDir);
|
|
87
|
+
// Filename format: run-<timestamp>-<runId>.json — match by exact suffix.
|
|
88
|
+
const match = entries.find((f) => f.endsWith(`-${runId}.json`));
|
|
89
|
+
if (!match)
|
|
90
|
+
return null;
|
|
91
|
+
const content = await readFile(join(logDir, match), "utf-8");
|
|
92
|
+
return RunLogSchema.parse(JSON.parse(content));
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
74
98
|
/**
|
|
75
99
|
* Find and parse the most recent run log file.
|
|
76
100
|
*
|
|
@@ -109,6 +133,24 @@ export async function readLatestRunLog(runStartTime) {
|
|
|
109
133
|
return null;
|
|
110
134
|
}
|
|
111
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Resolve the run log for an MCP `sequant_run` invocation (#631).
|
|
138
|
+
*
|
|
139
|
+
* Prefers exact-filename lookup by captured runId; falls back to the
|
|
140
|
+
* time-window heuristic when no runId was captured (older CLI, startup
|
|
141
|
+
* race) or when the runId lookup returned null (corrupted file, slow
|
|
142
|
+
* fsync). On lookup-miss with a captured runId, emit a debug line on
|
|
143
|
+
* the MCP server's own stderr so the silent fallback is observable.
|
|
144
|
+
*/
|
|
145
|
+
export async function resolveRunLog(capturedRunId, runStartTime) {
|
|
146
|
+
if (capturedRunId !== null) {
|
|
147
|
+
const byId = await readRunLogById(capturedRunId);
|
|
148
|
+
if (byId)
|
|
149
|
+
return byId;
|
|
150
|
+
console.error(`[mcp:run] runId ${capturedRunId} lookup miss — falling back to readLatestRunLog`);
|
|
151
|
+
}
|
|
152
|
+
return readLatestRunLog(runStartTime);
|
|
153
|
+
}
|
|
112
154
|
/**
|
|
113
155
|
* Build a structured response from a parsed RunLog
|
|
114
156
|
*/
|
|
@@ -199,6 +241,21 @@ function buildFallbackResponse(stdout, issueNumbers, overallStatus, phases, exit
|
|
|
199
241
|
}
|
|
200
242
|
/** Prefix used by the batch executor to emit structured progress lines. */
|
|
201
243
|
const PROGRESS_LINE_PREFIX = "SEQUANT_PROGRESS:";
|
|
244
|
+
/** Prefix used by the batch executor to emit the current run's UUID (#631). */
|
|
245
|
+
const RUN_ID_LINE_PREFIX = "SEQUANT_RUN_ID:";
|
|
246
|
+
/** UUID v4 pattern produced by `crypto.randomUUID()`. */
|
|
247
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
248
|
+
/**
|
|
249
|
+
* Parse a SEQUANT_RUN_ID line emitted by the batch executor (#631).
|
|
250
|
+
* Returns the runId UUID or null if the line isn't a runId line or the
|
|
251
|
+
* payload isn't a well-formed UUID.
|
|
252
|
+
*/
|
|
253
|
+
export function parseRunIdLine(line) {
|
|
254
|
+
if (!line.startsWith(RUN_ID_LINE_PREFIX))
|
|
255
|
+
return null;
|
|
256
|
+
const id = line.slice(RUN_ID_LINE_PREFIX.length).trim();
|
|
257
|
+
return UUID_RE.test(id) ? id : null;
|
|
258
|
+
}
|
|
202
259
|
/**
|
|
203
260
|
* Parse a SEQUANT_PROGRESS line emitted by the batch executor.
|
|
204
261
|
* Returns the parsed event or null if the line isn't a progress line.
|
|
@@ -233,6 +290,38 @@ export function parseProgressLine(line) {
|
|
|
233
290
|
return null;
|
|
234
291
|
}
|
|
235
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* Stateful capture of the per-run UUID emitted on stderr by the spawned
|
|
295
|
+
* CLI (#631). Each MCP request creates its own capture instance.
|
|
296
|
+
*
|
|
297
|
+
* `routeLine` consumes a complete stderr line:
|
|
298
|
+
* - Until a `SEQUANT_RUN_ID:` line is seen, attempts to capture it.
|
|
299
|
+
* - After capture (or for non-runId lines), delegates to `parseProgressLine`
|
|
300
|
+
* and returns the parsed `ProgressEvent`, if any.
|
|
301
|
+
*
|
|
302
|
+
* Returning `ProgressEvent | null` (instead of side-effecting) keeps the
|
|
303
|
+
* factory pure and lets callers wire emission (`emitProgress`) at the
|
|
304
|
+
* outer layer. This separation also makes the capture logic directly
|
|
305
|
+
* testable without driving the full MCP request handler.
|
|
306
|
+
*/
|
|
307
|
+
export function createRunIdCapture() {
|
|
308
|
+
let capturedRunId = null;
|
|
309
|
+
return {
|
|
310
|
+
routeLine(line) {
|
|
311
|
+
if (capturedRunId === null) {
|
|
312
|
+
const id = parseRunIdLine(line);
|
|
313
|
+
if (id) {
|
|
314
|
+
capturedRunId = id;
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return parseProgressLine(line);
|
|
319
|
+
},
|
|
320
|
+
getCapturedRunId() {
|
|
321
|
+
return capturedRunId;
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
}
|
|
236
325
|
/**
|
|
237
326
|
* Build a human-readable message for a progress notification (AC-3).
|
|
238
327
|
* @internal Exported for testing only.
|
|
@@ -368,22 +457,21 @@ export function registerRunTool(server) {
|
|
|
368
457
|
// Swallow synchronous errors (AC-6)
|
|
369
458
|
}
|
|
370
459
|
};
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
460
|
+
// Per-request capture of the runId line emitted by the spawned CLI
|
|
461
|
+
// (#631). `routeLine` also parses progress events; we wire emission
|
|
462
|
+
// at this layer so the capture factory stays pure / testable.
|
|
463
|
+
const { routeLine, getCapturedRunId } = createRunIdCapture();
|
|
375
464
|
const handleLine = (line) => {
|
|
376
|
-
const event =
|
|
465
|
+
const event = routeLine(line);
|
|
377
466
|
if (event)
|
|
378
467
|
emitProgress(event);
|
|
379
468
|
};
|
|
380
|
-
//
|
|
381
|
-
//
|
|
382
|
-
//
|
|
469
|
+
// `hasProgressToken` no longer gates the line buffer (always-on so
|
|
470
|
+
// runId capture works without a subscriber); it still gates the
|
|
471
|
+
// `onProgress` callback below, which controls spawnAsync's
|
|
472
|
+
// timeout-reset behavior on progress events.
|
|
383
473
|
const hasProgressToken = progressToken !== undefined;
|
|
384
|
-
const stderrLineBuffer =
|
|
385
|
-
? createLineBuffer(handleLine)
|
|
386
|
-
: undefined;
|
|
474
|
+
const stderrLineBuffer = createLineBuffer(handleLine);
|
|
387
475
|
// Register all issues as active runs for real-time status polling
|
|
388
476
|
for (const issue of issues) {
|
|
389
477
|
registerRun(issue);
|
|
@@ -405,8 +493,11 @@ export function registerRunTool(server) {
|
|
|
405
493
|
const stdout = result.stdout || "";
|
|
406
494
|
const stderr = result.stderr || "";
|
|
407
495
|
const overallStatus = result.exitCode === 0 ? "success" : "failure";
|
|
408
|
-
// Try to read structured log file for rich per-issue data
|
|
409
|
-
|
|
496
|
+
// Try to read structured log file for rich per-issue data.
|
|
497
|
+
// Prefer exact-filename lookup by captured runId (#631); fall back to
|
|
498
|
+
// the time-window heuristic when the CLI didn't emit a runId (older
|
|
499
|
+
// CLI, startup race) or the file is not yet visible on disk.
|
|
500
|
+
const runLog = await resolveRunLog(getCapturedRunId(), runStartTime);
|
|
410
501
|
let response;
|
|
411
502
|
if (runLog) {
|
|
412
503
|
response = buildStructuredResponse(runLog, stdout, overallStatus, result.exitCode, stderr || undefined);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type JSX } from "react";
|
|
2
|
+
import type { RunSnapshot } from "../../lib/workflow/run-state.js";
|
|
3
|
+
/**
|
|
4
|
+
* Root TUI component.
|
|
5
|
+
*
|
|
6
|
+
* Polls `getSnapshot` at 10 Hz. A 1 Hz "now" tick keeps the
|
|
7
|
+
* last-activity stamp moving even when the snapshot itself is unchanged.
|
|
8
|
+
* When the snapshot reports `done`, the component stops polling and
|
|
9
|
+
* invokes `onDone` so the caller can `unmount` the ink instance.
|
|
10
|
+
*/
|
|
11
|
+
export declare function App({ getSnapshot, onDone, }: {
|
|
12
|
+
getSnapshot: () => RunSnapshot;
|
|
13
|
+
onDone?: () => void;
|
|
14
|
+
}): JSX.Element;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { Box, useStdout } from "ink";
|
|
4
|
+
import { Header } from "./Header.js";
|
|
5
|
+
import { IssueBox } from "./IssueBox.js";
|
|
6
|
+
const POLL_MS = 100; // 10 Hz
|
|
7
|
+
/**
|
|
8
|
+
* Root TUI component.
|
|
9
|
+
*
|
|
10
|
+
* Polls `getSnapshot` at 10 Hz. A 1 Hz "now" tick keeps the
|
|
11
|
+
* last-activity stamp moving even when the snapshot itself is unchanged.
|
|
12
|
+
* When the snapshot reports `done`, the component stops polling and
|
|
13
|
+
* invokes `onDone` so the caller can `unmount` the ink instance.
|
|
14
|
+
*/
|
|
15
|
+
export function App({ getSnapshot, onDone, }) {
|
|
16
|
+
const [snapshot, setSnapshot] = useState(() => getSnapshot());
|
|
17
|
+
const [now, setNow] = useState(() => Date.now());
|
|
18
|
+
const doneFired = useRef(false);
|
|
19
|
+
const { stdout } = useStdout();
|
|
20
|
+
// Snapshot poller (drives all state transitions).
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const id = setInterval(() => {
|
|
23
|
+
const next = getSnapshot();
|
|
24
|
+
setSnapshot(next);
|
|
25
|
+
if (next.done && !doneFired.current) {
|
|
26
|
+
doneFired.current = true;
|
|
27
|
+
clearInterval(id);
|
|
28
|
+
onDone?.();
|
|
29
|
+
}
|
|
30
|
+
}, POLL_MS);
|
|
31
|
+
return () => clearInterval(id);
|
|
32
|
+
}, [getSnapshot, onDone]);
|
|
33
|
+
// Coarse 1 Hz tick for the last-activity stamp.
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const id = setInterval(() => setNow(Date.now()), 1000);
|
|
36
|
+
return () => clearInterval(id);
|
|
37
|
+
}, []);
|
|
38
|
+
const columns = stdout?.columns ?? 80;
|
|
39
|
+
const boxWidth = Math.min(columns - 2, 100);
|
|
40
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, { snapshot: snapshot }), snapshot.issues.map((issue, i) => (_jsx(IssueBox, { state: issue, slot: i, width: boxWidth, now: now }, issue.number)))] }));
|
|
41
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type JSX } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Per-issue elapsed timer. Owns its own interval so tick-driven re-renders
|
|
4
|
+
* are scoped to this leaf component and do not propagate to `IssueBox`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function ElapsedTimer({ startedAt }: {
|
|
7
|
+
startedAt?: Date;
|
|
8
|
+
}): JSX.Element;
|
|
9
|
+
/** Format an absolute timestamp as the "last activity Xs ago" stamp. */
|
|
10
|
+
export declare function formatSinceActivity(now: number, activityAt: Date): string;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { Text } from "ink";
|
|
4
|
+
/**
|
|
5
|
+
* Per-issue elapsed timer. Owns its own interval so tick-driven re-renders
|
|
6
|
+
* are scoped to this leaf component and do not propagate to `IssueBox`.
|
|
7
|
+
*/
|
|
8
|
+
export function ElapsedTimer({ startedAt }) {
|
|
9
|
+
const [now, setNow] = useState(() => Date.now());
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const id = setInterval(() => setNow(Date.now()), 1000);
|
|
12
|
+
return () => clearInterval(id);
|
|
13
|
+
}, []);
|
|
14
|
+
if (!startedAt)
|
|
15
|
+
return _jsx(Text, { children: "--:--" });
|
|
16
|
+
const secs = Math.max(0, Math.floor((now - startedAt.getTime()) / 1000));
|
|
17
|
+
const mm = Math.floor(secs / 60)
|
|
18
|
+
.toString()
|
|
19
|
+
.padStart(2, "0");
|
|
20
|
+
const ss = (secs % 60).toString().padStart(2, "0");
|
|
21
|
+
return _jsx(Text, { children: `${mm}:${ss}` });
|
|
22
|
+
}
|
|
23
|
+
/** Format an absolute timestamp as the "last activity Xs ago" stamp. */
|
|
24
|
+
export function formatSinceActivity(now, activityAt) {
|
|
25
|
+
const secs = Math.max(0, Math.floor((now - activityAt.getTime()) / 1000));
|
|
26
|
+
if (secs < 60)
|
|
27
|
+
return `${secs}s ago`;
|
|
28
|
+
const mm = Math.floor(secs / 60);
|
|
29
|
+
const ss = secs % 60;
|
|
30
|
+
return `${mm}m ${ss}s ago`;
|
|
31
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { JSX } from "react";
|
|
2
|
+
import type { RunSnapshot } from "../../lib/workflow/run-state.js";
|
|
3
|
+
/** Top-of-dashboard summary: count, concurrency, base, quality-loop. */
|
|
4
|
+
export declare function Header({ snapshot }: {
|
|
5
|
+
snapshot: RunSnapshot;
|
|
6
|
+
}): JSX.Element;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { DIVIDER_COLOR } from "./theme.js";
|
|
4
|
+
/** Top-of-dashboard summary: count, concurrency, base, quality-loop. */
|
|
5
|
+
export function Header({ snapshot }) {
|
|
6
|
+
const { config, issues } = snapshot;
|
|
7
|
+
const concurrency = config.concurrency > 1
|
|
8
|
+
? `parallel (${config.concurrency} concurrent)`
|
|
9
|
+
: "sequential";
|
|
10
|
+
const loop = config.qualityLoop ? "on" : "off";
|
|
11
|
+
const base = config.baseSha
|
|
12
|
+
? `${config.baseBranch} @${config.baseSha.slice(0, 7)}`
|
|
13
|
+
: config.baseBranch;
|
|
14
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "sequant run" }), _jsxs(Text, { color: DIVIDER_COLOR, children: [" ─ ", issues.length, " issue", issues.length === 1 ? "" : "s", " • ", concurrency, " • ", "quality loop ", loop] })] }), _jsxs(Box, { children: [_jsx(Text, { color: DIVIDER_COLOR, children: "base " }), _jsx(Text, { children: base })] })] }));
|
|
15
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { JSX } from "react";
|
|
2
|
+
import type { IssueRuntimeState } from "../../lib/workflow/run-state.js";
|
|
3
|
+
/**
|
|
4
|
+
* Three-cell rendering of a single issue's runtime state.
|
|
5
|
+
*
|
|
6
|
+
* Cells:
|
|
7
|
+
* 1. header — id, title, phase N/total, elapsed
|
|
8
|
+
* 2. context — branch, phase progression, log path
|
|
9
|
+
* 3. activity — current `now` line, last-activity stamp
|
|
10
|
+
*/
|
|
11
|
+
export declare function IssueBox({ state, slot, width, now, }: {
|
|
12
|
+
state: IssueRuntimeState;
|
|
13
|
+
slot: number;
|
|
14
|
+
width: number;
|
|
15
|
+
now: number;
|
|
16
|
+
}): JSX.Element;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { DIVIDER_COLOR, PHASE_GLYPHS, borderColorForIssue, phaseStatusColor, } from "./theme.js";
|
|
4
|
+
import { Spinner } from "./Spinner.js";
|
|
5
|
+
import { ElapsedTimer, formatSinceActivity } from "./ElapsedTimer.js";
|
|
6
|
+
import { truncateToWidth } from "./truncate.js";
|
|
7
|
+
/**
|
|
8
|
+
* Three-cell rendering of a single issue's runtime state.
|
|
9
|
+
*
|
|
10
|
+
* Cells:
|
|
11
|
+
* 1. header — id, title, phase N/total, elapsed
|
|
12
|
+
* 2. context — branch, phase progression, log path
|
|
13
|
+
* 3. activity — current `now` line, last-activity stamp
|
|
14
|
+
*/
|
|
15
|
+
export function IssueBox({ state, slot, width, now, }) {
|
|
16
|
+
const border = borderColorForIssue(state.status, slot);
|
|
17
|
+
const innerWidth = Math.max(10, width - 4);
|
|
18
|
+
const doneCount = state.phases.filter((p) => p.status === "done" || p.status === "failed").length;
|
|
19
|
+
const activePhaseIndex = state.phases.findIndex((p) => p.status === "running");
|
|
20
|
+
const displayPhaseN = activePhaseIndex >= 0 ? activePhaseIndex + 1 : doneCount;
|
|
21
|
+
const total = state.phases.length;
|
|
22
|
+
const headerTitle = truncateToWidth(`#${state.number} ${state.title}`, Math.max(10, innerWidth - 20));
|
|
23
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: border, paddingX: 1, marginBottom: 1, width: width, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: border, children: headerTitle }), _jsxs(Text, { color: DIVIDER_COLOR, children: ["phase ", displayPhaseN, "/", total, " \u2022", " ", _jsx(ElapsedTimer, { startedAt: state.startedAt })] })] }), _jsx(Divider, { width: innerWidth, borderColor: border }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: DIVIDER_COLOR, children: "branch " }), _jsx(Text, { children: truncateToWidth(state.branch, innerWidth - 8) })] }), _jsx(PhaseProgression, { phases: state.phases, borderColor: border }), state.currentPhase?.logPath ? (_jsxs(Box, { children: [_jsx(Text, { color: DIVIDER_COLOR, children: "log " }), _jsx(Text, { children: truncateToWidth(state.currentPhase.logPath, innerWidth - 8) })] })) : null] }), _jsx(Divider, { width: innerWidth, borderColor: border }), _jsx(Box, { flexDirection: "column", children: state.currentPhase ? (_jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsx(Text, { color: DIVIDER_COLOR, children: "now " }), _jsx(Spinner, { color: border }), _jsxs(Text, { children: [" ", truncateToWidth(state.currentPhase.nowLine, innerWidth - 12)] })] }), _jsx(Box, { children: _jsxs(Text, { color: DIVIDER_COLOR, children: [" └ last activity ", formatSinceActivity(now, state.currentPhase.lastActivityAt)] }) })] })) : (_jsx(Text, { color: DIVIDER_COLOR, children: statusLine(state) })) })] }));
|
|
24
|
+
}
|
|
25
|
+
function statusLine(state) {
|
|
26
|
+
switch (state.status) {
|
|
27
|
+
case "queued":
|
|
28
|
+
return "queued";
|
|
29
|
+
case "running":
|
|
30
|
+
return "working…";
|
|
31
|
+
case "passed":
|
|
32
|
+
return "completed";
|
|
33
|
+
case "failed":
|
|
34
|
+
return "failed";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function Divider({ width, borderColor, }) {
|
|
38
|
+
const mid = "─".repeat(Math.max(0, width - 2));
|
|
39
|
+
return (_jsxs(Text, { children: [_jsx(Text, { color: borderColor, children: "\u251C" }), _jsx(Text, { color: DIVIDER_COLOR, children: mid }), _jsx(Text, { color: borderColor, children: "\u2524" })] }));
|
|
40
|
+
}
|
|
41
|
+
function PhaseProgression({ phases, borderColor, }) {
|
|
42
|
+
return (_jsxs(Box, { flexWrap: "wrap", children: [_jsx(Text, { color: DIVIDER_COLOR, children: "phases " }), phases.map((p, i) => {
|
|
43
|
+
const isLast = i === phases.length - 1;
|
|
44
|
+
return (_jsxs(Box, { children: [_jsx(PhaseGlyph, { status: p.status, label: p.name, activeColor: borderColor, elapsedMs: p.elapsedMs }), !isLast ? (_jsxs(Text, { color: DIVIDER_COLOR, children: [" ", PHASE_GLYPHS.separator, " "] })) : null] }, `${p.name}-${i}`));
|
|
45
|
+
})] }));
|
|
46
|
+
}
|
|
47
|
+
function PhaseGlyph({ status, label, activeColor, elapsedMs, }) {
|
|
48
|
+
if (status === "running") {
|
|
49
|
+
return (_jsxs(Box, { children: [_jsx(Spinner, { color: activeColor }), _jsxs(Text, { children: [" ", label] })] }));
|
|
50
|
+
}
|
|
51
|
+
const glyph = status === "done"
|
|
52
|
+
? PHASE_GLYPHS.done
|
|
53
|
+
: status === "failed"
|
|
54
|
+
? PHASE_GLYPHS.failed
|
|
55
|
+
: PHASE_GLYPHS.pending;
|
|
56
|
+
const glyphColor = phaseStatusColor(status);
|
|
57
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: glyphColor, children: glyph }), _jsxs(Text, { color: DIVIDER_COLOR, children: [" ", label, elapsedMs != null ? ` ${formatShortDuration(elapsedMs)}` : ""] })] }));
|
|
58
|
+
}
|
|
59
|
+
function formatShortDuration(ms) {
|
|
60
|
+
const secs = Math.max(0, Math.floor(ms / 1000));
|
|
61
|
+
if (secs < 60)
|
|
62
|
+
return `${secs.toString().padStart(2, "0")}s`;
|
|
63
|
+
const mm = Math.floor(secs / 60)
|
|
64
|
+
.toString()
|
|
65
|
+
.padStart(2, "0");
|
|
66
|
+
const ss = (secs % 60).toString().padStart(2, "0");
|
|
67
|
+
return `${mm}:${ss}`;
|
|
68
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type JSX } from "react";
|
|
2
|
+
import { type BorderColor } from "./theme.js";
|
|
3
|
+
/**
|
|
4
|
+
* Braille spinner. Ticks at 10 Hz in its own component so the parent
|
|
5
|
+
* issue box is not forced to re-render on each frame.
|
|
6
|
+
*/
|
|
7
|
+
export declare function Spinner({ color }: {
|
|
8
|
+
color?: BorderColor;
|
|
9
|
+
}): JSX.Element;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { Text } from "ink";
|
|
4
|
+
import { SPINNER_FRAMES } from "./theme.js";
|
|
5
|
+
/**
|
|
6
|
+
* Braille spinner. Ticks at 10 Hz in its own component so the parent
|
|
7
|
+
* issue box is not forced to re-render on each frame.
|
|
8
|
+
*/
|
|
9
|
+
export function Spinner({ color }) {
|
|
10
|
+
const [frame, setFrame] = useState(0);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const id = setInterval(() => {
|
|
13
|
+
setFrame((f) => (f + 1) % SPINNER_FRAMES.length);
|
|
14
|
+
}, 100);
|
|
15
|
+
return () => clearInterval(id);
|
|
16
|
+
}, []);
|
|
17
|
+
return _jsx(Text, { color: color, children: SPINNER_FRAMES[frame] });
|
|
18
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Experimental multi-issue TUI entry point.
|
|
3
|
+
*
|
|
4
|
+
* Mounts an `ink` app that polls `RunOrchestrator.getSnapshot()` at 10 Hz.
|
|
5
|
+
* Unmounts when the orchestrator reports `done` so the shell returns
|
|
6
|
+
* cleanly. Only safe to call when `process.stdout.isTTY` is true.
|
|
7
|
+
*/
|
|
8
|
+
import type { RunOrchestrator } from "../../lib/workflow/run-orchestrator.js";
|
|
9
|
+
export interface TuiHandle {
|
|
10
|
+
/** Promise that resolves when the TUI unmounts. */
|
|
11
|
+
done: Promise<void>;
|
|
12
|
+
/** Force-unmount (e.g., on SIGINT fallback). */
|
|
13
|
+
unmount: () => void;
|
|
14
|
+
}
|
|
15
|
+
export declare function renderTui(orchestrator: RunOrchestrator): TuiHandle;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Experimental multi-issue TUI entry point.
|
|
3
|
+
*
|
|
4
|
+
* Mounts an `ink` app that polls `RunOrchestrator.getSnapshot()` at 10 Hz.
|
|
5
|
+
* Unmounts when the orchestrator reports `done` so the shell returns
|
|
6
|
+
* cleanly. Only safe to call when `process.stdout.isTTY` is true.
|
|
7
|
+
*/
|
|
8
|
+
import { createElement } from "react";
|
|
9
|
+
import { render } from "ink";
|
|
10
|
+
import { App } from "./App.js";
|
|
11
|
+
export function renderTui(orchestrator) {
|
|
12
|
+
let resolveDone;
|
|
13
|
+
const done = new Promise((resolve) => {
|
|
14
|
+
resolveDone = resolve;
|
|
15
|
+
});
|
|
16
|
+
const instance = render(createElement(App, {
|
|
17
|
+
getSnapshot: () => orchestrator.getSnapshot(),
|
|
18
|
+
onDone: () => {
|
|
19
|
+
instance.unmount();
|
|
20
|
+
},
|
|
21
|
+
}), { exitOnCtrlC: false });
|
|
22
|
+
instance.waitUntilExit().then(() => resolveDone());
|
|
23
|
+
return {
|
|
24
|
+
done,
|
|
25
|
+
unmount: () => {
|
|
26
|
+
instance.unmount();
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme tokens for the experimental multi-issue dashboard TUI.
|
|
3
|
+
*
|
|
4
|
+
* Palette rotates by start order (cyan → magenta → blue → yellow); issue
|
|
5
|
+
* status (failed / passed) overrides the rotation color where applicable.
|
|
6
|
+
* Respects `NO_COLOR` automatically via `ink`/`chalk`.
|
|
7
|
+
*/
|
|
8
|
+
import type { IssueStatus, PhaseStatus } from "../../lib/workflow/run-state.js";
|
|
9
|
+
/** Border-color palette rotated by issue start order. */
|
|
10
|
+
export declare const BORDER_ROTATION: readonly ["cyan", "magenta", "blue", "yellow"];
|
|
11
|
+
export type BorderColor = (typeof BORDER_ROTATION)[number] | "green" | "red" | "gray";
|
|
12
|
+
/** Gray used for horizontal dividers inside each box. */
|
|
13
|
+
export declare const DIVIDER_COLOR: "gray";
|
|
14
|
+
/**
|
|
15
|
+
* Pick the border color for an issue.
|
|
16
|
+
* Failed / passed states win over rotation; otherwise rotate by slot.
|
|
17
|
+
*/
|
|
18
|
+
export declare function borderColorForIssue(status: IssueStatus, slot: number): BorderColor;
|
|
19
|
+
/** Glyphs for the phase progression row. */
|
|
20
|
+
export declare const PHASE_GLYPHS: {
|
|
21
|
+
readonly pending: "○";
|
|
22
|
+
readonly done: "✓";
|
|
23
|
+
readonly failed: "✗";
|
|
24
|
+
readonly separator: "▸";
|
|
25
|
+
};
|
|
26
|
+
/** Braille spinner frames — 10 Hz rotation looks smooth at ~100ms tick. */
|
|
27
|
+
export declare const SPINNER_FRAMES: readonly ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
28
|
+
/** Color for a phase glyph based on its status. Active phase uses border color. */
|
|
29
|
+
export declare function phaseStatusColor(status: PhaseStatus): BorderColor;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme tokens for the experimental multi-issue dashboard TUI.
|
|
3
|
+
*
|
|
4
|
+
* Palette rotates by start order (cyan → magenta → blue → yellow); issue
|
|
5
|
+
* status (failed / passed) overrides the rotation color where applicable.
|
|
6
|
+
* Respects `NO_COLOR` automatically via `ink`/`chalk`.
|
|
7
|
+
*/
|
|
8
|
+
/** Border-color palette rotated by issue start order. */
|
|
9
|
+
export const BORDER_ROTATION = ["cyan", "magenta", "blue", "yellow"];
|
|
10
|
+
/** Gray used for horizontal dividers inside each box. */
|
|
11
|
+
export const DIVIDER_COLOR = "gray";
|
|
12
|
+
/**
|
|
13
|
+
* Pick the border color for an issue.
|
|
14
|
+
* Failed / passed states win over rotation; otherwise rotate by slot.
|
|
15
|
+
*/
|
|
16
|
+
export function borderColorForIssue(status, slot) {
|
|
17
|
+
if (status === "failed")
|
|
18
|
+
return "red";
|
|
19
|
+
if (status === "passed")
|
|
20
|
+
return "green";
|
|
21
|
+
const idx = ((slot % BORDER_ROTATION.length) + BORDER_ROTATION.length) %
|
|
22
|
+
BORDER_ROTATION.length;
|
|
23
|
+
return BORDER_ROTATION[idx];
|
|
24
|
+
}
|
|
25
|
+
/** Glyphs for the phase progression row. */
|
|
26
|
+
export const PHASE_GLYPHS = {
|
|
27
|
+
pending: "○",
|
|
28
|
+
done: "✓",
|
|
29
|
+
failed: "✗",
|
|
30
|
+
separator: "▸",
|
|
31
|
+
};
|
|
32
|
+
/** Braille spinner frames — 10 Hz rotation looks smooth at ~100ms tick. */
|
|
33
|
+
export const SPINNER_FRAMES = [
|
|
34
|
+
"⠋",
|
|
35
|
+
"⠙",
|
|
36
|
+
"⠹",
|
|
37
|
+
"⠸",
|
|
38
|
+
"⠼",
|
|
39
|
+
"⠴",
|
|
40
|
+
"⠦",
|
|
41
|
+
"⠧",
|
|
42
|
+
"⠇",
|
|
43
|
+
"⠏",
|
|
44
|
+
];
|
|
45
|
+
/** Color for a phase glyph based on its status. Active phase uses border color. */
|
|
46
|
+
export function phaseStatusColor(status) {
|
|
47
|
+
if (status === "done")
|
|
48
|
+
return "green";
|
|
49
|
+
if (status === "failed")
|
|
50
|
+
return "red";
|
|
51
|
+
return "gray";
|
|
52
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Display-width-aware truncation for terminal output.
|
|
3
|
+
*
|
|
4
|
+
* Uses `string-width` so wide glyphs (CJK, emoji) and ANSI escapes are
|
|
5
|
+
* counted correctly. Cheaper than ink's own truncation in hot paths.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Truncate `text` so its visible width does not exceed `max` columns.
|
|
9
|
+
* If truncation happens, appends a single `…` (which itself counts as 1 col).
|
|
10
|
+
*/
|
|
11
|
+
export declare function truncateToWidth(text: string, max: number): string;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Display-width-aware truncation for terminal output.
|
|
3
|
+
*
|
|
4
|
+
* Uses `string-width` so wide glyphs (CJK, emoji) and ANSI escapes are
|
|
5
|
+
* counted correctly. Cheaper than ink's own truncation in hot paths.
|
|
6
|
+
*/
|
|
7
|
+
import stringWidth from "string-width";
|
|
8
|
+
/**
|
|
9
|
+
* Truncate `text` so its visible width does not exceed `max` columns.
|
|
10
|
+
* If truncation happens, appends a single `…` (which itself counts as 1 col).
|
|
11
|
+
*/
|
|
12
|
+
export function truncateToWidth(text, max) {
|
|
13
|
+
if (max <= 0)
|
|
14
|
+
return "";
|
|
15
|
+
const width = stringWidth(text);
|
|
16
|
+
if (width <= max)
|
|
17
|
+
return text;
|
|
18
|
+
if (max === 1)
|
|
19
|
+
return "…";
|
|
20
|
+
let acc = "";
|
|
21
|
+
let accWidth = 0;
|
|
22
|
+
const budget = max - 1; // reserve one column for the ellipsis
|
|
23
|
+
for (const ch of text) {
|
|
24
|
+
const w = stringWidth(ch);
|
|
25
|
+
if (accWidth + w > budget)
|
|
26
|
+
break;
|
|
27
|
+
acc += ch;
|
|
28
|
+
accWidth += w;
|
|
29
|
+
}
|
|
30
|
+
return `${acc}…`;
|
|
31
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sequant",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Quantize your development workflow - Sequential AI phases with quality gates",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"lint": "eslint src/ bin/ --max-warnings 0",
|
|
28
28
|
"sync:skills": "cp -r templates/skills/* .claude/skills/",
|
|
29
29
|
"validate:skills": "for skill in templates/skills/*/; do npx skills-ref validate \"$skill\"; done",
|
|
30
|
+
"lint:skill-calls": "npx tsx scripts/lint-skill-calls.ts",
|
|
30
31
|
"prepare:marketplace": "npx tsx scripts/prepare-marketplace.ts",
|
|
31
32
|
"validate:marketplace": "npx tsx scripts/prepare-marketplace.ts --validate-only",
|
|
32
33
|
"prepublishOnly": "npm run build"
|
|
@@ -76,19 +77,23 @@
|
|
|
76
77
|
},
|
|
77
78
|
"dependencies": {
|
|
78
79
|
"@anthropic-ai/claude-agent-sdk": "^0.2.11",
|
|
79
|
-
"@hono/node-server": "^
|
|
80
|
+
"@hono/node-server": "^2.0.0",
|
|
80
81
|
"boxen": "^8.0.1",
|
|
81
82
|
"chalk": "^5.3.0",
|
|
82
83
|
"chokidar": "^5.0.0",
|
|
83
84
|
"cli-table3": "^0.6.5",
|
|
84
85
|
"commander": "^14.0.3",
|
|
85
|
-
"diff": "^
|
|
86
|
+
"diff": "^9.0.0",
|
|
86
87
|
"gradient-string": "^3.0.0",
|
|
87
88
|
"hono": "^4.12.1",
|
|
89
|
+
"ink": "^7.0.1",
|
|
88
90
|
"inquirer": "^13.3.0",
|
|
91
|
+
"log-update": "^7.0.1",
|
|
89
92
|
"open": "^11.0.0",
|
|
90
93
|
"ora": "^9.3.0",
|
|
91
94
|
"p-limit": "^7.3.0",
|
|
95
|
+
"react": "^19.2.5",
|
|
96
|
+
"string-width": "^8.2.0",
|
|
92
97
|
"yaml": "^2.7.0",
|
|
93
98
|
"zod": "^4.3.5"
|
|
94
99
|
},
|
|
@@ -97,10 +102,12 @@
|
|
|
97
102
|
"@types/gradient-string": "^1.1.6",
|
|
98
103
|
"@types/inquirer": "^9.0.7",
|
|
99
104
|
"@types/node": "^25.4.0",
|
|
105
|
+
"@types/react": "^19.2.14",
|
|
100
106
|
"@typescript-eslint/eslint-plugin": "^8.58.0",
|
|
101
107
|
"@typescript-eslint/parser": "^8.58.0",
|
|
102
108
|
"eslint": "^10.1.0",
|
|
103
109
|
"globals": "^17.0.0",
|
|
110
|
+
"ink-testing-library": "^4.0.0",
|
|
104
111
|
"tsx": "^4.19.2",
|
|
105
112
|
"typescript": "^6.0.2",
|
|
106
113
|
"typescript-eslint": "^8.58.0",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sequant-explorer
|
|
3
3
|
description: Codebase exploration agent for sequant /spec phase. Searches for existing patterns, components, database schemas, and file structures. Use when gathering context before planning a feature implementation.
|
|
4
|
+
# Note: per anthropics/claude-code#43869 this is currently a no-op; agent runs on parent's model
|
|
4
5
|
model: haiku
|
|
5
6
|
maxTurns: 15
|
|
6
7
|
tools:
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sequant-qa-checker
|
|
3
3
|
description: Quality check agent for sequant /qa phase. Runs type safety, scope/size, security, and documentation checks on diffs. Use when spawned by the /qa skill to perform parallel or sequential quality checks.
|
|
4
|
-
|
|
4
|
+
# Note: per anthropics/claude-code#43869 this is currently a no-op; agent runs on parent's model
|
|
5
|
+
model: sonnet
|
|
5
6
|
permissionMode: bypassPermissions
|
|
6
7
|
effort: low
|
|
7
8
|
maxTurns: 15
|