sequant 2.8.0 → 2.9.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 +9 -1
- package/dist/bin/cli.js +2 -1
- package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +1 -1
- package/dist/marketplace/external_plugins/sequant/README.md +2 -0
- package/dist/marketplace/external_plugins/sequant/hooks/post-tool.sh +18 -3
- package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +330 -57
- package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +96 -15
- package/dist/marketplace/external_plugins/sequant/skills/assess/references/predicted-collision-detection.md +9 -6
- package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +1 -1
- package/dist/marketplace/external_plugins/sequant/skills/reflect/SKILL.md +27 -13
- package/dist/marketplace/external_plugins/sequant/skills/reflect/references/documentation-tiers.md +80 -68
- package/dist/marketplace/external_plugins/sequant/skills/reflect/references/phase-reflection.md +31 -15
- package/dist/marketplace/external_plugins/sequant/skills/release/SKILL.md +10 -2
- package/dist/marketplace/external_plugins/sequant/skills/spec/references/verification-criteria.md +1 -1
- package/dist/src/commands/logs.js +6 -1
- package/dist/src/commands/run-display.d.ts +20 -0
- package/dist/src/commands/run-display.js +80 -1
- package/dist/src/commands/stats.js +47 -0
- package/dist/src/lib/assess-collision-detect.d.ts +19 -2
- package/dist/src/lib/assess-collision-detect.js +68 -4
- package/dist/src/lib/cli-ui/run-renderer.js +17 -9
- package/dist/src/lib/errors.d.ts +6 -0
- package/dist/src/lib/errors.js +9 -2
- package/dist/src/lib/manifest.js +1 -17
- package/dist/src/lib/version-check.js +1 -5
- package/dist/src/lib/workflow/batch-executor.d.ts +13 -0
- package/dist/src/lib/workflow/batch-executor.js +81 -18
- package/dist/src/lib/workflow/chain-preflight.d.ts +89 -0
- package/dist/src/lib/workflow/chain-preflight.js +199 -0
- package/dist/src/lib/workflow/chain-resume.d.ts +116 -0
- package/dist/src/lib/workflow/chain-resume.js +166 -0
- package/dist/src/lib/workflow/dependency-markers.d.ts +29 -0
- package/dist/src/lib/workflow/dependency-markers.js +79 -0
- package/dist/src/lib/workflow/drivers/claude-code.d.ts +7 -0
- package/dist/src/lib/workflow/drivers/claude-code.js +30 -6
- package/dist/src/lib/workflow/error-classifier.d.ts +9 -2
- package/dist/src/lib/workflow/error-classifier.js +14 -1
- package/dist/src/lib/workflow/log-writer.js +6 -8
- package/dist/src/lib/workflow/metrics-schema.d.ts +39 -0
- package/dist/src/lib/workflow/metrics-schema.js +16 -0
- package/dist/src/lib/workflow/metrics-writer.d.ts +2 -1
- package/dist/src/lib/workflow/phase-executor.d.ts +32 -0
- package/dist/src/lib/workflow/phase-executor.js +77 -5
- package/dist/src/lib/workflow/run-log-schema.d.ts +23 -0
- package/dist/src/lib/workflow/run-log-schema.js +45 -1
- package/dist/src/lib/workflow/run-orchestrator.d.ts +14 -0
- package/dist/src/lib/workflow/run-orchestrator.js +291 -30
- package/dist/src/lib/workflow/status-derivation.d.ts +30 -0
- package/dist/src/lib/workflow/status-derivation.js +27 -0
- package/dist/src/lib/workflow/types.d.ts +23 -0
- package/dist/src/lib/workflow/worktree-manager.d.ts +43 -1
- package/dist/src/lib/workflow/worktree-manager.js +103 -33
- package/dist/src/mcp/tools/run.d.ts +2 -0
- package/dist/src/mcp/tools/run.js +2 -0
- package/package.json +2 -4
- package/templates/hooks/post-tool.sh +18 -3
- package/templates/hooks/pre-tool.sh +330 -57
- package/templates/scripts/cleanup-worktree.sh +103 -14
- package/templates/skills/assess/SKILL.md +96 -15
- package/templates/skills/assess/references/predicted-collision-detection.md +9 -6
- package/templates/skills/fullsolve/SKILL.md +1 -1
- package/templates/skills/reflect/SKILL.md +27 -13
- package/templates/skills/reflect/references/documentation-tiers.md +80 -68
- package/templates/skills/reflect/references/phase-reflection.md +31 -15
- package/templates/skills/release/SKILL.md +10 -2
- package/templates/skills/spec/references/verification-criteria.md +1 -1
|
@@ -365,21 +365,14 @@ export async function ensureWorktree(issueNumber, title, verbose, packageManager
|
|
|
365
365
|
if (verbose) {
|
|
366
366
|
console.log(chalk.gray(` Rebasing existing worktree onto chain base (${baseBranch})...`));
|
|
367
367
|
}
|
|
368
|
-
const
|
|
369
|
-
if (
|
|
370
|
-
|
|
371
|
-
// Check if it's a conflict
|
|
372
|
-
if (rebaseError.includes("CONFLICT") ||
|
|
373
|
-
rebaseError.includes("could not apply")) {
|
|
368
|
+
const rebase = rebaseOntoLocalBranch(existingPath, baseBranch, verbose);
|
|
369
|
+
if (!rebase.success) {
|
|
370
|
+
if (rebase.conflict) {
|
|
374
371
|
console.log(chalk.yellow(` ! Rebase conflict detected. Aborting rebase and keeping original branch state.`));
|
|
375
372
|
console.log(chalk.yellow(` ℹ️ Branch ${branch} is not properly chained. Manual rebase may be required.`));
|
|
376
|
-
// Abort the rebase to restore branch state
|
|
377
|
-
spawnSync("git", ["-C", existingPath, "rebase", "--abort"], {
|
|
378
|
-
stdio: "pipe",
|
|
379
|
-
});
|
|
380
373
|
}
|
|
381
374
|
else {
|
|
382
|
-
console.log(chalk.yellow(` ! Rebase failed: ${
|
|
375
|
+
console.log(chalk.yellow(` ! Rebase failed: ${rebase.error ?? ""}`));
|
|
383
376
|
console.log(chalk.yellow(` ℹ️ Continuing with branch in its original state.`));
|
|
384
377
|
}
|
|
385
378
|
return {
|
|
@@ -390,9 +383,6 @@ export async function ensureWorktree(issueNumber, title, verbose, packageManager
|
|
|
390
383
|
rebased: false,
|
|
391
384
|
};
|
|
392
385
|
}
|
|
393
|
-
if (verbose) {
|
|
394
|
-
console.log(chalk.green(` ✔ Existing worktree rebased onto ${baseBranch}`));
|
|
395
|
-
}
|
|
396
386
|
return {
|
|
397
387
|
issue: issueNumber,
|
|
398
388
|
path: existingPath,
|
|
@@ -700,9 +690,10 @@ passed QA in chain mode. It serves as a recovery point if later issues fail.`;
|
|
|
700
690
|
const commitResult = spawnSync("git", ["-C", worktreePath, "commit", "-m", commitMessage], { stdio: "pipe" });
|
|
701
691
|
if (commitResult.status !== 0) {
|
|
702
692
|
const error = commitResult.stderr.toString();
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
693
|
+
// #760: chain resume rebases the next link onto this checkpoint, so a
|
|
694
|
+
// commit failure is surfaced regardless of --verbose (the call site adds
|
|
695
|
+
// the resume-impact warning; here we show the underlying git error).
|
|
696
|
+
console.log(chalk.yellow(` ! Could not create checkpoint commit for #${issueNumber}: ${error.trim()}`));
|
|
706
697
|
return false;
|
|
707
698
|
}
|
|
708
699
|
console.log(chalk.green(` 📌 Checkpoint commit created for #${issueNumber}`));
|
|
@@ -766,6 +757,39 @@ export function reinstallIfLockfileChanged(worktreePath, packageManager, verbose
|
|
|
766
757
|
console.log(chalk.green(` ✔ Dependencies reinstalled`));
|
|
767
758
|
return true;
|
|
768
759
|
}
|
|
760
|
+
/**
|
|
761
|
+
* Rebase a worktree's branch onto a *local* branch ref (e.g. a chain
|
|
762
|
+
* predecessor's feature branch), NOT origin/main. Used to chain a successor
|
|
763
|
+
* onto its predecessor's committed work at execution time (#748), and to
|
|
764
|
+
* re-chain a pre-existing worktree onto its chain base.
|
|
765
|
+
*
|
|
766
|
+
* On conflict, aborts the rebase to restore the original branch state and
|
|
767
|
+
* returns `{ success: false, conflict: true }` so callers can warn rather than
|
|
768
|
+
* silently treat a broken link as healthy.
|
|
769
|
+
*/
|
|
770
|
+
export function rebaseOntoLocalBranch(worktreePath, ontoBranch, verbose = false) {
|
|
771
|
+
const rebaseResult = spawnSync("git", ["-C", worktreePath, "rebase", ontoBranch], { stdio: "pipe" });
|
|
772
|
+
if (rebaseResult.status === 0) {
|
|
773
|
+
if (verbose) {
|
|
774
|
+
console.log(chalk.green(` ✔ Rebased worktree onto ${ontoBranch}`));
|
|
775
|
+
}
|
|
776
|
+
return { performed: true, success: true, conflict: false };
|
|
777
|
+
}
|
|
778
|
+
const rebaseError = rebaseResult.stderr.toString();
|
|
779
|
+
const isConflict = rebaseError.includes("CONFLICT") || rebaseError.includes("could not apply");
|
|
780
|
+
if (isConflict) {
|
|
781
|
+
// Abort to restore the original branch state.
|
|
782
|
+
spawnSync("git", ["-C", worktreePath, "rebase", "--abort"], {
|
|
783
|
+
stdio: "pipe",
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
return {
|
|
787
|
+
performed: true,
|
|
788
|
+
success: false,
|
|
789
|
+
conflict: isConflict,
|
|
790
|
+
error: rebaseError.trim(),
|
|
791
|
+
};
|
|
792
|
+
}
|
|
769
793
|
/**
|
|
770
794
|
* Rebase the worktree branch onto the base branch before PR creation.
|
|
771
795
|
* This ensures the branch is up-to-date and prevents lockfile drift.
|
|
@@ -832,6 +856,63 @@ export function rebaseBeforePR(worktreePath, issueNumber, packageManager, verbos
|
|
|
832
856
|
reinstalled,
|
|
833
857
|
};
|
|
834
858
|
}
|
|
859
|
+
/**
|
|
860
|
+
* Render a non-A+ QA verdict as a PR-body note, or `null` for an A+ / absent
|
|
861
|
+
* verdict.
|
|
862
|
+
*
|
|
863
|
+
* Only stopping states that still break to a PR reach this path
|
|
864
|
+
* (`AC_MET_BUT_NOT_A_PLUS`, `NEEDS_VERIFICATION`); `READY_FOR_MERGE` and an
|
|
865
|
+
* absent verdict produce no note. (#749)
|
|
866
|
+
*/
|
|
867
|
+
function qaVerdictNote(verdict) {
|
|
868
|
+
if (!verdict || verdict === "READY_FOR_MERGE")
|
|
869
|
+
return null;
|
|
870
|
+
switch (verdict) {
|
|
871
|
+
case "AC_MET_BUT_NOT_A_PLUS":
|
|
872
|
+
return `> **QA verdict: AC_MET_BUT_NOT_A_PLUS** — acceptance criteria met, but not A+. The run broke to this PR for review rather than entering the quality loop; see the QA review comment for improvement notes.`;
|
|
873
|
+
case "NEEDS_VERIFICATION":
|
|
874
|
+
return `> **QA verdict: NEEDS_VERIFICATION** — acceptance criteria met, pending external verification (e.g. CI or a manual test). See the QA review comment.`;
|
|
875
|
+
default:
|
|
876
|
+
return `> **QA verdict: ${verdict}** — see the QA review comment.`;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Build the automated PR body for `sequant run`.
|
|
881
|
+
*
|
|
882
|
+
* Pure and I/O-free, kept separate from {@link createPR} (which shells out to
|
|
883
|
+
* `gh`) so the body — including the non-A+ QA note (#749) — is unit-testable
|
|
884
|
+
* directly.
|
|
885
|
+
*
|
|
886
|
+
* @param issueNumber Issue the PR closes
|
|
887
|
+
* @param opts.stackManifest Appended before the trailer under --stacked (#605)
|
|
888
|
+
* @param opts.qaVerdict QA verdict for the run; a non-A+ stopping state
|
|
889
|
+
* (anything other than `READY_FOR_MERGE`) surfaces a note so a human reviewer
|
|
890
|
+
* sees why the run broke to PR without reaching A+ (#749).
|
|
891
|
+
* @internal Exported for testing
|
|
892
|
+
*/
|
|
893
|
+
export function buildAutomatedPRBody(issueNumber, opts) {
|
|
894
|
+
const bodyLines = [
|
|
895
|
+
`## Summary`,
|
|
896
|
+
``,
|
|
897
|
+
`Automated PR for issue #${issueNumber}.`,
|
|
898
|
+
``,
|
|
899
|
+
`Fixes #${issueNumber}`,
|
|
900
|
+
``,
|
|
901
|
+
];
|
|
902
|
+
// #749: surface a non-A+ QA verdict in the PR body (not just the run log) so
|
|
903
|
+
// a reviewer sees why the run broke to PR rather than reaching A+.
|
|
904
|
+
const note = qaVerdictNote(opts?.qaVerdict);
|
|
905
|
+
if (note) {
|
|
906
|
+
bodyLines.push(note, ``);
|
|
907
|
+
}
|
|
908
|
+
// #605 AC-4: emit stack manifest before the trailer so reviewers see the
|
|
909
|
+
// chain at the top of the body. Manifest is only set under --stacked.
|
|
910
|
+
if (opts?.stackManifest) {
|
|
911
|
+
bodyLines.push(opts.stackManifest, ``);
|
|
912
|
+
}
|
|
913
|
+
bodyLines.push(`---`, `🤖 Generated by \`sequant run\``);
|
|
914
|
+
return bodyLines.join("\n");
|
|
915
|
+
}
|
|
835
916
|
/**
|
|
836
917
|
* Push branch and create a PR after successful QA.
|
|
837
918
|
*
|
|
@@ -850,7 +931,7 @@ export function rebaseBeforePR(worktreePath, issueNumber, packageManager, verbos
|
|
|
850
931
|
* @returns PRCreationResult with PR info or error
|
|
851
932
|
* @internal Exported for testing
|
|
852
933
|
*/
|
|
853
|
-
export function createPR(worktreePath, issueNumber, issueTitle, branch, verbose, labels, stackOptions) {
|
|
934
|
+
export function createPR(worktreePath, issueNumber, issueTitle, branch, verbose, labels, stackOptions, qaVerdict) {
|
|
854
935
|
const github = new GitHubProvider();
|
|
855
936
|
// Step 1: Check for existing PR on this branch
|
|
856
937
|
const existingPRInfo = github.viewPRByBranchSync(branch, worktreePath);
|
|
@@ -886,21 +967,10 @@ export function createPR(worktreePath, issueNumber, issueTitle, branch, verbose,
|
|
|
886
967
|
const isBug = labels?.some((l) => /^bug/i.test(l));
|
|
887
968
|
const prefix = isBug ? "fix" : "feat";
|
|
888
969
|
const prTitle = `${prefix}(#${issueNumber}): ${issueTitle}`;
|
|
889
|
-
const
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
``,
|
|
894
|
-
`Fixes #${issueNumber}`,
|
|
895
|
-
``,
|
|
896
|
-
];
|
|
897
|
-
// #605 AC-4: emit stack manifest before the trailer so reviewers see the
|
|
898
|
-
// chain at the top of the body. Manifest is only set under --stacked.
|
|
899
|
-
if (stackOptions?.stackManifest) {
|
|
900
|
-
bodyLines.push(stackOptions.stackManifest, ``);
|
|
901
|
-
}
|
|
902
|
-
bodyLines.push(`---`, `🤖 Generated by \`sequant run\``);
|
|
903
|
-
const prBody = bodyLines.join("\n");
|
|
970
|
+
const prBody = buildAutomatedPRBody(issueNumber, {
|
|
971
|
+
stackManifest: stackOptions?.stackManifest,
|
|
972
|
+
qaVerdict,
|
|
973
|
+
});
|
|
904
974
|
const prResult = github.createPRCliSync(prTitle, prBody, branch, worktreePath, stackOptions?.prBase);
|
|
905
975
|
if (prResult.exitCode !== 0) {
|
|
906
976
|
const prError = prResult.stderr.trim() || "Unknown error";
|
|
@@ -182,6 +182,7 @@ export function buildStructuredResponse(runLog, rawOutput, overallStatus, exitCo
|
|
|
182
182
|
total: runLog.summary.totalIssues,
|
|
183
183
|
passed: runLog.summary.passed,
|
|
184
184
|
failed: runLog.summary.failed,
|
|
185
|
+
partial: runLog.summary.partial ?? 0,
|
|
185
186
|
durationSeconds: runLog.summary.totalDurationSeconds,
|
|
186
187
|
},
|
|
187
188
|
phases: phasesRan || runLog.config.phases.join(","),
|
|
@@ -232,6 +233,7 @@ function buildFallbackResponse(stdout, issueNumbers, overallStatus, phases, exit
|
|
|
232
233
|
total: issueNumbers.length,
|
|
233
234
|
passed: overallStatus === "success" ? issueNumbers.length : 0,
|
|
234
235
|
failed: overallStatus === "failure" ? issueNumbers.length : 0,
|
|
236
|
+
partial: 0,
|
|
235
237
|
durationSeconds: 0,
|
|
236
238
|
},
|
|
237
239
|
phases,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sequant",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.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": {
|
|
@@ -97,7 +97,6 @@
|
|
|
97
97
|
"cli-table3": "^0.6.5",
|
|
98
98
|
"commander": "^14.0.3",
|
|
99
99
|
"diff": "^9.0.0",
|
|
100
|
-
"gradient-string": "^3.0.0",
|
|
101
100
|
"hono": "^4.12.1",
|
|
102
101
|
"ink": "^7.0.1",
|
|
103
102
|
"inquirer": "^14.0.1",
|
|
@@ -112,9 +111,8 @@
|
|
|
112
111
|
},
|
|
113
112
|
"devDependencies": {
|
|
114
113
|
"@eslint/js": "^10.0.1",
|
|
115
|
-
"@types/gradient-string": "^1.1.6",
|
|
116
114
|
"@types/inquirer": "^9.0.7",
|
|
117
|
-
"@types/node": "^
|
|
115
|
+
"@types/node": "^26.0.1",
|
|
118
116
|
"@types/react": "^19.2.14",
|
|
119
117
|
"@typescript-eslint/eslint-plugin": "^8.58.0",
|
|
120
118
|
"@typescript-eslint/parser": "^8.58.0",
|
|
@@ -51,13 +51,28 @@ fi
|
|
|
51
51
|
|
|
52
52
|
_TMPDIR="${TMPDIR:-/tmp}"
|
|
53
53
|
|
|
54
|
-
#
|
|
55
|
-
|
|
54
|
+
# Log sink (#763 AC-5c: blocked-command history must survive to be a useful
|
|
55
|
+
# regression corpus, so $TMPDIR — which macOS purges — is a last resort only).
|
|
56
|
+
#
|
|
57
|
+
# This block MUST stay identical to the one in pre-tool.sh. The two hooks
|
|
58
|
+
# write the *same* claude-timing.log (START there, END here) and the same
|
|
59
|
+
# claude-quality.log, so any divergence silently splits every START/END pair
|
|
60
|
+
# across two files.
|
|
61
|
+
#
|
|
62
|
+
# Every candidate is absolute and lives outside the repo. A repo-local or
|
|
63
|
+
# relative path is wrong three times over: it resolves against the hook's cwd
|
|
64
|
+
# (which Claude Code does not pin), it yields one sink per directory instead
|
|
65
|
+
# of the single corpus AC-5 asks for, and — worst — creating it inside a repo
|
|
66
|
+
# makes `git status --porcelain` non-empty, which silently defeats pre-tool.sh's
|
|
67
|
+
# no-changes guard that reads exactly that output.
|
|
68
|
+
if [[ -n "${CLAUDE_PLUGIN_DATA:-}" ]]; then
|
|
56
69
|
_LOG_DIR="${CLAUDE_PLUGIN_DATA}/logs"
|
|
57
|
-
|
|
70
|
+
elif [[ -n "${HOME:-}" ]]; then
|
|
71
|
+
_LOG_DIR="${HOME}/.sequant/logs"
|
|
58
72
|
else
|
|
59
73
|
_LOG_DIR="${_TMPDIR}"
|
|
60
74
|
fi
|
|
75
|
+
mkdir -p "$_LOG_DIR" 2>/dev/null || _LOG_DIR="${_TMPDIR}"
|
|
61
76
|
|
|
62
77
|
TIMING_LOG="${_LOG_DIR}/claude-timing.log"
|
|
63
78
|
QUALITY_LOG="${_LOG_DIR}/claude-quality.log"
|