sequant 2.8.0 → 2.10.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 +23 -4
- package/dist/bin/cli.js +99 -18
- package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +1 -1
- package/dist/marketplace/external_plugins/sequant/.mcp.json +1 -1
- package/dist/marketplace/external_plugins/sequant/README.md +58 -2
- package/dist/marketplace/external_plugins/sequant/hooks/parallel-marker.sh +50 -0
- package/dist/marketplace/external_plugins/sequant/hooks/post-tool.sh +38 -10
- package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +405 -67
- package/dist/marketplace/external_plugins/sequant/skills/_shared/references/trust-model.md +18 -0
- package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +250 -344
- package/dist/marketplace/external_plugins/sequant/skills/assess/references/predicted-collision-detection.md +10 -7
- package/dist/marketplace/external_plugins/sequant/skills/docs/SKILL.md +4 -4
- package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +26 -6
- package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +4 -2
- package/dist/marketplace/external_plugins/sequant/skills/loop/SKILL.md +38 -2
- package/dist/marketplace/external_plugins/sequant/skills/merger/SKILL.md +2 -2
- package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +337 -57
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/anti-pattern-detection.md +6 -6
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/call-site-review.md +2 -2
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/code-review-checklist.md +2 -2
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/fixtures/injection-issue-body.md +36 -0
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/quality-gates.md +21 -7
- package/dist/marketplace/external_plugins/sequant/skills/qa/references/testing-requirements.md +1 -1
- package/dist/marketplace/external_plugins/sequant/skills/qa/scripts/quality-checks.sh +11 -11
- 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 +11 -3
- package/dist/marketplace/external_plugins/sequant/skills/setup/SKILL.md +5 -5
- package/dist/marketplace/external_plugins/sequant/skills/spec/SKILL.md +2 -0
- package/dist/marketplace/external_plugins/sequant/skills/spec/references/verification-criteria.md +1 -1
- package/dist/marketplace/external_plugins/sequant/skills/test/SKILL.md +1 -1
- package/dist/src/commands/assess-render.d.ts +23 -0
- package/dist/src/commands/assess-render.js +60 -0
- package/dist/src/commands/doctor.js +8 -13
- package/dist/src/commands/init.js +13 -1
- package/dist/src/commands/locks.js +2 -0
- package/dist/src/commands/logs.js +20 -6
- package/dist/src/commands/merge.js +154 -2
- package/dist/src/commands/ready-tui-adapter.js +6 -1
- package/dist/src/commands/ready.d.ts +25 -3
- package/dist/src/commands/ready.js +39 -13
- package/dist/src/commands/resume.d.ts +113 -0
- package/dist/src/commands/resume.js +193 -0
- package/dist/src/commands/run-display.d.ts +20 -0
- package/dist/src/commands/run-display.js +117 -5
- package/dist/src/commands/run-flags.d.ts +42 -1
- package/dist/src/commands/run-flags.js +53 -1
- package/dist/src/commands/run-progress.js +26 -1
- package/dist/src/commands/run.js +11 -10
- package/dist/src/commands/state.js +7 -0
- package/dist/src/commands/stats.js +47 -0
- package/dist/src/commands/status.d.ts +9 -0
- package/dist/src/commands/status.js +24 -1
- package/dist/src/commands/sync.js +90 -19
- package/dist/src/commands/update.js +28 -5
- package/dist/src/lib/ac-parser.d.ts +14 -0
- package/dist/src/lib/ac-parser.js +99 -5
- package/dist/src/lib/assess/index.d.ts +10 -0
- package/dist/src/lib/assess/index.js +9 -0
- package/dist/src/lib/assess/renderer.d.ts +64 -0
- package/dist/src/lib/assess/renderer.js +481 -0
- package/dist/src/lib/assess/types.d.ts +224 -0
- package/dist/src/lib/assess/types.js +241 -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/ci/outputs.d.ts +15 -2
- package/dist/src/lib/ci/outputs.js +17 -5
- package/dist/src/lib/cli-flags.d.ts +33 -0
- package/dist/src/lib/cli-flags.js +46 -0
- package/dist/src/lib/cli-ui/run-renderer-types.d.ts +21 -7
- package/dist/src/lib/cli-ui/run-renderer.d.ts +10 -0
- package/dist/src/lib/cli-ui/run-renderer.js +79 -9
- package/dist/src/lib/errors.d.ts +84 -6
- package/dist/src/lib/errors.js +142 -17
- package/dist/src/lib/locks/index.d.ts +3 -3
- package/dist/src/lib/locks/index.js +2 -2
- package/dist/src/lib/locks/lock-manager.d.ts +20 -2
- package/dist/src/lib/locks/lock-manager.js +47 -3
- package/dist/src/lib/locks/types.d.ts +34 -3
- package/dist/src/lib/locks/types.js +16 -0
- package/dist/src/lib/manifest.js +1 -17
- package/dist/src/lib/mcp-config.d.ts +45 -0
- package/dist/src/lib/mcp-config.js +77 -1
- package/dist/src/lib/merge-check/combined-branch-test.d.ts +21 -2
- package/dist/src/lib/merge-check/combined-branch-test.js +233 -110
- package/dist/src/lib/merge-check/command-result.d.ts +36 -0
- package/dist/src/lib/merge-check/command-result.js +56 -0
- package/dist/src/lib/merge-check/index.d.ts +4 -0
- package/dist/src/lib/merge-check/index.js +1 -1
- package/dist/src/lib/merge-check/report.js +5 -1
- package/dist/src/lib/merge-check/types.d.ts +9 -0
- package/dist/src/lib/merge-check/watch.d.ts +121 -0
- package/dist/src/lib/merge-check/watch.js +215 -0
- package/dist/src/lib/qa/infra-blocked-ci.d.ts +90 -0
- package/dist/src/lib/qa/infra-blocked-ci.js +80 -0
- package/dist/src/lib/settings.d.ts +14 -0
- package/dist/src/lib/settings.js +61 -8
- package/dist/src/lib/shutdown.d.ts +42 -2
- package/dist/src/lib/shutdown.js +75 -5
- package/dist/src/lib/skills-check.d.ts +26 -0
- package/dist/src/lib/skills-check.js +31 -0
- package/dist/src/lib/stacks.d.ts +110 -1
- package/dist/src/lib/stacks.js +244 -5
- package/dist/src/lib/templates.d.ts +74 -0
- package/dist/src/lib/templates.js +171 -16
- package/dist/src/lib/test-tautology-detector.d.ts +9 -2
- package/dist/src/lib/test-tautology-detector.js +153 -12
- package/dist/src/lib/version-check.d.ts +31 -0
- package/dist/src/lib/version-check.js +46 -7
- package/dist/src/lib/workflow/batch-executor.d.ts +138 -6
- package/dist/src/lib/workflow/batch-executor.js +550 -54
- 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 +118 -0
- package/dist/src/lib/workflow/chain-resume.js +170 -0
- package/dist/src/lib/workflow/completed-status.d.ts +52 -0
- package/dist/src/lib/workflow/completed-status.js +57 -0
- package/dist/src/lib/workflow/config-resolver.d.ts +26 -0
- package/dist/src/lib/workflow/config-resolver.js +54 -2
- 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/agent-driver.d.ts +7 -0
- package/dist/src/lib/workflow/drivers/aider.d.ts +2 -0
- package/dist/src/lib/workflow/drivers/aider.js +2 -0
- package/dist/src/lib/workflow/drivers/claude-code.d.ts +21 -0
- package/dist/src/lib/workflow/drivers/claude-code.js +50 -8
- 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/git-diff-utils.d.ts +35 -3
- package/dist/src/lib/workflow/git-diff-utils.js +77 -4
- package/dist/src/lib/workflow/heartbeat.d.ts +38 -0
- package/dist/src/lib/workflow/heartbeat.js +90 -0
- package/dist/src/lib/workflow/log-writer.d.ts +44 -1
- package/dist/src/lib/workflow/log-writer.js +81 -14
- package/dist/src/lib/workflow/metrics-schema.d.ts +42 -3
- 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 +234 -13
- package/dist/src/lib/workflow/phase-executor.js +533 -56
- package/dist/src/lib/workflow/phase-registry.js +2 -1
- package/dist/src/lib/workflow/platforms/github.d.ts +64 -0
- package/dist/src/lib/workflow/platforms/github.js +152 -0
- package/dist/src/lib/workflow/qa-cache.d.ts +6 -1
- package/dist/src/lib/workflow/qa-cache.js +22 -9
- package/dist/src/lib/workflow/ready-gate.d.ts +17 -4
- package/dist/src/lib/workflow/ready-gate.js +25 -9
- package/dist/src/lib/workflow/run-log-schema.d.ts +37 -0
- package/dist/src/lib/workflow/run-log-schema.js +92 -2
- package/dist/src/lib/workflow/run-orchestrator.d.ts +33 -0
- package/dist/src/lib/workflow/run-orchestrator.js +429 -47
- package/dist/src/lib/workflow/skills-preflight.d.ts +89 -0
- package/dist/src/lib/workflow/skills-preflight.js +112 -0
- package/dist/src/lib/workflow/state-cleanup.js +9 -2
- package/dist/src/lib/workflow/state-manager.d.ts +33 -0
- package/dist/src/lib/workflow/state-manager.js +83 -0
- package/dist/src/lib/workflow/state-schema.d.ts +26 -0
- package/dist/src/lib/workflow/state-schema.js +47 -0
- 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 +149 -4
- package/dist/src/lib/workflow/types.js +1 -0
- package/dist/src/lib/workflow/worktree-manager.d.ts +70 -2
- package/dist/src/lib/workflow/worktree-manager.js +201 -52
- package/dist/src/mcp/tools/run.d.ts +11 -1
- package/dist/src/mcp/tools/run.js +22 -1
- package/dist/src/mcp/tools/status.js +6 -0
- package/dist/src/ui/tui/ElapsedTimer.d.ts +12 -3
- package/dist/src/ui/tui/ElapsedTimer.js +12 -10
- package/dist/src/ui/tui/IssueBox.js +10 -2
- package/dist/src/ui/tui/row-cap.js +2 -1
- package/dist/src/ui/tui/theme.d.ts +7 -0
- package/dist/src/ui/tui/theme.js +9 -0
- package/package.json +9 -10
- package/templates/hooks/parallel-marker.sh +50 -0
- package/templates/hooks/post-tool.sh +38 -10
- package/templates/hooks/pre-tool.sh +405 -67
- package/templates/scripts/cleanup-worktree.sh +282 -30
- package/templates/scripts/new-feature.sh +264 -16
- package/templates/skills/_shared/references/trust-model.md +18 -0
- package/templates/skills/assess/SKILL.md +250 -344
- package/templates/skills/assess/references/predicted-collision-detection.md +10 -7
- package/templates/skills/docs/SKILL.md +4 -4
- package/templates/skills/exec/SKILL.md +26 -6
- package/templates/skills/fullsolve/SKILL.md +4 -2
- package/templates/skills/loop/SKILL.md +38 -2
- package/templates/skills/merger/SKILL.md +2 -2
- package/templates/skills/qa/SKILL.md +337 -57
- package/templates/skills/qa/references/anti-pattern-detection.md +6 -6
- package/templates/skills/qa/references/call-site-review.md +2 -2
- package/templates/skills/qa/references/code-review-checklist.md +2 -2
- package/templates/skills/qa/references/fixtures/injection-issue-body.md +36 -0
- package/templates/skills/qa/references/quality-gates.md +21 -7
- package/templates/skills/qa/references/testing-requirements.md +1 -1
- package/templates/skills/qa/scripts/quality-checks.sh +11 -11
- 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 +11 -3
- package/templates/skills/setup/SKILL.md +5 -5
- package/templates/skills/spec/SKILL.md +2 -0
- package/templates/skills/spec/references/verification-criteria.md +1 -1
- package/templates/skills/test/SKILL.md +1 -1
- package/dist/src/lib/phase-spinner.d.ts +0 -146
- package/dist/src/lib/phase-spinner.js +0 -255
- package/dist/src/lib/workflow/pr-operations.d.ts +0 -86
- package/dist/src/lib/workflow/pr-operations.js +0 -326
- package/dist/src/lib/workflow/run-summary.d.ts +0 -36
- package/dist/src/lib/workflow/run-summary.js +0 -142
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills pre-flight for `sequant run` (#813).
|
|
3
|
+
*
|
|
4
|
+
* The claude-code driver executes phases as slash-command skills loaded from
|
|
5
|
+
* project scope only (`settingSources: ["project"]`, #19/#711). Without
|
|
6
|
+
* `.claude/skills/` the phase agent hunts for a command that can never
|
|
7
|
+
* resolve, does no work, and the run surfaces as a bogus "spec retry"
|
|
8
|
+
* failure. This module computes the skills a run actually needs — from the
|
|
9
|
+
* phases resolved for that run, not a hardcoded triple — and checks them via
|
|
10
|
+
* the same `checkSkillsInstalled` helper `doctor` uses, so the two cannot
|
|
11
|
+
* drift.
|
|
12
|
+
*
|
|
13
|
+
* Drivers whose phase prompts do the work inline (aider's `driverOverrides`
|
|
14
|
+
* templates in phase-registry.ts) never resolve skills, so the pre-flight is
|
|
15
|
+
* skipped for them via `AgentDriver.resolvesSkills`.
|
|
16
|
+
*/
|
|
17
|
+
import type { AiderSettings } from "../settings.js";
|
|
18
|
+
import type { Phase } from "./types.js";
|
|
19
|
+
export interface SkillsPreflightInput {
|
|
20
|
+
/** Agent driver name (default claude-code). */
|
|
21
|
+
agent?: string;
|
|
22
|
+
/** Aider settings, forwarded to the driver factory. */
|
|
23
|
+
aiderSettings?: AiderSettings;
|
|
24
|
+
/** Base pipeline for explicit-phase runs (`config.phases`). */
|
|
25
|
+
phases: Phase[];
|
|
26
|
+
/** True when phases are auto-detected from labels (no explicit --phases). */
|
|
27
|
+
autoDetectPhases: boolean;
|
|
28
|
+
/** True when the quality loop may invoke the loop skill. */
|
|
29
|
+
qualityLoop: boolean;
|
|
30
|
+
/** Additive phase flags (`--testgen` / `--security-review`). */
|
|
31
|
+
testgen?: boolean;
|
|
32
|
+
securityReview?: boolean;
|
|
33
|
+
/** Issues in the run, with their labels (drives per-issue phase rules). */
|
|
34
|
+
issueNumbers: number[];
|
|
35
|
+
issueInfoMap: Map<number, {
|
|
36
|
+
title: string;
|
|
37
|
+
labels: string[];
|
|
38
|
+
}>;
|
|
39
|
+
/** Project root to check under (default: `process.cwd()`). */
|
|
40
|
+
cwd?: string;
|
|
41
|
+
}
|
|
42
|
+
export type SkillsPreflightResult = {
|
|
43
|
+
ok: true;
|
|
44
|
+
} | {
|
|
45
|
+
ok: false;
|
|
46
|
+
/** Human-readable cause, e.g. `missing skills: spec, exec, qa`. */
|
|
47
|
+
cause: string;
|
|
48
|
+
/** Missing skill names, in required order. */
|
|
49
|
+
missingSkills: string[];
|
|
50
|
+
/** Driver whose skill resolution triggered the check. */
|
|
51
|
+
driverName: string;
|
|
52
|
+
/** Remedy line for display. */
|
|
53
|
+
remedy: string;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Compute the union of skills required by the phases resolved for this run.
|
|
57
|
+
*
|
|
58
|
+
* Explicit-phase runs start from `phases` as given; auto-detect runs start
|
|
59
|
+
* from each issue's label-detected pipeline. Both then apply the additive
|
|
60
|
+
* `--testgen` / `--security-review` / UI-label rules via
|
|
61
|
+
* `determinePhasesForIssue`.
|
|
62
|
+
*
|
|
63
|
+
* Two deliberate over-approximations keep late-added phases covered:
|
|
64
|
+
*
|
|
65
|
+
* - An explicit `--testgen` / `--security-review` flag requires its skill
|
|
66
|
+
* unconditionally, even when `determinePhasesForIssue` would not insert
|
|
67
|
+
* the phase because `spec` is absent from the pipeline. On a resume where
|
|
68
|
+
* spec already completed, batch-executor inserts the phase anyway
|
|
69
|
+
* (`phases.includes("spec") || specAlreadyRan`), and the pre-flight cannot
|
|
70
|
+
* cheaply know `specAlreadyRan` — requiring the skill the user asked for
|
|
71
|
+
* is the safe superset.
|
|
72
|
+
* - The loop skill is required when the quality loop is enabled up front OR
|
|
73
|
+
* when any issue's labels would auto-enable it (`complex`/`refactor`/...,
|
|
74
|
+
* via `detectPhasesFromLabels().qualityLoop`), since the loop skill is
|
|
75
|
+
* invoked the same way as any phase skill.
|
|
76
|
+
*
|
|
77
|
+
* Phases recommended later by spec output (`parseRecommendedWorkflow`)
|
|
78
|
+
* remain unknowable at pre-flight time — the accepted gap documented on
|
|
79
|
+
* #813.
|
|
80
|
+
*
|
|
81
|
+
* Exported for direct unit testing (AC-2).
|
|
82
|
+
*/
|
|
83
|
+
export declare function resolveRequiredSkills(input: Pick<SkillsPreflightInput, "phases" | "autoDetectPhases" | "qualityLoop" | "testgen" | "securityReview" | "issueNumbers" | "issueInfoMap">): string[];
|
|
84
|
+
/**
|
|
85
|
+
* Run the skills pre-flight. Returns `{ok: true}` when the run may proceed:
|
|
86
|
+
* either every required skill is installed, or the selected driver does not
|
|
87
|
+
* resolve skills at all (AC-3).
|
|
88
|
+
*/
|
|
89
|
+
export declare function runSkillsPreflight(input: SkillsPreflightInput): Promise<SkillsPreflightResult>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills pre-flight for `sequant run` (#813).
|
|
3
|
+
*
|
|
4
|
+
* The claude-code driver executes phases as slash-command skills loaded from
|
|
5
|
+
* project scope only (`settingSources: ["project"]`, #19/#711). Without
|
|
6
|
+
* `.claude/skills/` the phase agent hunts for a command that can never
|
|
7
|
+
* resolve, does no work, and the run surfaces as a bogus "spec retry"
|
|
8
|
+
* failure. This module computes the skills a run actually needs — from the
|
|
9
|
+
* phases resolved for that run, not a hardcoded triple — and checks them via
|
|
10
|
+
* the same `checkSkillsInstalled` helper `doctor` uses, so the two cannot
|
|
11
|
+
* drift.
|
|
12
|
+
*
|
|
13
|
+
* Drivers whose phase prompts do the work inline (aider's `driverOverrides`
|
|
14
|
+
* templates in phase-registry.ts) never resolve skills, so the pre-flight is
|
|
15
|
+
* skipped for them via `AgentDriver.resolvesSkills`.
|
|
16
|
+
*/
|
|
17
|
+
import { getDriver } from "./drivers/index.js";
|
|
18
|
+
import { detectPhasesFromLabels, determinePhasesForIssue, } from "./phase-mapper.js";
|
|
19
|
+
import { phaseRegistry } from "./phase-registry.js";
|
|
20
|
+
import { checkSkillsInstalled, SKILLS_DIR } from "../skills-check.js";
|
|
21
|
+
/**
|
|
22
|
+
* Compute the union of skills required by the phases resolved for this run.
|
|
23
|
+
*
|
|
24
|
+
* Explicit-phase runs start from `phases` as given; auto-detect runs start
|
|
25
|
+
* from each issue's label-detected pipeline. Both then apply the additive
|
|
26
|
+
* `--testgen` / `--security-review` / UI-label rules via
|
|
27
|
+
* `determinePhasesForIssue`.
|
|
28
|
+
*
|
|
29
|
+
* Two deliberate over-approximations keep late-added phases covered:
|
|
30
|
+
*
|
|
31
|
+
* - An explicit `--testgen` / `--security-review` flag requires its skill
|
|
32
|
+
* unconditionally, even when `determinePhasesForIssue` would not insert
|
|
33
|
+
* the phase because `spec` is absent from the pipeline. On a resume where
|
|
34
|
+
* spec already completed, batch-executor inserts the phase anyway
|
|
35
|
+
* (`phases.includes("spec") || specAlreadyRan`), and the pre-flight cannot
|
|
36
|
+
* cheaply know `specAlreadyRan` — requiring the skill the user asked for
|
|
37
|
+
* is the safe superset.
|
|
38
|
+
* - The loop skill is required when the quality loop is enabled up front OR
|
|
39
|
+
* when any issue's labels would auto-enable it (`complex`/`refactor`/...,
|
|
40
|
+
* via `detectPhasesFromLabels().qualityLoop`), since the loop skill is
|
|
41
|
+
* invoked the same way as any phase skill.
|
|
42
|
+
*
|
|
43
|
+
* Phases recommended later by spec output (`parseRecommendedWorkflow`)
|
|
44
|
+
* remain unknowable at pre-flight time — the accepted gap documented on
|
|
45
|
+
* #813.
|
|
46
|
+
*
|
|
47
|
+
* Exported for direct unit testing (AC-2).
|
|
48
|
+
*/
|
|
49
|
+
export function resolveRequiredSkills(input) {
|
|
50
|
+
const additiveFlags = {
|
|
51
|
+
testgen: input.testgen,
|
|
52
|
+
securityReview: input.securityReview,
|
|
53
|
+
};
|
|
54
|
+
const requiredPhases = new Set();
|
|
55
|
+
let qualityLoop = input.qualityLoop;
|
|
56
|
+
for (const issueNumber of input.issueNumbers) {
|
|
57
|
+
const labels = input.issueInfoMap.get(issueNumber)?.labels ?? [];
|
|
58
|
+
const detected = input.autoDetectPhases
|
|
59
|
+
? detectPhasesFromLabels(labels)
|
|
60
|
+
: null;
|
|
61
|
+
if (detected?.qualityLoop)
|
|
62
|
+
qualityLoop = true;
|
|
63
|
+
const basePhases = detected ? detected.phases : input.phases;
|
|
64
|
+
for (const phase of determinePhasesForIssue(basePhases, labels, additiveFlags)) {
|
|
65
|
+
requiredPhases.add(phase);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (input.testgen)
|
|
69
|
+
requiredPhases.add("testgen");
|
|
70
|
+
if (input.securityReview)
|
|
71
|
+
requiredPhases.add("security-review");
|
|
72
|
+
if (qualityLoop)
|
|
73
|
+
requiredPhases.add("loop");
|
|
74
|
+
return [...requiredPhases].map((phase) => phaseRegistry.get(phase).skill);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Run the skills pre-flight. Returns `{ok: true}` when the run may proceed:
|
|
78
|
+
* either every required skill is installed, or the selected driver does not
|
|
79
|
+
* resolve skills at all (AC-3).
|
|
80
|
+
*/
|
|
81
|
+
export async function runSkillsPreflight(input) {
|
|
82
|
+
let driver;
|
|
83
|
+
try {
|
|
84
|
+
driver = getDriver(input.agent, {
|
|
85
|
+
aiderSettings: input.aiderSettings,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Unknown driver name (bad `settings.run.agent`). Don't let the
|
|
90
|
+
// pre-flight be the thing that crashes the run with a raw throw —
|
|
91
|
+
// skip it and let phase-executor surface the unknown-driver error
|
|
92
|
+
// through its normal per-issue failure path.
|
|
93
|
+
return { ok: true };
|
|
94
|
+
}
|
|
95
|
+
if (!driver.resolvesSkills)
|
|
96
|
+
return { ok: true };
|
|
97
|
+
const requiredSkills = resolveRequiredSkills(input);
|
|
98
|
+
const { skillsDirExists, missingSkills } = await checkSkillsInstalled(requiredSkills, input.cwd);
|
|
99
|
+
if (missingSkills.length === 0)
|
|
100
|
+
return { ok: true };
|
|
101
|
+
const cause = skillsDirExists
|
|
102
|
+
? `missing skills: ${missingSkills.join(", ")}`
|
|
103
|
+
: `missing ${SKILLS_DIR}/ directory (needs: ${missingSkills.join(", ")})`;
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
cause,
|
|
107
|
+
missingSkills,
|
|
108
|
+
driverName: driver.name,
|
|
109
|
+
remedy: `The ${driver.name} driver resolves phases from ${SKILLS_DIR}/ — ` +
|
|
110
|
+
`run \`sequant sync\` to install them, then re-run.`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -221,13 +221,20 @@ export async function reconcileStateAtStartup(options = {}) {
|
|
|
221
221
|
const state = await manager.getState();
|
|
222
222
|
const advanced = [];
|
|
223
223
|
const stillPending = [];
|
|
224
|
-
// Find issues
|
|
224
|
+
// Find issues whose PR may have merged since their status was written.
|
|
225
225
|
// in_progress covers PRs merged outside this session (#592).
|
|
226
226
|
// waiting_for_qa_gate covers PRs merged before the next QA-gate run (#606).
|
|
227
|
+
// waiting_for_human_merge covers #817's `--ready-gate` terminal: a gated
|
|
228
|
+
// issue never reaches ready_for_merge, so without it a gated issue whose PR
|
|
229
|
+
// a human then merged stayed here forever and never advanced to merged
|
|
230
|
+
// (#837). Note this list is deliberately WIDER than `isCompletedIssueStatus`
|
|
231
|
+
// — it asks "might this have a merged PR?", not "is this done?", which is
|
|
232
|
+
// why in_progress belongs here but not there.
|
|
227
233
|
for (const [issueNumStr, issueState] of Object.entries(state.issues)) {
|
|
228
234
|
if (issueState.status !== "ready_for_merge" &&
|
|
229
235
|
issueState.status !== "in_progress" &&
|
|
230
|
-
issueState.status !== "waiting_for_qa_gate"
|
|
236
|
+
issueState.status !== "waiting_for_qa_gate" &&
|
|
237
|
+
issueState.status !== "waiting_for_human_merge") {
|
|
231
238
|
continue;
|
|
232
239
|
}
|
|
233
240
|
const issueNum = parseInt(issueNumStr, 10);
|
|
@@ -126,6 +126,39 @@ export declare class StateManager {
|
|
|
126
126
|
* Update PR information for an issue
|
|
127
127
|
*/
|
|
128
128
|
updatePRInfo(issueNumber: number, pr: PRInfo): Promise<void>;
|
|
129
|
+
/**
|
|
130
|
+
* Record or clear an in-progress auto-wait (#860). `wakeAtMs` sets the
|
|
131
|
+
* pause marker (and which phase is paused); `null` clears it on wake.
|
|
132
|
+
*
|
|
133
|
+
* Unlike the sibling updaters this NEVER throws on an untracked issue —
|
|
134
|
+
* it is called from a liveness hook during a live wait, and a bookkeeping
|
|
135
|
+
* miss must not interrupt the pause it is describing.
|
|
136
|
+
*/
|
|
137
|
+
updateAutoWait(issueNumber: number, phase: string, wakeAtMs: number | null): Promise<void>;
|
|
138
|
+
/**
|
|
139
|
+
* Record a durable waitable-window halt (#892 AC-1). Preserves an existing
|
|
140
|
+
* re-entry counter so repeated halts on a still-closed window keep counting
|
|
141
|
+
* toward the re-entry bound instead of resetting it.
|
|
142
|
+
*
|
|
143
|
+
* Like `updateAutoWait`, NEVER throws on an untracked issue — it runs on a
|
|
144
|
+
* halt path where bookkeeping must not mask the real failure.
|
|
145
|
+
*/
|
|
146
|
+
updateWindowHalt(issueNumber: number, phase: string, resumeAtMs: number): Promise<void>;
|
|
147
|
+
/**
|
|
148
|
+
* Clear a stale window-halt record (#892) — on any phase success (the
|
|
149
|
+
* window demonstrably reopened; progress also resets the re-entry counter)
|
|
150
|
+
* or a non-window failure (the halt cause is no longer waitable, so an
|
|
151
|
+
* unattended re-entry must not fire on it). No-ops (no state write) when
|
|
152
|
+
* nothing is recorded; never throws on an untracked issue.
|
|
153
|
+
*/
|
|
154
|
+
clearWindowHalt(issueNumber: number): Promise<void>;
|
|
155
|
+
/**
|
|
156
|
+
* Consume one re-entry attempt for a halted issue (#892 AC-3). Returns the
|
|
157
|
+
* new count, or `null` when the issue has no window-halt record (nothing to
|
|
158
|
+
* resume). Called by `sequant resume` immediately before re-entry so a
|
|
159
|
+
* re-entry that halts again (window still closed) is already counted.
|
|
160
|
+
*/
|
|
161
|
+
incrementWindowHaltReentries(issueNumber: number): Promise<number | null>;
|
|
129
162
|
/**
|
|
130
163
|
* Update worktree information for an issue
|
|
131
164
|
*/
|
|
@@ -364,6 +364,89 @@ export class StateManager {
|
|
|
364
364
|
console.log(`State: PR #${pr.number} linked to issue #${issueNumber}`);
|
|
365
365
|
}
|
|
366
366
|
}
|
|
367
|
+
/**
|
|
368
|
+
* Record or clear an in-progress auto-wait (#860). `wakeAtMs` sets the
|
|
369
|
+
* pause marker (and which phase is paused); `null` clears it on wake.
|
|
370
|
+
*
|
|
371
|
+
* Unlike the sibling updaters this NEVER throws on an untracked issue —
|
|
372
|
+
* it is called from a liveness hook during a live wait, and a bookkeeping
|
|
373
|
+
* miss must not interrupt the pause it is describing.
|
|
374
|
+
*/
|
|
375
|
+
async updateAutoWait(issueNumber, phase, wakeAtMs) {
|
|
376
|
+
await this.withLock(async () => {
|
|
377
|
+
const state = await this.getState();
|
|
378
|
+
const issueState = state.issues[String(issueNumber)];
|
|
379
|
+
if (!issueState)
|
|
380
|
+
return;
|
|
381
|
+
issueState.autoWait =
|
|
382
|
+
wakeAtMs === null
|
|
383
|
+
? undefined
|
|
384
|
+
: { wakeAt: new Date(wakeAtMs).toISOString(), phase };
|
|
385
|
+
issueState.lastActivity = new Date().toISOString();
|
|
386
|
+
await this.saveState(state);
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Record a durable waitable-window halt (#892 AC-1). Preserves an existing
|
|
391
|
+
* re-entry counter so repeated halts on a still-closed window keep counting
|
|
392
|
+
* toward the re-entry bound instead of resetting it.
|
|
393
|
+
*
|
|
394
|
+
* Like `updateAutoWait`, NEVER throws on an untracked issue — it runs on a
|
|
395
|
+
* halt path where bookkeeping must not mask the real failure.
|
|
396
|
+
*/
|
|
397
|
+
async updateWindowHalt(issueNumber, phase, resumeAtMs) {
|
|
398
|
+
await this.withLock(async () => {
|
|
399
|
+
const state = await this.getState();
|
|
400
|
+
const issueState = state.issues[String(issueNumber)];
|
|
401
|
+
if (!issueState)
|
|
402
|
+
return;
|
|
403
|
+
issueState.windowHalt = {
|
|
404
|
+
resumeAt: new Date(resumeAtMs).toISOString(),
|
|
405
|
+
phase,
|
|
406
|
+
reentries: issueState.windowHalt?.reentries ?? 0,
|
|
407
|
+
};
|
|
408
|
+
issueState.lastActivity = new Date().toISOString();
|
|
409
|
+
await this.saveState(state);
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Clear a stale window-halt record (#892) — on any phase success (the
|
|
414
|
+
* window demonstrably reopened; progress also resets the re-entry counter)
|
|
415
|
+
* or a non-window failure (the halt cause is no longer waitable, so an
|
|
416
|
+
* unattended re-entry must not fire on it). No-ops (no state write) when
|
|
417
|
+
* nothing is recorded; never throws on an untracked issue.
|
|
418
|
+
*/
|
|
419
|
+
async clearWindowHalt(issueNumber) {
|
|
420
|
+
await this.withLock(async () => {
|
|
421
|
+
const state = await this.getState();
|
|
422
|
+
const issueState = state.issues[String(issueNumber)];
|
|
423
|
+
if (!issueState?.windowHalt)
|
|
424
|
+
return;
|
|
425
|
+
issueState.windowHalt = undefined;
|
|
426
|
+
issueState.lastActivity = new Date().toISOString();
|
|
427
|
+
await this.saveState(state);
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Consume one re-entry attempt for a halted issue (#892 AC-3). Returns the
|
|
432
|
+
* new count, or `null` when the issue has no window-halt record (nothing to
|
|
433
|
+
* resume). Called by `sequant resume` immediately before re-entry so a
|
|
434
|
+
* re-entry that halts again (window still closed) is already counted.
|
|
435
|
+
*/
|
|
436
|
+
async incrementWindowHaltReentries(issueNumber) {
|
|
437
|
+
let newCount = null;
|
|
438
|
+
await this.withLock(async () => {
|
|
439
|
+
const state = await this.getState();
|
|
440
|
+
const issueState = state.issues[String(issueNumber)];
|
|
441
|
+
if (!issueState?.windowHalt)
|
|
442
|
+
return;
|
|
443
|
+
issueState.windowHalt.reentries += 1;
|
|
444
|
+
newCount = issueState.windowHalt.reentries;
|
|
445
|
+
issueState.lastActivity = new Date().toISOString();
|
|
446
|
+
await this.saveState(state);
|
|
447
|
+
});
|
|
448
|
+
return newCount;
|
|
449
|
+
}
|
|
367
450
|
/**
|
|
368
451
|
* Update worktree information for an issue
|
|
369
452
|
*/
|
|
@@ -213,6 +213,14 @@ export declare const RelayStateSchema: z.ZodObject<{
|
|
|
213
213
|
messageCount: z.ZodNumber;
|
|
214
214
|
}, z.core.$strip>;
|
|
215
215
|
export type RelayState = z.infer<typeof RelayStateSchema>;
|
|
216
|
+
/**
|
|
217
|
+
* Maximum scheduler re-entries per window-halted issue (#892 AC-3). Mirrors
|
|
218
|
+
* `AUTO_WAIT_MAX_WAITS` (#804): both bound how often sequant returns to the
|
|
219
|
+
* same closed rate-limit window before declaring the halt terminal. Lives
|
|
220
|
+
* here — beside the `windowHalt.reentries` field it bounds — so display
|
|
221
|
+
* surfaces (`sequant status`) can import it without pulling in the run path.
|
|
222
|
+
*/
|
|
223
|
+
export declare const MAX_RESUME_REENTRIES = 2;
|
|
216
224
|
/**
|
|
217
225
|
* Complete state for a single issue
|
|
218
226
|
*/
|
|
@@ -342,6 +350,15 @@ export declare const IssueStateSchema: z.ZodObject<{
|
|
|
342
350
|
token: z.ZodString;
|
|
343
351
|
originCwd: z.ZodString;
|
|
344
352
|
}, z.core.$strip>>;
|
|
353
|
+
autoWait: z.ZodOptional<z.ZodObject<{
|
|
354
|
+
wakeAt: z.ZodString;
|
|
355
|
+
phase: z.ZodString;
|
|
356
|
+
}, z.core.$strip>>;
|
|
357
|
+
windowHalt: z.ZodOptional<z.ZodObject<{
|
|
358
|
+
resumeAt: z.ZodString;
|
|
359
|
+
phase: z.ZodString;
|
|
360
|
+
reentries: z.ZodNumber;
|
|
361
|
+
}, z.core.$strip>>;
|
|
345
362
|
resolvedAt: z.ZodOptional<z.ZodString>;
|
|
346
363
|
lastActivity: z.ZodString;
|
|
347
364
|
createdAt: z.ZodString;
|
|
@@ -482,6 +499,15 @@ export declare const WorkflowStateSchema: z.ZodObject<{
|
|
|
482
499
|
token: z.ZodString;
|
|
483
500
|
originCwd: z.ZodString;
|
|
484
501
|
}, z.core.$strip>>;
|
|
502
|
+
autoWait: z.ZodOptional<z.ZodObject<{
|
|
503
|
+
wakeAt: z.ZodString;
|
|
504
|
+
phase: z.ZodString;
|
|
505
|
+
}, z.core.$strip>>;
|
|
506
|
+
windowHalt: z.ZodOptional<z.ZodObject<{
|
|
507
|
+
resumeAt: z.ZodString;
|
|
508
|
+
phase: z.ZodString;
|
|
509
|
+
reentries: z.ZodNumber;
|
|
510
|
+
}, z.core.$strip>>;
|
|
485
511
|
resolvedAt: z.ZodOptional<z.ZodString>;
|
|
486
512
|
lastActivity: z.ZodString;
|
|
487
513
|
createdAt: z.ZodString;
|
|
@@ -197,6 +197,14 @@ export const RelayStateSchema = z.object({
|
|
|
197
197
|
startedAt: z.string().datetime(),
|
|
198
198
|
messageCount: z.number().int().nonnegative(),
|
|
199
199
|
});
|
|
200
|
+
/**
|
|
201
|
+
* Maximum scheduler re-entries per window-halted issue (#892 AC-3). Mirrors
|
|
202
|
+
* `AUTO_WAIT_MAX_WAITS` (#804): both bound how often sequant returns to the
|
|
203
|
+
* same closed rate-limit window before declaring the halt terminal. Lives
|
|
204
|
+
* here — beside the `windowHalt.reentries` field it bounds — so display
|
|
205
|
+
* surfaces (`sequant status`) can import it without pulling in the run path.
|
|
206
|
+
*/
|
|
207
|
+
export const MAX_RESUME_REENTRIES = 2;
|
|
200
208
|
/**
|
|
201
209
|
* Complete state for a single issue
|
|
202
210
|
*/
|
|
@@ -248,6 +256,45 @@ export const IssueStateSchema = z.object({
|
|
|
248
256
|
originCwd: z.string(),
|
|
249
257
|
})
|
|
250
258
|
.optional(),
|
|
259
|
+
/**
|
|
260
|
+
* An in-progress auto-wait (#860): the phase is deliberately paused until a
|
|
261
|
+
* rate-limit window reopens at `wakeAt`. Written at wait start and cleared
|
|
262
|
+
* on wake, so `sequant status` can say "waiting until 07:01" instead of
|
|
263
|
+
* showing an hours-stale in-progress phase indistinguishable from a hang
|
|
264
|
+
* (#856). The write itself also refreshes `state.json`'s mtime — the
|
|
265
|
+
* activity proxy several liveness surfaces poll.
|
|
266
|
+
*/
|
|
267
|
+
autoWait: z
|
|
268
|
+
.object({
|
|
269
|
+
/** ISO timestamp of the scheduled wake (resetsAt + buffer). */
|
|
270
|
+
wakeAt: z.string().datetime(),
|
|
271
|
+
/** Phase that is paused. */
|
|
272
|
+
phase: z.string(),
|
|
273
|
+
})
|
|
274
|
+
.optional(),
|
|
275
|
+
/**
|
|
276
|
+
* A durable halt on an exhausted rate-limit window (#892): the run exited
|
|
277
|
+
* cleanly (lock released) and can be re-entered by `sequant resume` once
|
|
278
|
+
* the window reopens at `resumeAt`. Unlike `autoWait` (a live in-process
|
|
279
|
+
* pause), this record survives reboots — it is the machine-readable
|
|
280
|
+
* contract between the halt and an unattended scheduler re-entry.
|
|
281
|
+
* Written at a waitable-window halt, cleared on any phase success or a
|
|
282
|
+
* non-window failure (the halt cause is then no longer waitable).
|
|
283
|
+
*/
|
|
284
|
+
windowHalt: z
|
|
285
|
+
.object({
|
|
286
|
+
/** ISO timestamp after which re-entry can proceed (resetsAt + buffer). */
|
|
287
|
+
resumeAt: z.string().datetime(),
|
|
288
|
+
/** Phase that halted. */
|
|
289
|
+
phase: z.string(),
|
|
290
|
+
/**
|
|
291
|
+
* Re-entry attempts consumed (#892 AC-3). Incremented by `sequant
|
|
292
|
+
* resume` before each re-entry; a window that never reopens is bounded
|
|
293
|
+
* by MAX_RESUME_REENTRIES instead of ping-ponging a scheduler.
|
|
294
|
+
*/
|
|
295
|
+
reentries: z.number().int().min(0),
|
|
296
|
+
})
|
|
297
|
+
.optional(),
|
|
251
298
|
/** When the issue transitioned to a terminal status (merged/abandoned/closed) */
|
|
252
299
|
resolvedAt: z.string().datetime().optional(),
|
|
253
300
|
/** Most recent activity timestamp */
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared status derivation for the run's live state machines (#766).
|
|
3
|
+
*
|
|
4
|
+
* Both the orchestrator's `IssueRuntimeState` machine and the renderer's
|
|
5
|
+
* `IssueState` machine used to *pin* an issue `failed` on any phase failure and
|
|
6
|
+
* never de-escalate — so a phase that failed on an early quality-loop iteration
|
|
7
|
+
* left the card red even after a later iteration recovered and shipped the PR.
|
|
8
|
+
*
|
|
9
|
+
* The fix derives the failed verdict from the CURRENT phase slots instead.
|
|
10
|
+
* `findOrAppendPhase` (orchestrator) and the renderer both key phases by name,
|
|
11
|
+
* so a retried phase overwrites its slot with the latest attempt — the slot
|
|
12
|
+
* already holds "the last attempt". The one phase that is NOT overwritten on
|
|
13
|
+
* recovery is `loop`: it is an auxiliary recovery step that only runs after a
|
|
14
|
+
* failure, so a stale failed `loop` slot must not pin the issue. Excluding
|
|
15
|
+
* `loop` never hides a genuine failure — an unrecovered pipeline failure always
|
|
16
|
+
* leaves a non-loop phase's latest slot failed too (the qa/exec the loop was
|
|
17
|
+
* trying to fix).
|
|
18
|
+
*/
|
|
19
|
+
/** Auxiliary recovery phase — excluded from the failed verdict (see above). */
|
|
20
|
+
export declare const LOOP_PHASE = "loop";
|
|
21
|
+
/** Minimal phase shape both live state machines satisfy. */
|
|
22
|
+
export interface DerivablePhase {
|
|
23
|
+
name: string;
|
|
24
|
+
status: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* True iff a non-loop phase's latest slot is `failed`. The single source of
|
|
28
|
+
* truth for "is this issue failed" across both live state machines (#766 AC-2).
|
|
29
|
+
*/
|
|
30
|
+
export declare function pipelineHasFailed(phases: DerivablePhase[]): boolean;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared status derivation for the run's live state machines (#766).
|
|
3
|
+
*
|
|
4
|
+
* Both the orchestrator's `IssueRuntimeState` machine and the renderer's
|
|
5
|
+
* `IssueState` machine used to *pin* an issue `failed` on any phase failure and
|
|
6
|
+
* never de-escalate — so a phase that failed on an early quality-loop iteration
|
|
7
|
+
* left the card red even after a later iteration recovered and shipped the PR.
|
|
8
|
+
*
|
|
9
|
+
* The fix derives the failed verdict from the CURRENT phase slots instead.
|
|
10
|
+
* `findOrAppendPhase` (orchestrator) and the renderer both key phases by name,
|
|
11
|
+
* so a retried phase overwrites its slot with the latest attempt — the slot
|
|
12
|
+
* already holds "the last attempt". The one phase that is NOT overwritten on
|
|
13
|
+
* recovery is `loop`: it is an auxiliary recovery step that only runs after a
|
|
14
|
+
* failure, so a stale failed `loop` slot must not pin the issue. Excluding
|
|
15
|
+
* `loop` never hides a genuine failure — an unrecovered pipeline failure always
|
|
16
|
+
* leaves a non-loop phase's latest slot failed too (the qa/exec the loop was
|
|
17
|
+
* trying to fix).
|
|
18
|
+
*/
|
|
19
|
+
/** Auxiliary recovery phase — excluded from the failed verdict (see above). */
|
|
20
|
+
export const LOOP_PHASE = "loop";
|
|
21
|
+
/**
|
|
22
|
+
* True iff a non-loop phase's latest slot is `failed`. The single source of
|
|
23
|
+
* truth for "is this issue failed" across both live state machines (#766 AC-2).
|
|
24
|
+
*/
|
|
25
|
+
export function pipelineHasFailed(phases) {
|
|
26
|
+
return phases.some((p) => p.name !== LOOP_PHASE && p.status === "failed");
|
|
27
|
+
}
|