sequant 2.9.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 +14 -3
- package/dist/bin/cli.js +97 -17
- 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 +56 -2
- package/dist/marketplace/external_plugins/sequant/hooks/parallel-marker.sh +50 -0
- package/dist/marketplace/external_plugins/sequant/hooks/post-tool.sh +20 -7
- package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +75 -10
- package/dist/marketplace/external_plugins/sequant/skills/_shared/references/trust-model.md +18 -0
- package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +215 -390
- package/dist/marketplace/external_plugins/sequant/skills/assess/references/predicted-collision-detection.md +1 -1
- 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 +3 -1
- 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/release/SKILL.md +1 -1
- 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/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 +14 -5
- 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.js +37 -4
- 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/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/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 +62 -0
- package/dist/src/lib/errors.d.ts +78 -6
- package/dist/src/lib/errors.js +133 -15
- 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/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 +45 -2
- package/dist/src/lib/workflow/batch-executor.d.ts +125 -6
- package/dist/src/lib/workflow/batch-executor.js +477 -44
- package/dist/src/lib/workflow/chain-resume.d.ts +13 -11
- package/dist/src/lib/workflow/chain-resume.js +24 -20
- 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/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 +14 -0
- package/dist/src/lib/workflow/drivers/claude-code.js +20 -2
- 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 +75 -6
- package/dist/src/lib/workflow/metrics-schema.d.ts +3 -3
- package/dist/src/lib/workflow/phase-executor.d.ts +203 -14
- package/dist/src/lib/workflow/phase-executor.js +457 -52
- 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 +14 -0
- package/dist/src/lib/workflow/run-log-schema.js +47 -1
- package/dist/src/lib/workflow/run-orchestrator.d.ts +19 -0
- package/dist/src/lib/workflow/run-orchestrator.js +141 -20
- 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/types.d.ts +126 -4
- package/dist/src/lib/workflow/types.js +1 -0
- package/dist/src/lib/workflow/worktree-manager.d.ts +28 -2
- package/dist/src/lib/workflow/worktree-manager.js +102 -23
- package/dist/src/mcp/tools/run.d.ts +9 -1
- package/dist/src/mcp/tools/run.js +20 -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 +8 -7
- package/templates/hooks/parallel-marker.sh +50 -0
- package/templates/hooks/post-tool.sh +20 -7
- package/templates/hooks/pre-tool.sh +75 -10
- package/templates/scripts/cleanup-worktree.sh +182 -19
- package/templates/scripts/new-feature.sh +264 -16
- package/templates/skills/_shared/references/trust-model.md +18 -0
- package/templates/skills/assess/SKILL.md +215 -390
- package/templates/skills/assess/references/predicted-collision-detection.md +1 -1
- package/templates/skills/docs/SKILL.md +4 -4
- package/templates/skills/exec/SKILL.md +26 -6
- package/templates/skills/fullsolve/SKILL.md +3 -1
- 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/release/SKILL.md +1 -1
- package/templates/skills/setup/SKILL.md +5 -5
- package/templates/skills/spec/SKILL.md +2 -0
- 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
|
@@ -100,9 +100,16 @@ export declare function extractTestBlocks(content: string): Array<{
|
|
|
100
100
|
style: "it" | "test";
|
|
101
101
|
}>;
|
|
102
102
|
/**
|
|
103
|
-
* Check if a test block
|
|
103
|
+
* Check if a test block calls production code. A block counts as non-tautological
|
|
104
|
+
* when it references an imported production function, directly spawns the
|
|
105
|
+
* project's build output, or calls a helper that (transitively) does so.
|
|
106
|
+
*
|
|
107
|
+
* @param spawnHandles Names of describe/module-scope helpers that spawn the
|
|
108
|
+
* build output (see {@link collectSpawnHandles}).
|
|
109
|
+
* @param buildOutputVars Variable names bound to a build-output path (see
|
|
110
|
+
* {@link collectBuildOutputVars}).
|
|
104
111
|
*/
|
|
105
|
-
export declare function testBlockCallsProductionCode(body: string, importedFunctions: ImportedFunction[]): boolean;
|
|
112
|
+
export declare function testBlockCallsProductionCode(body: string, importedFunctions: ImportedFunction[], spawnHandles?: string[], buildOutputVars?: string[]): boolean;
|
|
106
113
|
/**
|
|
107
114
|
* Check if a file opts out of tautology detection via pragma comment.
|
|
108
115
|
*
|
|
@@ -341,20 +341,159 @@ function escapeRegex(str) {
|
|
|
341
341
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
342
342
|
}
|
|
343
343
|
/**
|
|
344
|
-
*
|
|
344
|
+
* Build a whole-identifier reference matcher for `name`, bounded by
|
|
345
|
+
* non-identifier chars ([\w$]). Catches direct calls, method calls, callback
|
|
346
|
+
* references, and assignments while rejecting substring matches.
|
|
345
347
|
*/
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
348
|
+
function referenceMatcher(name) {
|
|
349
|
+
return new RegExp(`(?<![\\w$])${escapeRegex(name)}(?![\\w$])`);
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Child-process spawn functions. A test that spawns the project's *build
|
|
353
|
+
* output* is exercising production code across a process boundary that static
|
|
354
|
+
* import analysis cannot see through — issue #885.
|
|
355
|
+
*
|
|
356
|
+
* Two alternates by name ambiguity: the long names are unambiguous
|
|
357
|
+
* child-process API and match anywhere, including method-style calls from a
|
|
358
|
+
* namespace import (`cp.execSync(...)`). The short names (`exec`, `spawn`,
|
|
359
|
+
* `fork`) collide with unrelated methods — `RegExp.prototype.exec` most of
|
|
360
|
+
* all — so they must not be preceded by `.` (or an identifier char). The
|
|
361
|
+
* cost is that method-style callback `cp.exec(...)` no longer counts; tests
|
|
362
|
+
* that spawn build output overwhelmingly use the sync variants, and a false
|
|
363
|
+
* tautology report is loud where the `.exec()` collision was silent.
|
|
364
|
+
*/
|
|
365
|
+
const SPAWN_PATTERN = /(?:\b(?:execFileSync|spawnSync|execSync|execFile)\s*\(|(?<![\w$.])(?:exec|fork|spawn)\s*\()/;
|
|
366
|
+
/**
|
|
367
|
+
* Marker for the project's build-output directory. A spawn whose arguments
|
|
368
|
+
* reach this path is running compiled production code.
|
|
369
|
+
*/
|
|
370
|
+
const BUILD_OUTPUT_PATTERN = /\bdist\//;
|
|
371
|
+
/**
|
|
372
|
+
* Collect names of variables bound to a build-output path, e.g.
|
|
373
|
+
* const cliPath = resolve(projectRoot, "dist/bin/cli.js");
|
|
374
|
+
* captures `cliPath`. Tests almost always spawn via such a handle rather than
|
|
375
|
+
* an inline string, so these names stand in for the literal build path.
|
|
376
|
+
*
|
|
377
|
+
* The right-hand side is statement-bounded (`[^;]`) so a match cannot bleed
|
|
378
|
+
* across declarations, and must contain the `dist/` marker.
|
|
379
|
+
*/
|
|
380
|
+
function collectBuildOutputVars(content) {
|
|
381
|
+
const names = new Set();
|
|
382
|
+
const pattern = /(?:const|let|var)\s+(\w+)\s*=\s*[^;]*?\bdist\//g;
|
|
383
|
+
let match;
|
|
384
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
385
|
+
names.add(match[1]);
|
|
386
|
+
}
|
|
387
|
+
return [...names];
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Whether a code body references a build-output token: either the literal
|
|
391
|
+
* `dist/` marker or one of the collected build-path variable names.
|
|
392
|
+
*/
|
|
393
|
+
function referencesBuildOutput(body, buildOutputVars) {
|
|
394
|
+
if (BUILD_OUTPUT_PATTERN.test(body)) {
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
return buildOutputVars.some((name) => referenceMatcher(name).test(body));
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Whether a code body itself spawns the build output: it must contain BOTH a
|
|
401
|
+
* child-process spawn call AND a build-output token. Requiring co-occurrence
|
|
402
|
+
* keeps a helper that merely mentions `dist/` in a string (but never spawns)
|
|
403
|
+
* from counting as production (#885 AC-5).
|
|
404
|
+
*/
|
|
405
|
+
function spawnsBuildOutput(body, buildOutputVars) {
|
|
406
|
+
return (SPAWN_PATTERN.test(body) && referencesBuildOutput(body, buildOutputVars));
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Extract module/describe-scope helper definitions (named block-bodied arrow
|
|
410
|
+
* consts) as { name, body } pairs.
|
|
411
|
+
*
|
|
412
|
+
* Anchors on `=> {` so a return-type object annotation
|
|
413
|
+
* const run = (): { stdout: string } => { ... }
|
|
414
|
+
* is not mistaken for the function body. Params are matched with `[^()]*` (no
|
|
415
|
+
* nested parens) to keep the scan from running away across the file. Only
|
|
416
|
+
* block-bodied arrows are collected; the subprocess integration tests this
|
|
417
|
+
* targets all use them.
|
|
418
|
+
*/
|
|
419
|
+
function extractHelperDefinitions(content) {
|
|
420
|
+
const helpers = [];
|
|
421
|
+
const pattern = /(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^()]*\)\s*(?::[^=]*?)?=>\s*\{/g;
|
|
422
|
+
let match;
|
|
423
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
424
|
+
if (isInsideString(content, match.index)) {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
const name = match[1];
|
|
428
|
+
// The final `{` of the match is the function body's opening brace.
|
|
429
|
+
const braceIndex = match.index + match[0].length - 1;
|
|
430
|
+
const body = extractBlockBody(content.substring(braceIndex));
|
|
431
|
+
helpers.push({ name, body });
|
|
432
|
+
}
|
|
433
|
+
return helpers;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Collect the names of helper functions that spawn the build output, resolving
|
|
437
|
+
* indirection transitively: a helper that calls an already-known spawn helper
|
|
438
|
+
* is itself a spawn helper. This lets a test that only calls
|
|
439
|
+
* `expectFlagAccepted(...)` (which calls `runInUninitializedDir`, which spawns
|
|
440
|
+
* the CLI) count as exercising production code.
|
|
441
|
+
*/
|
|
442
|
+
function collectSpawnHandles(content, buildOutputVars) {
|
|
443
|
+
const helpers = extractHelperDefinitions(content);
|
|
444
|
+
const handles = new Set();
|
|
445
|
+
// Seed: helpers that directly spawn the build output.
|
|
446
|
+
for (const helper of helpers) {
|
|
447
|
+
if (spawnsBuildOutput(helper.body, buildOutputVars)) {
|
|
448
|
+
handles.add(helper.name);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
// Transitive closure: a helper referencing a known spawn helper is one too.
|
|
452
|
+
let changed = true;
|
|
453
|
+
while (changed) {
|
|
454
|
+
changed = false;
|
|
455
|
+
for (const helper of helpers) {
|
|
456
|
+
if (handles.has(helper.name)) {
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
for (const known of handles) {
|
|
460
|
+
if (referenceMatcher(known).test(helper.body)) {
|
|
461
|
+
handles.add(helper.name);
|
|
462
|
+
changed = true;
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
349
467
|
}
|
|
468
|
+
return [...handles];
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Check if a test block calls production code. A block counts as non-tautological
|
|
472
|
+
* when it references an imported production function, directly spawns the
|
|
473
|
+
* project's build output, or calls a helper that (transitively) does so.
|
|
474
|
+
*
|
|
475
|
+
* @param spawnHandles Names of describe/module-scope helpers that spawn the
|
|
476
|
+
* build output (see {@link collectSpawnHandles}).
|
|
477
|
+
* @param buildOutputVars Variable names bound to a build-output path (see
|
|
478
|
+
* {@link collectBuildOutputVars}).
|
|
479
|
+
*/
|
|
480
|
+
export function testBlockCallsProductionCode(body, importedFunctions, spawnHandles = [], buildOutputVars = []) {
|
|
481
|
+
// 1. References an imported production function.
|
|
350
482
|
for (const fn of importedFunctions) {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
483
|
+
if (referenceMatcher(fn.name).test(body)) {
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
// 2. Directly spawns the project's build output (#885). Static import
|
|
488
|
+
// analysis can't see through a subprocess boundary, so a test that runs
|
|
489
|
+
// `dist/bin/cli.js` looks import-less but exercises production code.
|
|
490
|
+
if (spawnsBuildOutput(body, buildOutputVars)) {
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
493
|
+
// 3. Calls a describe/module-scope helper that (transitively) spawns the
|
|
494
|
+
// build output.
|
|
495
|
+
for (const handle of spawnHandles) {
|
|
496
|
+
if (referenceMatcher(handle).test(body)) {
|
|
358
497
|
return true;
|
|
359
498
|
}
|
|
360
499
|
}
|
|
@@ -389,12 +528,14 @@ export function analyzeTestFile(content, filePath) {
|
|
|
389
528
|
}
|
|
390
529
|
try {
|
|
391
530
|
const importedFunctions = extractImports(content);
|
|
531
|
+
const buildOutputVars = collectBuildOutputVars(content);
|
|
532
|
+
const spawnHandles = collectSpawnHandles(content, buildOutputVars);
|
|
392
533
|
const testBlocks = extractTestBlocks(content);
|
|
393
534
|
const analyzedBlocks = testBlocks.map((block) => ({
|
|
394
535
|
description: block.description,
|
|
395
536
|
lineNumber: block.lineNumber,
|
|
396
537
|
style: block.style,
|
|
397
|
-
isTautological: !testBlockCallsProductionCode(block.body, importedFunctions),
|
|
538
|
+
isTautological: !testBlockCallsProductionCode(block.body, importedFunctions, spawnHandles, buildOutputVars),
|
|
398
539
|
}));
|
|
399
540
|
const tautologicalCount = analyzedBlocks.filter((b) => b.isTautological).length;
|
|
400
541
|
const totalTests = analyzedBlocks.length;
|
|
@@ -53,6 +53,37 @@ export declare function isGlobalInstall(installPath?: string): boolean;
|
|
|
53
53
|
* neither receives the project-local "use npm update sequant" warning.
|
|
54
54
|
*/
|
|
55
55
|
export declare function isLocalNodeModulesInstall(installPath?: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Check if running from the npx cache (~/.npm/_npx/<hash>/node_modules/sequant).
|
|
58
|
+
*
|
|
59
|
+
* Separated from `isLocalNodeModulesInstall`, which deliberately excludes the
|
|
60
|
+
* cache: that predicate answers "is this a project dependency the user can
|
|
61
|
+
* `npm update`", while this one answers "did the user reach us through npx".
|
|
62
|
+
* Backslashes are normalized first, so the single forward-slash form also
|
|
63
|
+
* matches a Windows `…\.npm\_npx\…` path.
|
|
64
|
+
*/
|
|
65
|
+
export declare function isNpxCacheInstall(installPath?: string): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Resolve the invocation to name in user-facing "run this next" messages.
|
|
68
|
+
*
|
|
69
|
+
* A hardcoded `npx sequant` is right for the documented install path (README:
|
|
70
|
+
* `npm install sequant` then `npx sequant init`) — npx resolves the project's
|
|
71
|
+
* `node_modules/.bin/sequant`, and a bare `sequant` would not be on PATH. It is
|
|
72
|
+
* wrong for a global or `npm link`ed install with no local dependency: there,
|
|
73
|
+
* `npx sequant` cannot resolve locally and falls through to the npx cache,
|
|
74
|
+
* which can hold an older *published* copy than the one currently running. The
|
|
75
|
+
* observed symptom is a remediation that fights itself — the recommended
|
|
76
|
+
* command reinstalls the very content whose staleness triggered the message.
|
|
77
|
+
*
|
|
78
|
+
* So recommend the form that actually reaches *this* build:
|
|
79
|
+
* - project-local dependency or npx cache → `npx sequant`
|
|
80
|
+
* - global, `npm link`ed, or a dev checkout → `sequant`
|
|
81
|
+
*
|
|
82
|
+
* A linked checkout resolves through its realpath (Node resolves symlinks
|
|
83
|
+
* unless `--preserve-symlinks` is set), so it looks like neither a global nor a
|
|
84
|
+
* local install and correctly lands on the bare form.
|
|
85
|
+
*/
|
|
86
|
+
export declare function resolveCliInvocation(installPath?: string): "sequant" | "npx sequant";
|
|
56
87
|
/**
|
|
57
88
|
* Walk up from the given directory to find the directory containing
|
|
58
89
|
* sequant's package.json. Returns null if not found.
|
|
@@ -8,7 +8,7 @@ import fs from "fs";
|
|
|
8
8
|
import os from "os";
|
|
9
9
|
import path from "path";
|
|
10
10
|
import { fileURLToPath } from "url";
|
|
11
|
-
import { detectPackageManagerSync,
|
|
11
|
+
import { detectPackageManagerSync, resolvePackageManagerConfig, } from "./stacks.js";
|
|
12
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
13
|
const __dirname = path.dirname(__filename);
|
|
14
14
|
const PACKAGE_NAME = "sequant";
|
|
@@ -92,6 +92,44 @@ export function isLocalNodeModulesInstall(installPath = __dirname) {
|
|
|
92
92
|
normalizedPath.includes("\\.npm\\_npx\\");
|
|
93
93
|
return inNodeModules && !inNpxCache && !isGlobalInstall(normalizedPath);
|
|
94
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Check if running from the npx cache (~/.npm/_npx/<hash>/node_modules/sequant).
|
|
97
|
+
*
|
|
98
|
+
* Separated from `isLocalNodeModulesInstall`, which deliberately excludes the
|
|
99
|
+
* cache: that predicate answers "is this a project dependency the user can
|
|
100
|
+
* `npm update`", while this one answers "did the user reach us through npx".
|
|
101
|
+
* Backslashes are normalized first, so the single forward-slash form also
|
|
102
|
+
* matches a Windows `…\.npm\_npx\…` path.
|
|
103
|
+
*/
|
|
104
|
+
export function isNpxCacheInstall(installPath = __dirname) {
|
|
105
|
+
return installPath.replace(/\\/g, "/").includes("/.npm/_npx/");
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Resolve the invocation to name in user-facing "run this next" messages.
|
|
109
|
+
*
|
|
110
|
+
* A hardcoded `npx sequant` is right for the documented install path (README:
|
|
111
|
+
* `npm install sequant` then `npx sequant init`) — npx resolves the project's
|
|
112
|
+
* `node_modules/.bin/sequant`, and a bare `sequant` would not be on PATH. It is
|
|
113
|
+
* wrong for a global or `npm link`ed install with no local dependency: there,
|
|
114
|
+
* `npx sequant` cannot resolve locally and falls through to the npx cache,
|
|
115
|
+
* which can hold an older *published* copy than the one currently running. The
|
|
116
|
+
* observed symptom is a remediation that fights itself — the recommended
|
|
117
|
+
* command reinstalls the very content whose staleness triggered the message.
|
|
118
|
+
*
|
|
119
|
+
* So recommend the form that actually reaches *this* build:
|
|
120
|
+
* - project-local dependency or npx cache → `npx sequant`
|
|
121
|
+
* - global, `npm link`ed, or a dev checkout → `sequant`
|
|
122
|
+
*
|
|
123
|
+
* A linked checkout resolves through its realpath (Node resolves symlinks
|
|
124
|
+
* unless `--preserve-symlinks` is set), so it looks like neither a global nor a
|
|
125
|
+
* local install and correctly lands on the bare form.
|
|
126
|
+
*/
|
|
127
|
+
export function resolveCliInvocation(installPath = __dirname) {
|
|
128
|
+
return isLocalNodeModulesInstall(installPath) ||
|
|
129
|
+
isNpxCacheInstall(installPath)
|
|
130
|
+
? "npx sequant"
|
|
131
|
+
: "sequant";
|
|
132
|
+
}
|
|
95
133
|
/**
|
|
96
134
|
* Walk up from the given directory to find the directory containing
|
|
97
135
|
* sequant's package.json. Returns null if not found.
|
|
@@ -314,7 +352,12 @@ export function getVersionWarning(currentVersion, latestVersion, isLocal) {
|
|
|
314
352
|
const isLocalInstall = isLocal ?? isLocalNodeModulesInstall();
|
|
315
353
|
if (isLocalInstall) {
|
|
316
354
|
const pm = detectPackageManagerSync();
|
|
317
|
-
|
|
355
|
+
// Resolved, not read off PM_CONFIG: `updatePkg` is one of the fields whose
|
|
356
|
+
// yarn spelling depends on the major (berry `yarn up` vs classic
|
|
357
|
+
// `yarn upgrade`), and this string is a command we are telling the user to
|
|
358
|
+
// run. Both `detectPackageManagerSync` and the resolver read the cwd, which
|
|
359
|
+
// is the project this local install belongs to (#871).
|
|
360
|
+
const pmConfig = resolvePackageManagerConfig(pm, process.cwd());
|
|
318
361
|
return `sequant ${latestVersion} is available (you have ${currentVersion})
|
|
319
362
|
Run: ${pmConfig.updatePkg} sequant
|
|
320
363
|
Note: You have sequant as a local dependency. npx uses your node_modules version.`;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* (including quality-loop retries, checkpoint commits, rebasing, and PR
|
|
8
8
|
* creation).
|
|
9
9
|
*/
|
|
10
|
+
import { LogWriter } from "./log-writer.js";
|
|
10
11
|
import { ExecutionConfig, PhaseResult, IssueResult, type RunOptions, type IssueExecutionContext, type BatchExecutionContext, type ProgressCallback } from "./types.js";
|
|
11
12
|
import { type ErrorCategory } from "./error-classifier.js";
|
|
12
13
|
export type { RunOptions, ProgressCallback, IssueExecutionContext, BatchExecutionContext, } from "./types.js";
|
|
@@ -21,15 +22,32 @@ export type { RunOptions, ProgressCallback, IssueExecutionContext, BatchExecutio
|
|
|
21
22
|
* @param extra - Optional fields: durationSeconds (on complete), error (on failed)
|
|
22
23
|
*/
|
|
23
24
|
/**
|
|
24
|
-
* Wrap an `ExecutionConfig` with
|
|
25
|
-
* agent-output ping as
|
|
25
|
+
* Wrap an `ExecutionConfig` with the runtime liveness hooks:
|
|
26
|
+
* - `onActivity` — re-emits each agent-output ping as an `"activity"` progress
|
|
27
|
+
* event for the dashboard (#543).
|
|
28
|
+
* - `onAutoWait` — re-emits each auto-wait tick as a `"waiting"` progress
|
|
29
|
+
* event so the renderer and heartbeat can show the pause and its wake time
|
|
30
|
+
* (#804 AC-7). Under `SEQUANT_ORCHESTRATOR` it additionally emits throttled
|
|
31
|
+
* `SEQUANT_PROGRESS` waiting lines (#860): an MCP-driven wait was previously
|
|
32
|
+
* invisible on the JSON channel — indistinguishable from a hang — AND was
|
|
33
|
+
* killed by the MCP inactivity timeout, which resets on progress lines.
|
|
34
|
+
* Optionally notifies `onWaitTransition` on wait start/end so the caller
|
|
35
|
+
* can persist the wait to issue state (`sequant status` truthfulness).
|
|
26
36
|
*
|
|
27
|
-
* Returns the input config unchanged when no
|
|
28
|
-
*
|
|
37
|
+
* Returns the input config unchanged when there is no consumer at all (no
|
|
38
|
+
* `onProgress`, no orchestrator channel, no transition callback), so plain
|
|
39
|
+
* non-TUI runs pay no overhead.
|
|
29
40
|
*
|
|
30
41
|
* @internal Exported for testing only
|
|
31
42
|
*/
|
|
32
|
-
export declare function withActivityHook(base: ExecutionConfig, issueNumber: number, phase: string, onProgress: ProgressCallback | undefined): ExecutionConfig;
|
|
43
|
+
export declare function withActivityHook(base: ExecutionConfig, issueNumber: number, phase: string, onProgress: ProgressCallback | undefined, onWaitTransition?: (wakeAtMs: number | null) => void): ExecutionConfig;
|
|
44
|
+
/**
|
|
45
|
+
* Cadence of orchestrator-channel waiting lines during an auto-wait (#860).
|
|
46
|
+
* See {@link withActivityHook}.
|
|
47
|
+
*
|
|
48
|
+
* @internal Exported for testing only
|
|
49
|
+
*/
|
|
50
|
+
export declare const AUTO_WAIT_PROGRESS_LINE_INTERVAL_MS = 60000;
|
|
33
51
|
/**
|
|
34
52
|
* Build enriched prompt context for the /loop phase from a failed phase result (#488).
|
|
35
53
|
* Passes QA verdict, failed ACs, and error directly so the /loop skill doesn't need
|
|
@@ -38,10 +56,12 @@ export declare function withActivityHook(base: ExecutionConfig, issueNumber: num
|
|
|
38
56
|
* @internal Exported for testing only
|
|
39
57
|
*/
|
|
40
58
|
export declare function buildLoopContext(failedResult: PhaseResult): string;
|
|
41
|
-
export declare function emitProgressLine(issue: number, phase: string, event?: "start" | "complete" | "failed", extra?: {
|
|
59
|
+
export declare function emitProgressLine(issue: number, phase: string, event?: "start" | "complete" | "failed" | "waiting", extra?: {
|
|
42
60
|
durationSeconds?: number;
|
|
43
61
|
error?: string;
|
|
44
62
|
iteration?: number;
|
|
63
|
+
wakeAtMs?: number;
|
|
64
|
+
remainingMs?: number;
|
|
45
65
|
}): void;
|
|
46
66
|
/**
|
|
47
67
|
* Emit the current run's UUID on stderr so MCP callers can look up the exact
|
|
@@ -73,6 +93,32 @@ export declare function parseBatches(batchArgs: string[]): number[][];
|
|
|
73
93
|
* Parse environment variables for CI configuration
|
|
74
94
|
*/
|
|
75
95
|
export declare function getEnvConfig(): Partial<RunOptions>;
|
|
96
|
+
/**
|
|
97
|
+
* Record an issue's completion in the run log in ONE place (#879): PR info, the
|
|
98
|
+
* PR-failure status flip, then finalize. Extracted so every batch loop shares a
|
|
99
|
+
* single completion sequence and cannot drift.
|
|
100
|
+
*
|
|
101
|
+
* The #879 defect was exactly such a drift: `markIssueFailed` was wired into
|
|
102
|
+
* `executeBatch`'s loop, but the live `sequant run` path is
|
|
103
|
+
* `RunOrchestrator.executeOneIssue`, which called `setPRInfo` + `completeIssue`
|
|
104
|
+
* without it — so a real run left the run-log status at `success` on a
|
|
105
|
+
* PR-creation failure. Both call sites now go through this helper.
|
|
106
|
+
*
|
|
107
|
+
* A PR-creation failure occurs after every phase has been logged, so
|
|
108
|
+
* `deriveIssueLogStatus` (last run at phase-log time) leaves the issue at
|
|
109
|
+
* `success`; the flip here is what counts it under `failed`. Safe post-hoc:
|
|
110
|
+
* no further phase is logged before `completeIssue`.
|
|
111
|
+
*/
|
|
112
|
+
export declare function recordIssueCompletion(logWriter: LogWriter, result: IssueResult, issueNumber?: number): void;
|
|
113
|
+
/**
|
|
114
|
+
* @deprecated No live caller — `sequant run` executes issues via
|
|
115
|
+
* `RunOrchestrator.executeOneIssue`; this survives only as a
|
|
116
|
+
* `commands/run-compat` re-export. Do not build a new execution loop on it:
|
|
117
|
+
* any path that completes an issue MUST go through
|
|
118
|
+
* {@link recordIssueCompletion}, or the #879 status-drift returns (a
|
|
119
|
+
* completion path that skips the PR-failure flip logs a failed issue as
|
|
120
|
+
* `success`). Slated for removal with the run-compat surface.
|
|
121
|
+
*/
|
|
76
122
|
export declare function executeBatch(issueNumbers: number[], batchCtx: BatchExecutionContext): Promise<IssueResult[]>;
|
|
77
123
|
/**
|
|
78
124
|
* Derive the bounded-enum failure category for a failed issue (#761 AC-7).
|
|
@@ -86,4 +132,77 @@ export declare function executeBatch(issueNumbers: number[], batchCtx: BatchExec
|
|
|
86
132
|
* @internal Exported for testing
|
|
87
133
|
*/
|
|
88
134
|
export declare function deriveFailureCategory(phaseResults: PhaseResult[]): ErrorCategory | undefined;
|
|
135
|
+
/**
|
|
136
|
+
* "Halt, don't loop" predicate for the outer `-Q` quality loop (#799).
|
|
137
|
+
*
|
|
138
|
+
* A billing / out-of-credits failure (`BillingError`) or a window-exhausted
|
|
139
|
+
* rate limit (reset hours away, per `isWindowExhaustedRateLimit`) cannot be
|
|
140
|
+
* recovered by re-running the phase — every retry re-spawns into the same
|
|
141
|
+
* closed window and, worse, mislabels the halt as a downstream
|
|
142
|
+
* `QA completed without a parseable verdict`. Mirrors the `haltedByCap` (#739)
|
|
143
|
+
* treatment: surface the real cause and halt so the user resumes once credits
|
|
144
|
+
* or the rate-limit window are restored.
|
|
145
|
+
*
|
|
146
|
+
* A transient / metadata-absent rate limit is NOT a halt: it returns false and
|
|
147
|
+
* keeps today's outer-loop behavior (the inner retry ladder in `phase-executor`
|
|
148
|
+
* handles its backoff, per #761 AC-4/AC-9).
|
|
149
|
+
*
|
|
150
|
+
* @internal Exported for testing
|
|
151
|
+
*/
|
|
152
|
+
export declare function isBillingOrWindowHalt(result: PhaseResult): boolean;
|
|
153
|
+
/**
|
|
154
|
+
* The billing half of {@link isBillingOrWindowHalt} (#804 AC-8).
|
|
155
|
+
*
|
|
156
|
+
* Split out because the two causes stopped being interchangeable once
|
|
157
|
+
* `--auto-wait` existed: a closed window can now reopen on its own, while
|
|
158
|
+
* out-of-credits cannot — credits are purchased, not waited out. Callers that
|
|
159
|
+
* need to reason about recoverability must be able to tell them apart.
|
|
160
|
+
*
|
|
161
|
+
* @internal Exported for testing
|
|
162
|
+
*/
|
|
163
|
+
export declare function isBillingHalt(result: PhaseResult): boolean;
|
|
164
|
+
/**
|
|
165
|
+
* The rate-limit-window half of {@link isBillingOrWindowHalt} (#804 AC-8).
|
|
166
|
+
*
|
|
167
|
+
* NOTE — this predicate needed no behavioral change for auto-wait, and that is
|
|
168
|
+
* a deliberate finding rather than an oversight. AC-8 anticipated that a phase
|
|
169
|
+
* which waits and then succeeds would still halt the `-Q` loop. It cannot:
|
|
170
|
+
* every call site (`:~700` spec, `:~1030` progress label, `:~1150` halt flag)
|
|
171
|
+
* sits inside the `else` of an `if (result.success)`, so a successful
|
|
172
|
+
* post-wait result never reaches this predicate at all. When auto-wait does
|
|
173
|
+
* NOT fire — the default, an exhausted budget, or a spent wait bound — the
|
|
174
|
+
* result is still a failure carrying a window-exhausted `RateLimitError`, and
|
|
175
|
+
* halting is then the correct outcome (#799 behavior, preserved exactly).
|
|
176
|
+
*
|
|
177
|
+
* @internal Exported for testing
|
|
178
|
+
*/
|
|
179
|
+
export declare function isWindowHalt(result: PhaseResult): boolean;
|
|
180
|
+
/**
|
|
181
|
+
* Human-readable halt reason for a billing / rate-limit-window failure (#799
|
|
182
|
+
* AC-3). Surfaces the driver's real cause verbatim — `result.error` is already
|
|
183
|
+
* the well-formatted message the driver built via `formatRateLimitMessage`
|
|
184
|
+
* (`Out of credits` for billing, `Rate limited — resets at <local time>` for a
|
|
185
|
+
* throttle with a known reset), so the phase-failed line and run summary name
|
|
186
|
+
* the actual cause instead of a downstream `QA completed without a parseable
|
|
187
|
+
* verdict`.
|
|
188
|
+
*
|
|
189
|
+
* Do NOT re-append `resetsAt` here: the rate-limit message already carries the
|
|
190
|
+
* reset time, and doing so produced a doubled, timezone-inconsistent string
|
|
191
|
+
* (`… resets at 07-24 14:32 — resets at 2026-…Z`). Credits failures carry no
|
|
192
|
+
* reset time by design (they need purchasing, not a window wait).
|
|
193
|
+
*
|
|
194
|
+
* @internal Exported for testing
|
|
195
|
+
*/
|
|
196
|
+
export declare function billingHaltReason(result: PhaseResult): string;
|
|
197
|
+
/**
|
|
198
|
+
* Epoch ms after which a waitable-window halt can be re-entered (#892 AC-1):
|
|
199
|
+
* the window's `resetsAt` normalized to ms plus the same buffer auto-wait
|
|
200
|
+
* applies (`AUTO_WAIT_BUFFER_MS`), so in-process waits and durable halts wake
|
|
201
|
+
* on the same clock. Returns `null` when the result carries no future-reset
|
|
202
|
+
* rate-limit window — callers must then skip the `windowHalt` write rather
|
|
203
|
+
* than invent a resume time.
|
|
204
|
+
*
|
|
205
|
+
* @internal Exported for testing
|
|
206
|
+
*/
|
|
207
|
+
export declare function windowHaltResumeAtMs(result: PhaseResult): number | null;
|
|
89
208
|
export declare function runIssueWithLogging(ctx: IssueExecutionContext): Promise<IssueResult>;
|