sequant 2.6.1 → 2.7.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 +4 -0
- package/dist/bin/cli.js +28 -4
- package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +1 -1
- package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +3 -0
- package/dist/marketplace/external_plugins/sequant/skills/clean/SKILL.md +3 -0
- package/dist/marketplace/external_plugins/sequant/skills/docs/SKILL.md +3 -0
- package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +3 -0
- package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +3 -0
- package/dist/marketplace/external_plugins/sequant/skills/improve/SKILL.md +4 -1
- package/dist/marketplace/external_plugins/sequant/skills/loop/SKILL.md +3 -0
- package/dist/marketplace/external_plugins/sequant/skills/merger/SKILL.md +3 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +3 -0
- package/dist/marketplace/external_plugins/sequant/skills/reflect/SKILL.md +3 -0
- package/dist/marketplace/external_plugins/sequant/skills/security-review/SKILL.md +3 -0
- package/dist/marketplace/external_plugins/sequant/skills/setup/SKILL.md +3 -0
- package/dist/marketplace/external_plugins/sequant/skills/solve/SKILL.md +3 -0
- package/dist/marketplace/external_plugins/sequant/skills/spec/SKILL.md +3 -0
- package/dist/marketplace/external_plugins/sequant/skills/test/SKILL.md +3 -0
- package/dist/marketplace/external_plugins/sequant/skills/testgen/SKILL.md +3 -0
- package/dist/marketplace/external_plugins/sequant/skills/verify/SKILL.md +3 -0
- package/dist/src/commands/ready.js +1 -1
- package/dist/src/commands/run.js +1 -1
- package/dist/src/commands/sync.d.ts +44 -5
- package/dist/src/commands/sync.js +244 -18
- package/dist/src/commands/update.d.ts +1 -0
- package/dist/src/commands/update.js +80 -68
- package/dist/src/lib/templates.d.ts +50 -0
- package/dist/src/lib/templates.js +134 -15
- package/dist/src/ui/tui/App.js +24 -2
- package/dist/src/ui/tui/IssueBox.js +4 -4
- package/dist/src/ui/tui/load.d.ts +25 -0
- package/dist/src/ui/tui/load.js +41 -0
- package/dist/src/ui/tui/theme.d.ts +21 -3
- package/dist/src/ui/tui/theme.js +22 -4
- package/package.json +1 -1
- package/templates/skills/assess/SKILL.md +3 -0
- package/templates/skills/clean/SKILL.md +3 -0
- package/templates/skills/docs/SKILL.md +3 -0
- package/templates/skills/exec/SKILL.md +3 -0
- package/templates/skills/fullsolve/SKILL.md +3 -0
- package/templates/skills/improve/SKILL.md +4 -1
- package/templates/skills/loop/SKILL.md +3 -0
- package/templates/skills/merger/SKILL.md +3 -0
- package/templates/skills/qa/SKILL.md +3 -0
- package/templates/skills/reflect/SKILL.md +3 -0
- package/templates/skills/security-review/SKILL.md +3 -0
- package/templates/skills/setup/SKILL.md +3 -0
- package/templates/skills/solve/SKILL.md +3 -0
- package/templates/skills/spec/SKILL.md +3 -0
- package/templates/skills/test/SKILL.md +3 -0
- package/templates/skills/testgen/SKILL.md +3 -0
- package/templates/skills/verify/SKILL.md +3 -0
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { readdir, chmod } from "fs/promises";
|
|
5
5
|
import { join, dirname, relative, isAbsolute } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
|
+
import { diffLines } from "diff";
|
|
7
8
|
import { readFile, writeFile, ensureDir, fileExists, isSymlink, createSymlink, removeFileOrSymlink, } from "./fs.js";
|
|
8
9
|
import { getPackageVersion } from "./manifest.js";
|
|
9
10
|
const SKILLS_VERSION_PATH = ".claude/skills/.sequant-version";
|
|
@@ -12,6 +13,11 @@ import { isNativeWindows } from "./system.js";
|
|
|
12
13
|
import { getProjectName } from "./project-name.js";
|
|
13
14
|
// Get the package templates directory
|
|
14
15
|
export function getTemplatesDir() {
|
|
16
|
+
// Allow overriding the templates source (used by tests; also lets the dir be
|
|
17
|
+
// relocated without relying on the compiled-output layout below).
|
|
18
|
+
if (process.env.SEQUANT_TEMPLATES_DIR) {
|
|
19
|
+
return process.env.SEQUANT_TEMPLATES_DIR;
|
|
20
|
+
}
|
|
15
21
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
22
|
// Compiled structure: dist/src/lib/templates.js
|
|
17
23
|
// So we need ../../../templates to reach project root templates/
|
|
@@ -64,6 +70,132 @@ export async function getTemplateContent(templatePath) {
|
|
|
64
70
|
const fullPath = join(templatesDir, relativePath);
|
|
65
71
|
return readFile(fullPath);
|
|
66
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Files that are meant to be edited in place per project (e.g. the
|
|
75
|
+
* constitution). When one of these diverges from the rendered template
|
|
76
|
+
* without a parallel `.claude/.local/` file, it is treated as a protected
|
|
77
|
+
* local override rather than a stale "modified" file — so the default
|
|
78
|
+
* (non-`--force`) update/sync path never silently overwrites it.
|
|
79
|
+
*/
|
|
80
|
+
export const CUSTOMIZABLE_FILES = [".claude/memory/constitution.md"];
|
|
81
|
+
/**
|
|
82
|
+
* Whether a local path is a customizable file edited in place per project.
|
|
83
|
+
*/
|
|
84
|
+
export function isCustomizableFile(localPath) {
|
|
85
|
+
// Normalize OS path separators so the allow-list match holds on Windows,
|
|
86
|
+
// where template paths are assembled with backslashes (#708).
|
|
87
|
+
return CUSTOMIZABLE_FILES.includes(localPath.replace(/\\/g, "/"));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Build the full set of template variables used when rendering templates.
|
|
91
|
+
*
|
|
92
|
+
* This is the single source of truth shared by `copyTemplates` (write time)
|
|
93
|
+
* and `computeTemplateChanges` (diff time) so the two can never drift — a
|
|
94
|
+
* mismatch here is what caused `constitution.md` to read as "modified" on
|
|
95
|
+
* every project (the diff used a different/incomplete variable set than the
|
|
96
|
+
* write). See #708.
|
|
97
|
+
*/
|
|
98
|
+
export async function buildTemplateVariables(stack, tokens, options = {}) {
|
|
99
|
+
const stackConfig = getStackConfig(stack);
|
|
100
|
+
// Detect project name from available sources (package.json, Cargo.toml, etc.)
|
|
101
|
+
const projectName = await getProjectName();
|
|
102
|
+
// Get stack-specific notes for constitution template
|
|
103
|
+
// Use multi-stack notes if additional stacks are provided
|
|
104
|
+
const stackNotes = options.additionalStacks && options.additionalStacks.length > 0
|
|
105
|
+
? getMultiStackNotes(stack, options.additionalStacks)
|
|
106
|
+
: getStackNotes(stack);
|
|
107
|
+
return {
|
|
108
|
+
...stackConfig.variables,
|
|
109
|
+
...tokens,
|
|
110
|
+
PROJECT_NAME: projectName,
|
|
111
|
+
STACK: stack,
|
|
112
|
+
STACK_NOTES: stackNotes,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Compare bundled template content against what's installed under `.claude/`.
|
|
117
|
+
*
|
|
118
|
+
* Templates are rendered with the project's variables *before* comparison, so
|
|
119
|
+
* an unmodified file (e.g. a constitution with `{{PROJECT_NAME}}` expanded)
|
|
120
|
+
* reads as `unchanged` rather than `modified`. A file that diverges in place is
|
|
121
|
+
* `local-override` (skip-by-default) when it has a parallel `.claude/.local/`
|
|
122
|
+
* file or is in the customizable allow-list; otherwise it is `modified`.
|
|
123
|
+
*/
|
|
124
|
+
export async function computeTemplateChanges(stack, tokens, options = {}) {
|
|
125
|
+
const variables = await buildTemplateVariables(stack, tokens, options);
|
|
126
|
+
const templateFiles = await listTemplateFiles();
|
|
127
|
+
const changes = [];
|
|
128
|
+
for (const templatePath of templateFiles) {
|
|
129
|
+
// Normalize separators first: listTemplateFiles builds paths with the OS
|
|
130
|
+
// separator (backslashes on Windows), but the prefix swap and the .local/
|
|
131
|
+
// and customizable-file checks below all assume forward slashes (#708).
|
|
132
|
+
const localPath = templatePath
|
|
133
|
+
.replace(/\\/g, "/")
|
|
134
|
+
.replace("templates/", ".claude/");
|
|
135
|
+
// Skip .local files (user customizations are never overwritten)
|
|
136
|
+
if (localPath.includes(".local/")) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const rendered = processTemplate(await getTemplateContent(templatePath), variables);
|
|
140
|
+
const exists = await fileExists(localPath);
|
|
141
|
+
if (!exists) {
|
|
142
|
+
changes.push({ path: localPath, templatePath, status: "new", rendered });
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const localContent = await readFile(localPath);
|
|
146
|
+
if (localContent === rendered) {
|
|
147
|
+
changes.push({
|
|
148
|
+
path: localPath,
|
|
149
|
+
templatePath,
|
|
150
|
+
status: "unchanged",
|
|
151
|
+
rendered,
|
|
152
|
+
});
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
// Content differs after rendering. Protect in-place customizations:
|
|
156
|
+
// a parallel `.claude/.local/` override, or a known customizable file.
|
|
157
|
+
//
|
|
158
|
+
// Note: this protects a managed file that was *edited in place* (e.g. the
|
|
159
|
+
// constitution) when a parallel `.claude/.local/` twin exists. It is NOT a
|
|
160
|
+
// skill-loading mechanism — the harness never loads `.claude/.local/skills/
|
|
161
|
+
// <name>/SKILL.md`, so a full-file SKILL.md shadow does nothing at runtime
|
|
162
|
+
// (#711). Skills are instead customized via a runtime overlay: each managed
|
|
163
|
+
// SKILL.md opens (before its first heading) with a directive to honor
|
|
164
|
+
// `.claude/.local/skills/<name>/overrides.md`, and that overrides file is
|
|
165
|
+
// auto-skipped above because it lives under `.local/`. The directive sits at
|
|
166
|
+
// the top, not end-of-file, so it fires reliably even in 3000-line skills.
|
|
167
|
+
// See docs/guides/customization.md.
|
|
168
|
+
const localOverridePath = localPath.replace(".claude/", ".claude/.local/");
|
|
169
|
+
const hasLocalOverride = await fileExists(localOverridePath);
|
|
170
|
+
if (hasLocalOverride || isCustomizableFile(localPath)) {
|
|
171
|
+
changes.push({
|
|
172
|
+
path: localPath,
|
|
173
|
+
templatePath,
|
|
174
|
+
status: "local-override",
|
|
175
|
+
rendered,
|
|
176
|
+
});
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const diff = diffLines(localContent, rendered)
|
|
180
|
+
.map((part) => {
|
|
181
|
+
const prefix = part.added ? "+" : part.removed ? "-" : " ";
|
|
182
|
+
return part.value
|
|
183
|
+
.split("\n")
|
|
184
|
+
.filter((l) => l)
|
|
185
|
+
.map((l) => `${prefix} ${l}`)
|
|
186
|
+
.join("\n");
|
|
187
|
+
})
|
|
188
|
+
.join("\n");
|
|
189
|
+
changes.push({
|
|
190
|
+
path: localPath,
|
|
191
|
+
templatePath,
|
|
192
|
+
status: "modified",
|
|
193
|
+
rendered,
|
|
194
|
+
diff,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return changes;
|
|
198
|
+
}
|
|
67
199
|
/**
|
|
68
200
|
* Create symlinks for files in a directory, with fallback to copy
|
|
69
201
|
* @param srcDir Source directory containing template files
|
|
@@ -159,21 +291,8 @@ export async function symlinkDir(srcDir, destDir, options = {}) {
|
|
|
159
291
|
*/
|
|
160
292
|
export async function copyTemplates(stack, tokens, options = {}) {
|
|
161
293
|
const templatesDir = getTemplatesDir();
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const projectName = await getProjectName();
|
|
165
|
-
// Get stack-specific notes for constitution template
|
|
166
|
-
// Use multi-stack notes if additional stacks are provided
|
|
167
|
-
const stackNotes = options.additionalStacks && options.additionalStacks.length > 0
|
|
168
|
-
? getMultiStackNotes(stack, options.additionalStacks)
|
|
169
|
-
: getStackNotes(stack);
|
|
170
|
-
const variables = {
|
|
171
|
-
...stackConfig.variables,
|
|
172
|
-
...tokens,
|
|
173
|
-
PROJECT_NAME: projectName,
|
|
174
|
-
STACK: stack,
|
|
175
|
-
STACK_NOTES: stackNotes,
|
|
176
|
-
};
|
|
294
|
+
// Single source of truth for template variables (shared with the diff path)
|
|
295
|
+
const variables = await buildTemplateVariables(stack, tokens, options);
|
|
177
296
|
async function copyDir(srcDir, destDir) {
|
|
178
297
|
try {
|
|
179
298
|
const entries = await readdir(srcDir, { withFileTypes: true });
|
package/dist/src/ui/tui/App.js
CHANGED
|
@@ -19,6 +19,7 @@ export function App({ getSnapshot, onDone, }) {
|
|
|
19
19
|
const [now, setNow] = useState(() => Date.now());
|
|
20
20
|
const doneFired = useRef(false);
|
|
21
21
|
const { stdout } = useStdout();
|
|
22
|
+
const [columns, setColumns] = useState(() => stdout?.columns ?? 80);
|
|
22
23
|
// Snapshot poller (drives all state transitions).
|
|
23
24
|
useEffect(() => {
|
|
24
25
|
const id = setInterval(() => {
|
|
@@ -37,8 +38,29 @@ export function App({ getSnapshot, onDone, }) {
|
|
|
37
38
|
const id = setInterval(() => setNow(Date.now()), 1000);
|
|
38
39
|
return () => clearInterval(id);
|
|
39
40
|
}, []);
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
// Track the terminal width reactively. ink's own resize handler re-renders
|
|
42
|
+
// the existing React tree but does NOT re-run this component, so a width read
|
|
43
|
+
// imperatively in render goes stale until the next poll. In that window ink
|
|
44
|
+
// repaints boxes at the old (now too-wide) width and the lines wrap, which
|
|
45
|
+
// misaligns the box borders into the duplicate/garbled frames. Updating
|
|
46
|
+
// `columns` from the resize event forces an immediate re-layout at the new
|
|
47
|
+
// width. A 1 Hz fallback poll covers terminals that don't emit `resize`.
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (!stdout)
|
|
50
|
+
return;
|
|
51
|
+
const sync = () => setColumns(stdout.columns ?? 80);
|
|
52
|
+
stdout.on("resize", sync);
|
|
53
|
+
sync();
|
|
54
|
+
const id = setInterval(sync, 1000);
|
|
55
|
+
return () => {
|
|
56
|
+
stdout.off("resize", sync);
|
|
57
|
+
clearInterval(id);
|
|
58
|
+
};
|
|
59
|
+
}, [stdout]);
|
|
60
|
+
// Clamp each box to the current terminal width (minus a 2-col safety margin)
|
|
61
|
+
// so a box line can never equal or exceed the terminal width and wrap.
|
|
62
|
+
const safeColumns = columns > 0 ? columns : 80;
|
|
63
|
+
const boxWidth = Math.max(20, Math.min(safeColumns - 2, 100));
|
|
42
64
|
// #699 AC-4: clamp the number of boxes to the terminal height so a large
|
|
43
65
|
// batch on a short terminal can't overflow the frame (parity with the plain
|
|
44
66
|
// renderer's #624 row cap). Older completed issues collapse into `✔ N done`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
-
import { DIVIDER_COLOR, PHASE_GLYPHS, borderColorForIssue, phaseStatusColor, } from "./theme.js";
|
|
3
|
+
import { ACTIVE_PHASE_COLOR, DIVIDER_COLOR, PHASE_GLYPHS, borderColorForIssue, phaseStatusColor, } from "./theme.js";
|
|
4
4
|
import { Spinner } from "./Spinner.js";
|
|
5
5
|
import { ElapsedTimer, formatSinceActivity } from "./ElapsedTimer.js";
|
|
6
6
|
import { truncateToWidth } from "./truncate.js";
|
|
@@ -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 }), _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,
|
|
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, activeColor: ACTIVE_PHASE_COLOR }), 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: ACTIVE_PHASE_COLOR }), _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) {
|
|
@@ -37,10 +37,10 @@ function statusLine(state) {
|
|
|
37
37
|
function Divider({ width }) {
|
|
38
38
|
return _jsx(Text, { color: DIVIDER_COLOR, children: "─".repeat(Math.max(0, width)) });
|
|
39
39
|
}
|
|
40
|
-
function PhaseProgression({ phases,
|
|
40
|
+
function PhaseProgression({ phases, activeColor, }) {
|
|
41
41
|
return (_jsxs(Box, { flexWrap: "wrap", children: [_jsx(Text, { color: DIVIDER_COLOR, children: "phases " }), phases.map((p, i) => {
|
|
42
42
|
const isLast = i === phases.length - 1;
|
|
43
|
-
return (_jsxs(Box, { children: [_jsx(PhaseGlyph, { status: p.status, label: p.name, activeColor:
|
|
43
|
+
return (_jsxs(Box, { children: [_jsx(PhaseGlyph, { status: p.status, label: p.name, activeColor: activeColor, elapsedMs: p.elapsedMs }), !isLast ? (_jsxs(Text, { color: DIVIDER_COLOR, children: [" ", PHASE_GLYPHS.separator, " "] })) : null] }, `${p.name}-${i}`));
|
|
44
44
|
})] }));
|
|
45
45
|
}
|
|
46
46
|
function PhaseGlyph({ status, label, activeColor, elapsedMs, }) {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loader for the Ink TUI module that forces React into its production build.
|
|
3
|
+
*
|
|
4
|
+
* `react-reconciler` (pulled in by `ink`) selects its dev-vs-prod bundle from
|
|
5
|
+
* `process.env.NODE_ENV` **at module-evaluation time**. The development bundle
|
|
6
|
+
* emits a `performance.measure()` per component render and never calls
|
|
7
|
+
* `performance.clearMeasures()`. Driven by the TUI's 10 Hz poll over a long
|
|
8
|
+
* `sequant run`, those entries accumulate in Node's global performance buffer
|
|
9
|
+
* until it overflows its ~1,000,000-entry cap and prints
|
|
10
|
+
* `MaxPerformanceEntryBufferExceededWarning` to stderr — a memory leak that
|
|
11
|
+
* also corrupts the dashboard's in-place redraw (the stderr write scrolls the
|
|
12
|
+
* terminal between log-update frames; see #647/#664).
|
|
13
|
+
*
|
|
14
|
+
* The TUI module is the *only* importer of `react`/`ink`/`react-reconciler`,
|
|
15
|
+
* and it is always reached through a dynamic `import()`, so bracketing that one
|
|
16
|
+
* import with `NODE_ENV=production` caches the production reconciler (which has
|
|
17
|
+
* zero `performance.measure` calls). We restore `NODE_ENV` immediately after so
|
|
18
|
+
* spawned child processes (claude phases, `npm install`, build steps) do NOT
|
|
19
|
+
* inherit `NODE_ENV=production` — which would, e.g., make `npm install` skip
|
|
20
|
+
* devDependencies.
|
|
21
|
+
*
|
|
22
|
+
* Only overrides when `NODE_ENV` is unset/empty: an explicit `development` or
|
|
23
|
+
* `test` (the test runner) is respected so dev warnings remain available there.
|
|
24
|
+
*/
|
|
25
|
+
export declare function loadTui(): Promise<typeof import("./index.js")>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loader for the Ink TUI module that forces React into its production build.
|
|
3
|
+
*
|
|
4
|
+
* `react-reconciler` (pulled in by `ink`) selects its dev-vs-prod bundle from
|
|
5
|
+
* `process.env.NODE_ENV` **at module-evaluation time**. The development bundle
|
|
6
|
+
* emits a `performance.measure()` per component render and never calls
|
|
7
|
+
* `performance.clearMeasures()`. Driven by the TUI's 10 Hz poll over a long
|
|
8
|
+
* `sequant run`, those entries accumulate in Node's global performance buffer
|
|
9
|
+
* until it overflows its ~1,000,000-entry cap and prints
|
|
10
|
+
* `MaxPerformanceEntryBufferExceededWarning` to stderr — a memory leak that
|
|
11
|
+
* also corrupts the dashboard's in-place redraw (the stderr write scrolls the
|
|
12
|
+
* terminal between log-update frames; see #647/#664).
|
|
13
|
+
*
|
|
14
|
+
* The TUI module is the *only* importer of `react`/`ink`/`react-reconciler`,
|
|
15
|
+
* and it is always reached through a dynamic `import()`, so bracketing that one
|
|
16
|
+
* import with `NODE_ENV=production` caches the production reconciler (which has
|
|
17
|
+
* zero `performance.measure` calls). We restore `NODE_ENV` immediately after so
|
|
18
|
+
* spawned child processes (claude phases, `npm install`, build steps) do NOT
|
|
19
|
+
* inherit `NODE_ENV=production` — which would, e.g., make `npm install` skip
|
|
20
|
+
* devDependencies.
|
|
21
|
+
*
|
|
22
|
+
* Only overrides when `NODE_ENV` is unset/empty: an explicit `development` or
|
|
23
|
+
* `test` (the test runner) is respected so dev warnings remain available there.
|
|
24
|
+
*/
|
|
25
|
+
export async function loadTui() {
|
|
26
|
+
const prev = process.env.NODE_ENV;
|
|
27
|
+
const override = prev === undefined || prev === "";
|
|
28
|
+
if (override)
|
|
29
|
+
process.env.NODE_ENV = "production";
|
|
30
|
+
try {
|
|
31
|
+
return await import("./index.js");
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
if (override) {
|
|
35
|
+
if (prev === undefined)
|
|
36
|
+
delete process.env.NODE_ENV;
|
|
37
|
+
else
|
|
38
|
+
process.env.NODE_ENV = prev;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -6,14 +6,32 @@
|
|
|
6
6
|
* Respects `NO_COLOR` automatically via `ink`/`chalk`.
|
|
7
7
|
*/
|
|
8
8
|
import type { IssueStatus, PhaseStatus } from "../../lib/workflow/run-state.js";
|
|
9
|
+
/**
|
|
10
|
+
* Sequant brand accents, sourced from sequant-landing `src/styles/tokens.css`:
|
|
11
|
+
* - `BRAND_ORANGE` is the primary brand color (`--color-primary` dark mode).
|
|
12
|
+
* - `BRAND_GREEN` is the accent/success green (`--color-accent`).
|
|
13
|
+
*
|
|
14
|
+
* Used to brand the two color signals that matter most at a glance — the
|
|
15
|
+
* live/active phase and success — while issue-distinction (border rotation),
|
|
16
|
+
* failure (red), and dividers (gray) stay on robust named ANSI colors.
|
|
17
|
+
*
|
|
18
|
+
* Ink/chalk auto-downsamples hex to the nearest ANSI color on terminals
|
|
19
|
+
* without truecolor, and `NO_COLOR` still strips all color, so these degrade
|
|
20
|
+
* gracefully without a manual capability check.
|
|
21
|
+
*/
|
|
22
|
+
export declare const BRAND_ORANGE: "#FF8012";
|
|
23
|
+
export declare const BRAND_GREEN: "#10b981";
|
|
9
24
|
/** Border-color palette rotated by issue start order. */
|
|
10
25
|
export declare const BORDER_ROTATION: readonly ["cyan", "magenta", "blue", "yellow"];
|
|
11
|
-
export type BorderColor = (typeof BORDER_ROTATION)[number] |
|
|
26
|
+
export type BorderColor = (typeof BORDER_ROTATION)[number] | typeof BRAND_GREEN | typeof BRAND_ORANGE | "red" | "gray";
|
|
12
27
|
/** Gray used for horizontal dividers inside each box. */
|
|
13
28
|
export declare const DIVIDER_COLOR: "gray";
|
|
14
|
-
/**
|
|
29
|
+
/** Brand orange for the live/active phase spinner — the one element the eye
|
|
30
|
+
* tracks. Border rotation still distinguishes concurrent issues. */
|
|
31
|
+
export declare const ACTIVE_PHASE_COLOR: "#FF8012";
|
|
32
|
+
/** Brand green for the rolled-up `✔ N done` summary line (#699, parity with the
|
|
15
33
|
* plain renderer's #624 rollup). */
|
|
16
|
-
export declare const ROLLUP_COLOR: "
|
|
34
|
+
export declare const ROLLUP_COLOR: "#10b981";
|
|
17
35
|
/**
|
|
18
36
|
* Pick the border color for an issue.
|
|
19
37
|
* Failed / passed states win over rotation; otherwise rotate by slot.
|
package/dist/src/ui/tui/theme.js
CHANGED
|
@@ -5,13 +5,31 @@
|
|
|
5
5
|
* status (failed / passed) overrides the rotation color where applicable.
|
|
6
6
|
* Respects `NO_COLOR` automatically via `ink`/`chalk`.
|
|
7
7
|
*/
|
|
8
|
+
/**
|
|
9
|
+
* Sequant brand accents, sourced from sequant-landing `src/styles/tokens.css`:
|
|
10
|
+
* - `BRAND_ORANGE` is the primary brand color (`--color-primary` dark mode).
|
|
11
|
+
* - `BRAND_GREEN` is the accent/success green (`--color-accent`).
|
|
12
|
+
*
|
|
13
|
+
* Used to brand the two color signals that matter most at a glance — the
|
|
14
|
+
* live/active phase and success — while issue-distinction (border rotation),
|
|
15
|
+
* failure (red), and dividers (gray) stay on robust named ANSI colors.
|
|
16
|
+
*
|
|
17
|
+
* Ink/chalk auto-downsamples hex to the nearest ANSI color on terminals
|
|
18
|
+
* without truecolor, and `NO_COLOR` still strips all color, so these degrade
|
|
19
|
+
* gracefully without a manual capability check.
|
|
20
|
+
*/
|
|
21
|
+
export const BRAND_ORANGE = "#FF8012";
|
|
22
|
+
export const BRAND_GREEN = "#10b981";
|
|
8
23
|
/** Border-color palette rotated by issue start order. */
|
|
9
24
|
export const BORDER_ROTATION = ["cyan", "magenta", "blue", "yellow"];
|
|
10
25
|
/** Gray used for horizontal dividers inside each box. */
|
|
11
26
|
export const DIVIDER_COLOR = "gray";
|
|
12
|
-
/**
|
|
27
|
+
/** Brand orange for the live/active phase spinner — the one element the eye
|
|
28
|
+
* tracks. Border rotation still distinguishes concurrent issues. */
|
|
29
|
+
export const ACTIVE_PHASE_COLOR = BRAND_ORANGE;
|
|
30
|
+
/** Brand green for the rolled-up `✔ N done` summary line (#699, parity with the
|
|
13
31
|
* plain renderer's #624 rollup). */
|
|
14
|
-
export const ROLLUP_COLOR =
|
|
32
|
+
export const ROLLUP_COLOR = BRAND_GREEN;
|
|
15
33
|
/**
|
|
16
34
|
* Pick the border color for an issue.
|
|
17
35
|
* Failed / passed states win over rotation; otherwise rotate by slot.
|
|
@@ -20,7 +38,7 @@ export function borderColorForIssue(status, slot) {
|
|
|
20
38
|
if (status === "failed")
|
|
21
39
|
return "red";
|
|
22
40
|
if (status === "passed")
|
|
23
|
-
return
|
|
41
|
+
return BRAND_GREEN;
|
|
24
42
|
const idx = ((slot % BORDER_ROTATION.length) + BORDER_ROTATION.length) %
|
|
25
43
|
BORDER_ROTATION.length;
|
|
26
44
|
return BORDER_ROTATION[idx];
|
|
@@ -48,7 +66,7 @@ export const SPINNER_FRAMES = [
|
|
|
48
66
|
/** Color for a phase glyph based on its status. Active phase uses border color. */
|
|
49
67
|
export function phaseStatusColor(status) {
|
|
50
68
|
if (status === "done")
|
|
51
|
-
return
|
|
69
|
+
return BRAND_GREEN;
|
|
52
70
|
if (status === "failed")
|
|
53
71
|
return "red";
|
|
54
72
|
return "gray";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sequant",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
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": {
|
|
@@ -13,6 +13,9 @@ allowed-tools:
|
|
|
13
13
|
- Bash(gh *)
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
+
<!-- sequant:local-override -->
|
|
17
|
+
> **Local overrides (read this first).** Before following any instruction below, check whether `.claude/.local/skills/assess/overrides.md` exists. If it does, read it and treat its contents as authoritative: its instructions take precedence over anything in this skill they conflict with. This is the supported way to tailor `/assess` without forking it — `overrides.md` lives under `.claude/.local/`, which `sequant update` and `sync` never overwrite.
|
|
18
|
+
|
|
16
19
|
# Unified Issue Assessment & Triage
|
|
17
20
|
|
|
18
21
|
You are the "Assessment Agent" for the current repository.
|
|
@@ -27,6 +27,9 @@ allowed-tools:
|
|
|
27
27
|
- Grep
|
|
28
28
|
---
|
|
29
29
|
|
|
30
|
+
<!-- sequant:local-override -->
|
|
31
|
+
> **Local overrides (read this first).** Before following any instruction below, check whether `.claude/.local/skills/clean/overrides.md` exists. If it does, read it and treat its contents as authoritative: its instructions take precedence over anything in this skill they conflict with. This is the supported way to tailor `/clean` without forking it — `overrides.md` lives under `.claude/.local/`, which `sequant update` and `sync` never overwrite.
|
|
32
|
+
|
|
30
33
|
# Repository Cleanup Command
|
|
31
34
|
|
|
32
35
|
Comprehensive, safe repository cleanup that archives stale files, removes artifacts, and commits changes.
|
|
@@ -18,6 +18,9 @@ allowed-tools:
|
|
|
18
18
|
- Bash(gh pr diff:*)
|
|
19
19
|
---
|
|
20
20
|
|
|
21
|
+
<!-- sequant:local-override -->
|
|
22
|
+
> **Local overrides (read this first).** Before following any instruction below, check whether `.claude/.local/skills/docs/overrides.md` exists. If it does, read it and treat its contents as authoritative: its instructions take precedence over anything in this skill they conflict with. This is the supported way to tailor `/docs` without forking it — `overrides.md` lives under `.claude/.local/`, which `sequant update` and `sync` never overwrite.
|
|
23
|
+
|
|
21
24
|
# Documentation Generator
|
|
22
25
|
|
|
23
26
|
You are the Phase 4 "Documentation Agent" for the current repository.
|
|
@@ -43,6 +43,9 @@ allowed-tools:
|
|
|
43
43
|
- TodoWrite
|
|
44
44
|
---
|
|
45
45
|
|
|
46
|
+
<!-- sequant:local-override -->
|
|
47
|
+
> **Local overrides (read this first).** Before following any instruction below, check whether `.claude/.local/skills/exec/overrides.md` exists. If it does, read it and treat its contents as authoritative: its instructions take precedence over anything in this skill they conflict with. This is the supported way to tailor `/exec` without forking it — `overrides.md` lives under `.claude/.local/`, which `sequant update` and `sync` never overwrite.
|
|
48
|
+
|
|
46
49
|
# Implementation Command
|
|
47
50
|
|
|
48
51
|
You are the Phase 2 "Implementation Agent" for the current repository.
|
|
@@ -39,6 +39,9 @@ allowed-tools:
|
|
|
39
39
|
- Bash(./scripts/list-worktrees.sh:*)
|
|
40
40
|
---
|
|
41
41
|
|
|
42
|
+
<!-- sequant:local-override -->
|
|
43
|
+
> **Local overrides (read this first).** Before following any instruction below, check whether `.claude/.local/skills/fullsolve/overrides.md` exists. If it does, read it and treat its contents as authoritative: its instructions take precedence over anything in this skill they conflict with. This is the supported way to tailor `/fullsolve` without forking it — `overrides.md` lives under `.claude/.local/`, which `sequant update` and `sync` never overwrite.
|
|
44
|
+
|
|
42
45
|
# Full Solve Command
|
|
43
46
|
|
|
44
47
|
You are the "Full Solve Agent" for the current repository.
|
|
@@ -16,6 +16,9 @@ allowed-tools:
|
|
|
16
16
|
- AskUserQuestion
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
+
<!-- sequant:local-override -->
|
|
20
|
+
> **Local overrides (read this first).** Before following any instruction below, check whether `.claude/.local/skills/improve/overrides.md` exists. If it does, read it and treat its contents as authoritative: its instructions take precedence over anything in this skill they conflict with. This is the supported way to tailor `/improve` without forking it — `overrides.md` lives under `.claude/.local/`, which `sequant update` and `sync` never overwrite.
|
|
21
|
+
|
|
19
22
|
# Improve Command
|
|
20
23
|
|
|
21
24
|
You are the "Improvement Agent" for the current repository.
|
|
@@ -665,4 +668,4 @@ Error: Path `src/nonexistent/` not found.
|
|
|
665
668
|
- [ ] **Recommendations** - Tips for running quick wins vs larger refactors
|
|
666
669
|
|
|
667
670
|
**DO NOT proceed to issue creation without user selection.**
|
|
668
|
-
**DO NOT respond until all items are verified.**
|
|
671
|
+
**DO NOT respond until all items are verified.**
|
|
@@ -25,6 +25,9 @@ allowed-tools:
|
|
|
25
25
|
- Bash(git status:*)
|
|
26
26
|
---
|
|
27
27
|
|
|
28
|
+
<!-- sequant:local-override -->
|
|
29
|
+
> **Local overrides (read this first).** Before following any instruction below, check whether `.claude/.local/skills/loop/overrides.md` exists. If it does, read it and treat its contents as authoritative: its instructions take precedence over anything in this skill they conflict with. This is the supported way to tailor `/loop` without forking it — `overrides.md` lives under `.claude/.local/`, which `sequant update` and `sync` never overwrite.
|
|
30
|
+
|
|
28
31
|
# Quality Loop Command
|
|
29
32
|
|
|
30
33
|
You are the "Quality Loop Agent" for the current repository.
|
|
@@ -16,6 +16,9 @@ allowed-tools:
|
|
|
16
16
|
- Glob
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
+
<!-- sequant:local-override -->
|
|
20
|
+
> **Local overrides (read this first).** Before following any instruction below, check whether `.claude/.local/skills/merger/overrides.md` exists. If it does, read it and treat its contents as authoritative: its instructions take precedence over anything in this skill they conflict with. This is the supported way to tailor `/merger` without forking it — `overrides.md` lives under `.claude/.local/`, which `sequant update` and `sync` never overwrite.
|
|
21
|
+
|
|
19
22
|
# Merger Skill
|
|
20
23
|
|
|
21
24
|
You are the "Merger Agent" for handling post-QA integration of completed worktrees.
|
|
@@ -24,6 +24,9 @@ allowed-tools:
|
|
|
24
24
|
- AgentOutputTool
|
|
25
25
|
---
|
|
26
26
|
|
|
27
|
+
<!-- sequant:local-override -->
|
|
28
|
+
> **Local overrides (read this first).** Before following any instruction below, check whether `.claude/.local/skills/qa/overrides.md` exists. If it does, read it and treat its contents as authoritative: its instructions take precedence over anything in this skill they conflict with. This is the supported way to tailor `/qa` without forking it — `overrides.md` lives under `.claude/.local/`, which `sequant update` and `sync` never overwrite.
|
|
29
|
+
|
|
27
30
|
# QA & Code Review
|
|
28
31
|
|
|
29
32
|
You are the Phase 3 "QA & Code Review Agent" for the current repository.
|
|
@@ -12,6 +12,9 @@ allowed-tools:
|
|
|
12
12
|
- Grep
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
+
<!-- sequant:local-override -->
|
|
16
|
+
> **Local overrides (read this first).** Before following any instruction below, check whether `.claude/.local/skills/reflect/overrides.md` exists. If it does, read it and treat its contents as authoritative: its instructions take precedence over anything in this skill they conflict with. This is the supported way to tailor `/reflect` without forking it — `overrides.md` lives under `.claude/.local/`, which `sequant update` and `sync` never overwrite.
|
|
17
|
+
|
|
15
18
|
# Reflection Agent
|
|
16
19
|
|
|
17
20
|
You are the "Reflection Agent" for the current repository.
|
|
@@ -17,6 +17,9 @@ allowed-tools:
|
|
|
17
17
|
- Bash(gh issue comment:*)
|
|
18
18
|
---
|
|
19
19
|
|
|
20
|
+
<!-- sequant:local-override -->
|
|
21
|
+
> **Local overrides (read this first).** Before following any instruction below, check whether `.claude/.local/skills/security-review/overrides.md` exists. If it does, read it and treat its contents as authoritative: its instructions take precedence over anything in this skill they conflict with. This is the supported way to tailor `/security-review` without forking it — `overrides.md` lives under `.claude/.local/`, which `sequant update` and `sync` never overwrite.
|
|
22
|
+
|
|
20
23
|
# Security Review Command
|
|
21
24
|
|
|
22
25
|
You are the Security Review Agent for the current repository.
|
|
@@ -30,6 +30,9 @@ allowed-tools:
|
|
|
30
30
|
- Bash(curl:*)
|
|
31
31
|
---
|
|
32
32
|
|
|
33
|
+
<!-- sequant:local-override -->
|
|
34
|
+
> **Local overrides (read this first).** Before following any instruction below, check whether `.claude/.local/skills/setup/overrides.md` exists. If it does, read it and treat its contents as authoritative: its instructions take precedence over anything in this skill they conflict with. This is the supported way to tailor `/setup` without forking it — `overrides.md` lives under `.claude/.local/`, which `sequant update` and `sync` never overwrite.
|
|
35
|
+
|
|
33
36
|
# Sequant Setup
|
|
34
37
|
|
|
35
38
|
Initialize Sequant workflow system in your current project.
|
|
@@ -13,6 +13,9 @@ allowed-tools:
|
|
|
13
13
|
- Bash(gh *)
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
+
<!-- sequant:local-override -->
|
|
17
|
+
> **Local overrides (read this first).** Before following any instruction below, check whether `.claude/.local/skills/solve/overrides.md` exists. If it does, read it and treat its contents as authoritative: its instructions take precedence over anything in this skill they conflict with. This is the supported way to tailor `/solve` without forking it — `overrides.md` lives under `.claude/.local/`, which `sequant update` and `sync` never overwrite.
|
|
18
|
+
|
|
16
19
|
# /solve — Deprecated Alias for /assess
|
|
17
20
|
|
|
18
21
|
**This command has been merged into `/assess`.** Use `/assess` instead.
|
|
@@ -17,6 +17,9 @@ allowed-tools:
|
|
|
17
17
|
- AgentOutputTool
|
|
18
18
|
---
|
|
19
19
|
|
|
20
|
+
<!-- sequant:local-override -->
|
|
21
|
+
> **Local overrides (read this first).** Before following any instruction below, check whether `.claude/.local/skills/spec/overrides.md` exists. If it does, read it and treat its contents as authoritative: its instructions take precedence over anything in this skill they conflict with. This is the supported way to tailor `/spec` without forking it — `overrides.md` lives under `.claude/.local/`, which `sequant update` and `sync` never overwrite.
|
|
22
|
+
|
|
20
23
|
# Planning Agent
|
|
21
24
|
|
|
22
25
|
Phase 1 "Planning Agent." Understands the issue and AC, reviews or synthesizes a plan, identifies gaps and risks, and drafts a GitHub issue comment.
|
|
@@ -19,6 +19,9 @@ allowed-tools:
|
|
|
19
19
|
- Bash(npx tsx:*)
|
|
20
20
|
---
|
|
21
21
|
|
|
22
|
+
<!-- sequant:local-override -->
|
|
23
|
+
> **Local overrides (read this first).** Before following any instruction below, check whether `.claude/.local/skills/test/overrides.md` exists. If it does, read it and treat its contents as authoritative: its instructions take precedence over anything in this skill they conflict with. This is the supported way to tailor `/test` without forking it — `overrides.md` lives under `.claude/.local/`, which `sequant update` and `sync` never overwrite.
|
|
24
|
+
|
|
22
25
|
# Browser Testing Command
|
|
23
26
|
|
|
24
27
|
You are the "Testing Agent" for the current repository.
|
|
@@ -20,6 +20,9 @@ allowed-tools:
|
|
|
20
20
|
- Agent(sequant-testgen)
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
+
<!-- sequant:local-override -->
|
|
24
|
+
> **Local overrides (read this first).** Before following any instruction below, check whether `.claude/.local/skills/testgen/overrides.md` exists. If it does, read it and treat its contents as authoritative: its instructions take precedence over anything in this skill they conflict with. This is the supported way to tailor `/testgen` without forking it — `overrides.md` lives under `.claude/.local/`, which `sequant update` and `sync` never overwrite.
|
|
25
|
+
|
|
23
26
|
# Test Generation Command
|
|
24
27
|
|
|
25
28
|
You are the "Test Generation Agent" for the current repository.
|
|
@@ -15,6 +15,9 @@ allowed-tools:
|
|
|
15
15
|
- Bash(gh issue comment:*)
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
+
<!-- sequant:local-override -->
|
|
19
|
+
> **Local overrides (read this first).** Before following any instruction below, check whether `.claude/.local/skills/verify/overrides.md` exists. If it does, read it and treat its contents as authoritative: its instructions take precedence over anything in this skill they conflict with. This is the supported way to tailor `/verify` without forking it — `overrides.md` lives under `.claude/.local/`, which `sequant update` and `sync` never overwrite.
|
|
20
|
+
|
|
18
21
|
# Execution Verification
|
|
19
22
|
|
|
20
23
|
You are the "Execution Verification Agent" for the current repository.
|