sequant 1.14.1 → 1.15.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sequant",
3
3
  "description": "Structured workflow system for Claude Code - GitHub issue resolution with spec, exec, test, and QA phases",
4
- "version": "1.14.1",
4
+ "version": "1.15.0",
5
5
  "author": {
6
6
  "name": "admarble",
7
7
  "email": "github@admarble.com"
package/README.md CHANGED
@@ -223,6 +223,7 @@ See [Customization Guide](docs/guides/customization.md) for all options.
223
223
  - [Complete Workflow](docs/guides/workflow.md) — Full workflow including post-QA patterns
224
224
  - [Getting Started](docs/getting-started/installation.md)
225
225
  - [What We've Built](docs/internal/what-weve-built.md) — Comprehensive project overview
226
+ - [What Is Sequant](docs/concepts/what-is-sequant.md) — Elevator pitch, pipeline diagram, architecture
226
227
  - [Workflow Concepts](docs/concepts/workflow-phases.md)
227
228
  - [Run Command](docs/reference/run-command.md)
228
229
  - [Git Workflows](docs/guides/git-workflows.md)
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
@@ -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
  */
@@ -1575,7 +1606,7 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
1575
1606
  }
1576
1607
  const specStartTime = new Date();
1577
1608
  // Note: spec runs in main repo (not worktree) for planning
1578
- const specResult = await executePhase(issueNumber, "spec", config, sessionId, worktreePath, // Will be ignored for spec (non-isolated phase)
1609
+ const specResult = await executePhaseWithRetry(issueNumber, "spec", config, sessionId, worktreePath, // Will be ignored for spec (non-isolated phase)
1579
1610
  shutdownManager, specSpinner);
1580
1611
  const specEndTime = new Date();
1581
1612
  if (specResult.sessionId) {
@@ -1718,7 +1749,7 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
1718
1749
  }
1719
1750
  }
1720
1751
  const phaseStartTime = new Date();
1721
- const result = await executePhase(issueNumber, phase, config, sessionId, worktreePath, shutdownManager, phaseSpinner);
1752
+ const result = await executePhaseWithRetry(issueNumber, phase, config, sessionId, worktreePath, shutdownManager, phaseSpinner);
1722
1753
  const phaseEndTime = new Date();
1723
1754
  // Capture session ID for subsequent phases
1724
1755
  if (result.sessionId) {
@@ -1778,7 +1809,7 @@ async function runIssueWithLogging(issueNumber, config, logWriter, stateManager,
1778
1809
  iteration,
1779
1810
  });
1780
1811
  loopSpinner.start();
1781
- const loopResult = await executePhase(issueNumber, "loop", config, sessionId, worktreePath, shutdownManager, loopSpinner);
1812
+ const loopResult = await executePhaseWithRetry(issueNumber, "loop", config, sessionId, worktreePath, shutdownManager, loopSpinner);
1782
1813
  phaseResults.push(loopResult);
1783
1814
  if (loopResult.sessionId) {
1784
1815
  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(`Installed version: ${manifest.version}`));
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sequant",
3
- "version": "1.14.1",
3
+ "version": "1.15.0",
4
4
  "description": "Quantize your development workflow - Sequential AI phases with quality gates",
5
5
  "type": "module",
6
6
  "bin": {