sequant 2.11.0 → 2.12.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 -0
- 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/hooks/pre-tool.sh +331 -12
- package/dist/marketplace/external_plugins/sequant/skills/_shared/references/subagent-types.md +7 -18
- package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +5 -1
- package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +62 -8
- package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +187 -28
- package/dist/marketplace/external_plugins/sequant/skills/loop/SKILL.md +127 -23
- package/dist/marketplace/external_plugins/sequant/skills/merger/SKILL.md +130 -13
- package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +306 -8
- package/dist/marketplace/external_plugins/sequant/skills/release/SKILL.md +79 -0
- package/dist/marketplace/external_plugins/sequant/skills/spec/SKILL.md +40 -20
- package/dist/marketplace/external_plugins/sequant/skills/spec/references/recommended-workflow.md +14 -1
- package/dist/marketplace/external_plugins/sequant/skills/test/SKILL.md +1 -1
- package/dist/marketplace/external_plugins/sequant/skills/testgen/SKILL.md +23 -6
- package/dist/src/commands/doctor.js +20 -18
- package/dist/src/commands/ready.js +4 -0
- package/dist/src/lib/ac-linter.js +26 -0
- package/dist/src/lib/ac-parser.d.ts +40 -0
- package/dist/src/lib/ac-parser.js +202 -16
- package/dist/src/lib/markdown-fence.d.ts +24 -0
- package/dist/src/lib/markdown-fence.js +51 -0
- package/dist/src/lib/mcp-config.d.ts +24 -0
- package/dist/src/lib/mcp-config.js +51 -0
- package/dist/src/lib/scope/analyzer.d.ts +4 -0
- package/dist/src/lib/scope/analyzer.js +7 -1
- package/dist/src/lib/settings.d.ts +38 -1
- package/dist/src/lib/settings.js +14 -0
- package/dist/src/lib/system.d.ts +7 -3
- package/dist/src/lib/system.js +7 -3
- package/dist/src/lib/test-tautology-detector.js +50 -3
- package/dist/src/lib/workflow/batch-executor.d.ts +20 -1
- package/dist/src/lib/workflow/batch-executor.js +77 -5
- package/dist/src/lib/workflow/config-resolver.js +1 -0
- package/dist/src/lib/workflow/drivers/agent-driver.d.ts +7 -0
- package/dist/src/lib/workflow/drivers/claude-code.js +9 -3
- package/dist/src/lib/workflow/mutation-marker.d.ts +86 -0
- package/dist/src/lib/workflow/mutation-marker.js +97 -0
- package/dist/src/lib/workflow/phase-executor.d.ts +17 -0
- package/dist/src/lib/workflow/phase-executor.js +50 -4
- package/dist/src/lib/workflow/qa-gaps-marker.d.ts +38 -0
- package/dist/src/lib/workflow/qa-gaps-marker.js +66 -0
- package/dist/src/lib/workflow/ready-gate.d.ts +25 -1
- package/dist/src/lib/workflow/ready-gate.js +81 -11
- package/dist/src/lib/workflow/run-log-schema.d.ts +120 -0
- package/dist/src/lib/workflow/run-log-schema.js +40 -0
- package/dist/src/lib/workflow/state-schema.d.ts +5 -1
- package/dist/src/lib/workflow/state-schema.js +8 -1
- package/dist/src/lib/workflow/types.d.ts +14 -0
- package/package.json +2 -2
- package/templates/hooks/pre-tool.sh +108 -17
- package/templates/skills/exec/SKILL.md +1 -1
- package/templates/skills/fullsolve/SKILL.md +62 -9
- package/templates/skills/loop/SKILL.md +71 -12
- package/templates/skills/merger/SKILL.md +32 -3
- package/templates/skills/qa/SKILL.md +247 -2
- package/templates/skills/spec/SKILL.md +9 -5
- package/templates/skills/test/SKILL.md +1 -1
|
@@ -129,6 +129,50 @@ export declare const ErrorContextSchema: z.ZodObject<{
|
|
|
129
129
|
isRetryable: z.ZodOptional<z.ZodBoolean>;
|
|
130
130
|
}, z.core.$strip>;
|
|
131
131
|
export type ErrorContext = z.infer<typeof ErrorContextSchema>;
|
|
132
|
+
/**
|
|
133
|
+
* Finite taxonomy for a single gap finding surfaced by `/qa` (#937).
|
|
134
|
+
*
|
|
135
|
+
* Replaces the lossy prose-scrape gap channel (`parseListSection` against
|
|
136
|
+
* `**Issues:**`/`**Gaps**` headers, which misses AC-table NOT_MET rows, §6d
|
|
137
|
+
* Adversarial Re-Read findings, and §5 Risk Assessment). `evidence` is
|
|
138
|
+
* required so a finding can't be speculative — see #608's 0%-action-rate
|
|
139
|
+
* result for open-ended "what might we be missing" findings.
|
|
140
|
+
*/
|
|
141
|
+
export declare const GapCategorySchema: z.ZodEnum<{
|
|
142
|
+
requirement_gap: "requirement_gap";
|
|
143
|
+
dependency_gap: "dependency_gap";
|
|
144
|
+
test_gap: "test_gap";
|
|
145
|
+
repository_gap: "repository_gap";
|
|
146
|
+
risk_gap: "risk_gap";
|
|
147
|
+
execution_gap: "execution_gap";
|
|
148
|
+
}>;
|
|
149
|
+
export type GapCategory = z.infer<typeof GapCategorySchema>;
|
|
150
|
+
export declare const GapActionSchema: z.ZodEnum<{
|
|
151
|
+
fix_now: "fix_now";
|
|
152
|
+
document: "document";
|
|
153
|
+
pause_for_human: "pause_for_human";
|
|
154
|
+
}>;
|
|
155
|
+
export type GapAction = z.infer<typeof GapActionSchema>;
|
|
156
|
+
export declare const GapFindingSchema: z.ZodObject<{
|
|
157
|
+
category: z.ZodEnum<{
|
|
158
|
+
requirement_gap: "requirement_gap";
|
|
159
|
+
dependency_gap: "dependency_gap";
|
|
160
|
+
test_gap: "test_gap";
|
|
161
|
+
repository_gap: "repository_gap";
|
|
162
|
+
risk_gap: "risk_gap";
|
|
163
|
+
execution_gap: "execution_gap";
|
|
164
|
+
}>;
|
|
165
|
+
evidence: z.ZodString;
|
|
166
|
+
description: z.ZodString;
|
|
167
|
+
recommendedAction: z.ZodEnum<{
|
|
168
|
+
fix_now: "fix_now";
|
|
169
|
+
document: "document";
|
|
170
|
+
pause_for_human: "pause_for_human";
|
|
171
|
+
}>;
|
|
172
|
+
affectedAcs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
173
|
+
nonGoal: z.ZodOptional<z.ZodBoolean>;
|
|
174
|
+
}, z.core.$strip>;
|
|
175
|
+
export type GapFinding = z.infer<typeof GapFindingSchema>;
|
|
132
176
|
/**
|
|
133
177
|
* Condensed QA verdict summary for structured log output (#434).
|
|
134
178
|
*
|
|
@@ -141,6 +185,25 @@ export declare const QaSummarySchema: z.ZodObject<{
|
|
|
141
185
|
acTotal: z.ZodNumber;
|
|
142
186
|
gaps: z.ZodArray<z.ZodString>;
|
|
143
187
|
suggestions: z.ZodArray<z.ZodString>;
|
|
188
|
+
findings: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
189
|
+
category: z.ZodEnum<{
|
|
190
|
+
requirement_gap: "requirement_gap";
|
|
191
|
+
dependency_gap: "dependency_gap";
|
|
192
|
+
test_gap: "test_gap";
|
|
193
|
+
repository_gap: "repository_gap";
|
|
194
|
+
risk_gap: "risk_gap";
|
|
195
|
+
execution_gap: "execution_gap";
|
|
196
|
+
}>;
|
|
197
|
+
evidence: z.ZodString;
|
|
198
|
+
description: z.ZodString;
|
|
199
|
+
recommendedAction: z.ZodEnum<{
|
|
200
|
+
fix_now: "fix_now";
|
|
201
|
+
document: "document";
|
|
202
|
+
pause_for_human: "pause_for_human";
|
|
203
|
+
}>;
|
|
204
|
+
affectedAcs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
205
|
+
nonGoal: z.ZodOptional<z.ZodBoolean>;
|
|
206
|
+
}, z.core.$strip>>>;
|
|
144
207
|
}, z.core.$strip>;
|
|
145
208
|
export type QaSummary = z.infer<typeof QaSummarySchema>;
|
|
146
209
|
/**
|
|
@@ -175,6 +238,25 @@ export declare const PhaseLogSchema: z.ZodObject<{
|
|
|
175
238
|
acTotal: z.ZodNumber;
|
|
176
239
|
gaps: z.ZodArray<z.ZodString>;
|
|
177
240
|
suggestions: z.ZodArray<z.ZodString>;
|
|
241
|
+
findings: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
242
|
+
category: z.ZodEnum<{
|
|
243
|
+
requirement_gap: "requirement_gap";
|
|
244
|
+
dependency_gap: "dependency_gap";
|
|
245
|
+
test_gap: "test_gap";
|
|
246
|
+
repository_gap: "repository_gap";
|
|
247
|
+
risk_gap: "risk_gap";
|
|
248
|
+
execution_gap: "execution_gap";
|
|
249
|
+
}>;
|
|
250
|
+
evidence: z.ZodString;
|
|
251
|
+
description: z.ZodString;
|
|
252
|
+
recommendedAction: z.ZodEnum<{
|
|
253
|
+
fix_now: "fix_now";
|
|
254
|
+
document: "document";
|
|
255
|
+
pause_for_human: "pause_for_human";
|
|
256
|
+
}>;
|
|
257
|
+
affectedAcs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
258
|
+
nonGoal: z.ZodOptional<z.ZodBoolean>;
|
|
259
|
+
}, z.core.$strip>>>;
|
|
178
260
|
}, z.core.$strip>>;
|
|
179
261
|
commitHash: z.ZodOptional<z.ZodString>;
|
|
180
262
|
fileDiffStats: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
@@ -255,6 +337,25 @@ export declare const IssueLogSchema: z.ZodObject<{
|
|
|
255
337
|
acTotal: z.ZodNumber;
|
|
256
338
|
gaps: z.ZodArray<z.ZodString>;
|
|
257
339
|
suggestions: z.ZodArray<z.ZodString>;
|
|
340
|
+
findings: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
341
|
+
category: z.ZodEnum<{
|
|
342
|
+
requirement_gap: "requirement_gap";
|
|
343
|
+
dependency_gap: "dependency_gap";
|
|
344
|
+
test_gap: "test_gap";
|
|
345
|
+
repository_gap: "repository_gap";
|
|
346
|
+
risk_gap: "risk_gap";
|
|
347
|
+
execution_gap: "execution_gap";
|
|
348
|
+
}>;
|
|
349
|
+
evidence: z.ZodString;
|
|
350
|
+
description: z.ZodString;
|
|
351
|
+
recommendedAction: z.ZodEnum<{
|
|
352
|
+
fix_now: "fix_now";
|
|
353
|
+
document: "document";
|
|
354
|
+
pause_for_human: "pause_for_human";
|
|
355
|
+
}>;
|
|
356
|
+
affectedAcs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
357
|
+
nonGoal: z.ZodOptional<z.ZodBoolean>;
|
|
358
|
+
}, z.core.$strip>>>;
|
|
258
359
|
}, z.core.$strip>>;
|
|
259
360
|
commitHash: z.ZodOptional<z.ZodString>;
|
|
260
361
|
fileDiffStats: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
@@ -390,6 +491,25 @@ export declare const RunLogSchema: z.ZodObject<{
|
|
|
390
491
|
acTotal: z.ZodNumber;
|
|
391
492
|
gaps: z.ZodArray<z.ZodString>;
|
|
392
493
|
suggestions: z.ZodArray<z.ZodString>;
|
|
494
|
+
findings: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
495
|
+
category: z.ZodEnum<{
|
|
496
|
+
requirement_gap: "requirement_gap";
|
|
497
|
+
dependency_gap: "dependency_gap";
|
|
498
|
+
test_gap: "test_gap";
|
|
499
|
+
repository_gap: "repository_gap";
|
|
500
|
+
risk_gap: "risk_gap";
|
|
501
|
+
execution_gap: "execution_gap";
|
|
502
|
+
}>;
|
|
503
|
+
evidence: z.ZodString;
|
|
504
|
+
description: z.ZodString;
|
|
505
|
+
recommendedAction: z.ZodEnum<{
|
|
506
|
+
fix_now: "fix_now";
|
|
507
|
+
document: "document";
|
|
508
|
+
pause_for_human: "pause_for_human";
|
|
509
|
+
}>;
|
|
510
|
+
affectedAcs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
511
|
+
nonGoal: z.ZodOptional<z.ZodBoolean>;
|
|
512
|
+
}, z.core.$strip>>>;
|
|
393
513
|
}, z.core.$strip>>;
|
|
394
514
|
commitHash: z.ZodOptional<z.ZodString>;
|
|
395
515
|
fileDiffStats: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
@@ -129,6 +129,39 @@ export const ErrorContextSchema = z.object({
|
|
|
129
129
|
/** Whether this error type is retryable (AC-9) */
|
|
130
130
|
isRetryable: z.boolean().optional(),
|
|
131
131
|
});
|
|
132
|
+
/**
|
|
133
|
+
* Finite taxonomy for a single gap finding surfaced by `/qa` (#937).
|
|
134
|
+
*
|
|
135
|
+
* Replaces the lossy prose-scrape gap channel (`parseListSection` against
|
|
136
|
+
* `**Issues:**`/`**Gaps**` headers, which misses AC-table NOT_MET rows, §6d
|
|
137
|
+
* Adversarial Re-Read findings, and §5 Risk Assessment). `evidence` is
|
|
138
|
+
* required so a finding can't be speculative — see #608's 0%-action-rate
|
|
139
|
+
* result for open-ended "what might we be missing" findings.
|
|
140
|
+
*/
|
|
141
|
+
export const GapCategorySchema = z.enum([
|
|
142
|
+
"requirement_gap",
|
|
143
|
+
"dependency_gap",
|
|
144
|
+
"test_gap",
|
|
145
|
+
"repository_gap",
|
|
146
|
+
"risk_gap",
|
|
147
|
+
"execution_gap",
|
|
148
|
+
]);
|
|
149
|
+
export const GapActionSchema = z.enum([
|
|
150
|
+
"fix_now",
|
|
151
|
+
"document",
|
|
152
|
+
"pause_for_human",
|
|
153
|
+
]);
|
|
154
|
+
export const GapFindingSchema = z.object({
|
|
155
|
+
category: GapCategorySchema,
|
|
156
|
+
/** Concrete observation grounding the finding — never speculation. */
|
|
157
|
+
evidence: z.string().min(1),
|
|
158
|
+
description: z.string().min(1),
|
|
159
|
+
recommendedAction: GapActionSchema,
|
|
160
|
+
/** ACs this finding relates to, e.g. ["AC-3"]. */
|
|
161
|
+
affectedAcs: z.array(z.string()).optional(),
|
|
162
|
+
/** True when the finding overlaps one of the issue's Non-Goals. */
|
|
163
|
+
nonGoal: z.boolean().optional(),
|
|
164
|
+
});
|
|
132
165
|
/**
|
|
133
166
|
* Condensed QA verdict summary for structured log output (#434).
|
|
134
167
|
*
|
|
@@ -145,6 +178,13 @@ export const QaSummarySchema = z.object({
|
|
|
145
178
|
gaps: z.array(z.string()),
|
|
146
179
|
/** List of improvement suggestions from QA */
|
|
147
180
|
suggestions: z.array(z.string()),
|
|
181
|
+
/**
|
|
182
|
+
* Structured gap findings parsed from the `SEQUANT_QA_GAPS` marker (#937).
|
|
183
|
+
* Present only when the marker was found and validated; `gaps` above
|
|
184
|
+
* always carries the union of marker + prose descriptions (dedupe'd) so
|
|
185
|
+
* marker-unaware consumers never regress.
|
|
186
|
+
*/
|
|
187
|
+
findings: z.array(GapFindingSchema).optional(),
|
|
148
188
|
});
|
|
149
189
|
/**
|
|
150
190
|
* Log entry for a single phase execution
|
|
@@ -149,6 +149,7 @@ export declare const AcceptanceCriterionSchema: z.ZodObject<{
|
|
|
149
149
|
}>;
|
|
150
150
|
verifiedAt: z.ZodOptional<z.ZodString>;
|
|
151
151
|
notes: z.ZodOptional<z.ZodString>;
|
|
152
|
+
evidence: z.ZodOptional<z.ZodString>;
|
|
152
153
|
}, z.core.$strip>;
|
|
153
154
|
export type AcceptanceCriterion = z.infer<typeof AcceptanceCriterionSchema>;
|
|
154
155
|
/**
|
|
@@ -172,6 +173,7 @@ export declare const AcceptanceCriteriaSchema: z.ZodObject<{
|
|
|
172
173
|
}>;
|
|
173
174
|
verifiedAt: z.ZodOptional<z.ZodString>;
|
|
174
175
|
notes: z.ZodOptional<z.ZodString>;
|
|
176
|
+
evidence: z.ZodOptional<z.ZodString>;
|
|
175
177
|
}, z.core.$strip>>;
|
|
176
178
|
extractedAt: z.ZodString;
|
|
177
179
|
summary: z.ZodObject<{
|
|
@@ -281,6 +283,7 @@ export declare const IssueStateSchema: z.ZodObject<{
|
|
|
281
283
|
}>;
|
|
282
284
|
verifiedAt: z.ZodOptional<z.ZodString>;
|
|
283
285
|
notes: z.ZodOptional<z.ZodString>;
|
|
286
|
+
evidence: z.ZodOptional<z.ZodString>;
|
|
284
287
|
}, z.core.$strip>>;
|
|
285
288
|
extractedAt: z.ZodString;
|
|
286
289
|
summary: z.ZodObject<{
|
|
@@ -430,6 +433,7 @@ export declare const WorkflowStateSchema: z.ZodObject<{
|
|
|
430
433
|
}>;
|
|
431
434
|
verifiedAt: z.ZodOptional<z.ZodString>;
|
|
432
435
|
notes: z.ZodOptional<z.ZodString>;
|
|
436
|
+
evidence: z.ZodOptional<z.ZodString>;
|
|
433
437
|
}, z.core.$strip>>;
|
|
434
438
|
extractedAt: z.ZodString;
|
|
435
439
|
summary: z.ZodObject<{
|
|
@@ -538,7 +542,7 @@ export declare function createPhaseState(status?: PhaseStatus): PhaseState;
|
|
|
538
542
|
/**
|
|
539
543
|
* Create acceptance criterion
|
|
540
544
|
*/
|
|
541
|
-
export declare function createAcceptanceCriterion(id: string, description: string, verificationMethod?: ACVerificationMethod): AcceptanceCriterion;
|
|
545
|
+
export declare function createAcceptanceCriterion(id: string, description: string, verificationMethod?: ACVerificationMethod, evidence?: string): AcceptanceCriterion;
|
|
542
546
|
/**
|
|
543
547
|
* Create acceptance criteria tracking structure
|
|
544
548
|
*/
|
|
@@ -149,6 +149,12 @@ export const AcceptanceCriterionSchema = z.object({
|
|
|
149
149
|
verifiedAt: z.string().datetime().optional(),
|
|
150
150
|
/** Additional notes about verification */
|
|
151
151
|
notes: z.string().optional(),
|
|
152
|
+
/**
|
|
153
|
+
* Explicit `Evidence:` clause declared on the AC line (#938). When present,
|
|
154
|
+
* this is what verificationMethod was resolved from instead of keyword
|
|
155
|
+
* inference — see `resolveVerificationMethod` in ac-parser.ts.
|
|
156
|
+
*/
|
|
157
|
+
evidence: z.string().optional(),
|
|
152
158
|
});
|
|
153
159
|
/**
|
|
154
160
|
* Acceptance criteria tracking for an issue
|
|
@@ -369,12 +375,13 @@ export function createPhaseState(status = "pending") {
|
|
|
369
375
|
/**
|
|
370
376
|
* Create acceptance criterion
|
|
371
377
|
*/
|
|
372
|
-
export function createAcceptanceCriterion(id, description, verificationMethod = "manual") {
|
|
378
|
+
export function createAcceptanceCriterion(id, description, verificationMethod = "manual", evidence) {
|
|
373
379
|
return {
|
|
374
380
|
id,
|
|
375
381
|
description,
|
|
376
382
|
verificationMethod,
|
|
377
383
|
status: "pending",
|
|
384
|
+
...(evidence !== undefined ? { evidence } : {}),
|
|
378
385
|
};
|
|
379
386
|
}
|
|
380
387
|
/**
|
|
@@ -83,6 +83,15 @@ export interface ExecutionConfig {
|
|
|
83
83
|
dryRun: boolean;
|
|
84
84
|
/** Enable MCP servers in headless mode (true by default, false if --no-mcp flag used) */
|
|
85
85
|
mcp: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Claude Desktop MCP server names explicitly opted in via
|
|
88
|
+
* `settings.run.mcpAllowlist` (#936). Both `ExecutionConfig` producers
|
|
89
|
+
* (`buildExecutionConfig`, `ready-gate.ts`'s `buildPhaseConfig`) read
|
|
90
|
+
* this from settings the same way, so it cannot drift between them the
|
|
91
|
+
* way #833's `phaseTimeout` once did. `phase-executor.ts` forwards it
|
|
92
|
+
* verbatim onto `AgentExecutionConfig.mcpAllowlist`.
|
|
93
|
+
*/
|
|
94
|
+
mcpAllowlist?: string[];
|
|
86
95
|
/**
|
|
87
96
|
* Enable automatic retry with MCP fallback.
|
|
88
97
|
* When true (default), failed phases are retried with MCP disabled.
|
|
@@ -705,4 +714,9 @@ export interface IssueExecutionContext {
|
|
|
705
714
|
* around verbose Claude streaming.
|
|
706
715
|
*/
|
|
707
716
|
phasePauseHandle?: PhasePauseHandle;
|
|
717
|
+
/**
|
|
718
|
+
* @internal Test seam for the #964 qa-verdict comment post. Defaults to a
|
|
719
|
+
* real `GitHubProvider().postComment` when unset.
|
|
720
|
+
*/
|
|
721
|
+
postComment?: (issueNumber: number, body: string) => Promise<void>;
|
|
708
722
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sequant",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.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": {
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"@anthropic-ai/claude-agent-sdk": "^0.3.142",
|
|
95
95
|
"@hono/node-server": "^2.0.0",
|
|
96
96
|
"boxen": "^8.0.1",
|
|
97
|
-
"chalk": "^
|
|
97
|
+
"chalk": "^6.0.0",
|
|
98
98
|
"chokidar": "^5.0.0",
|
|
99
99
|
"cli-table3": "^0.6.5",
|
|
100
100
|
"commander": "^14.0.3",
|
|
@@ -39,7 +39,55 @@ else
|
|
|
39
39
|
HOOK_CWD=$(echo "$INPUT_JSON" | grep -oE '"cwd"\s*:\s*"[^"]+"' | head -1 | cut -d'"' -f4)
|
|
40
40
|
# For Bash tool, extract command from tool_input; for others, extract the whole object
|
|
41
41
|
if [[ "$TOOL_NAME" == "Bash" ]]; then
|
|
42
|
-
|
|
42
|
+
# Escape-aware extraction (#963 gap B): the naive `grep -oE '"[^"]+"'`
|
|
43
|
+
# form (still used for the simpler fields above, where an embedded
|
|
44
|
+
# `\"` is implausible) stops at the FIRST escaped quote inside the
|
|
45
|
+
# JSON string, truncating any command containing one — e.g.
|
|
46
|
+
# `git commit -m "msg with \"quotes\""` would be cut down to just
|
|
47
|
+
# `git commit -m ` before it ever reaches the guards below.
|
|
48
|
+
#
|
|
49
|
+
# sed's `(([^"\\]|\\.)*)` captures the full escaped run — any run of
|
|
50
|
+
# non-quote/non-backslash chars, or a backslash paired with whatever
|
|
51
|
+
# follows it — up to the closing unescaped `"`. `JSON.stringify`
|
|
52
|
+
# guarantees the whole payload is one physical line, so a single
|
|
53
|
+
# sed pass is enough (no multi-line `-z` needed, which BSD sed lacks
|
|
54
|
+
# anyway).
|
|
55
|
+
#
|
|
56
|
+
# The captured text still carries JSON string escapes literally
|
|
57
|
+
# (`\"`, `\\`, `\n`, `\t`, ...); the awk pass resolves them in ONE
|
|
58
|
+
# left-to-right scan, consuming two characters per recognized
|
|
59
|
+
# escape. That ordering is what a chain of separate sed/tr
|
|
60
|
+
# substitutions cannot get right without a placeholder: a literal
|
|
61
|
+
# `\\n` in the original command is an escaped backslash (`\\`)
|
|
62
|
+
# immediately followed by a literal `n`, and must stay a backslash
|
|
63
|
+
# plus 'n' — not become an escaped-newline (`\n`) if the two escapes
|
|
64
|
+
# were resolved out of order. Scanning once and advancing past both
|
|
65
|
+
# characters of whichever escape is recognized sidesteps that
|
|
66
|
+
# ambiguity entirely.
|
|
67
|
+
TOOL_INPUT=$(printf '%s' "$INPUT_JSON" \
|
|
68
|
+
| sed -E -n 's/.*"command"[[:space:]]*:[[:space:]]*"(([^"\\]|\\.)*)".*/\1/p' \
|
|
69
|
+
| head -1 \
|
|
70
|
+
| awk '
|
|
71
|
+
{
|
|
72
|
+
s = $0; out = ""; n = length(s)
|
|
73
|
+
for (i = 1; i <= n; i++) {
|
|
74
|
+
c = substr(s, i, 1)
|
|
75
|
+
if (c == "\\" && i < n) {
|
|
76
|
+
nc = substr(s, i + 1, 1)
|
|
77
|
+
if (nc == "\"") { out = out "\""; i++ }
|
|
78
|
+
else if (nc == "\\") { out = out "\\"; i++ }
|
|
79
|
+
else if (nc == "n") { out = out "\n"; i++ }
|
|
80
|
+
else if (nc == "t") { out = out "\t"; i++ }
|
|
81
|
+
else if (nc == "r") { out = out "\r"; i++ }
|
|
82
|
+
else if (nc == "/") { out = out "/"; i++ }
|
|
83
|
+
else { out = out c }
|
|
84
|
+
} else {
|
|
85
|
+
out = out c
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
print out
|
|
89
|
+
}
|
|
90
|
+
')
|
|
43
91
|
else
|
|
44
92
|
TOOL_INPUT=$(echo "$INPUT_JSON" | grep -oE '"tool_input"\s*:\s*\{[^}]+\}' | head -1)
|
|
45
93
|
fi
|
|
@@ -258,6 +306,43 @@ seg_match() {
|
|
|
258
306
|
[[ -n "$SEGMENTS" ]] && grep -qE "$1" <<< "$SEGMENTS"
|
|
259
307
|
}
|
|
260
308
|
|
|
309
|
+
# resolve_cd_target <tool_input> — print the target directory of the LAST
|
|
310
|
+
# `cd <path>` line in a (possibly multi-line) Bash command, if and only if
|
|
311
|
+
# the path is a static literal (quoted or unquoted) that resolves to an
|
|
312
|
+
# existing directory. Scans the raw command, not $SEGMENTS — emit_segments
|
|
313
|
+
# drops double-quoted regions, so `cd "$WT"` would vanish there before this
|
|
314
|
+
# ever saw it. Prints nothing when there is no `cd` line, the target is
|
|
315
|
+
# dynamic (contains `$` or a backtick), or the path doesn't exist — callers
|
|
316
|
+
# must treat empty output as "fail open", never as license to guess a
|
|
317
|
+
# directory (#963).
|
|
318
|
+
resolve_cd_target() {
|
|
319
|
+
local input="$1" line target
|
|
320
|
+
line=$(printf '%s\n' "$input" | grep -E '^[[:space:]]*cd[[:space:]]+' | tail -1)
|
|
321
|
+
[[ -z "$line" ]] && return 0
|
|
322
|
+
|
|
323
|
+
target=$(printf '%s' "$line" | sed -E 's/^[[:space:]]*cd[[:space:]]+//; s/[[:space:]]*[;&|].*$//; s/[[:space:]]+$//')
|
|
324
|
+
|
|
325
|
+
# Strip one layer of surrounding matching quotes.
|
|
326
|
+
case "$target" in
|
|
327
|
+
\"*\") target="${target#\"}"; target="${target%\"}" ;;
|
|
328
|
+
\'*\') target="${target#\'}"; target="${target%\'}" ;;
|
|
329
|
+
esac
|
|
330
|
+
|
|
331
|
+
# Fail open on anything dynamic — resolving shell expansions means
|
|
332
|
+
# reimplementing the shell, which is disproportionate; `git commit`
|
|
333
|
+
# itself already rejects a genuinely empty commit. A backslash is
|
|
334
|
+
# rejected too: it can escape a following `$`/`` ` `` into a form this
|
|
335
|
+
# literal-string check would otherwise miss, and a backslash also carries
|
|
336
|
+
# its own shell meaning (line continuation, escaped chars) that this
|
|
337
|
+
# function does not attempt to resolve — failing open is the safe
|
|
338
|
+
# direction either way (#963).
|
|
339
|
+
case "$target" in
|
|
340
|
+
*'$'*|*'`'*|*'\'*) return 0 ;;
|
|
341
|
+
esac
|
|
342
|
+
|
|
343
|
+
[[ -n "$target" && -d "$target" ]] && printf '%s' "$target"
|
|
344
|
+
}
|
|
345
|
+
|
|
261
346
|
# Path of the session->issue binding the checkout guard maintains (#906).
|
|
262
347
|
# $1 = repo toplevel, $2 = session id. The id is opaque, so squash everything
|
|
263
348
|
# outside a filename-safe set — it must not be able to escape the directory.
|
|
@@ -552,11 +637,10 @@ if [[ -z "${SEQUANT_ORCHESTRATOR:-}" ]] \
|
|
|
552
637
|
# `.cwd` is part of Claude Code's PreToolUse envelope (verified against a
|
|
553
638
|
# live payload alongside `session_id`), with $PWD as the fallback.
|
|
554
639
|
_CO_CWD="${HOOK_CWD:-$PWD}"
|
|
555
|
-
# Honor a
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
fi
|
|
640
|
+
# Honor a `cd <dir>` the same way the commit guard below does — including
|
|
641
|
+
# multi-line commands and quoted/dynamic targets (#963).
|
|
642
|
+
_CO_CD=$(resolve_cd_target "$TOOL_INPUT")
|
|
643
|
+
[[ -n "$_CO_CD" ]] && _CO_CWD="$_CO_CD"
|
|
560
644
|
|
|
561
645
|
# A linked worktree's toplevel has `.git` as a FILE; the main checkout has
|
|
562
646
|
# it as a directory.
|
|
@@ -782,18 +866,25 @@ fi
|
|
|
782
866
|
# Skips for --amend since amending doesn't require new changes
|
|
783
867
|
if [[ "$TOOL_NAME" == "Bash" ]] && seg_match 'git commit'; then
|
|
784
868
|
if ! echo "$TOOL_INPUT" | grep -qE -- '--amend|--allow-empty'; then
|
|
785
|
-
#
|
|
786
|
-
#
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
if [[ -n "$TARGET_DIR"
|
|
794
|
-
CHANGES=$(
|
|
869
|
+
# Resolve where to check for changes: the last resolvable `cd`
|
|
870
|
+
# target if the command has one (multi-line commands included —
|
|
871
|
+
# #963), else the command's own cwd from the hook payload (never
|
|
872
|
+
# this hook process's own cwd, which need not match).
|
|
873
|
+
TARGET_DIR=$(resolve_cd_target "$TOOL_INPUT")
|
|
874
|
+
HAS_CD_LINE=false
|
|
875
|
+
echo "$TOOL_INPUT" | grep -qE '^[[:space:]]*cd[[:space:]]+' && HAS_CD_LINE=true
|
|
876
|
+
|
|
877
|
+
if [[ -n "$TARGET_DIR" ]]; then
|
|
878
|
+
CHANGES=$(git -C "$TARGET_DIR" status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
879
|
+
elif [[ "$HAS_CD_LINE" == true ]]; then
|
|
880
|
+
# A `cd` line is present but its target is dynamic (a shell
|
|
881
|
+
# variable/command substitution) or doesn't exist as a
|
|
882
|
+
# directory — fail open rather than check the wrong
|
|
883
|
+
# directory. `git commit` itself already rejects a genuinely
|
|
884
|
+
# empty commit, so this costs one harmless git error (#963).
|
|
885
|
+
CHANGES=1
|
|
795
886
|
else
|
|
796
|
-
CHANGES=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
887
|
+
CHANGES=$(git -C "${HOOK_CWD:-$PWD}" status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
797
888
|
fi
|
|
798
889
|
|
|
799
890
|
if [[ "$CHANGES" -eq 0 ]]; then
|
|
@@ -1955,7 +1955,7 @@ The goal is to satisfy AC with the smallest, safest change possible.
|
|
|
1955
1955
|
### 6. Progress Summary and Draft Issue Update
|
|
1956
1956
|
|
|
1957
1957
|
**If orchestrated (SEQUANT_ORCHESTRATOR is set):**
|
|
1958
|
-
- Skip posting progress comments to GitHub
|
|
1958
|
+
- Skip posting progress comments to GitHub — no per-phase comment is posted under `sequant run`; progress surfaces through the run summary and the PR body (#964)
|
|
1959
1959
|
- Still provide AC coverage summary in output for orchestrator to capture
|
|
1960
1960
|
- Let orchestrator handle final GitHub update
|
|
1961
1961
|
|
|
@@ -23,7 +23,6 @@ allowed-tools:
|
|
|
23
23
|
- Bash(gh issue edit:*)
|
|
24
24
|
- Bash(gh pr create:*)
|
|
25
25
|
- Bash(gh pr list:*)
|
|
26
|
-
- Bash(gh pr merge:*)
|
|
27
26
|
- Bash(npm test:*)
|
|
28
27
|
- Bash(npm run build:*)
|
|
29
28
|
- Bash(git diff:*)
|
|
@@ -52,22 +51,28 @@ When invoked as `/fullsolve <issue-number>`, execute the complete issue resoluti
|
|
|
52
51
|
|
|
53
52
|
## CRITICAL: Auto-Progression Between Phases
|
|
54
53
|
|
|
55
|
-
**DO NOT wait for user confirmation between phases.** This is an autonomous workflow.
|
|
54
|
+
**DO NOT wait for user confirmation between phases.** This is an autonomous workflow — through PR creation.
|
|
56
55
|
|
|
57
56
|
After each phase completes successfully, **immediately proceed** to the next phase:
|
|
58
57
|
1. `/spec` completes → **immediately** invoke `/exec`
|
|
59
58
|
2. `/exec` completes → **immediately** invoke `/test` (if UI) or `/qa`
|
|
60
59
|
3. `/test` completes → **immediately** invoke `/qa`
|
|
61
|
-
4. `/qa` completes → **immediately** create PR
|
|
60
|
+
4. `/qa` completes → **immediately** create the PR and post the final summary
|
|
62
61
|
|
|
63
|
-
|
|
62
|
+
<!-- BEGIN: merge-gate (#958) -->
|
|
63
|
+
**The workflow's terminal state is PR created + final summary posted — not merged.** Merging (§5.3), post-merge verification (§5.4), and the auto-merge-path lock release (§5.5) run **only** when `--auto-merge` is passed, `run.autoMerge` is `true` in `.sequant/settings.json`, or the user has explicitly instructed a merge in this conversation (invoking `/fullsolve` alone does not count). Without one of those, `/fullsolve` stops after the final summary — the PR is left open for human review. See "Merge Gate" below for how this is resolved, and §5.3 for the gate itself.
|
|
64
|
+
|
|
65
|
+
**The user invoked `/fullsolve` expecting end-to-end automation up to a mergeable PR.** Only stop for:
|
|
64
66
|
- Unrecoverable errors (after retry attempts exhausted)
|
|
65
|
-
- Final summary after PR creation
|
|
67
|
+
- Final summary after PR creation — **this is the workflow's terminal state**, not a pause
|
|
66
68
|
- Explicit user interruption
|
|
69
|
+
<!-- END: merge-gate (#958) -->
|
|
67
70
|
|
|
68
71
|
```
|
|
69
72
|
WRONG: "Spec complete. Ready for exec phase." [waits]
|
|
70
73
|
RIGHT: "Spec complete. Proceeding to exec..." [invokes /exec immediately]
|
|
74
|
+
WRONG (no --auto-merge): [creates PR, immediately runs `gh pr merge`]
|
|
75
|
+
RIGHT (no --auto-merge): [creates PR, posts final summary, stops]
|
|
71
76
|
```
|
|
72
77
|
|
|
73
78
|
## Workflow Overview
|
|
@@ -125,6 +130,7 @@ RIGHT: "Spec complete. Proceeding to exec..." [invokes /exec immediately]
|
|
|
125
130
|
/fullsolve 218 --max-iterations 5 # Override max fix iterations
|
|
126
131
|
/fullsolve 218 --parallel # Force parallel agent execution (faster, higher token usage)
|
|
127
132
|
/fullsolve 218 --sequential # Force sequential agent execution (slower, lower token usage)
|
|
133
|
+
/fullsolve 218 --auto-merge # Merge the PR automatically once QA passes (default: off)
|
|
128
134
|
```
|
|
129
135
|
|
|
130
136
|
## Agent Execution Mode
|
|
@@ -151,6 +157,30 @@ When spawning sub-agents for quality checks, determine the execution mode:
|
|
|
151
157
|
|
|
152
158
|
**Pass execution mode to child skills:** When invoking `/qa` or other skills that spawn agents, pass the `--parallel` or `--sequential` flag to maintain consistency.
|
|
153
159
|
|
|
160
|
+
## Merge Gate (#958)
|
|
161
|
+
|
|
162
|
+
Determine whether Phase 5.3–5.5's merge workflow runs at all. This mirrors the Agent Execution Mode resolution above — flag first, then settings, defaulting closed.
|
|
163
|
+
|
|
164
|
+
1. **Check for CLI flag override:**
|
|
165
|
+
- `--auto-merge` → run the merge workflow (§5.3) after the final summary
|
|
166
|
+
- No flag → do not merge automatically; fall through to step 2
|
|
167
|
+
|
|
168
|
+
2. **If no flag, read project settings:**
|
|
169
|
+
Use the Read tool to check project settings:
|
|
170
|
+
```
|
|
171
|
+
Read(file_path=".sequant/settings.json")
|
|
172
|
+
# Parse JSON and extract run.autoMerge (default: false)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
3. **Default:** off. `/fullsolve` ends at PR creation + final summary — this
|
|
176
|
+
preserves the human merge gate recorded in #817–#819 (`sequant ready`
|
|
177
|
+
drives an issue to merge-*readiness*; a human runs `sequant merge` to
|
|
178
|
+
actually merge it).
|
|
179
|
+
|
|
180
|
+
**Explicit user instruction overrides the gate independent of the flag or setting.** If the user has told you in this conversation to merge once ready (not merely "run `/fullsolve`"), treat that as satisfying the gate for this run.
|
|
181
|
+
|
|
182
|
+
**If the gate does not fire:** skip §5.3 and §5.4 entirely. Release the concurrency locks immediately after §5.2 (see §5.5) and stop — do not attempt `gh pr merge` under any circumstance without one of the three conditions above.
|
|
183
|
+
|
|
154
184
|
## Orchestration Context
|
|
155
185
|
|
|
156
186
|
This skill acts as an **orchestrator** and sets environment variables for child skills to optimize their behavior:
|
|
@@ -306,7 +336,7 @@ npx sequant locks checkout acquire \
|
|
|
306
336
|
|
|
307
337
|
Release it alongside the per-issue lock: `npx sequant locks checkout release --issue=<issue-number> || true`. **`--issue` is mandatory** (#906) — it is what proves you are the holder. `--skip-pid-check` means the acquiring shell's PID is already dead, so PID identity is unavailable and a release without `--issue` is refused, not merely ineffective. Stale recovery is therefore age-based only for this lock: the 6h `SEQUANT_SKILL_LOCK_TTL_MS` and the 24h `SEQUANT_MAX_LOCK_AGE_MS` ceiling, *not* same-host dead-PID recovery, which `--skip-pid-check` disables by definition. An abandoned holder still cannot wedge the checkout permanently.
|
|
308
338
|
|
|
309
|
-
**Release contract:**
|
|
339
|
+
**Release contract (#958):** the happy path releases both locks **right after §5.2's final summary** — that is the workflow's default terminal state, since §5.3–5.4 do not run without the Merge Gate firing. Only when the Merge Gate *does* fire does release move to §5.5, after merge (§5.3) and post-merge verification (§5.4) complete. On ANY branch that **exits the workflow without reaching Phase 5** — spec failure, exec iterations exhausted, unrecoverable error — you MUST run `npx sequant locks release <issue-number> || true` and `npx sequant locks checkout release --issue=<issue-number> || true` **before** printing the halt message. The explicit release calls below cover the known branches; if you add a new early-exit path, add a release call there too.
|
|
310
340
|
|
|
311
341
|
**Do NOT release at a branch that continues to Phase 5.** QA-loop exhaustion and the stagnation halt both fall through to PR creation, which still runs git in this tree — releasing there would leave Phase 5 unprotected.
|
|
312
342
|
|
|
@@ -763,9 +793,17 @@ Post completion comment to issue with:
|
|
|
763
793
|
- PR link
|
|
764
794
|
- Quality metrics
|
|
765
795
|
|
|
766
|
-
### 5.3 Merge Workflow (
|
|
796
|
+
### 5.3 Merge Workflow (Opt-In Only) (#958)
|
|
767
797
|
|
|
768
|
-
**
|
|
798
|
+
**STOP — do not run this section unless the Merge Gate above fired.** That
|
|
799
|
+
means one of: `--auto-merge` was passed on this `/fullsolve` invocation,
|
|
800
|
+
`run.autoMerge` is `true` in `.sequant/settings.json`, or the user
|
|
801
|
+
explicitly instructed a merge in this conversation. If none of those hold,
|
|
802
|
+
**do not run `gh pr merge`.** Stop after §5.2's final summary instead — the
|
|
803
|
+
PR stays open, awaiting human review. That is the default terminal state,
|
|
804
|
+
not a fallback.
|
|
805
|
+
|
|
806
|
+
**IMPORTANT (once the gate above has fired):** Merge the PR first, then clean up the worktree.
|
|
769
807
|
|
|
770
808
|
```bash
|
|
771
809
|
# 1. Merge PR (without --delete-branch; cleanup happens after success)
|
|
@@ -783,6 +821,8 @@ gh pr merge <N> --squash
|
|
|
783
821
|
|
|
784
822
|
### 5.4 Post-Merge Verification
|
|
785
823
|
|
|
824
|
+
**Skip this section if §5.3 did not run.** Nothing to verify post-merge when there was no merge.
|
|
825
|
+
|
|
786
826
|
**Recommended:** After merge, verify the build and CLI still work:
|
|
787
827
|
|
|
788
828
|
```bash
|
|
@@ -800,7 +840,14 @@ If any command fails, fix immediately on main before continuing. This catches is
|
|
|
800
840
|
|
|
801
841
|
### 5.5 Release Concurrency Locks (#625, #901)
|
|
802
842
|
|
|
803
|
-
|
|
843
|
+
**Default path (Merge Gate did not fire):** release runs immediately after
|
|
844
|
+
§5.2's final summary — that is the happy path, since §5.3–5.4 never execute.
|
|
845
|
+
Run the release calls below there, not here.
|
|
846
|
+
|
|
847
|
+
**Auto-merge path (Merge Gate fired):** release runs here, after §5.3
|
|
848
|
+
(merge) and §5.4 (post-merge verification) complete.
|
|
849
|
+
|
|
850
|
+
Either way, release both locks so other sessions can claim them:
|
|
804
851
|
|
|
805
852
|
```bash
|
|
806
853
|
npx sequant locks release <issue-number> || true
|
|
@@ -949,6 +996,7 @@ Do **not** run it on the two branches below that continue to Phase 5 — "test l
|
|
|
949
996
|
| MAX_QA_ITERATIONS | 2 | Max fix loops for QA phase |
|
|
950
997
|
| SKIP_TEST | false | Skip testing phase |
|
|
951
998
|
| AUTO_PR | true | Create PR automatically |
|
|
999
|
+
| AUTO_MERGE | false | Merge the PR automatically once QA passes (#958) |
|
|
952
1000
|
|
|
953
1001
|
## Smart Tests Integration
|
|
954
1002
|
|
|
@@ -993,6 +1041,11 @@ npx tsx scripts/dev/analyze-hook-logs.ts --tests
|
|
|
993
1041
|
/fullsolve 218 --max-iterations 5
|
|
994
1042
|
```
|
|
995
1043
|
|
|
1044
|
+
**End-to-end including merge:**
|
|
1045
|
+
```
|
|
1046
|
+
/fullsolve 218 --auto-merge
|
|
1047
|
+
```
|
|
1048
|
+
|
|
996
1049
|
## Batch Processing
|
|
997
1050
|
|
|
998
1051
|
For multiple issues, run `/fullsolve` on each sequentially:
|