sequant 1.14.2 → 1.15.1
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/dist/bin/cli.js
CHANGED
|
@@ -43,7 +43,8 @@ import { logsCommand } from "../src/commands/logs.js";
|
|
|
43
43
|
import { statsCommand } from "../src/commands/stats.js";
|
|
44
44
|
import { dashboardCommand } from "../src/commands/dashboard.js";
|
|
45
45
|
import { stateInitCommand, stateRebuildCommand, stateCleanCommand, } from "../src/commands/state.js";
|
|
46
|
-
import { syncCommand } from "../src/commands/sync.js";
|
|
46
|
+
import { syncCommand, areSkillsOutdated } from "../src/commands/sync.js";
|
|
47
|
+
import { getManifest } from "../src/lib/manifest.js";
|
|
47
48
|
const program = new Command();
|
|
48
49
|
// Handle --no-color before parsing
|
|
49
50
|
if (process.argv.includes("--no-color")) {
|
|
@@ -191,6 +192,21 @@ stateCmd
|
|
|
191
192
|
.option("--max-age <days>", "Remove entries older than N days", parseInt)
|
|
192
193
|
.option("--all", "Remove all orphaned entries (merged and abandoned)")
|
|
193
194
|
.action(stateCleanCommand);
|
|
195
|
+
// Auto-sync skills after npm upgrade (version mismatch detection)
|
|
196
|
+
// Only triggers when skills were previously synced (has .sequant-version marker).
|
|
197
|
+
// Projects that manage skills manually (no marker) are not affected.
|
|
198
|
+
program.hook("preAction", async (thisCommand) => {
|
|
199
|
+
const cmd = thisCommand.name();
|
|
200
|
+
if (cmd === "init" || cmd === "sync")
|
|
201
|
+
return;
|
|
202
|
+
const manifest = await getManifest();
|
|
203
|
+
if (!manifest)
|
|
204
|
+
return;
|
|
205
|
+
const { outdated, currentVersion } = await areSkillsOutdated();
|
|
206
|
+
if (outdated && currentVersion !== null) {
|
|
207
|
+
await syncCommand({ quiet: true });
|
|
208
|
+
}
|
|
209
|
+
});
|
|
194
210
|
// Parse and execute
|
|
195
211
|
program.parse();
|
|
196
212
|
// Show help if no command provided
|
package/dist/src/commands/run.js
CHANGED
|
@@ -835,6 +835,37 @@ async function executePhase(issueNumber, phase, config, sessionId, worktreePath,
|
|
|
835
835
|
};
|
|
836
836
|
}
|
|
837
837
|
}
|
|
838
|
+
/**
|
|
839
|
+
* Cold-start retry threshold in seconds.
|
|
840
|
+
* Failures under this duration are likely Claude Code subprocess initialization
|
|
841
|
+
* issues rather than genuine phase failures (based on empirical data: cold-start
|
|
842
|
+
* failures consistently complete in 15-39s vs 150-310s for real work).
|
|
843
|
+
*/
|
|
844
|
+
const COLD_START_THRESHOLD_SECONDS = 60;
|
|
845
|
+
const COLD_START_MAX_RETRIES = 2;
|
|
846
|
+
/**
|
|
847
|
+
* Execute a phase with automatic retry for cold-start failures.
|
|
848
|
+
* If a phase fails within COLD_START_THRESHOLD_SECONDS, it's likely a subprocess
|
|
849
|
+
* initialization issue — retry up to COLD_START_MAX_RETRIES times before giving up.
|
|
850
|
+
*/
|
|
851
|
+
async function executePhaseWithRetry(issueNumber, phase, config, sessionId, worktreePath, shutdownManager, spinner) {
|
|
852
|
+
let lastResult;
|
|
853
|
+
for (let attempt = 0; attempt <= COLD_START_MAX_RETRIES; attempt++) {
|
|
854
|
+
lastResult = await executePhase(issueNumber, phase, config, sessionId, worktreePath, shutdownManager, spinner);
|
|
855
|
+
const duration = lastResult.durationSeconds ?? 0;
|
|
856
|
+
// Success or genuine failure (took long enough to be real work)
|
|
857
|
+
if (lastResult.success || duration >= COLD_START_THRESHOLD_SECONDS) {
|
|
858
|
+
return lastResult;
|
|
859
|
+
}
|
|
860
|
+
// Cold-start failure detected — retry
|
|
861
|
+
if (attempt < COLD_START_MAX_RETRIES) {
|
|
862
|
+
if (config.verbose) {
|
|
863
|
+
console.log(chalk.yellow(`\n ⟳ Cold-start failure detected (${duration.toFixed(1)}s), retrying... (attempt ${attempt + 2}/${COLD_START_MAX_RETRIES + 1})`));
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
return lastResult;
|
|
868
|
+
}
|
|
838
869
|
/**
|
|
839
870
|
* Fetch issue info from GitHub
|
|
840
871
|
*/
|
|
@@ -1141,6 +1172,12 @@ export async function runCommand(issues, options) {
|
|
|
1141
1172
|
noSmartTests: mergedOptions.noSmartTests ?? false,
|
|
1142
1173
|
mcp: mcpEnabled,
|
|
1143
1174
|
};
|
|
1175
|
+
// Propagate verbose mode to UI config so spinners use text-only mode.
|
|
1176
|
+
// This prevents animated spinner control characters from colliding with
|
|
1177
|
+
// verbose console.log() calls from StateManager/MetricsWriter (#282).
|
|
1178
|
+
if (config.verbose) {
|
|
1179
|
+
ui.configure({ verbose: true });
|
|
1180
|
+
}
|
|
1144
1181
|
// Initialize log writer if JSON logging enabled
|
|
1145
1182
|
// Default: enabled via settings (logJson: true), can be disabled with --no-log
|
|
1146
1183
|
let logWriter = null;
|
|
@@ -1575,7 +1612,7 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
|
|
|
1575
1612
|
}
|
|
1576
1613
|
const specStartTime = new Date();
|
|
1577
1614
|
// Note: spec runs in main repo (not worktree) for planning
|
|
1578
|
-
const specResult = await
|
|
1615
|
+
const specResult = await executePhaseWithRetry(issueNumber, "spec", config, sessionId, worktreePath, // Will be ignored for spec (non-isolated phase)
|
|
1579
1616
|
shutdownManager, specSpinner);
|
|
1580
1617
|
const specEndTime = new Date();
|
|
1581
1618
|
if (specResult.sessionId) {
|
|
@@ -1718,7 +1755,7 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
|
|
|
1718
1755
|
}
|
|
1719
1756
|
}
|
|
1720
1757
|
const phaseStartTime = new Date();
|
|
1721
|
-
const result = await
|
|
1758
|
+
const result = await executePhaseWithRetry(issueNumber, phase, config, sessionId, worktreePath, shutdownManager, phaseSpinner);
|
|
1722
1759
|
const phaseEndTime = new Date();
|
|
1723
1760
|
// Capture session ID for subsequent phases
|
|
1724
1761
|
if (result.sessionId) {
|
|
@@ -1778,7 +1815,7 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
|
|
|
1778
1815
|
iteration,
|
|
1779
1816
|
});
|
|
1780
1817
|
loopSpinner.start();
|
|
1781
|
-
const loopResult = await
|
|
1818
|
+
const loopResult = await executePhaseWithRetry(issueNumber, "loop", config, sessionId, worktreePath, shutdownManager, loopSpinner);
|
|
1782
1819
|
phaseResults.push(loopResult);
|
|
1783
1820
|
if (loopResult.sessionId) {
|
|
1784
1821
|
sessionId = loopResult.sessionId;
|
|
@@ -7,7 +7,23 @@ import { getManifest, getPackageVersion } from "../lib/manifest.js";
|
|
|
7
7
|
import { fileExists } from "../lib/fs.js";
|
|
8
8
|
import { readdir } from "fs/promises";
|
|
9
9
|
import { StateManager } from "../lib/workflow/state-manager.js";
|
|
10
|
-
import { rebuildStateFromLogs, cleanupStaleEntries, } from "../lib/workflow/state-utils.js";
|
|
10
|
+
import { rebuildStateFromLogs, cleanupStaleEntries, checkPRMergeStatus, } from "../lib/workflow/state-utils.js";
|
|
11
|
+
/**
|
|
12
|
+
* Auto-detect merged PRs and update state for issues stuck at ready_for_merge.
|
|
13
|
+
* Queries GitHub for each ready_for_merge issue that has a PR number.
|
|
14
|
+
*/
|
|
15
|
+
async function refreshMergedStatuses(stateManager, issues) {
|
|
16
|
+
const readyIssues = issues.filter((i) => i.status === "ready_for_merge" && i.pr?.number);
|
|
17
|
+
if (readyIssues.length === 0)
|
|
18
|
+
return;
|
|
19
|
+
for (const issue of readyIssues) {
|
|
20
|
+
const prStatus = checkPRMergeStatus(issue.pr.number);
|
|
21
|
+
if (prStatus === "MERGED") {
|
|
22
|
+
issue.status = "merged";
|
|
23
|
+
await stateManager.updateIssueStatus(issue.number, "merged");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
11
27
|
/**
|
|
12
28
|
* Color-code issue status
|
|
13
29
|
*/
|
|
@@ -220,7 +236,7 @@ export async function statusCommand(options = {}) {
|
|
|
220
236
|
return;
|
|
221
237
|
}
|
|
222
238
|
console.log(chalk.green("Status: Initialized"));
|
|
223
|
-
console.log(chalk.gray(`
|
|
239
|
+
console.log(chalk.gray(`Skills version: ${manifest.version}`));
|
|
224
240
|
console.log(chalk.gray(`Stack: ${manifest.stack}`));
|
|
225
241
|
console.log(chalk.gray(`Installed: ${manifest.installedAt}`));
|
|
226
242
|
if (manifest.updatedAt) {
|
|
@@ -250,6 +266,7 @@ export async function statusCommand(options = {}) {
|
|
|
250
266
|
const allIssues = await stateManager.getAllIssueStates();
|
|
251
267
|
const issues = Object.values(allIssues);
|
|
252
268
|
if (issues.length > 0) {
|
|
269
|
+
await refreshMergedStatuses(stateManager, issues);
|
|
253
270
|
displayIssueSummary(issues);
|
|
254
271
|
}
|
|
255
272
|
}
|
|
@@ -279,6 +296,9 @@ async function displayIssueState(options) {
|
|
|
279
296
|
if (options.issue !== undefined) {
|
|
280
297
|
// Show single issue details
|
|
281
298
|
const issueState = await stateManager.getIssueState(options.issue);
|
|
299
|
+
if (issueState) {
|
|
300
|
+
await refreshMergedStatuses(stateManager, [issueState]);
|
|
301
|
+
}
|
|
282
302
|
if (options.json) {
|
|
283
303
|
console.log(JSON.stringify(issueState, null, 2));
|
|
284
304
|
}
|
|
@@ -294,6 +314,7 @@ async function displayIssueState(options) {
|
|
|
294
314
|
// Show all issues
|
|
295
315
|
const allIssues = await stateManager.getAllIssueStates();
|
|
296
316
|
const issues = Object.values(allIssues);
|
|
317
|
+
await refreshMergedStatuses(stateManager, issues);
|
|
297
318
|
if (options.json) {
|
|
298
319
|
console.log(JSON.stringify({ issues: allIssues }, null, 2));
|
|
299
320
|
}
|
|
@@ -93,9 +93,6 @@ export class MetricsWriter {
|
|
|
93
93
|
fs.renameSync(tempPath, this.metricsPath);
|
|
94
94
|
// Update cache
|
|
95
95
|
this.cachedMetrics = metrics;
|
|
96
|
-
if (this.verbose) {
|
|
97
|
-
console.log(`📊 Metrics saved: ${this.metricsPath}`);
|
|
98
|
-
}
|
|
99
96
|
}
|
|
100
97
|
catch (error) {
|
|
101
98
|
// Clean up temp file on error
|
|
@@ -95,9 +95,6 @@ export class StateManager {
|
|
|
95
95
|
fs.renameSync(tempPath, this.statePath);
|
|
96
96
|
// Update cache
|
|
97
97
|
this.cachedState = state;
|
|
98
|
-
if (this.verbose) {
|
|
99
|
-
console.log(`📊 State saved: ${this.statePath}`);
|
|
100
|
-
}
|
|
101
98
|
}
|
|
102
99
|
catch (error) {
|
|
103
100
|
// Clean up temp file on error
|