sequant 2.2.0 → 2.4.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 +81 -5
- package/dist/bin/cli.js +140 -13
- package/dist/src/commands/abort.d.ts +36 -0
- package/dist/src/commands/abort.js +138 -0
- package/dist/src/commands/doctor.d.ts +25 -0
- package/dist/src/commands/doctor.js +36 -1
- package/dist/src/commands/locks.d.ts +67 -0
- package/dist/src/commands/locks.js +290 -0
- package/dist/src/commands/merge.js +11 -0
- package/dist/src/commands/prompt.d.ts +46 -0
- package/dist/src/commands/prompt.js +273 -0
- package/dist/src/commands/run-display.d.ts +11 -2
- package/dist/src/commands/run-display.js +62 -28
- package/dist/src/commands/run-progress.d.ts +42 -0
- package/dist/src/commands/run-progress.js +93 -0
- package/dist/src/commands/run.js +90 -18
- package/dist/src/commands/stats.d.ts +2 -0
- package/dist/src/commands/stats.js +94 -8
- package/dist/src/commands/status.js +12 -0
- package/dist/src/commands/watch.d.ts +18 -0
- package/dist/src/commands/watch.js +211 -0
- package/dist/src/lib/ac-linter.d.ts +1 -1
- package/dist/src/lib/ac-linter.js +81 -0
- package/dist/src/lib/assess-collision-detect.d.ts +91 -0
- package/dist/src/lib/assess-collision-detect.js +217 -0
- package/dist/src/lib/assess-comment-parser.d.ts +59 -1
- package/dist/src/lib/assess-comment-parser.js +124 -2
- package/dist/src/lib/cli-ui/format.d.ts +19 -0
- package/dist/src/lib/cli-ui/format.js +34 -0
- package/dist/src/lib/cli-ui/run-renderer-types.d.ts +220 -0
- package/dist/src/lib/cli-ui/run-renderer-types.js +7 -0
- package/dist/src/lib/cli-ui/run-renderer.d.ts +265 -0
- package/dist/src/lib/cli-ui/run-renderer.js +1390 -0
- package/dist/src/lib/cli-ui/scrollback-harness.d.ts +112 -0
- package/dist/src/lib/cli-ui/scrollback-harness.js +294 -0
- package/dist/src/lib/heuristics/behavior-rule-detector.d.ts +94 -0
- package/dist/src/lib/heuristics/behavior-rule-detector.js +467 -0
- package/dist/src/lib/locks/index.d.ts +7 -0
- package/dist/src/lib/locks/index.js +5 -0
- package/dist/src/lib/locks/lock-manager.d.ts +168 -0
- package/dist/src/lib/locks/lock-manager.js +433 -0
- package/dist/src/lib/locks/types.d.ts +59 -0
- package/dist/src/lib/locks/types.js +31 -0
- package/dist/src/lib/merge-check/types.js +1 -1
- package/dist/src/lib/qa/markdown-only-ci.d.ts +46 -0
- package/dist/src/lib/qa/markdown-only-ci.js +74 -0
- package/dist/src/lib/relay/activation.d.ts +60 -0
- package/dist/src/lib/relay/activation.js +122 -0
- package/dist/src/lib/relay/archive.d.ts +34 -0
- package/dist/src/lib/relay/archive.js +112 -0
- package/dist/src/lib/relay/frame.d.ts +20 -0
- package/dist/src/lib/relay/frame.js +76 -0
- package/dist/src/lib/relay/index.d.ts +13 -0
- package/dist/src/lib/relay/index.js +13 -0
- package/dist/src/lib/relay/paths.d.ts +43 -0
- package/dist/src/lib/relay/paths.js +59 -0
- package/dist/src/lib/relay/pid.d.ts +34 -0
- package/dist/src/lib/relay/pid.js +72 -0
- package/dist/src/lib/relay/reader.d.ts +35 -0
- package/dist/src/lib/relay/reader.js +115 -0
- package/dist/src/lib/relay/types.d.ts +70 -0
- package/dist/src/lib/relay/types.js +85 -0
- package/dist/src/lib/relay/writer.d.ts +48 -0
- package/dist/src/lib/relay/writer.js +113 -0
- package/dist/src/lib/settings.d.ts +31 -1
- package/dist/src/lib/settings.js +18 -3
- package/dist/src/lib/version-check.d.ts +60 -5
- package/dist/src/lib/version-check.js +97 -9
- package/dist/src/lib/workflow/batch-executor.d.ts +20 -1
- package/dist/src/lib/workflow/batch-executor.js +274 -185
- package/dist/src/lib/workflow/config-resolver.js +4 -0
- package/dist/src/lib/workflow/drivers/agent-driver.d.ts +48 -1
- package/dist/src/lib/workflow/drivers/aider.d.ts +7 -1
- package/dist/src/lib/workflow/drivers/aider.js +9 -0
- package/dist/src/lib/workflow/drivers/claude-code.d.ts +17 -1
- package/dist/src/lib/workflow/drivers/claude-code.js +51 -2
- package/dist/src/lib/workflow/drivers/index.d.ts +1 -1
- package/dist/src/lib/workflow/event-emitter.d.ts +157 -0
- package/dist/src/lib/workflow/event-emitter.js +102 -0
- package/dist/src/lib/workflow/heartbeat.d.ts +71 -0
- package/dist/src/lib/workflow/heartbeat.js +194 -0
- package/dist/src/lib/workflow/notice.d.ts +32 -0
- package/dist/src/lib/workflow/notice.js +38 -0
- package/dist/src/lib/workflow/phase-executor.d.ts +58 -16
- package/dist/src/lib/workflow/phase-executor.js +244 -130
- package/dist/src/lib/workflow/phase-mapper.d.ts +27 -13
- package/dist/src/lib/workflow/phase-mapper.js +70 -51
- package/dist/src/lib/workflow/phase-registry.d.ts +127 -0
- package/dist/src/lib/workflow/phase-registry.js +233 -0
- package/dist/src/lib/workflow/platforms/github.d.ts +1 -1
- package/dist/src/lib/workflow/platforms/github.js +20 -3
- package/dist/src/lib/workflow/pr-status.d.ts +18 -2
- package/dist/src/lib/workflow/pr-status.js +41 -9
- package/dist/src/lib/workflow/qa-stagnation.d.ts +117 -0
- package/dist/src/lib/workflow/qa-stagnation.js +179 -0
- package/dist/src/lib/workflow/run-log-schema.d.ts +5 -55
- package/dist/src/lib/workflow/run-orchestrator.d.ts +70 -1
- package/dist/src/lib/workflow/run-orchestrator.js +464 -25
- package/dist/src/lib/workflow/run-reflect.js +1 -1
- package/dist/src/lib/workflow/run-state.d.ts +71 -0
- package/dist/src/lib/workflow/run-state.js +14 -0
- package/dist/src/lib/workflow/state-cleanup.d.ts +13 -5
- package/dist/src/lib/workflow/state-cleanup.js +17 -5
- package/dist/src/lib/workflow/state-manager.d.ts +31 -2
- package/dist/src/lib/workflow/state-manager.js +64 -1
- package/dist/src/lib/workflow/state-schema.d.ts +82 -35
- package/dist/src/lib/workflow/state-schema.js +63 -4
- package/dist/src/lib/workflow/types.d.ts +139 -16
- package/dist/src/lib/workflow/types.js +18 -13
- package/dist/src/lib/workflow/worktree-manager.d.ts +8 -1
- package/dist/src/lib/workflow/worktree-manager.js +15 -6
- package/dist/src/mcp/tools/run.d.ts +44 -0
- package/dist/src/mcp/tools/run.js +104 -13
- package/dist/src/ui/tui/App.d.ts +14 -0
- package/dist/src/ui/tui/App.js +41 -0
- package/dist/src/ui/tui/ElapsedTimer.d.ts +10 -0
- package/dist/src/ui/tui/ElapsedTimer.js +31 -0
- package/dist/src/ui/tui/Header.d.ts +6 -0
- package/dist/src/ui/tui/Header.js +15 -0
- package/dist/src/ui/tui/IssueBox.d.ts +16 -0
- package/dist/src/ui/tui/IssueBox.js +68 -0
- package/dist/src/ui/tui/Spinner.d.ts +9 -0
- package/dist/src/ui/tui/Spinner.js +18 -0
- package/dist/src/ui/tui/index.d.ts +15 -0
- package/dist/src/ui/tui/index.js +29 -0
- package/dist/src/ui/tui/theme.d.ts +29 -0
- package/dist/src/ui/tui/theme.js +52 -0
- package/dist/src/ui/tui/truncate.d.ts +11 -0
- package/dist/src/ui/tui/truncate.js +31 -0
- package/package.json +14 -6
- package/templates/agents/sequant-explorer.md +1 -0
- package/templates/agents/sequant-qa-checker.md +2 -1
- package/templates/agents/sequant-testgen.md +1 -0
- package/templates/hooks/post-tool.sh +92 -0
- package/templates/hooks/pre-tool.sh +18 -9
- package/templates/hooks/relay-check.sh +107 -0
- package/templates/relay/frame.txt +11 -0
- package/templates/scripts/cleanup-worktree.sh +25 -3
- package/templates/scripts/new-feature.sh +6 -0
- package/templates/skills/_shared/references/behavior-rule-detection.md +205 -0
- package/templates/skills/_shared/references/subagent-types.md +21 -8
- package/templates/skills/assess/SKILL.md +122 -68
- package/templates/skills/assess/references/predicted-collision-detection.md +109 -0
- package/templates/skills/docs/SKILL.md +141 -22
- package/templates/skills/exec/SKILL.md +10 -8
- package/templates/skills/fullsolve/SKILL.md +79 -5
- package/templates/skills/loop/SKILL.md +28 -0
- package/templates/skills/merger/SKILL.md +621 -0
- package/templates/skills/qa/SKILL.md +727 -8
- package/templates/skills/setup/SKILL.md +12 -6
- package/templates/skills/spec/SKILL.md +52 -0
- package/templates/skills/spec/references/parallel-groups.md +7 -0
- package/templates/skills/spec/references/recommended-workflow.md +4 -2
- package/templates/skills/testgen/SKILL.md +24 -17
|
@@ -7,61 +7,69 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module phase-mapper
|
|
9
9
|
*/
|
|
10
|
+
import { phaseRegistry } from "./phase-registry.js";
|
|
10
11
|
/**
|
|
11
|
-
*
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
*
|
|
12
|
+
* Bug-related labels (used by downstream metadata consumers).
|
|
13
|
+
*
|
|
14
|
+
* Issue-type metadata — NOT phase-trigger rules. The registry-driven
|
|
15
|
+
* `detectPhasesFromLabels` below does not consult this list. It stays
|
|
16
|
+
* here because `batch-executor.ts` and other modules read it for
|
|
17
|
+
* `issueType` propagation and similar non-phase concerns.
|
|
16
18
|
*/
|
|
17
19
|
export const BUG_LABELS = ["bug", "fix", "hotfix", "patch"];
|
|
18
20
|
/**
|
|
19
|
-
* Documentation labels
|
|
21
|
+
* Documentation labels (used for issueType propagation and downstream metadata).
|
|
22
|
+
*
|
|
23
|
+
* Issue-type metadata — NOT phase-trigger rules. See BUG_LABELS comment.
|
|
20
24
|
*/
|
|
21
25
|
export const DOCS_LABELS = ["docs", "documentation", "readme"];
|
|
22
26
|
/**
|
|
23
|
-
* Complex labels that enable quality loop
|
|
27
|
+
* Complex labels that enable quality loop.
|
|
28
|
+
*
|
|
29
|
+
* Quality-loop trigger — NOT a phase-trigger rule (does not add the loop
|
|
30
|
+
* *phase*; only flips the `qualityLoop` flag on the run config). Kept
|
|
31
|
+
* out of the phase registry by design.
|
|
24
32
|
*/
|
|
25
33
|
export const COMPLEX_LABELS = ["complex", "refactor", "breaking", "major"];
|
|
26
34
|
/**
|
|
27
|
-
*
|
|
35
|
+
* Look up label-based detect rules from the registry, returning the set
|
|
36
|
+
* of phases whose `detect.labels` intersect the issue's labels. Comparison
|
|
37
|
+
* is case-insensitive (labels lowercased at the call site).
|
|
28
38
|
*/
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
function detectPhasesFromRegistry(lowerLabels) {
|
|
40
|
+
const matched = new Set();
|
|
41
|
+
for (const def of phaseRegistry.list()) {
|
|
42
|
+
const triggers = def.detect?.labels;
|
|
43
|
+
if (!triggers || triggers.length === 0)
|
|
44
|
+
continue;
|
|
45
|
+
const hit = triggers.some((t) => lowerLabels.includes(t.toLowerCase()));
|
|
46
|
+
if (hit)
|
|
47
|
+
matched.add(def.name);
|
|
48
|
+
}
|
|
49
|
+
return matched;
|
|
50
|
+
}
|
|
36
51
|
/**
|
|
37
|
-
* Detect phases based on issue labels (like /assess logic)
|
|
52
|
+
* Detect phases based on issue labels (like /assess logic).
|
|
53
|
+
*
|
|
54
|
+
* Label → phase mapping now lives in `PhaseDefinition.detect.labels`. Only
|
|
55
|
+
* the *insertion position* of detected phases remains baked in here, because
|
|
56
|
+
* pipeline ordering depends on the phase's role (security-review goes after
|
|
57
|
+
* spec; test goes before qa).
|
|
38
58
|
*/
|
|
39
59
|
export function detectPhasesFromLabels(labels) {
|
|
40
60
|
const lowerLabels = labels.map((l) => l.toLowerCase());
|
|
41
|
-
//
|
|
42
|
-
const isBugFix = lowerLabels.some((label) => BUG_LABELS.some((bugLabel) => label === bugLabel));
|
|
43
|
-
// Check for docs labels → exec → qa (skip spec)
|
|
44
|
-
const isDocs = lowerLabels.some((label) => DOCS_LABELS.some((docsLabel) => label === docsLabel));
|
|
45
|
-
// Check for UI labels → add test phase
|
|
46
|
-
const isUI = lowerLabels.some((label) => UI_LABELS.some((uiLabel) => label === uiLabel));
|
|
47
|
-
// Check for complex labels → enable quality loop
|
|
61
|
+
// Quality loop is a registry-independent label trigger (see COMPLEX_LABELS).
|
|
48
62
|
const isComplex = lowerLabels.some((label) => COMPLEX_LABELS.some((complexLabel) => label === complexLabel));
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
phases = ["spec", "exec", "test", "qa"];
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
// Standard workflow: spec → exec → qa
|
|
63
|
-
phases = ["spec", "exec", "qa"];
|
|
64
|
-
}
|
|
63
|
+
const matched = detectPhasesFromRegistry(lowerLabels);
|
|
64
|
+
const isUI = matched.has("test");
|
|
65
|
+
const isSecurity = matched.has("security-review");
|
|
66
|
+
// Build phase list — spec is always included by default (#533).
|
|
67
|
+
// Bug/docs labels no longer short-circuit spec; downstream consumers
|
|
68
|
+
// (e.g. `issueType: "docs"` propagation) still use DOCS_LABELS for
|
|
69
|
+
// metadata purposes, not for phase selection.
|
|
70
|
+
const phases = isUI
|
|
71
|
+
? ["spec", "exec", "test", "qa"]
|
|
72
|
+
: ["spec", "exec", "qa"];
|
|
65
73
|
// Add security-review phase after spec if security labels detected
|
|
66
74
|
if (isSecurity && phases.includes("spec")) {
|
|
67
75
|
const specIndex = phases.indexOf("spec");
|
|
@@ -89,18 +97,10 @@ export function parseRecommendedWorkflow(output) {
|
|
|
89
97
|
.split(/\s*→\s*|\s*->\s*|\s*,\s*/)
|
|
90
98
|
.map((p) => p.trim().toLowerCase())
|
|
91
99
|
.filter((p) => p.length > 0);
|
|
92
|
-
// Validate
|
|
100
|
+
// Validate against the registry — accepts any registered phase.
|
|
93
101
|
const validPhases = [];
|
|
94
102
|
for (const name of phaseNames) {
|
|
95
|
-
if (
|
|
96
|
-
"spec",
|
|
97
|
-
"security-review",
|
|
98
|
-
"testgen",
|
|
99
|
-
"exec",
|
|
100
|
-
"test",
|
|
101
|
-
"qa",
|
|
102
|
-
"loop",
|
|
103
|
-
].includes(name)) {
|
|
103
|
+
if (phaseRegistry.has(name)) {
|
|
104
104
|
validPhases.push(name);
|
|
105
105
|
}
|
|
106
106
|
}
|
|
@@ -115,10 +115,21 @@ export function parseRecommendedWorkflow(output) {
|
|
|
115
115
|
return { phases: validPhases, qualityLoop };
|
|
116
116
|
}
|
|
117
117
|
/**
|
|
118
|
-
* Check if an issue has UI-related labels
|
|
118
|
+
* Check if an issue has UI-related labels.
|
|
119
|
+
*
|
|
120
|
+
* Sources the label list from the `test` phase's `detect.labels` entry in
|
|
121
|
+
* the registry — same data as `detectPhasesFromLabels` consults, just
|
|
122
|
+
* exposed as a boolean for callers that only need the yes/no answer
|
|
123
|
+
* (e.g. test phase insertion in `determinePhasesForIssue`).
|
|
119
124
|
*/
|
|
120
125
|
export function hasUILabels(labels) {
|
|
121
|
-
|
|
126
|
+
const testTriggers = phaseRegistry.has("test")
|
|
127
|
+
? (phaseRegistry.get("test").detect?.labels ?? [])
|
|
128
|
+
: [];
|
|
129
|
+
if (testTriggers.length === 0)
|
|
130
|
+
return false;
|
|
131
|
+
const lowered = new Set(testTriggers.map((t) => t.toLowerCase()));
|
|
132
|
+
return labels.some((label) => lowered.has(label.toLowerCase()));
|
|
122
133
|
}
|
|
123
134
|
/**
|
|
124
135
|
* Determine phases to run based on options and issue labels
|
|
@@ -132,6 +143,14 @@ export function determinePhasesForIssue(basePhases, labels, options) {
|
|
|
132
143
|
phases.splice(specIndex + 1, 0, "testgen");
|
|
133
144
|
}
|
|
134
145
|
}
|
|
146
|
+
// Add security-review phase after spec if requested.
|
|
147
|
+
// Idempotent vs label-based auto-detection in detectPhasesFromLabels.
|
|
148
|
+
if (options.securityReview && phases.includes("spec")) {
|
|
149
|
+
const specIndex = phases.indexOf("spec");
|
|
150
|
+
if (!phases.includes("security-review")) {
|
|
151
|
+
phases.splice(specIndex + 1, 0, "security-review");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
135
154
|
// Auto-detect UI issues and add test phase
|
|
136
155
|
if (hasUILabels(labels) && !phases.includes("test")) {
|
|
137
156
|
// Add test phase before qa if present, otherwise at the end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase registry — single source of truth for workflow phase definitions.
|
|
3
|
+
*
|
|
4
|
+
* Replaces scattered constants (`PHASE_PROMPTS`, `AIDER_PHASE_PROMPTS`,
|
|
5
|
+
* `ISOLATED_PHASES`, `UI_LABELS`, `SECURITY_LABELS`) with a uniform record
|
|
6
|
+
* per phase. All 9 built-in phases register here at module load — no
|
|
7
|
+
* special-casing inside consumer code.
|
|
8
|
+
*
|
|
9
|
+
* Built-in registrations live at the bottom of this file rather than in a
|
|
10
|
+
* separate `built-in-phases.ts` module. The colocated layout follows the
|
|
11
|
+
* existing `drivers/index.ts` pattern and avoids the ESM-cycle pitfall of
|
|
12
|
+
* a separate bootstrap module that re-imports the registry singleton
|
|
13
|
+
* before the singleton is fully initialized.
|
|
14
|
+
*
|
|
15
|
+
* User-extensibility (filesystem discovery of `.sequant/phases/`) is
|
|
16
|
+
* deliberately deferred — see issue #505 descoping comment.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Per-driver overrides for a phase. Today only `promptTemplate` is supported;
|
|
20
|
+
* extend the inner record when additional fields need per-driver values.
|
|
21
|
+
*/
|
|
22
|
+
export interface DriverOverride {
|
|
23
|
+
promptTemplate?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Retry policy for a single phase. Phases without this field fall back to
|
|
27
|
+
* the global cold-start retry defaults in `phase-executor.ts`. The fields
|
|
28
|
+
* are deliberately optional — a phase only needs to specify what differs.
|
|
29
|
+
*/
|
|
30
|
+
export interface RetryStrategy {
|
|
31
|
+
/** Override the cold-start retry attempt count for this phase. */
|
|
32
|
+
maxRetries?: number;
|
|
33
|
+
/** Initial backoff in ms before the first retry. */
|
|
34
|
+
backoffMs?: number;
|
|
35
|
+
/** Override the cold-start threshold (seconds). */
|
|
36
|
+
coldStartThreshold?: number;
|
|
37
|
+
/** Extra retries beyond cold-start (e.g. for transient API errors). */
|
|
38
|
+
extraRetries?: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Auto-detection rules consumed by phase-mapper.ts.
|
|
42
|
+
* `labels` is an exact-match list (case-insensitive at the call site).
|
|
43
|
+
*/
|
|
44
|
+
export interface DetectRules {
|
|
45
|
+
labels?: string[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Definition of a workflow phase. Registered at startup via
|
|
49
|
+
* `phaseRegistry.register(...)` and consumed by phase-executor, phase-mapper,
|
|
50
|
+
* and the CLI validator.
|
|
51
|
+
*/
|
|
52
|
+
export interface PhaseDefinition {
|
|
53
|
+
/** Phase name (matches the skill template directory + CLI `--phases` token). */
|
|
54
|
+
name: string;
|
|
55
|
+
/** Skill template directory under `templates/skills/<skill>/SKILL.md`. */
|
|
56
|
+
skill: string;
|
|
57
|
+
/**
|
|
58
|
+
* Natural-language prompt for the default (Claude Code) driver. The token
|
|
59
|
+
* `{issue}` is substituted with the GitHub issue number at execution time.
|
|
60
|
+
*/
|
|
61
|
+
promptTemplate: string;
|
|
62
|
+
/**
|
|
63
|
+
* When true, phase-executor runs this phase inside the issue worktree.
|
|
64
|
+
* `spec`, `verify`, and `merger` run in the main repo (no worktree).
|
|
65
|
+
*/
|
|
66
|
+
requiresWorktree: boolean;
|
|
67
|
+
/** Optional per-phase retry overrides. */
|
|
68
|
+
retryStrategy?: RetryStrategy;
|
|
69
|
+
/** Optional auto-detection rules. */
|
|
70
|
+
detect?: DetectRules;
|
|
71
|
+
/**
|
|
72
|
+
* Per-driver overrides. Keyed by agent name (e.g. `"aider"`). When the
|
|
73
|
+
* orchestrator runs a non-Claude driver, the corresponding override's
|
|
74
|
+
* `promptTemplate` (if present) replaces the default.
|
|
75
|
+
*/
|
|
76
|
+
driverOverrides?: Record<string, DriverOverride>;
|
|
77
|
+
/**
|
|
78
|
+
* Insertion order hint. Used by phase-mapper to sort label-detected phases
|
|
79
|
+
* into a deterministic pipeline position. Defaults to 0.
|
|
80
|
+
*/
|
|
81
|
+
order?: number;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* In-memory registry of phase definitions. Single mutable instance lives
|
|
85
|
+
* in this module — see the exported `phaseRegistry` constant.
|
|
86
|
+
*
|
|
87
|
+
* The class is intentionally minimal (no lifecycle hooks, no async). All
|
|
88
|
+
* mutations happen synchronously at module load by the built-in registrations
|
|
89
|
+
* at the bottom of this file.
|
|
90
|
+
*/
|
|
91
|
+
export declare class PhaseRegistry {
|
|
92
|
+
private readonly definitions;
|
|
93
|
+
/**
|
|
94
|
+
* Register a phase definition. Throws on duplicate names so misconfigured
|
|
95
|
+
* bootstrap modules surface immediately instead of silently overwriting.
|
|
96
|
+
*/
|
|
97
|
+
register(definition: PhaseDefinition): void;
|
|
98
|
+
/**
|
|
99
|
+
* Retrieve a phase by name. Throws with a "did you mean" list when the
|
|
100
|
+
* lookup fails — clearer than a downstream "undefined.promptTemplate".
|
|
101
|
+
*/
|
|
102
|
+
get(name: string): PhaseDefinition;
|
|
103
|
+
/** True when a phase with this name is registered. */
|
|
104
|
+
has(name: string): boolean;
|
|
105
|
+
/**
|
|
106
|
+
* All registered phase definitions in insertion order. Insertion order
|
|
107
|
+
* is also the canonical pipeline order (see registrations below).
|
|
108
|
+
*/
|
|
109
|
+
list(): PhaseDefinition[];
|
|
110
|
+
/**
|
|
111
|
+
* All registered phase names in insertion order. Replaces the
|
|
112
|
+
* `PhaseSchema.options` array literal exposed by the previous typed-enum
|
|
113
|
+
* `PhaseSchema`.
|
|
114
|
+
*/
|
|
115
|
+
names(): string[];
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Singleton registry instance. All consumer modules (phase-executor,
|
|
119
|
+
* phase-mapper, types.ts, CLI) read from this same instance — there is
|
|
120
|
+
* no second registry.
|
|
121
|
+
*/
|
|
122
|
+
export declare const phaseRegistry: PhaseRegistry;
|
|
123
|
+
/**
|
|
124
|
+
* Convenience accessor for the registered phase names. Used by Zod refines,
|
|
125
|
+
* tests, and CLI validation in place of the removed `PhaseSchema.options`.
|
|
126
|
+
*/
|
|
127
|
+
export declare function getPhaseNames(): string[];
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase registry — single source of truth for workflow phase definitions.
|
|
3
|
+
*
|
|
4
|
+
* Replaces scattered constants (`PHASE_PROMPTS`, `AIDER_PHASE_PROMPTS`,
|
|
5
|
+
* `ISOLATED_PHASES`, `UI_LABELS`, `SECURITY_LABELS`) with a uniform record
|
|
6
|
+
* per phase. All 9 built-in phases register here at module load — no
|
|
7
|
+
* special-casing inside consumer code.
|
|
8
|
+
*
|
|
9
|
+
* Built-in registrations live at the bottom of this file rather than in a
|
|
10
|
+
* separate `built-in-phases.ts` module. The colocated layout follows the
|
|
11
|
+
* existing `drivers/index.ts` pattern and avoids the ESM-cycle pitfall of
|
|
12
|
+
* a separate bootstrap module that re-imports the registry singleton
|
|
13
|
+
* before the singleton is fully initialized.
|
|
14
|
+
*
|
|
15
|
+
* User-extensibility (filesystem discovery of `.sequant/phases/`) is
|
|
16
|
+
* deliberately deferred — see issue #505 descoping comment.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* In-memory registry of phase definitions. Single mutable instance lives
|
|
20
|
+
* in this module — see the exported `phaseRegistry` constant.
|
|
21
|
+
*
|
|
22
|
+
* The class is intentionally minimal (no lifecycle hooks, no async). All
|
|
23
|
+
* mutations happen synchronously at module load by the built-in registrations
|
|
24
|
+
* at the bottom of this file.
|
|
25
|
+
*/
|
|
26
|
+
export class PhaseRegistry {
|
|
27
|
+
definitions = new Map();
|
|
28
|
+
/**
|
|
29
|
+
* Register a phase definition. Throws on duplicate names so misconfigured
|
|
30
|
+
* bootstrap modules surface immediately instead of silently overwriting.
|
|
31
|
+
*/
|
|
32
|
+
register(definition) {
|
|
33
|
+
if (this.definitions.has(definition.name)) {
|
|
34
|
+
throw new Error(`PhaseRegistry: phase "${definition.name}" is already registered`);
|
|
35
|
+
}
|
|
36
|
+
this.definitions.set(definition.name, definition);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Retrieve a phase by name. Throws with a "did you mean" list when the
|
|
40
|
+
* lookup fails — clearer than a downstream "undefined.promptTemplate".
|
|
41
|
+
*/
|
|
42
|
+
get(name) {
|
|
43
|
+
const def = this.definitions.get(name);
|
|
44
|
+
if (!def) {
|
|
45
|
+
const available = this.names().join(", ");
|
|
46
|
+
throw new Error(`PhaseRegistry: unknown phase "${name}". Available: ${available}`);
|
|
47
|
+
}
|
|
48
|
+
return def;
|
|
49
|
+
}
|
|
50
|
+
/** True when a phase with this name is registered. */
|
|
51
|
+
has(name) {
|
|
52
|
+
return this.definitions.has(name);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* All registered phase definitions in insertion order. Insertion order
|
|
56
|
+
* is also the canonical pipeline order (see registrations below).
|
|
57
|
+
*/
|
|
58
|
+
list() {
|
|
59
|
+
return [...this.definitions.values()];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* All registered phase names in insertion order. Replaces the
|
|
63
|
+
* `PhaseSchema.options` array literal exposed by the previous typed-enum
|
|
64
|
+
* `PhaseSchema`.
|
|
65
|
+
*/
|
|
66
|
+
names() {
|
|
67
|
+
return [...this.definitions.keys()];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Singleton registry instance. All consumer modules (phase-executor,
|
|
72
|
+
* phase-mapper, types.ts, CLI) read from this same instance — there is
|
|
73
|
+
* no second registry.
|
|
74
|
+
*/
|
|
75
|
+
export const phaseRegistry = new PhaseRegistry();
|
|
76
|
+
/**
|
|
77
|
+
* Convenience accessor for the registered phase names. Used by Zod refines,
|
|
78
|
+
* tests, and CLI validation in place of the removed `PhaseSchema.options`.
|
|
79
|
+
*/
|
|
80
|
+
export function getPhaseNames() {
|
|
81
|
+
return phaseRegistry.names();
|
|
82
|
+
}
|
|
83
|
+
// ─── Built-in phase registrations ────────────────────────────────────────
|
|
84
|
+
//
|
|
85
|
+
// Insertion order below IS the canonical pipeline order — preserved from the
|
|
86
|
+
// pre-registry `PhaseSchema` literal (`spec, security-review, exec, testgen,
|
|
87
|
+
// test, verify, qa, loop, merger`). Reordering these entries changes the
|
|
88
|
+
// order returned by `phaseRegistry.list()` / `getPhaseNames()` and the
|
|
89
|
+
// downstream `WORKFLOW_PHASES` constant in `state-schema.ts`.
|
|
90
|
+
// Spec — runs in the main repo (planning only, no worktree mutation)
|
|
91
|
+
phaseRegistry.register({
|
|
92
|
+
name: "spec",
|
|
93
|
+
skill: "spec",
|
|
94
|
+
promptTemplate: "Review GitHub issue #{issue} and create an implementation plan with verification criteria. Run the /spec {issue} workflow.",
|
|
95
|
+
requiresWorktree: false,
|
|
96
|
+
// Spec has a higher transient failure rate (~8.6%) than other phases due
|
|
97
|
+
// to GitHub API issues and rate limits. phase-executor.ts reads these
|
|
98
|
+
// values directly from the registry at module load (see
|
|
99
|
+
// SPEC_RETRY_BACKOFF_MS / SPEC_EXTRA_RETRIES).
|
|
100
|
+
retryStrategy: { extraRetries: 1, backoffMs: 5000 },
|
|
101
|
+
driverOverrides: {
|
|
102
|
+
aider: {
|
|
103
|
+
promptTemplate: `Read GitHub issue #{issue} using 'gh issue view #{issue}'.
|
|
104
|
+
Create a spec comment on the issue with:
|
|
105
|
+
1. Implementation plan
|
|
106
|
+
2. Acceptance criteria as a checklist
|
|
107
|
+
3. Risk assessment
|
|
108
|
+
Post the comment using 'gh issue comment #{issue} --body "<comment>"'.`,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
// Security review — worktree-isolated, label-triggered
|
|
113
|
+
phaseRegistry.register({
|
|
114
|
+
name: "security-review",
|
|
115
|
+
skill: "security-review",
|
|
116
|
+
promptTemplate: "Perform a deep security analysis for GitHub issue #{issue} focusing on auth, permissions, and sensitive operations. Run the /security-review {issue} workflow.",
|
|
117
|
+
requiresWorktree: true,
|
|
118
|
+
detect: {
|
|
119
|
+
labels: ["security", "auth", "authentication", "permissions", "admin"],
|
|
120
|
+
},
|
|
121
|
+
driverOverrides: {
|
|
122
|
+
aider: {
|
|
123
|
+
promptTemplate: `Perform a security review for GitHub issue #{issue}.
|
|
124
|
+
Read the issue with 'gh issue view #{issue}'.
|
|
125
|
+
Check for auth, permissions, injection, and sensitive data issues.
|
|
126
|
+
Post findings as a comment on the issue.`,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
// Exec — worktree-isolated
|
|
131
|
+
phaseRegistry.register({
|
|
132
|
+
name: "exec",
|
|
133
|
+
skill: "exec",
|
|
134
|
+
promptTemplate: "Implement the feature for GitHub issue #{issue} following the spec. Run the /exec {issue} workflow.",
|
|
135
|
+
requiresWorktree: true,
|
|
136
|
+
driverOverrides: {
|
|
137
|
+
aider: {
|
|
138
|
+
promptTemplate: `Implement the feature described in GitHub issue #{issue}.
|
|
139
|
+
Read the issue and any spec comments with 'gh issue view #{issue} --comments'.
|
|
140
|
+
Follow the implementation plan from the spec.
|
|
141
|
+
Write tests for new functionality.
|
|
142
|
+
Ensure the build passes with 'npm test' and 'npm run build'.`,
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
// Testgen — worktree-isolated
|
|
147
|
+
phaseRegistry.register({
|
|
148
|
+
name: "testgen",
|
|
149
|
+
skill: "testgen",
|
|
150
|
+
promptTemplate: "Generate test stubs for GitHub issue #{issue} based on the specification. Run the /testgen {issue} workflow.",
|
|
151
|
+
requiresWorktree: true,
|
|
152
|
+
driverOverrides: {
|
|
153
|
+
aider: {
|
|
154
|
+
promptTemplate: `Generate test stubs for GitHub issue #{issue}.
|
|
155
|
+
Read the spec comments on the issue with 'gh issue view #{issue} --comments'.
|
|
156
|
+
Create test files with describe/it blocks covering the acceptance criteria.
|
|
157
|
+
Use the project's existing test framework.`,
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
// Test — worktree-isolated, label-triggered (UI/frontend issues)
|
|
162
|
+
phaseRegistry.register({
|
|
163
|
+
name: "test",
|
|
164
|
+
skill: "test",
|
|
165
|
+
promptTemplate: "Execute structured browser-based testing for GitHub issue #{issue}. Run the /test {issue} workflow.",
|
|
166
|
+
requiresWorktree: true,
|
|
167
|
+
detect: { labels: ["ui", "frontend", "admin", "web", "browser"] },
|
|
168
|
+
driverOverrides: {
|
|
169
|
+
aider: {
|
|
170
|
+
promptTemplate: `Test the implementation for GitHub issue #{issue}.
|
|
171
|
+
Run 'npm test' and verify all tests pass.
|
|
172
|
+
Check for edge cases and error handling.`,
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
// Verify — runs in main repo (CLI-only feature verification)
|
|
177
|
+
phaseRegistry.register({
|
|
178
|
+
name: "verify",
|
|
179
|
+
skill: "verify",
|
|
180
|
+
promptTemplate: "Verify the implementation for GitHub issue #{issue} by running commands and capturing output. Run the /verify {issue} workflow.",
|
|
181
|
+
requiresWorktree: false,
|
|
182
|
+
driverOverrides: {
|
|
183
|
+
aider: {
|
|
184
|
+
promptTemplate: `Verify the implementation for GitHub issue #{issue}.
|
|
185
|
+
Run relevant commands and capture their output for review.`,
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
// QA — worktree-isolated
|
|
190
|
+
phaseRegistry.register({
|
|
191
|
+
name: "qa",
|
|
192
|
+
skill: "qa",
|
|
193
|
+
promptTemplate: "Review the implementation for GitHub issue #{issue} against acceptance criteria. Run the /qa {issue} workflow.",
|
|
194
|
+
requiresWorktree: true,
|
|
195
|
+
driverOverrides: {
|
|
196
|
+
aider: {
|
|
197
|
+
promptTemplate: `Review the changes for GitHub issue #{issue}.
|
|
198
|
+
Run 'npm test' and 'npm run build' to verify everything works.
|
|
199
|
+
Check each acceptance criterion from the issue comments.
|
|
200
|
+
Output a verdict: READY_FOR_MERGE, AC_MET_BUT_NOT_A_PLUS, or AC_NOT_MET
|
|
201
|
+
with format "### Verdict: <VERDICT>" followed by an explanation.`,
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
// Loop — worktree-isolated. `maxRetries: 0` encodes the
|
|
206
|
+
// "skip cold-start retries" rule consumed by phase-executor.ts (#488).
|
|
207
|
+
phaseRegistry.register({
|
|
208
|
+
name: "loop",
|
|
209
|
+
skill: "loop",
|
|
210
|
+
promptTemplate: "Parse test/QA findings for GitHub issue #{issue} and iterate until quality gates pass. Run the /loop {issue} workflow.",
|
|
211
|
+
requiresWorktree: true,
|
|
212
|
+
retryStrategy: { maxRetries: 0 },
|
|
213
|
+
driverOverrides: {
|
|
214
|
+
aider: {
|
|
215
|
+
promptTemplate: `Review test and QA findings for GitHub issue #{issue}.
|
|
216
|
+
Fix any issues identified in the QA feedback.
|
|
217
|
+
Re-run 'npm test' and 'npm run build' until all quality gates pass.`,
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
// Merger — runs in main repo (multi-worktree integration)
|
|
222
|
+
phaseRegistry.register({
|
|
223
|
+
name: "merger",
|
|
224
|
+
skill: "merger",
|
|
225
|
+
promptTemplate: "Integrate and merge completed worktrees for GitHub issue #{issue}. Run the /merger {issue} workflow.",
|
|
226
|
+
requiresWorktree: false,
|
|
227
|
+
driverOverrides: {
|
|
228
|
+
aider: {
|
|
229
|
+
promptTemplate: `Integrate and merge completed worktrees for GitHub issue #{issue}.
|
|
230
|
+
Ensure all branches are up to date and merge cleanly.`,
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
});
|
|
@@ -94,7 +94,7 @@ export declare class GitHubProvider implements PlatformProvider {
|
|
|
94
94
|
* Create a PR via `gh pr create` CLI, returning raw result.
|
|
95
95
|
* Used by worktree-manager.ts which needs access to stdout for URL extraction.
|
|
96
96
|
*/
|
|
97
|
-
createPRCliSync(title: string, body: string, head: string, cwd?: string): CreatePRCliResult;
|
|
97
|
+
createPRCliSync(title: string, body: string, head: string, cwd?: string, base?: string): CreatePRCliResult;
|
|
98
98
|
/**
|
|
99
99
|
* Batch fetch issue and PR status in a single GraphQL call.
|
|
100
100
|
* Returns a map keyed by issue/PR number.
|
|
@@ -137,8 +137,25 @@ export class GitHubProvider {
|
|
|
137
137
|
* Create a PR via `gh pr create` CLI, returning raw result.
|
|
138
138
|
* Used by worktree-manager.ts which needs access to stdout for URL extraction.
|
|
139
139
|
*/
|
|
140
|
-
createPRCliSync(title, body, head, cwd) {
|
|
141
|
-
const
|
|
140
|
+
createPRCliSync(title, body, head, cwd, base) {
|
|
141
|
+
const args = [
|
|
142
|
+
"pr",
|
|
143
|
+
"create",
|
|
144
|
+
"--title",
|
|
145
|
+
title,
|
|
146
|
+
"--body",
|
|
147
|
+
body,
|
|
148
|
+
"--head",
|
|
149
|
+
head,
|
|
150
|
+
];
|
|
151
|
+
if (base) {
|
|
152
|
+
args.push("--base", base);
|
|
153
|
+
}
|
|
154
|
+
const result = spawnSync("gh", args, {
|
|
155
|
+
stdio: "pipe",
|
|
156
|
+
cwd,
|
|
157
|
+
timeout: 30000,
|
|
158
|
+
});
|
|
142
159
|
return {
|
|
143
160
|
stdout: result.stdout?.toString() ?? "",
|
|
144
161
|
stderr: result.stderr?.toString() ?? "",
|
|
@@ -415,7 +432,7 @@ export class GitHubProvider {
|
|
|
415
432
|
});
|
|
416
433
|
}
|
|
417
434
|
async createPR(opts) {
|
|
418
|
-
const result = this.createPRCliSync(opts.title, opts.body, opts.head);
|
|
435
|
+
const result = this.createPRCliSync(opts.title, opts.body, opts.head, undefined, opts.base);
|
|
419
436
|
if (result.exitCode !== 0) {
|
|
420
437
|
const error = result.stderr.trim() || "Unknown error";
|
|
421
438
|
throw new Error(`gh pr create failed: ${error}`);
|
|
@@ -28,16 +28,32 @@ export declare function checkPRMergeStatus(prNumber: number): PRMergeStatus;
|
|
|
28
28
|
/**
|
|
29
29
|
* Check if a branch has been merged into a base branch using git
|
|
30
30
|
*
|
|
31
|
+
* "Merged" here means the branch was the source of an actual merge commit on
|
|
32
|
+
* the base branch — i.e., the branch tip appears as a non-first parent of some
|
|
33
|
+
* merge commit reachable from baseBranch. This deliberately excludes the case
|
|
34
|
+
* where the branch tip is just an ancestor of baseBranch with no commits ever
|
|
35
|
+
* added (e.g., a worktree branch created from main that was abandoned before
|
|
36
|
+
* any commits were made). Those branches are reachable from main but were
|
|
37
|
+
* never merged in any meaningful sense; the older `git branch --merged` check
|
|
38
|
+
* misclassified them as merged and caused subsequent runs to skip the still-
|
|
39
|
+
* open issue.
|
|
40
|
+
*
|
|
41
|
+
* Squash-merged branches do not satisfy this check (their tip is not on main
|
|
42
|
+
* after squash) — callers that need to detect squash merges should rely on
|
|
43
|
+
* commit-message detection (see {@link isIssueMergedIntoMain}'s `--grep` path)
|
|
44
|
+
* or a PR API check.
|
|
45
|
+
*
|
|
31
46
|
* @param branchName - The branch name to check (e.g., "feature/33-some-title")
|
|
32
47
|
* @param baseBranch - The base branch to check against (default: "main")
|
|
33
|
-
* @returns true if
|
|
48
|
+
* @returns true if a merge commit on baseBranch records branchName's tip as a
|
|
49
|
+
* non-first parent, false otherwise
|
|
34
50
|
*/
|
|
35
51
|
export declare function isBranchMergedIntoMain(branchName: string, baseBranch?: string): boolean;
|
|
36
52
|
/**
|
|
37
53
|
* Check if a feature branch for an issue is merged into a base branch
|
|
38
54
|
*
|
|
39
55
|
* Tries multiple detection methods:
|
|
40
|
-
* 1.
|
|
56
|
+
* 1. Find `feature/<N>-*` branches with `git branch -a` and check via {@link isBranchMergedIntoMain}
|
|
41
57
|
* 2. Check for merge commits mentioning the issue
|
|
42
58
|
*
|
|
43
59
|
* @param issueNumber - The issue number to check
|