sequant 2.6.2 → 2.8.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 +13 -1
- package/dist/bin/cli.d.ts +1 -1
- package/dist/bin/cli.js +11 -1
- package/dist/bin/preflight.d.ts +21 -0
- package/dist/bin/preflight.js +45 -0
- package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +1 -1
- package/dist/marketplace/external_plugins/sequant/skills/_shared/references/force-push.md +34 -0
- package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +24 -7
- package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +29 -0
- package/dist/marketplace/external_plugins/sequant/skills/loop/SKILL.md +100 -2
- package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +24 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/anti-pattern-detection.md +285 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/call-site-review.md +202 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/quality-gates.md +287 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/test-quality-checklist.md +272 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/testing-requirements.md +40 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/scripts/quality-checks.sh +95 -11
- package/dist/marketplace/external_plugins/sequant/skills/references/shared/framework-gotchas.md +186 -0
- package/dist/marketplace/external_plugins/sequant/skills/release/SKILL.md +661 -0
- package/dist/marketplace/external_plugins/sequant/skills/test/references/browser-testing-patterns.md +423 -0
- package/dist/marketplace/external_plugins/sequant/skills/upstream/SKILL.md +419 -0
- package/dist/src/commands/sync.d.ts +1 -0
- package/dist/src/commands/sync.js +56 -1
- package/dist/src/commands/update.js +7 -0
- package/dist/src/lib/errors.d.ts +85 -0
- package/dist/src/lib/errors.js +111 -0
- package/dist/src/lib/version-check.d.ts +19 -0
- package/dist/src/lib/version-check.js +44 -0
- package/dist/src/lib/workflow/batch-executor.js +61 -6
- package/dist/src/lib/workflow/drivers/agent-driver.d.ts +17 -0
- package/dist/src/lib/workflow/drivers/claude-code.d.ts +22 -0
- package/dist/src/lib/workflow/drivers/claude-code.js +111 -7
- package/dist/src/lib/workflow/log-writer.d.ts +1 -1
- package/dist/src/lib/workflow/phase-executor.d.ts +18 -0
- package/dist/src/lib/workflow/phase-executor.js +76 -14
- package/dist/src/lib/workflow/run-log-schema.d.ts +3 -0
- package/dist/src/lib/workflow/run-log-schema.js +7 -0
- package/dist/src/lib/workflow/state-manager.d.ts +1 -0
- package/dist/src/lib/workflow/state-manager.js +6 -0
- package/dist/src/lib/workflow/state-schema.d.ts +3 -0
- package/dist/src/lib/workflow/state-schema.js +7 -0
- package/dist/src/lib/workflow/types.d.ts +17 -0
- package/dist/src/ui/tui/theme.d.ts +18 -4
- package/dist/src/ui/tui/theme.js +18 -4
- package/package.json +4 -3
- package/templates/skills/_shared/references/force-push.md +34 -0
- package/templates/skills/assess/SKILL.md +24 -7
- package/templates/skills/exec/SKILL.md +29 -0
- package/templates/skills/loop/SKILL.md +100 -2
- package/templates/skills/qa/SKILL.md +24 -0
- package/templates/skills/qa/references/anti-pattern-detection.md +285 -0
- package/templates/skills/qa/references/call-site-review.md +202 -0
- package/templates/skills/qa/references/quality-gates.md +287 -0
- package/templates/skills/qa/references/test-quality-checklist.md +272 -0
- package/templates/skills/qa/references/testing-requirements.md +40 -0
- package/templates/skills/qa/scripts/quality-checks.sh +95 -11
- package/templates/skills/references/shared/framework-gotchas.md +186 -0
- package/templates/skills/release/SKILL.md +661 -0
- package/templates/skills/test/references/browser-testing-patterns.md +423 -0
- package/templates/skills/upstream/SKILL.md +419 -0
|
@@ -12,7 +12,7 @@ import { execSync, execFileSync } from "child_process";
|
|
|
12
12
|
import { readAgentsMd } from "../agents-md.js";
|
|
13
13
|
import { getDriver } from "./drivers/index.js";
|
|
14
14
|
import { classifyError } from "./error-classifier.js";
|
|
15
|
-
import { ApiError } from "../errors.js";
|
|
15
|
+
import { ApiError, BillingError } from "../errors.js";
|
|
16
16
|
import { phaseRegistry } from "./phase-registry.js";
|
|
17
17
|
import { bracketedConsoleLog } from "./notice.js";
|
|
18
18
|
/**
|
|
@@ -408,6 +408,43 @@ export function mapAgentSuccessToPhaseResult(phase, agentResult, durationSeconds
|
|
|
408
408
|
...tails,
|
|
409
409
|
};
|
|
410
410
|
}
|
|
411
|
+
/**
|
|
412
|
+
* Map a failed driver result to a `PhaseResult`.
|
|
413
|
+
*
|
|
414
|
+
* Symmetric to {@link mapAgentSuccessToPhaseResult}; extracted so the
|
|
415
|
+
* failure-path mapping (notably the #739 capped/output gating) is unit-testable
|
|
416
|
+
* without spawning a driver.
|
|
417
|
+
*
|
|
418
|
+
* `output` is propagated **only** for a capped phase (#739): a capped result is
|
|
419
|
+
* incomplete-but-not-hard-failed, so its partial work must survive downstream.
|
|
420
|
+
* A genuine (non-capped) failure keeps the historical behaviour of dropping
|
|
421
|
+
* `output`, leaving the `/loop` fix-context (`formatFailureContext`) unchanged.
|
|
422
|
+
*
|
|
423
|
+
* @internal Exported for testing only
|
|
424
|
+
*/
|
|
425
|
+
export function mapAgentFailureToPhaseResult(phase, agentResult, durationSeconds) {
|
|
426
|
+
return {
|
|
427
|
+
phase,
|
|
428
|
+
success: false,
|
|
429
|
+
durationSeconds,
|
|
430
|
+
error: agentResult.error,
|
|
431
|
+
// Propagate the driver's typed cause (#732) so the retry logic can prefer
|
|
432
|
+
// it over stderr-regex classification and gate the MCP fallback.
|
|
433
|
+
structuredError: agentResult.structuredError,
|
|
434
|
+
// Propagate the turn-cap flag and the partial output (#739). On the failure
|
|
435
|
+
// path `output` was previously dropped entirely — for a capped phase the
|
|
436
|
+
// partial work is usable and must be preserved, mirroring the driver/skill
|
|
437
|
+
// slice from #733. Gating `output` on `capped` keeps non-capped failures
|
|
438
|
+
// byte-for-byte identical to pre-#739 behaviour.
|
|
439
|
+
capped: agentResult.capped,
|
|
440
|
+
output: agentResult.capped ? agentResult.output : undefined,
|
|
441
|
+
sessionId: agentResult.sessionId,
|
|
442
|
+
resumeHandle: agentResult.resumeHandle,
|
|
443
|
+
stderrTail: agentResult.stderrTail,
|
|
444
|
+
stdoutTail: agentResult.stdoutTail,
|
|
445
|
+
exitCode: agentResult.exitCode,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
411
448
|
/**
|
|
412
449
|
* Get the prompt for a phase with the issue number substituted.
|
|
413
450
|
* Selects self-contained prompts for non-Claude agents.
|
|
@@ -642,17 +679,7 @@ async function executePhase(issueNumber, phase, config, resumeHandle, worktreePa
|
|
|
642
679
|
if (agentResult.success) {
|
|
643
680
|
return mapAgentSuccessToPhaseResult(phase, agentResult, durationSeconds, cwd);
|
|
644
681
|
}
|
|
645
|
-
return
|
|
646
|
-
phase,
|
|
647
|
-
success: false,
|
|
648
|
-
durationSeconds,
|
|
649
|
-
error: agentResult.error,
|
|
650
|
-
sessionId: agentResult.sessionId,
|
|
651
|
-
resumeHandle: agentResult.resumeHandle,
|
|
652
|
-
stderrTail: agentResult.stderrTail,
|
|
653
|
-
stdoutTail: agentResult.stdoutTail,
|
|
654
|
-
exitCode: agentResult.exitCode,
|
|
655
|
-
};
|
|
682
|
+
return mapAgentFailureToPhaseResult(phase, agentResult, durationSeconds);
|
|
656
683
|
}
|
|
657
684
|
/**
|
|
658
685
|
* Execute a phase with automatic retry for cold-start failures and MCP fallback.
|
|
@@ -693,6 +720,14 @@ delayFn = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) {
|
|
|
693
720
|
if (lastResult.success) {
|
|
694
721
|
return lastResult;
|
|
695
722
|
}
|
|
723
|
+
// Turn-capped phase (#739): incomplete-but-not-hard-failed. A retry cannot
|
|
724
|
+
// un-cap a turn limit, so short-circuit before any fallback — same rationale
|
|
725
|
+
// as the billing skip (#732), but capped must skip *all* retries (incl.
|
|
726
|
+
// cold-start), so an explicit early return is required, not just a guard
|
|
727
|
+
// flag at the MCP gate.
|
|
728
|
+
if (lastResult.capped) {
|
|
729
|
+
return lastResult;
|
|
730
|
+
}
|
|
696
731
|
}
|
|
697
732
|
else {
|
|
698
733
|
// Phase 1: Cold-start retry attempts (with MCP enabled if configured)
|
|
@@ -703,11 +738,23 @@ delayFn = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) {
|
|
|
703
738
|
if (lastResult.success) {
|
|
704
739
|
return lastResult;
|
|
705
740
|
}
|
|
741
|
+
// Turn-capped phase (#739): short-circuit before cold-start retries, the
|
|
742
|
+
// MCP fallback, and the spec-extra retry — a retry cannot un-cap a turn
|
|
743
|
+
// limit. The early return here (rather than a guard at the MCP gate alone)
|
|
744
|
+
// is what skips the cold-start re-spawns, unlike the billing case which
|
|
745
|
+
// still cold-start-retries in the <60s window.
|
|
746
|
+
if (lastResult.capped) {
|
|
747
|
+
return lastResult;
|
|
748
|
+
}
|
|
706
749
|
// Genuine failure (took long enough to be real work) → skip cold-start retries.
|
|
707
750
|
// Use error classification (AC-9): if the error is retryable (e.g., API
|
|
708
751
|
// rate limit, transient 503), allow one more attempt even for genuine failures.
|
|
709
752
|
if (duration >= COLD_START_THRESHOLD_SECONDS) {
|
|
710
|
-
|
|
753
|
+
// Prefer the driver's structured cause (#732) — it reflects the real
|
|
754
|
+
// SDK rate-limit/billing signal — over stderr-regex classification,
|
|
755
|
+
// which only sees text and never the structured data.
|
|
756
|
+
const typedError = lastResult.structuredError ??
|
|
757
|
+
classifyError(lastResult.stderrTail ?? [], lastResult.exitCode);
|
|
711
758
|
if (typedError.isRetryable && attempt < COLD_START_MAX_RETRIES) {
|
|
712
759
|
if (config.verbose) {
|
|
713
760
|
const label = typedError instanceof ApiError
|
|
@@ -735,7 +782,22 @@ delayFn = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) {
|
|
|
735
782
|
// Phase 2: MCP fallback - if MCP is enabled and we're still failing, try without MCP
|
|
736
783
|
// This handles npx-based MCP servers that fail on first run due to cold-cache issues.
|
|
737
784
|
// Skip for `loop` phase — MCP is never the cause of loop failures (#488).
|
|
738
|
-
|
|
785
|
+
//
|
|
786
|
+
// Also skip when the failure is a billing/credits error (#732): a no-MCP
|
|
787
|
+
// retry cannot refill credits, so the misleading "retrying without MCP"
|
|
788
|
+
// noise (#592) would only mask the real cause. The accurate structured
|
|
789
|
+
// message (e.g. "Out of credits") is surfaced instead.
|
|
790
|
+
const failureIsBilling = lastResult.structuredError instanceof BillingError;
|
|
791
|
+
// Belt-and-suspenders (#739): the capped early-returns above already exit
|
|
792
|
+
// before reaching here, but gate the MCP fallback on `!failureIsCapped` too so
|
|
793
|
+
// intent is documented and future code paths can't accidentally re-spawn a
|
|
794
|
+
// capped phase without MCP.
|
|
795
|
+
const failureIsCapped = lastResult.capped === true;
|
|
796
|
+
if (config.mcp &&
|
|
797
|
+
!lastResult.success &&
|
|
798
|
+
!skipColdStartRetry &&
|
|
799
|
+
!failureIsBilling &&
|
|
800
|
+
!failureIsCapped) {
|
|
739
801
|
bracketedConsoleLog(spinner, chalk.yellow(`\n ! Phase failed with MCP enabled, retrying without MCP...`));
|
|
740
802
|
// Create config copy with MCP disabled
|
|
741
803
|
const configWithoutMcp = {
|
|
@@ -125,6 +125,7 @@ export declare const PhaseLogSchema: z.ZodObject<{
|
|
|
125
125
|
timeout: "timeout";
|
|
126
126
|
}>;
|
|
127
127
|
error: z.ZodOptional<z.ZodString>;
|
|
128
|
+
capped: z.ZodOptional<z.ZodBoolean>;
|
|
128
129
|
iterations: z.ZodOptional<z.ZodNumber>;
|
|
129
130
|
filesModified: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
130
131
|
testsRun: z.ZodOptional<z.ZodNumber>;
|
|
@@ -201,6 +202,7 @@ export declare const IssueLogSchema: z.ZodObject<{
|
|
|
201
202
|
timeout: "timeout";
|
|
202
203
|
}>;
|
|
203
204
|
error: z.ZodOptional<z.ZodString>;
|
|
205
|
+
capped: z.ZodOptional<z.ZodBoolean>;
|
|
204
206
|
iterations: z.ZodOptional<z.ZodNumber>;
|
|
205
207
|
filesModified: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
206
208
|
testsRun: z.ZodOptional<z.ZodNumber>;
|
|
@@ -318,6 +320,7 @@ export declare const RunLogSchema: z.ZodObject<{
|
|
|
318
320
|
timeout: "timeout";
|
|
319
321
|
}>;
|
|
320
322
|
error: z.ZodOptional<z.ZodString>;
|
|
323
|
+
capped: z.ZodOptional<z.ZodBoolean>;
|
|
321
324
|
iterations: z.ZodOptional<z.ZodNumber>;
|
|
322
325
|
filesModified: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
323
326
|
testsRun: z.ZodOptional<z.ZodNumber>;
|
|
@@ -130,6 +130,13 @@ export const PhaseLogSchema = z.object({
|
|
|
130
130
|
status: PhaseStatusSchema,
|
|
131
131
|
/** Error message if failed */
|
|
132
132
|
error: z.string().optional(),
|
|
133
|
+
/**
|
|
134
|
+
* Set when the phase hit its turn cap (`error_max_turns`) (#739). Distinguishes
|
|
135
|
+
* an incomplete-but-not-hard-failed phase (partial output preserved) from a
|
|
136
|
+
* genuine failure. Reuses the `"failure"` status — additive boolean rather than
|
|
137
|
+
* a new `PhaseStatus` enum value, to keep the persisted-log schema stable.
|
|
138
|
+
*/
|
|
139
|
+
capped: z.boolean().optional(),
|
|
133
140
|
/** Number of iterations (for loop phase) */
|
|
134
141
|
iterations: z.number().int().nonnegative().optional(),
|
|
135
142
|
/** Files modified during this phase */
|
|
@@ -116,6 +116,7 @@ export declare class StateManager {
|
|
|
116
116
|
updatePhaseStatus(issueNumber: number, phase: Phase, status: PhaseStatus, options?: {
|
|
117
117
|
error?: string;
|
|
118
118
|
iteration?: number;
|
|
119
|
+
capped?: boolean;
|
|
119
120
|
}): Promise<void>;
|
|
120
121
|
/**
|
|
121
122
|
* Update the overall issue status
|
|
@@ -300,6 +300,12 @@ export class StateManager {
|
|
|
300
300
|
if (options?.iteration !== undefined) {
|
|
301
301
|
phaseState.iteration = options.iteration;
|
|
302
302
|
}
|
|
303
|
+
// Persist the turn-cap marker (#739) so a halted-on-cap phase is
|
|
304
|
+
// distinguishable from a genuine failure in state, not just the run-log —
|
|
305
|
+
// this is what makes the "reversible later" resume path first-class.
|
|
306
|
+
if (options?.capped !== undefined) {
|
|
307
|
+
phaseState.capped = options.capped;
|
|
308
|
+
}
|
|
303
309
|
// Preserve startedAt if already set
|
|
304
310
|
const existingPhase = issueState.phases[phase];
|
|
305
311
|
if (existingPhase?.startedAt && status !== "pending") {
|
|
@@ -88,6 +88,7 @@ export declare const PhaseStateSchema: z.ZodObject<{
|
|
|
88
88
|
startedAt: z.ZodOptional<z.ZodString>;
|
|
89
89
|
completedAt: z.ZodOptional<z.ZodString>;
|
|
90
90
|
error: z.ZodOptional<z.ZodString>;
|
|
91
|
+
capped: z.ZodOptional<z.ZodBoolean>;
|
|
91
92
|
iteration: z.ZodOptional<z.ZodNumber>;
|
|
92
93
|
}, z.core.$strip>;
|
|
93
94
|
export type PhaseState = z.infer<typeof PhaseStateSchema>;
|
|
@@ -242,6 +243,7 @@ export declare const IssueStateSchema: z.ZodObject<{
|
|
|
242
243
|
startedAt: z.ZodOptional<z.ZodString>;
|
|
243
244
|
completedAt: z.ZodOptional<z.ZodString>;
|
|
244
245
|
error: z.ZodOptional<z.ZodString>;
|
|
246
|
+
capped: z.ZodOptional<z.ZodBoolean>;
|
|
245
247
|
iteration: z.ZodOptional<z.ZodNumber>;
|
|
246
248
|
}, z.core.$strip>>;
|
|
247
249
|
pr: z.ZodOptional<z.ZodObject<{
|
|
@@ -381,6 +383,7 @@ export declare const WorkflowStateSchema: z.ZodObject<{
|
|
|
381
383
|
startedAt: z.ZodOptional<z.ZodString>;
|
|
382
384
|
completedAt: z.ZodOptional<z.ZodString>;
|
|
383
385
|
error: z.ZodOptional<z.ZodString>;
|
|
386
|
+
capped: z.ZodOptional<z.ZodBoolean>;
|
|
384
387
|
iteration: z.ZodOptional<z.ZodNumber>;
|
|
385
388
|
}, z.core.$strip>>;
|
|
386
389
|
pr: z.ZodOptional<z.ZodObject<{
|
|
@@ -85,6 +85,13 @@ export const PhaseStateSchema = z.object({
|
|
|
85
85
|
completedAt: z.string().datetime().optional(),
|
|
86
86
|
/** Error message if phase failed */
|
|
87
87
|
error: z.string().optional(),
|
|
88
|
+
/**
|
|
89
|
+
* Set when the phase halted on its turn cap (`error_max_turns`) (#739).
|
|
90
|
+
* Distinguishes a recoverable cap (partial output preserved, resume to
|
|
91
|
+
* continue) from a genuine failure while keeping `status: "failed"`.
|
|
92
|
+
* Additive/optional — existing persisted state is unaffected.
|
|
93
|
+
*/
|
|
94
|
+
capped: z.boolean().optional(),
|
|
88
95
|
/** Number of loop iterations (for loop phase) */
|
|
89
96
|
iteration: z.number().int().nonnegative().optional(),
|
|
90
97
|
});
|
|
@@ -7,6 +7,7 @@ import type { LogWriter } from "./log-writer.js";
|
|
|
7
7
|
import type { StateManager } from "./state-manager.js";
|
|
8
8
|
import type { ShutdownManager } from "../shutdown.js";
|
|
9
9
|
import type { WorktreeInfo } from "./worktree-manager.js";
|
|
10
|
+
import type { SequantError } from "../errors.js";
|
|
10
11
|
export type { WorkflowEventEmitter, WorkflowEvents, WorkflowEventListener, IssueEventStatus, BaseEventPayload, RunEventPayload, PhaseStartedPayload, PhaseCompletedPayload, PhaseFailedPayload, IssueStatusChangedPayload, QaVerdictPayload, ProgressPayload, } from "./event-emitter.js";
|
|
11
12
|
/**
|
|
12
13
|
* Canonical Zod schema for all workflow phases.
|
|
@@ -160,6 +161,22 @@ export interface PhaseResult {
|
|
|
160
161
|
success: boolean;
|
|
161
162
|
durationSeconds?: number;
|
|
162
163
|
error?: string;
|
|
164
|
+
/**
|
|
165
|
+
* Typed error with structured cause data, propagated from the driver's
|
|
166
|
+
* `AgentPhaseResult.structuredError` (#732). When present, the retry logic
|
|
167
|
+
* prefers it over stderr-regex classification and uses its type to gate the
|
|
168
|
+
* MCP fallback (a `BillingError` skips the misleading retry — #592).
|
|
169
|
+
*/
|
|
170
|
+
structuredError?: SequantError;
|
|
171
|
+
/**
|
|
172
|
+
* Set when the phase hit its turn cap (`error_max_turns`), propagated from the
|
|
173
|
+
* driver's `AgentPhaseResult.capped` (#733/#739). A capped phase is
|
|
174
|
+
* incomplete-but-not-hard-failed: the partial work in `output` is preserved
|
|
175
|
+
* rather than discarded, and the retry logic treats it like the `BillingError`
|
|
176
|
+
* skip (#732) — a retry cannot un-cap a turn limit. Additive/optional, same
|
|
177
|
+
* shape as `structuredError?`; existing consumers are unaffected.
|
|
178
|
+
*/
|
|
179
|
+
capped?: boolean;
|
|
163
180
|
/** Captured output from the phase (used for parsing spec recommendations) */
|
|
164
181
|
output?: string;
|
|
165
182
|
/** Parsed QA verdict (only for qa phase) */
|
|
@@ -12,8 +12,10 @@ import type { IssueStatus, PhaseStatus } from "../../lib/workflow/run-state.js";
|
|
|
12
12
|
* - `BRAND_GREEN` is the accent/success green (`--color-accent`).
|
|
13
13
|
*
|
|
14
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)
|
|
15
|
+
* live/active phase and success — while issue-distinction (border rotation)
|
|
16
|
+
* and failure (red) stay on robust named ANSI colors. The muted gray for
|
|
17
|
+
* secondary chrome is a fixed mid-gray (`DIVIDER_COLOR`) chosen for WCAG
|
|
18
|
+
* contrast rather than the too-dim ANSI bright-black.
|
|
17
19
|
*
|
|
18
20
|
* Ink/chalk auto-downsamples hex to the nearest ANSI color on terminals
|
|
19
21
|
* without truecolor, and `NO_COLOR` still strips all color, so these degrade
|
|
@@ -24,8 +26,20 @@ export declare const BRAND_GREEN: "#10b981";
|
|
|
24
26
|
/** Border-color palette rotated by issue start order. */
|
|
25
27
|
export declare const BORDER_ROTATION: readonly ["cyan", "magenta", "blue", "yellow"];
|
|
26
28
|
export type BorderColor = (typeof BORDER_ROTATION)[number] | typeof BRAND_GREEN | typeof BRAND_ORANGE | "red" | "gray";
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Muted gray for secondary chrome: dividers, field labels (`branch`/`now`/
|
|
31
|
+
* `log`), the `phase N/total` + elapsed line, the `last activity` stamp, and
|
|
32
|
+
* the terminal status line.
|
|
33
|
+
*
|
|
34
|
+
* Lifted off ANSI `"gray"` (bright-black), which the brand's own dark theme
|
|
35
|
+
* renders at ~2.4:1 — below WCAG AA (4.5) and even the 3.0 large-text/UI floor.
|
|
36
|
+
* This fixed mid-gray clears AA (~4.9:1) on the dark theme while staying above
|
|
37
|
+
* 3.0 on light terminals (~3.4:1). It still degrades gracefully: chalk
|
|
38
|
+
* downsamples the hex to the nearest ANSI color on non-truecolor terminals, and
|
|
39
|
+
* `NO_COLOR` strips it entirely. The not-yet-started phase glyph stays on ANSI
|
|
40
|
+
* `"gray"` (see `phaseStatusColor`) so pending work remains the most recessed.
|
|
41
|
+
*/
|
|
42
|
+
export declare const DIVIDER_COLOR: "#8B8B9A";
|
|
29
43
|
/** Brand orange for the live/active phase spinner — the one element the eye
|
|
30
44
|
* tracks. Border rotation still distinguishes concurrent issues. */
|
|
31
45
|
export declare const ACTIVE_PHASE_COLOR: "#FF8012";
|
package/dist/src/ui/tui/theme.js
CHANGED
|
@@ -11,8 +11,10 @@
|
|
|
11
11
|
* - `BRAND_GREEN` is the accent/success green (`--color-accent`).
|
|
12
12
|
*
|
|
13
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)
|
|
14
|
+
* live/active phase and success — while issue-distinction (border rotation)
|
|
15
|
+
* and failure (red) stay on robust named ANSI colors. The muted gray for
|
|
16
|
+
* secondary chrome is a fixed mid-gray (`DIVIDER_COLOR`) chosen for WCAG
|
|
17
|
+
* contrast rather than the too-dim ANSI bright-black.
|
|
16
18
|
*
|
|
17
19
|
* Ink/chalk auto-downsamples hex to the nearest ANSI color on terminals
|
|
18
20
|
* without truecolor, and `NO_COLOR` still strips all color, so these degrade
|
|
@@ -22,8 +24,20 @@ export const BRAND_ORANGE = "#FF8012";
|
|
|
22
24
|
export const BRAND_GREEN = "#10b981";
|
|
23
25
|
/** Border-color palette rotated by issue start order. */
|
|
24
26
|
export const BORDER_ROTATION = ["cyan", "magenta", "blue", "yellow"];
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Muted gray for secondary chrome: dividers, field labels (`branch`/`now`/
|
|
29
|
+
* `log`), the `phase N/total` + elapsed line, the `last activity` stamp, and
|
|
30
|
+
* the terminal status line.
|
|
31
|
+
*
|
|
32
|
+
* Lifted off ANSI `"gray"` (bright-black), which the brand's own dark theme
|
|
33
|
+
* renders at ~2.4:1 — below WCAG AA (4.5) and even the 3.0 large-text/UI floor.
|
|
34
|
+
* This fixed mid-gray clears AA (~4.9:1) on the dark theme while staying above
|
|
35
|
+
* 3.0 on light terminals (~3.4:1). It still degrades gracefully: chalk
|
|
36
|
+
* downsamples the hex to the nearest ANSI color on non-truecolor terminals, and
|
|
37
|
+
* `NO_COLOR` strips it entirely. The not-yet-started phase glyph stays on ANSI
|
|
38
|
+
* `"gray"` (see `phaseStatusColor`) so pending work remains the most recessed.
|
|
39
|
+
*/
|
|
40
|
+
export const DIVIDER_COLOR = "#8B8B9A";
|
|
27
41
|
/** Brand orange for the live/active phase spinner — the one element the eye
|
|
28
42
|
* tracks. Border rotation still distinguishes concurrent issues. */
|
|
29
43
|
export const ACTIVE_PHASE_COLOR = BRAND_ORANGE;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sequant",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.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": {
|
|
@@ -25,10 +25,11 @@
|
|
|
25
25
|
"dev": "tsx bin/cli.ts",
|
|
26
26
|
"test": "vitest run",
|
|
27
27
|
"lint": "eslint src/ bin/ --max-warnings 0",
|
|
28
|
-
"sync:skills": "
|
|
28
|
+
"sync:skills": "npx tsx scripts/check-skill-sync.ts --fix",
|
|
29
29
|
"sync:hooks": "bash scripts/sync-hooks.sh",
|
|
30
|
-
"validate:skills": "for skill in templates/skills/*/; do npx skills-ref validate \"$skill\"; done",
|
|
30
|
+
"validate:skills": "for skill in templates/skills/*/; do case \"$skill\" in *_shared*|*/references/*) continue;; esac; npx skills-ref validate \"$skill\"; done",
|
|
31
31
|
"lint:skill-calls": "npx tsx scripts/lint-skill-calls.ts",
|
|
32
|
+
"lint:skill-sync": "npx tsx scripts/check-skill-sync.ts",
|
|
32
33
|
"prepare:marketplace": "npx tsx scripts/prepare-marketplace.ts",
|
|
33
34
|
"validate:marketplace": "npx tsx scripts/prepare-marketplace.ts --validate-only",
|
|
34
35
|
"prepublishOnly": "npm run build"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Force-push handoff pattern
|
|
2
|
+
|
|
3
|
+
When you encounter `HOOK_BLOCKED: Force push` from `.claude/hooks/pre-tool.sh:106-111`, **do not attempt to bypass the hook**. The block is intentional — force-pushing rewrites history and can destroy work for others sharing the branch.
|
|
4
|
+
|
|
5
|
+
## The pattern: hand the command to the user
|
|
6
|
+
|
|
7
|
+
When a force push is genuinely required (e.g., cleaning contamination off a feature branch after a clean rebase), present the exact command to the user prefixed with `!` so they execute it in-session:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
! git push --force-with-lease origin feature/<branch>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The user pastes that into the prompt; the `!` runs it in their shell, output streams back into the conversation, and you continue from there.
|
|
14
|
+
|
|
15
|
+
**Always prefer `--force-with-lease` over raw `--force`.** `--force-with-lease` refuses to overwrite the remote ref if someone else pushed in the meantime, turning a silent stomp into a clean error.
|
|
16
|
+
|
|
17
|
+
## Why bypass attempts fail
|
|
18
|
+
|
|
19
|
+
`CLAUDE_HOOKS_DISABLED=true git push --force ...` does **not** work. The hook reads `CLAUDE_HOOKS_DISABLED` at the harness level *before* Bash executes the command, so prefixing the env var inside the command line has no effect. Setting it via `export` in a prior tool call doesn't help either — each Bash tool call is a fresh subprocess.
|
|
20
|
+
|
|
21
|
+
## When force push is legitimate vs. not
|
|
22
|
+
|
|
23
|
+
| Legitimate | Not legitimate |
|
|
24
|
+
|------------|----------------|
|
|
25
|
+
| Cleaning rebase contamination off your own feature branch before PR | Rewriting history on `main`/`master` |
|
|
26
|
+
| Removing accidentally-committed secrets after rotation | "Squashing for cleanliness" on a shared branch |
|
|
27
|
+
| Recovering from a mistakenly-pushed merge commit | Force-pushing over someone else's work |
|
|
28
|
+
|
|
29
|
+
For shared branches, prefer `git revert` over force push.
|
|
30
|
+
|
|
31
|
+
## Reference
|
|
32
|
+
|
|
33
|
+
- Block definition: `.claude/hooks/pre-tool.sh:106-111`
|
|
34
|
+
- Regex: `git push.*(--force| -f($| ))` — note this can also match the literal strings inside quoted `gh issue/pr` bodies (workaround: write the body to a file first)
|
|
@@ -69,6 +69,20 @@ If the output is non-empty, paste every line verbatim above the dashboard table
|
|
|
69
69
|
|
|
70
70
|
The orchestrator/MCP mode (`SEQUANT_ORCHESTRATOR` set) returns no output, so the call is safe to make unconditionally.
|
|
71
71
|
|
|
72
|
+
**Command prefix (#740, read-only):**
|
|
73
|
+
|
|
74
|
+
Probe once here for a global/PATH `sequant`, and reuse the result for every emitted run command below. `npx sequant` is the invocation most prone to version skew (a dual node prefix plus npx cache reuse can silently run a *stale* binary while a directly-installed `sequant` on PATH is current), so prefer a resolvable global install when one exists.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Resolve CMD_PREFIX once here; reuse it for every emitted run command below.
|
|
78
|
+
command -v sequant >/dev/null 2>&1 && CMD_PREFIX="sequant" || CMD_PREFIX="npx sequant"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
- Global install on PATH → `CMD_PREFIX="sequant"` → emit `sequant run …`
|
|
82
|
+
- No global install (npx-only) → `CMD_PREFIX="npx sequant"` → emit `npx sequant run …` (unchanged default — zero behavior change for npx-only users)
|
|
83
|
+
|
|
84
|
+
The probe is read-only and side-effect-free, so it runs unconditionally, including in orchestrator/MCP mode (`SEQUANT_ORCHESTRATOR` set).
|
|
85
|
+
|
|
72
86
|
**From GitHub (parallel for all issues):**
|
|
73
87
|
|
|
74
88
|
```bash
|
|
@@ -182,7 +196,7 @@ Triggers (any one):
|
|
|
182
196
|
- Issue body or comments mention `"depends on #N"`, `"blocked by #N"`, or `"after #N"`
|
|
183
197
|
- One issue's described output is another issue's input (e.g., A changes a function signature that B consumes)
|
|
184
198
|
|
|
185
|
-
Format: `Chain:
|
|
199
|
+
Format: `Chain: <CMD_PREFIX> run <N1> <N2> --chain --qa-gate -Q <phases> # alternative — <one-line reason>` (`<CMD_PREFIX>` resolved in Step 1)
|
|
186
200
|
|
|
187
201
|
Flag references:
|
|
188
202
|
- `--chain` chains issues (each branches from previous; implies `--sequential`)
|
|
@@ -233,15 +247,15 @@ False-positive guards and tunables (excluded paths, the path regex, the slash-co
|
|
|
233
247
|
...
|
|
234
248
|
────────────────────────────────────────────────────────────────
|
|
235
249
|
Commands:
|
|
236
|
-
|
|
237
|
-
|
|
250
|
+
<CMD_PREFIX> run <N1> <N2> <flags>
|
|
251
|
+
<CMD_PREFIX> run <N3> <flags> # resume
|
|
238
252
|
────────────────────────────────────────────────────────────────
|
|
239
253
|
Order: <N> → <N> (<dependency reason>)
|
|
240
254
|
|
|
241
255
|
⚠ #<N> <warning>
|
|
242
256
|
⚠ #<N> <warning>
|
|
243
257
|
|
|
244
|
-
Chain:
|
|
258
|
+
Chain: <CMD_PREFIX> run <N1> <N2> --chain --qa-gate -Q <phases> # alternative — <reason>
|
|
245
259
|
|
|
246
260
|
Flags:
|
|
247
261
|
<flag> <one-line reason>
|
|
@@ -286,6 +300,7 @@ The commands block is headed by `Commands:` — no box-drawing, no character cou
|
|
|
286
300
|
6. If ALL issues share the same workflow, emit a single command
|
|
287
301
|
7. **Line splitting:** When a single command would contain more than 6 issue numbers, split into multiple commands of at most 6 issues each, grouped by compatible workflow. Example: 11 issues → two commands (6 + 5)
|
|
288
302
|
8. **Minimal flags:** Omit `--phases` when the resulting workflow equals the CLI default (registered at `bin/cli.ts:186`, defined as `DEFAULT_PHASES` in `src/lib/workflow/types.ts`). Prefer additive flags over restating phases — additive flags: `--testgen` and `--security-review` (`bin/cli.ts:208-209`). Use `--testgen` instead of `--phases spec,testgen,exec,qa` (or `…,testgen,…,test,qa` for ui-labelled issues, since `phase-mapper.determinePhasesForIssue` auto-adds `test` from the ui label). Use `--security-review` instead of `--phases spec,security-review,exec,qa`. The posted marker (`<!-- assess:phases=… -->`) records the full resolved workflow regardless — markers are machine-readable, displayed commands are human shorthand. This intentional divergence is fine: parsers consume markers, humans copy commands.
|
|
303
|
+
9. **Command prefix:** Substitute the Step-1 `CMD_PREFIX` for **every** emitted `sequant run` command — the Commands block, the `Chain:` line, and both single-issue detail-mode commands (PROCEED and the REWRITE "fresh start"). `Cleanup:` commands are `git`/`gh`, not `sequant`, so they are unaffected. A resolvable global `sequant` on PATH yields `sequant run …`; npx-only yields `npx sequant run …` (the default). Never mix prefixes within a single assessment.
|
|
289
304
|
|
|
290
305
|
#### Annotation Rules
|
|
291
306
|
|
|
@@ -303,7 +318,7 @@ Emit annotations in this order between the separators that follow `Commands:`:
|
|
|
303
318
|
- `⚠ #412 bug + auth labels — domain label (auth) takes priority over bug`
|
|
304
319
|
|
|
305
320
|
- **`Chain:`** — Only when 2+ PROCEED issues have a detected dependency (see "Chain detection" in Step 4). Suggests an alternative execution topology. Does not replace the default per-issue commands. Format:
|
|
306
|
-
`Chain:
|
|
321
|
+
`Chain: <CMD_PREFIX> run <N1> <N2> --chain --qa-gate -Q <phases> # alternative — <one-line reason>` (`<CMD_PREFIX>` resolved in Step 1)
|
|
307
322
|
|
|
308
323
|
- **`Flags:`** — Only when non-default flags appear in the commands and the reason isn't obvious. One line per **distinct** flag used across all commands. Omit entire section when `-Q` is the only non-default flag AND its reason is obvious (e.g., all issues are enhancements). Format:
|
|
309
324
|
```
|
|
@@ -321,6 +336,8 @@ Emit annotations in this order between the separators that follow `Commands:`:
|
|
|
321
336
|
|
|
322
337
|
Not all issues have explicit `- [ ]` checkboxes, so the `ACs` column is omitted.
|
|
323
338
|
|
|
339
|
+
> **Prefix in examples:** The worked examples in this doc show the `npx sequant` default (the zero-install path). When the Step-1 probe resolves a global `sequant` on PATH, `CMD_PREFIX="sequant"` and every emitted command uses `sequant run …` instead — consistently within one assessment (see Commands Block Rule #9).
|
|
340
|
+
|
|
324
341
|
```
|
|
325
342
|
# Action Reason Run
|
|
326
343
|
462 PARK Manual measurement task ‖
|
|
@@ -485,7 +502,7 @@ More context since you're focused on one issue. Separators between every section
|
|
|
485
502
|
→ PROCEED — <one-line reason>
|
|
486
503
|
|
|
487
504
|
Commands:
|
|
488
|
-
|
|
505
|
+
<CMD_PREFIX> run <N> <flags>
|
|
489
506
|
|
|
490
507
|
<phases> · <N> ACs
|
|
491
508
|
|
|
@@ -617,7 +634,7 @@ Need: <specific information required>
|
|
|
617
634
|
→ REWRITE — <reason>
|
|
618
635
|
|
|
619
636
|
Commands:
|
|
620
|
-
|
|
637
|
+
<CMD_PREFIX> run <N> <flags> # fresh start
|
|
621
638
|
|
|
622
639
|
<phases> · <N> ACs
|
|
623
640
|
────────────────────────────────────────────────────────────────
|
|
@@ -1657,6 +1657,35 @@ Parse the agent's output text for these patterns to detect failures:
|
|
|
1657
1657
|
| `blocked by hook` | Operation was blocked by pre-tool hook |
|
|
1658
1658
|
| `I'm unable to` | Agent hit a blocking constraint |
|
|
1659
1659
|
|
|
1660
|
+
### 4b2. Detecting Turn-Capped Implementers (Incomplete, not Failed)
|
|
1661
|
+
|
|
1662
|
+
Every spawned implementer runs under a `maxTurns` cap (live since #484). Hitting the cap is **not** a failure — the agent did real, partial work before running out of turns, and that work is preserved (driver returns it flagged `capped: true` and warns rather than erroring, #733). Treat a capped implementer as **incomplete**, distinct from the hook-block failures in Section 4b.
|
|
1663
|
+
|
|
1664
|
+
**Turn-cap detection keywords** (parse the agent's output text):
|
|
1665
|
+
|
|
1666
|
+
| Pattern | Meaning |
|
|
1667
|
+
|---------|---------|
|
|
1668
|
+
| `error_max_turns` | Agent hit its turn cap |
|
|
1669
|
+
| `turn cap` / `Returning partial results` | Driver-emitted turn-cap warning |
|
|
1670
|
+
| Output ends mid-task with no completion report | Likely capped |
|
|
1671
|
+
|
|
1672
|
+
**How to handle a capped implementer:**
|
|
1673
|
+
|
|
1674
|
+
- Do **not** discard its changes or roll back the group — keep the partial work it committed.
|
|
1675
|
+
- Record, per task, **which tasks finished vs. which were capped**. A capped task is incomplete, not done.
|
|
1676
|
+
- Continue with the remaining (non-capped) tasks normally; one cap does not abort the whole `/exec` run.
|
|
1677
|
+
- The next `/exec` iteration (or a resumed session) picks up the capped task to finish it — note it explicitly so it is not mistaken for complete.
|
|
1678
|
+
- Reflect capped tasks honestly in the AC verification table (⚠️ Partial) and the progress update, rather than reporting the AC as fully satisfied.
|
|
1679
|
+
|
|
1680
|
+
```markdown
|
|
1681
|
+
### Parallel Group Results
|
|
1682
|
+
|
|
1683
|
+
| Task | Status |
|
|
1684
|
+
|------|--------|
|
|
1685
|
+
| Create types/metrics.ts | ✅ Finished |
|
|
1686
|
+
| Refactor batch-executor | ⚠️ Capped (incomplete — resume next iteration) |
|
|
1687
|
+
```
|
|
1688
|
+
|
|
1660
1689
|
### 4c. Prompt Templates for Sub-Agents
|
|
1661
1690
|
|
|
1662
1691
|
When spawning sub-agents for implementation tasks, use task-specific prompt templates for better results. See [prompt-templates.md](../_shared/references/prompt-templates.md) for the full reference.
|