sequant 2.3.0 → 2.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -2
- package/README.md +125 -160
- package/dist/bin/cli.js +59 -4
- package/dist/dashboard/server.js +1 -0
- package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +2 -2
- package/dist/marketplace/external_plugins/sequant/README.md +6 -3
- package/dist/marketplace/external_plugins/sequant/hooks/post-tool.sh +92 -0
- package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +18 -9
- package/dist/marketplace/external_plugins/sequant/hooks/relay-check.sh +107 -0
- package/dist/marketplace/external_plugins/sequant/skills/_shared/references/behavior-rule-detection.md +205 -0
- package/dist/marketplace/external_plugins/sequant/skills/_shared/references/subagent-types.md +21 -8
- package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +302 -86
- package/dist/marketplace/external_plugins/sequant/skills/assess/references/predicted-collision-detection.md +109 -0
- package/dist/marketplace/external_plugins/sequant/skills/docs/SKILL.md +141 -22
- package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +83 -78
- package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +377 -137
- package/dist/marketplace/external_plugins/sequant/skills/loop/SKILL.md +28 -0
- package/dist/marketplace/external_plugins/sequant/skills/merger/SKILL.md +621 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +741 -232
- package/dist/marketplace/external_plugins/sequant/skills/qa/scripts/quality-checks.sh +47 -1
- package/dist/marketplace/external_plugins/sequant/skills/setup/SKILL.md +12 -6
- package/dist/marketplace/external_plugins/sequant/skills/spec/SKILL.md +217 -964
- package/dist/marketplace/external_plugins/sequant/skills/spec/references/parallel-groups.md +7 -0
- package/dist/marketplace/external_plugins/sequant/skills/spec/references/quality-checklist.md +75 -0
- package/dist/marketplace/external_plugins/sequant/skills/spec/references/recommended-workflow.md +4 -2
- package/dist/marketplace/external_plugins/sequant/skills/test/SKILL.md +0 -27
- package/dist/marketplace/external_plugins/sequant/skills/testgen/SKILL.md +24 -44
- package/dist/src/commands/abort.d.ts +36 -0
- package/dist/src/commands/abort.js +138 -0
- package/dist/src/commands/prompt.d.ts +7 -0
- package/dist/src/commands/prompt.js +101 -7
- package/dist/src/commands/ready-tui-adapter.d.ts +59 -0
- package/dist/src/commands/ready-tui-adapter.js +130 -0
- package/dist/src/commands/ready.d.ts +49 -0
- package/dist/src/commands/ready.js +243 -0
- package/dist/src/commands/run-progress.d.ts +11 -1
- package/dist/src/commands/run-progress.js +20 -3
- package/dist/src/commands/run.js +12 -2
- package/dist/src/commands/status.js +4 -0
- package/dist/src/commands/watch.d.ts +2 -0
- package/dist/src/commands/watch.js +67 -3
- package/dist/src/lib/assess-collision-detect.js +1 -1
- package/dist/src/lib/cli-ui/run-renderer-types.d.ts +39 -0
- package/dist/src/lib/cli-ui/run-renderer.d.ts +34 -2
- package/dist/src/lib/cli-ui/run-renderer.js +250 -33
- package/dist/src/lib/cli-ui/scrollback-harness.d.ts +112 -0
- package/dist/src/lib/cli-ui/scrollback-harness.js +294 -0
- package/dist/src/lib/merge-check/types.js +1 -1
- package/dist/src/lib/relay/archive.js +6 -0
- package/dist/src/lib/relay/types.d.ts +2 -0
- package/dist/src/lib/relay/types.js +9 -0
- package/dist/src/lib/settings.d.ts +34 -0
- package/dist/src/lib/settings.js +23 -1
- package/dist/src/lib/workflow/batch-executor.js +34 -18
- package/dist/src/lib/workflow/drivers/agent-driver.d.ts +48 -1
- package/dist/src/lib/workflow/drivers/aider.d.ts +7 -1
- package/dist/src/lib/workflow/drivers/aider.js +9 -0
- package/dist/src/lib/workflow/drivers/claude-code.d.ts +17 -1
- package/dist/src/lib/workflow/drivers/claude-code.js +51 -2
- package/dist/src/lib/workflow/drivers/index.d.ts +1 -1
- package/dist/src/lib/workflow/event-emitter.d.ts +157 -0
- package/dist/src/lib/workflow/event-emitter.js +102 -0
- package/dist/src/lib/workflow/notice.d.ts +32 -0
- package/dist/src/lib/workflow/notice.js +38 -0
- package/dist/src/lib/workflow/phase-executor.d.ts +9 -21
- package/dist/src/lib/workflow/phase-executor.js +105 -117
- package/dist/src/lib/workflow/phase-mapper.d.ts +26 -13
- package/dist/src/lib/workflow/phase-mapper.js +55 -33
- package/dist/src/lib/workflow/phase-registry.d.ts +127 -0
- package/dist/src/lib/workflow/phase-registry.js +233 -0
- package/dist/src/lib/workflow/platforms/github.d.ts +6 -0
- package/dist/src/lib/workflow/platforms/github.js +17 -0
- package/dist/src/lib/workflow/ready-gate.d.ts +155 -0
- package/dist/src/lib/workflow/ready-gate.js +374 -0
- package/dist/src/lib/workflow/reconcile.js +6 -0
- package/dist/src/lib/workflow/run-log-schema.d.ts +5 -55
- package/dist/src/lib/workflow/run-orchestrator.d.ts +32 -2
- package/dist/src/lib/workflow/run-orchestrator.js +125 -11
- package/dist/src/lib/workflow/state-manager.d.ts +19 -1
- package/dist/src/lib/workflow/state-manager.js +27 -1
- package/dist/src/lib/workflow/state-schema.d.ts +23 -35
- package/dist/src/lib/workflow/state-schema.js +29 -3
- package/dist/src/lib/workflow/types.d.ts +74 -15
- package/dist/src/lib/workflow/types.js +18 -13
- package/dist/src/ui/tui/App.js +8 -2
- package/dist/src/ui/tui/IssueBox.js +3 -4
- package/dist/src/ui/tui/index.d.ts +13 -4
- package/dist/src/ui/tui/index.js +19 -5
- package/dist/src/ui/tui/row-cap.d.ts +51 -0
- package/dist/src/ui/tui/row-cap.js +76 -0
- package/dist/src/ui/tui/teardown.d.ts +20 -0
- package/dist/src/ui/tui/teardown.js +29 -0
- package/dist/src/ui/tui/theme.d.ts +3 -0
- package/dist/src/ui/tui/theme.js +3 -0
- package/package.json +23 -11
- package/templates/hooks/post-tool.sh +81 -0
- package/templates/skills/assess/SKILL.md +28 -28
- package/templates/skills/assess/references/predicted-collision-detection.md +1 -1
- package/templates/skills/qa/SKILL.md +5 -2
- package/templates/skills/setup/SKILL.md +6 -6
package/dist/src/ui/tui/App.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from "react";
|
|
3
|
-
import { Box, useStdout } from "ink";
|
|
3
|
+
import { Box, Text, useStdout } from "ink";
|
|
4
4
|
import { Header } from "./Header.js";
|
|
5
5
|
import { IssueBox } from "./IssueBox.js";
|
|
6
|
+
import { selectVisibleIssues } from "./row-cap.js";
|
|
7
|
+
import { ROLLUP_COLOR } from "./theme.js";
|
|
6
8
|
const POLL_MS = 100; // 10 Hz
|
|
7
9
|
/**
|
|
8
10
|
* Root TUI component.
|
|
@@ -37,5 +39,9 @@ export function App({ getSnapshot, onDone, }) {
|
|
|
37
39
|
}, []);
|
|
38
40
|
const columns = stdout?.columns ?? 80;
|
|
39
41
|
const boxWidth = Math.min(columns - 2, 100);
|
|
40
|
-
|
|
42
|
+
// #699 AC-4: clamp the number of boxes to the terminal height so a large
|
|
43
|
+
// batch on a short terminal can't overflow the frame (parity with the plain
|
|
44
|
+
// renderer's #624 row cap). Older completed issues collapse into `✔ N done`.
|
|
45
|
+
const { visible, rolledUpDoneCount } = selectVisibleIssues(snapshot.issues, stdout?.rows);
|
|
46
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, { snapshot: snapshot }), visible.map((issue, i) => (_jsx(IssueBox, { state: issue, slot: i, width: boxWidth, now: now }, issue.number))), rolledUpDoneCount > 0 ? (_jsx(Text, { color: ROLLUP_COLOR, children: `✔ ${rolledUpDoneCount} done` })) : null] }));
|
|
41
47
|
}
|
|
@@ -20,7 +20,7 @@ export function IssueBox({ state, slot, width, now, }) {
|
|
|
20
20
|
const displayPhaseN = activePhaseIndex >= 0 ? activePhaseIndex + 1 : doneCount;
|
|
21
21
|
const total = state.phases.length;
|
|
22
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
|
|
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 }), _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 }), _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
24
|
}
|
|
25
25
|
function statusLine(state) {
|
|
26
26
|
switch (state.status) {
|
|
@@ -34,9 +34,8 @@ function statusLine(state) {
|
|
|
34
34
|
return "failed";
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
-
function Divider({ width
|
|
38
|
-
|
|
39
|
-
return (_jsxs(Text, { children: [_jsx(Text, { color: borderColor, children: "\u251C" }), _jsx(Text, { color: DIVIDER_COLOR, children: mid }), _jsx(Text, { color: borderColor, children: "\u2524" })] }));
|
|
37
|
+
function Divider({ width }) {
|
|
38
|
+
return _jsx(Text, { color: DIVIDER_COLOR, children: "─".repeat(Math.max(0, width)) });
|
|
40
39
|
}
|
|
41
40
|
function PhaseProgression({ phases, borderColor, }) {
|
|
42
41
|
return (_jsxs(Box, { flexWrap: "wrap", children: [_jsx(Text, { color: DIVIDER_COLOR, children: "phases " }), phases.map((p, i) => {
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Experimental multi-issue TUI entry point.
|
|
3
3
|
*
|
|
4
|
-
* Mounts an `ink` app that polls `
|
|
5
|
-
* Unmounts when the
|
|
4
|
+
* Mounts an `ink` app that polls a snapshot provider's `getSnapshot()` at
|
|
5
|
+
* 10 Hz. Unmounts when the snapshot reports `done` so the shell returns
|
|
6
6
|
* cleanly. Only safe to call when `process.stdout.isTTY` is true.
|
|
7
|
+
*
|
|
8
|
+
* The provider is structural (`{ getSnapshot(): RunSnapshot }`) so any source
|
|
9
|
+
* of run state can drive the TUI — `RunOrchestrator` for `sequant run`, or the
|
|
10
|
+
* single-issue adapter `sequant ready` owns (#699). The TUI only ever reads
|
|
11
|
+
* `getSnapshot()`, never the orchestrator's batch lifecycle.
|
|
7
12
|
*/
|
|
8
|
-
import type {
|
|
13
|
+
import type { RunSnapshot } from "../../lib/workflow/run-state.js";
|
|
14
|
+
/** Minimal structural contract the TUI needs from its state source. */
|
|
15
|
+
export interface SnapshotProvider {
|
|
16
|
+
getSnapshot(): RunSnapshot;
|
|
17
|
+
}
|
|
9
18
|
export interface TuiHandle {
|
|
10
19
|
/** Promise that resolves when the TUI unmounts. */
|
|
11
20
|
done: Promise<void>;
|
|
12
21
|
/** Force-unmount (e.g., on SIGINT fallback). */
|
|
13
22
|
unmount: () => void;
|
|
14
23
|
}
|
|
15
|
-
export declare function renderTui(
|
|
24
|
+
export declare function renderTui(provider: SnapshotProvider): TuiHandle;
|
package/dist/src/ui/tui/index.js
CHANGED
|
@@ -1,25 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Experimental multi-issue TUI entry point.
|
|
3
3
|
*
|
|
4
|
-
* Mounts an `ink` app that polls `
|
|
5
|
-
* Unmounts when the
|
|
4
|
+
* Mounts an `ink` app that polls a snapshot provider's `getSnapshot()` at
|
|
5
|
+
* 10 Hz. Unmounts when the snapshot reports `done` so the shell returns
|
|
6
6
|
* cleanly. Only safe to call when `process.stdout.isTTY` is true.
|
|
7
|
+
*
|
|
8
|
+
* The provider is structural (`{ getSnapshot(): RunSnapshot }`) so any source
|
|
9
|
+
* of run state can drive the TUI — `RunOrchestrator` for `sequant run`, or the
|
|
10
|
+
* single-issue adapter `sequant ready` owns (#699). The TUI only ever reads
|
|
11
|
+
* `getSnapshot()`, never the orchestrator's batch lifecycle.
|
|
7
12
|
*/
|
|
8
13
|
import { createElement } from "react";
|
|
9
14
|
import { render } from "ink";
|
|
10
15
|
import { App } from "./App.js";
|
|
11
|
-
|
|
16
|
+
import { composeTeardownSummary } from "./teardown.js";
|
|
17
|
+
export function renderTui(provider) {
|
|
12
18
|
let resolveDone;
|
|
13
19
|
const done = new Promise((resolve) => {
|
|
14
20
|
resolveDone = resolve;
|
|
15
21
|
});
|
|
16
22
|
const instance = render(createElement(App, {
|
|
17
|
-
getSnapshot: () =>
|
|
23
|
+
getSnapshot: () => provider.getSnapshot(),
|
|
18
24
|
onDone: () => {
|
|
19
25
|
instance.unmount();
|
|
20
26
|
},
|
|
21
27
|
}), { exitOnCtrlC: false });
|
|
22
|
-
instance.waitUntilExit().then(() =>
|
|
28
|
+
instance.waitUntilExit().then(() => {
|
|
29
|
+
// #699 AC-5: ink leaves no per-issue history on unmount, so write a durable
|
|
30
|
+
// `✔/✘` transcript from the final snapshot into scrollback. Runs before
|
|
31
|
+
// `done` resolves so the caller's own report (e.g. `ready`'s) prints after.
|
|
32
|
+
const summary = composeTeardownSummary(provider.getSnapshot());
|
|
33
|
+
if (summary)
|
|
34
|
+
process.stdout.write(summary + "\n");
|
|
35
|
+
resolveDone();
|
|
36
|
+
});
|
|
23
37
|
return {
|
|
24
38
|
done,
|
|
25
39
|
unmount: () => {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Row cap + frame-height clamp for the Ink TUI (#699 AC-4).
|
|
3
|
+
*
|
|
4
|
+
* Parity with the plain renderer's #624 behavior (`run-renderer.ts`
|
|
5
|
+
* `applyRowCap` / `effectiveRowCap`): N issues must never render more boxes
|
|
6
|
+
* than the terminal can hold. Keep every active (queued/running) issue, fill
|
|
7
|
+
* the remaining slots with the most-recently-completed issues, and roll older
|
|
8
|
+
* completed issues into a single `✔ N done` summary line.
|
|
9
|
+
*
|
|
10
|
+
* The TUI difference is vertical density: each plain-grid issue is ~3 lines,
|
|
11
|
+
* but a full Ink box is ~9–11 lines (round border + 3 cells + 2 dividers +
|
|
12
|
+
* bottom margin). `LINES_PER_BOX` is sized accordingly so the dynamic cap
|
|
13
|
+
* reflects how many boxes actually fit.
|
|
14
|
+
*/
|
|
15
|
+
import type { IssueRuntimeState } from "../../lib/workflow/run-state.js";
|
|
16
|
+
/** Static row cap (matches the plain renderer's default). */
|
|
17
|
+
export declare const DEFAULT_TUI_ROW_CAP = 10;
|
|
18
|
+
/**
|
|
19
|
+
* Approximate height of one rendered Ink box, in terminal rows: round border
|
|
20
|
+
* top/bottom (2) + header (1) + two dividers (2) + context cell (~3) +
|
|
21
|
+
* activity cell (~2) + bottom margin (1) ≈ 11.
|
|
22
|
+
*/
|
|
23
|
+
export declare const TUI_LINES_PER_BOX = 11;
|
|
24
|
+
/**
|
|
25
|
+
* Effective cap: the smaller of the static cap and a dynamic terminal-height
|
|
26
|
+
* ceiling. Mirrors `run-renderer.ts` `effectiveRowCap`, but with a box-height
|
|
27
|
+
* `linesPerBox` rather than the plain grid's 3.
|
|
28
|
+
*
|
|
29
|
+
* When `rows` is unknown (no TTY size), trust the static cap directly rather
|
|
30
|
+
* than guessing a height — the same "don't over-clamp" intent as the plain
|
|
31
|
+
* renderer's tall default, without picking an arbitrary fallback row count.
|
|
32
|
+
*
|
|
33
|
+
* @internal Exported for testing.
|
|
34
|
+
*/
|
|
35
|
+
export declare function effectiveTuiRowCap(rows: number | undefined, staticCap?: number, linesPerBox?: number): number;
|
|
36
|
+
export interface VisibleSelection {
|
|
37
|
+
/** Boxes to render, in order: active issues first, then recent done. */
|
|
38
|
+
visible: IssueRuntimeState[];
|
|
39
|
+
/** Older completed issues collapsed into the `✔ N done` summary (0 if none). */
|
|
40
|
+
rolledUpDoneCount: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Select which issue boxes to render so the frame never exceeds the terminal
|
|
44
|
+
* height. Parity with `run-renderer.ts` `applyRowCap`.
|
|
45
|
+
*
|
|
46
|
+
* - Under the cap → render everything, no rollup.
|
|
47
|
+
* - Over the cap → keep all active issues; fill remaining slots (minus one
|
|
48
|
+
* reserved for the rollup line) with the most-recently-completed issues;
|
|
49
|
+
* the rest roll up into `rolledUpDoneCount`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function selectVisibleIssues(issues: IssueRuntimeState[], rows: number | undefined, staticCap?: number, linesPerBox?: number): VisibleSelection;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Row cap + frame-height clamp for the Ink TUI (#699 AC-4).
|
|
3
|
+
*
|
|
4
|
+
* Parity with the plain renderer's #624 behavior (`run-renderer.ts`
|
|
5
|
+
* `applyRowCap` / `effectiveRowCap`): N issues must never render more boxes
|
|
6
|
+
* than the terminal can hold. Keep every active (queued/running) issue, fill
|
|
7
|
+
* the remaining slots with the most-recently-completed issues, and roll older
|
|
8
|
+
* completed issues into a single `✔ N done` summary line.
|
|
9
|
+
*
|
|
10
|
+
* The TUI difference is vertical density: each plain-grid issue is ~3 lines,
|
|
11
|
+
* but a full Ink box is ~9–11 lines (round border + 3 cells + 2 dividers +
|
|
12
|
+
* bottom margin). `LINES_PER_BOX` is sized accordingly so the dynamic cap
|
|
13
|
+
* reflects how many boxes actually fit.
|
|
14
|
+
*/
|
|
15
|
+
/** Static row cap (matches the plain renderer's default). */
|
|
16
|
+
export const DEFAULT_TUI_ROW_CAP = 10;
|
|
17
|
+
/**
|
|
18
|
+
* Approximate height of one rendered Ink box, in terminal rows: round border
|
|
19
|
+
* top/bottom (2) + header (1) + two dividers (2) + context cell (~3) +
|
|
20
|
+
* activity cell (~2) + bottom margin (1) ≈ 11.
|
|
21
|
+
*/
|
|
22
|
+
export const TUI_LINES_PER_BOX = 11;
|
|
23
|
+
/**
|
|
24
|
+
* Fixed vertical overhead outside the issue boxes: the Header block plus the
|
|
25
|
+
* rolled-up `✔ N done` summary line.
|
|
26
|
+
*/
|
|
27
|
+
const FIXED_OVERHEAD = 4;
|
|
28
|
+
/** A queued or running issue is "active"; passed/failed are terminal. */
|
|
29
|
+
function isActive(issue) {
|
|
30
|
+
return issue.status === "queued" || issue.status === "running";
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Effective cap: the smaller of the static cap and a dynamic terminal-height
|
|
34
|
+
* ceiling. Mirrors `run-renderer.ts` `effectiveRowCap`, but with a box-height
|
|
35
|
+
* `linesPerBox` rather than the plain grid's 3.
|
|
36
|
+
*
|
|
37
|
+
* When `rows` is unknown (no TTY size), trust the static cap directly rather
|
|
38
|
+
* than guessing a height — the same "don't over-clamp" intent as the plain
|
|
39
|
+
* renderer's tall default, without picking an arbitrary fallback row count.
|
|
40
|
+
*
|
|
41
|
+
* @internal Exported for testing.
|
|
42
|
+
*/
|
|
43
|
+
export function effectiveTuiRowCap(rows, staticCap = DEFAULT_TUI_ROW_CAP, linesPerBox = TUI_LINES_PER_BOX) {
|
|
44
|
+
if (!rows || rows <= 0)
|
|
45
|
+
return staticCap;
|
|
46
|
+
const dynamicCap = Math.max(1, Math.floor((rows - FIXED_OVERHEAD) / Math.max(1, linesPerBox)));
|
|
47
|
+
return Math.min(staticCap, dynamicCap);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Select which issue boxes to render so the frame never exceeds the terminal
|
|
51
|
+
* height. Parity with `run-renderer.ts` `applyRowCap`.
|
|
52
|
+
*
|
|
53
|
+
* - Under the cap → render everything, no rollup.
|
|
54
|
+
* - Over the cap → keep all active issues; fill remaining slots (minus one
|
|
55
|
+
* reserved for the rollup line) with the most-recently-completed issues;
|
|
56
|
+
* the rest roll up into `rolledUpDoneCount`.
|
|
57
|
+
*/
|
|
58
|
+
export function selectVisibleIssues(issues, rows, staticCap = DEFAULT_TUI_ROW_CAP, linesPerBox = TUI_LINES_PER_BOX) {
|
|
59
|
+
const cap = effectiveTuiRowCap(rows, staticCap, linesPerBox);
|
|
60
|
+
if (issues.length <= cap) {
|
|
61
|
+
return { visible: issues, rolledUpDoneCount: 0 };
|
|
62
|
+
}
|
|
63
|
+
const active = issues.filter(isActive);
|
|
64
|
+
const done = issues
|
|
65
|
+
.filter((i) => !isActive(i))
|
|
66
|
+
.sort((a, b) => (b.completedAt?.getTime() ?? 0) - (a.completedAt?.getTime() ?? 0));
|
|
67
|
+
// Reserve one slot for the rollup line; the rest go to visible boxes.
|
|
68
|
+
const visibleSlots = Math.max(1, cap - 1);
|
|
69
|
+
const remainingForDone = Math.max(0, visibleSlots - active.length);
|
|
70
|
+
const visibleDone = done.slice(0, remainingForDone);
|
|
71
|
+
const rolledUpDoneCount = done.length - visibleDone.length;
|
|
72
|
+
return {
|
|
73
|
+
visible: [...active, ...visibleDone],
|
|
74
|
+
rolledUpDoneCount,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable teardown summary for the Ink TUI (#699 AC-5).
|
|
3
|
+
*
|
|
4
|
+
* Ink repaints a live region in place; unlike the plain renderer it appends no
|
|
5
|
+
* per-issue `✔/✘` history when it unmounts. So on teardown we compose a compact
|
|
6
|
+
* transcript line per issue from the final snapshot and write it to stdout
|
|
7
|
+
* (outside ink's managed region) so a completed run leaves a record in
|
|
8
|
+
* scrollback. Emitting it here in the shared entry point means both `run` and
|
|
9
|
+
* `sequant ready` inherit it.
|
|
10
|
+
*/
|
|
11
|
+
import type { RunSnapshot } from "../../lib/workflow/run-state.js";
|
|
12
|
+
/**
|
|
13
|
+
* Compose the durable teardown summary from a final snapshot.
|
|
14
|
+
*
|
|
15
|
+
* Returns a newline-joined block of one line per issue, or an empty string when
|
|
16
|
+
* there are no issues (nothing to record).
|
|
17
|
+
*
|
|
18
|
+
* @internal Exported for testing.
|
|
19
|
+
*/
|
|
20
|
+
export declare function composeTeardownSummary(snapshot: RunSnapshot): string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable teardown summary for the Ink TUI (#699 AC-5).
|
|
3
|
+
*
|
|
4
|
+
* Ink repaints a live region in place; unlike the plain renderer it appends no
|
|
5
|
+
* per-issue `✔/✘` history when it unmounts. So on teardown we compose a compact
|
|
6
|
+
* transcript line per issue from the final snapshot and write it to stdout
|
|
7
|
+
* (outside ink's managed region) so a completed run leaves a record in
|
|
8
|
+
* scrollback. Emitting it here in the shared entry point means both `run` and
|
|
9
|
+
* `sequant ready` inherit it.
|
|
10
|
+
*/
|
|
11
|
+
/** One transcript line per issue, e.g. `✔ #699 Upgrade ready to the Ink TUI`. */
|
|
12
|
+
function issueLine(issue) {
|
|
13
|
+
const glyph = issue.status === "failed" ? "✘" : "✔";
|
|
14
|
+
const title = issue.title ? ` ${issue.title}` : "";
|
|
15
|
+
return `${glyph} #${issue.number}${title}`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Compose the durable teardown summary from a final snapshot.
|
|
19
|
+
*
|
|
20
|
+
* Returns a newline-joined block of one line per issue, or an empty string when
|
|
21
|
+
* there are no issues (nothing to record).
|
|
22
|
+
*
|
|
23
|
+
* @internal Exported for testing.
|
|
24
|
+
*/
|
|
25
|
+
export function composeTeardownSummary(snapshot) {
|
|
26
|
+
if (!snapshot.issues.length)
|
|
27
|
+
return "";
|
|
28
|
+
return snapshot.issues.map(issueLine).join("\n");
|
|
29
|
+
}
|
|
@@ -11,6 +11,9 @@ export declare const BORDER_ROTATION: readonly ["cyan", "magenta", "blue", "yell
|
|
|
11
11
|
export type BorderColor = (typeof BORDER_ROTATION)[number] | "green" | "red" | "gray";
|
|
12
12
|
/** Gray used for horizontal dividers inside each box. */
|
|
13
13
|
export declare const DIVIDER_COLOR: "gray";
|
|
14
|
+
/** Green used for the rolled-up `✔ N done` summary line (#699, parity with the
|
|
15
|
+
* plain renderer's #624 rollup). */
|
|
16
|
+
export declare const ROLLUP_COLOR: "green";
|
|
14
17
|
/**
|
|
15
18
|
* Pick the border color for an issue.
|
|
16
19
|
* Failed / passed states win over rotation; otherwise rotate by slot.
|
package/dist/src/ui/tui/theme.js
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
export const BORDER_ROTATION = ["cyan", "magenta", "blue", "yellow"];
|
|
10
10
|
/** Gray used for horizontal dividers inside each box. */
|
|
11
11
|
export const DIVIDER_COLOR = "gray";
|
|
12
|
+
/** Green used for the rolled-up `✔ N done` summary line (#699, parity with the
|
|
13
|
+
* plain renderer's #624 rollup). */
|
|
14
|
+
export const ROLLUP_COLOR = "green";
|
|
12
15
|
/**
|
|
13
16
|
* Pick the border color for an issue.
|
|
14
17
|
* Failed / passed states win over rotation; otherwise rotate by slot.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sequant",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.5.0",
|
|
4
|
+
"description": "AI coding agent orchestrator — resolve GitHub issues end-to-end with isolated git worktrees, quality gates, and an MCP server. Works with Claude Code or Aider.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"sequant": "dist/bin/cli.js"
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"test": "vitest run",
|
|
27
27
|
"lint": "eslint src/ bin/ --max-warnings 0",
|
|
28
28
|
"sync:skills": "cp -r templates/skills/* .claude/skills/",
|
|
29
|
+
"sync:hooks": "bash scripts/sync-hooks.sh",
|
|
29
30
|
"validate:skills": "for skill in templates/skills/*/; do npx skills-ref validate \"$skill\"; done",
|
|
30
31
|
"lint:skill-calls": "npx tsx scripts/lint-skill-calls.ts",
|
|
31
32
|
"prepare:marketplace": "npx tsx scripts/prepare-marketplace.ts",
|
|
@@ -33,26 +34,37 @@
|
|
|
33
34
|
"prepublishOnly": "npm run build"
|
|
34
35
|
},
|
|
35
36
|
"keywords": [
|
|
36
|
-
"ai",
|
|
37
37
|
"claude",
|
|
38
38
|
"claude-code",
|
|
39
|
+
"claude-code-plugin",
|
|
39
40
|
"aider",
|
|
40
41
|
"mcp",
|
|
42
|
+
"mcp-server",
|
|
41
43
|
"cli",
|
|
44
|
+
"headless",
|
|
45
|
+
"ci",
|
|
42
46
|
"workflow",
|
|
43
47
|
"automation",
|
|
48
|
+
"parallel",
|
|
44
49
|
"coding-agent",
|
|
45
|
-
"agent",
|
|
50
|
+
"ai-coding-agent",
|
|
51
|
+
"agent-orchestrator",
|
|
52
|
+
"autonomous-agent",
|
|
53
|
+
"agentic",
|
|
46
54
|
"github",
|
|
47
55
|
"github-issues",
|
|
56
|
+
"pull-request",
|
|
57
|
+
"issue-resolution",
|
|
58
|
+
"spec-driven",
|
|
59
|
+
"acceptance-criteria",
|
|
60
|
+
"human-in-the-loop",
|
|
61
|
+
"code-review",
|
|
48
62
|
"quality-gates",
|
|
49
63
|
"code-quality",
|
|
50
64
|
"orchestrator",
|
|
65
|
+
"git-worktree",
|
|
51
66
|
"developer-tools",
|
|
52
|
-
"
|
|
53
|
-
"llm",
|
|
54
|
-
"ai-coding",
|
|
55
|
-
"copilot"
|
|
67
|
+
"ai-coding"
|
|
56
68
|
],
|
|
57
69
|
"author": "Sequant Contributors",
|
|
58
70
|
"license": "MIT",
|
|
@@ -65,7 +77,7 @@
|
|
|
65
77
|
},
|
|
66
78
|
"homepage": "https://sequant.io",
|
|
67
79
|
"engines": {
|
|
68
|
-
"node": ">=
|
|
80
|
+
"node": ">=22.12.0"
|
|
69
81
|
},
|
|
70
82
|
"peerDependencies": {
|
|
71
83
|
"@modelcontextprotocol/sdk": "^1.27.1"
|
|
@@ -76,7 +88,7 @@
|
|
|
76
88
|
}
|
|
77
89
|
},
|
|
78
90
|
"dependencies": {
|
|
79
|
-
"@anthropic-ai/claude-agent-sdk": "^0.
|
|
91
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.142",
|
|
80
92
|
"@hono/node-server": "^2.0.0",
|
|
81
93
|
"boxen": "^8.0.1",
|
|
82
94
|
"chalk": "^5.3.0",
|
|
@@ -87,7 +99,7 @@
|
|
|
87
99
|
"gradient-string": "^3.0.0",
|
|
88
100
|
"hono": "^4.12.1",
|
|
89
101
|
"ink": "^7.0.1",
|
|
90
|
-
"inquirer": "^
|
|
102
|
+
"inquirer": "^14.0.1",
|
|
91
103
|
"log-update": "^7.0.1",
|
|
92
104
|
"open": "^11.0.0",
|
|
93
105
|
"ora": "^9.3.0",
|
|
@@ -227,6 +227,60 @@ if [[ "$TOOL_NAME" == "Bash" ]] && echo "$TOOL_INPUT" | grep -qE '(npm run build
|
|
|
227
227
|
fi
|
|
228
228
|
fi
|
|
229
229
|
|
|
230
|
+
# === TEST COVERAGE ANALYSIS (P3) ===
|
|
231
|
+
# Opt-in: Set CLAUDE_HOOKS_COVERAGE=true to enable
|
|
232
|
+
# Automatically appends coverage analysis to npm test output
|
|
233
|
+
# Logs which changed files have/don't have corresponding tests
|
|
234
|
+
if [[ "${CLAUDE_HOOKS_COVERAGE:-}" == "true" ]]; then
|
|
235
|
+
if [[ "$TOOL_NAME" == "Bash" ]] && echo "$TOOL_INPUT" | grep -qE '(npm (test|run test)|bun (test|run test)|yarn (test|run test)|pnpm (test|run test))'; then
|
|
236
|
+
# Only run if tests passed (don't clutter failure output)
|
|
237
|
+
if ! echo "$TOOL_OUTPUT" | grep -qE '(FAIL|failed|Error:)'; then
|
|
238
|
+
COVERAGE_LOG="${_LOG_DIR}/claude-coverage.log"
|
|
239
|
+
|
|
240
|
+
# Get changed source files (excluding tests)
|
|
241
|
+
changed_files=$(git diff main...HEAD --name-only 2>/dev/null | grep -E '\.(ts|tsx|js|jsx)$' | grep -v -E '\.test\.|\.spec\.|__tests__' || true)
|
|
242
|
+
|
|
243
|
+
if [[ -n "$changed_files" ]]; then
|
|
244
|
+
echo "$(date +%H:%M:%S) COVERAGE_ANALYSIS: Checking test coverage for changed files" >> "$QUALITY_LOG"
|
|
245
|
+
|
|
246
|
+
files_with_tests=0
|
|
247
|
+
files_without_tests=0
|
|
248
|
+
critical_without_tests=""
|
|
249
|
+
|
|
250
|
+
while IFS= read -r file; do
|
|
251
|
+
[[ -z "$file" ]] && continue
|
|
252
|
+
base=$(basename "$file" .ts | sed 's/\.tsx$//')
|
|
253
|
+
|
|
254
|
+
# Check for test file
|
|
255
|
+
if find . -name "${base}.test.*" -o -name "${base}.spec.*" 2>/dev/null | grep -q .; then
|
|
256
|
+
((files_with_tests++))
|
|
257
|
+
else
|
|
258
|
+
((files_without_tests++))
|
|
259
|
+
# Check if critical path
|
|
260
|
+
if echo "$file" | grep -qE 'auth|payment|security|server-action|middleware|admin'; then
|
|
261
|
+
critical_without_tests="$critical_without_tests $file"
|
|
262
|
+
fi
|
|
263
|
+
fi
|
|
264
|
+
done <<< "$changed_files"
|
|
265
|
+
|
|
266
|
+
total=$((files_with_tests + files_without_tests))
|
|
267
|
+
|
|
268
|
+
# Log coverage summary
|
|
269
|
+
echo "$(date +%H:%M:%S) COVERAGE: $files_with_tests/$total changed files have tests" >> "$COVERAGE_LOG"
|
|
270
|
+
|
|
271
|
+
if [[ -n "$critical_without_tests" ]]; then
|
|
272
|
+
echo "$(date +%H:%M:%S) ⚠️ CRITICAL_NO_TESTS:$critical_without_tests" >> "$COVERAGE_LOG"
|
|
273
|
+
echo "$(date +%H:%M:%S) CRITICAL_NO_TESTS:$critical_without_tests" >> "$QUALITY_LOG"
|
|
274
|
+
fi
|
|
275
|
+
|
|
276
|
+
if [[ $files_without_tests -gt 0 ]]; then
|
|
277
|
+
echo "$(date +%H:%M:%S) COVERAGE_GAP: $files_without_tests files without tests" >> "$QUALITY_LOG"
|
|
278
|
+
fi
|
|
279
|
+
fi
|
|
280
|
+
fi
|
|
281
|
+
fi
|
|
282
|
+
fi
|
|
283
|
+
|
|
230
284
|
# === SMART TEST RUNNING (P3) ===
|
|
231
285
|
# Opt-in: Set CLAUDE_HOOKS_SMART_TESTS=true to enable
|
|
232
286
|
# Runs related tests asynchronously after file edits
|
|
@@ -311,4 +365,31 @@ if [[ -n "${CLAUDE_HOOKS_WEBHOOK_URL:-}" ]]; then
|
|
|
311
365
|
fi
|
|
312
366
|
fi
|
|
313
367
|
|
|
368
|
+
# === POST-MERGE WORKTREE CLEANUP ===
|
|
369
|
+
# Clean up worktree AFTER `gh pr merge` succeeds (not before).
|
|
370
|
+
# Previous approach removed worktree pre-merge, which lost work if merge failed.
|
|
371
|
+
if [[ "$TOOL_NAME" == "Bash" ]] && echo "$TOOL_INPUT" | grep -qE 'gh pr merge'; then
|
|
372
|
+
# Only clean up if merge succeeded (output contains merge confirmation)
|
|
373
|
+
if echo "$TOOL_OUTPUT" | grep -qiE '(merged|Merged pull request|Pull request .* merged)'; then
|
|
374
|
+
PR_NUM=$(echo "$TOOL_INPUT" | grep -oE 'gh pr merge [0-9]+' | grep -oE '[0-9]+')
|
|
375
|
+
|
|
376
|
+
if [[ -n "$PR_NUM" ]]; then
|
|
377
|
+
BRANCH_NAME=$(gh pr view "$PR_NUM" --json headRefName --jq '.headRefName' 2>/dev/null || true)
|
|
378
|
+
|
|
379
|
+
if [[ -n "$BRANCH_NAME" ]]; then
|
|
380
|
+
# Note: worktree line is 2 lines before branch line in porcelain output
|
|
381
|
+
WORKTREE_PATH=$(git worktree list --porcelain 2>/dev/null | grep -B2 "branch refs/heads/$BRANCH_NAME" | grep "^worktree " | sed 's/^worktree //' || true)
|
|
382
|
+
|
|
383
|
+
if [[ -n "$WORKTREE_PATH" && -d "$WORKTREE_PATH" ]]; then
|
|
384
|
+
git worktree remove "$WORKTREE_PATH" --force 2>/dev/null || true
|
|
385
|
+
echo "POST-MERGE: Removed worktree $WORKTREE_PATH for branch $BRANCH_NAME" >> "${_LOG_DIR}/claude-hook.log"
|
|
386
|
+
fi
|
|
387
|
+
|
|
388
|
+
# Clean up local branch
|
|
389
|
+
git branch -D "$BRANCH_NAME" 2>/dev/null || true
|
|
390
|
+
fi
|
|
391
|
+
fi
|
|
392
|
+
fi
|
|
393
|
+
fi
|
|
394
|
+
|
|
314
395
|
exit 0
|