sequant 2.9.0 → 2.11.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 +20 -5
- package/dist/bin/cli.js +143 -18
- package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +1 -1
- package/dist/marketplace/external_plugins/sequant/.mcp.json +1 -1
- package/dist/marketplace/external_plugins/sequant/README.md +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.d.ts +20 -1
- package/dist/src/commands/locks.js +208 -4
- 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 +31 -3
- package/dist/src/commands/ready.js +53 -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 +38 -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/commands/worktree.d.ts +31 -0
- package/dist/src/commands/worktree.js +95 -0
- 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 +56 -0
- package/dist/src/lib/cli-flags.js +89 -0
- package/dist/src/lib/cli-ui/run-renderer-types.d.ts +23 -7
- package/dist/src/lib/cli-ui/run-renderer.d.ts +10 -0
- package/dist/src/lib/cli-ui/run-renderer.js +69 -1
- package/dist/src/lib/errors.d.ts +78 -6
- package/dist/src/lib/errors.js +133 -15
- package/dist/src/lib/locks/checkout-lock.d.ts +193 -0
- package/dist/src/lib/locks/checkout-lock.js +389 -0
- package/dist/src/lib/locks/index.d.ts +7 -4
- package/dist/src/lib/locks/index.js +5 -3
- package/dist/src/lib/locks/lock-manager.d.ts +101 -3
- package/dist/src/lib/locks/lock-manager.js +277 -8
- package/dist/src/lib/locks/types.d.ts +106 -3
- package/dist/src/lib/locks/types.js +44 -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 +87 -0
- package/dist/src/lib/settings.js +106 -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 +10 -2
- package/dist/src/lib/test-tautology-detector.js +213 -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 +552 -60
- 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 +51 -0
- package/dist/src/lib/workflow/config-resolver.js +143 -2
- package/dist/src/lib/workflow/drivers/agent-driver.d.ts +22 -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 +25 -2
- package/dist/src/lib/workflow/effort-escalation.d.ts +73 -0
- package/dist/src/lib/workflow/effort-escalation.js +82 -0
- package/dist/src/lib/workflow/error-classifier.d.ts +4 -1
- package/dist/src/lib/workflow/error-classifier.js +4 -0
- 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 +54 -2
- package/dist/src/lib/workflow/log-writer.js +95 -6
- package/dist/src/lib/workflow/metrics-schema.d.ts +52 -9
- package/dist/src/lib/workflow/metrics-schema.js +33 -0
- package/dist/src/lib/workflow/metrics-writer.d.ts +11 -0
- package/dist/src/lib/workflow/phase-detection.d.ts +12 -0
- package/dist/src/lib/workflow/phase-detection.js +5 -1
- package/dist/src/lib/workflow/phase-executor.d.ts +203 -14
- package/dist/src/lib/workflow/phase-executor.js +467 -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 +45 -4
- package/dist/src/lib/workflow/ready-gate.js +49 -12
- package/dist/src/lib/workflow/run-log-schema.d.ts +69 -0
- package/dist/src/lib/workflow/run-log-schema.js +78 -2
- package/dist/src/lib/workflow/run-orchestrator.d.ts +19 -0
- package/dist/src/lib/workflow/run-orchestrator.js +168 -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/spec-recommendation.d.ts +71 -0
- package/dist/src/lib/workflow/spec-recommendation.js +142 -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 +190 -4
- package/dist/src/lib/workflow/types.js +1 -0
- package/dist/src/lib/workflow/worktree-manager.d.ts +36 -3
- package/dist/src/lib/workflow/worktree-manager.js +111 -24
- package/dist/src/lib/workflow/worktree-resolver.d.ts +73 -0
- package/dist/src/lib/workflow/worktree-resolver.js +126 -0
- 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 +10 -8
- package/templates/hooks/parallel-marker.sh +50 -0
- package/templates/hooks/post-tool.sh +20 -7
- package/templates/hooks/pre-tool.sh +303 -10
- package/templates/scripts/cleanup-worktree.sh +217 -33
- package/templates/scripts/new-feature.sh +289 -35
- package/templates/skills/_shared/references/subagent-types.md +7 -18
- package/templates/skills/_shared/references/trust-model.md +18 -0
- package/templates/skills/assess/SKILL.md +220 -391
- 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 +87 -13
- package/templates/skills/fullsolve/SKILL.md +130 -22
- package/templates/skills/loop/SKILL.md +94 -13
- package/templates/skills/merger/SKILL.md +100 -12
- package/templates/skills/qa/SKILL.md +396 -63
- 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 +80 -1
- package/templates/skills/setup/SKILL.md +5 -5
- package/templates/skills/spec/SKILL.md +33 -15
- package/templates/skills/spec/references/recommended-workflow.md +14 -1
- package/templates/skills/test/SKILL.md +1 -1
- package/templates/skills/testgen/SKILL.md +23 -6
- 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
- package/templates/agents/sequant-explorer.md +0 -24
|
@@ -14,6 +14,22 @@ export const DEFAULT_STALE_AGE_MS = 2 * 60 * 60 * 1000; // 2h
|
|
|
14
14
|
* Override per-process via `SEQUANT_SKILL_LOCK_TTL_MS` (milliseconds).
|
|
15
15
|
*/
|
|
16
16
|
export const DEFAULT_SKILL_LOCK_TTL_MS = 6 * 60 * 60 * 1000; // 6h
|
|
17
|
+
/**
|
|
18
|
+
* Absolute age ceiling (ms) beyond which a lock is stale no matter what
|
|
19
|
+
* (#856). Unlike the two TTLs above this one is NOT conditional on host or
|
|
20
|
+
* PID liveness: the same-host branch of `classifyStaleness` treats a live PID
|
|
21
|
+
* as authoritative proof of freshness, so before this ceiling existed a lock
|
|
22
|
+
* whose PID had been recycled by the OS read as fresh forever and blocked its
|
|
23
|
+
* issue permanently (observed: `505.lock` from 2026-05-14, `708.lock`,
|
|
24
|
+
* `803.lock`). It is also the recovery path for locks leaked by a SIGKILLed
|
|
25
|
+
* run, where no in-process release handler can ever fire.
|
|
26
|
+
*
|
|
27
|
+
* 24h is ~48x the 30-minute default phase timeout and 4x
|
|
28
|
+
* `DEFAULT_SKILL_LOCK_TTL_MS`, so no real run can reach it.
|
|
29
|
+
*
|
|
30
|
+
* Override per-process via `SEQUANT_MAX_LOCK_AGE_MS` (milliseconds).
|
|
31
|
+
*/
|
|
32
|
+
export const DEFAULT_MAX_LOCK_AGE_MS = 24 * 60 * 60 * 1000; // 24h
|
|
17
33
|
/** Default lock directory relative to the project root. */
|
|
18
34
|
export const DEFAULT_LOCKS_DIR = ".sequant/locks";
|
|
19
35
|
/** On-disk lock payload. */
|
|
@@ -29,3 +45,31 @@ export const LockFileSchema = z.object({
|
|
|
29
45
|
*/
|
|
30
46
|
skipPidCheck: z.boolean().optional(),
|
|
31
47
|
});
|
|
48
|
+
/**
|
|
49
|
+
* Filename of the checkout-scoped lock (#901), stored alongside the numeric
|
|
50
|
+
* `<issue>.lock` files. Deliberately non-numeric so `LockManager.list()` —
|
|
51
|
+
* which parses each filename as an issue number and skips anything else —
|
|
52
|
+
* ignores it rather than surfacing a bogus `NaN` issue.
|
|
53
|
+
*/
|
|
54
|
+
export const CHECKOUT_LOCK_FILENAME = "checkout.lock";
|
|
55
|
+
/**
|
|
56
|
+
* On-disk payload for the checkout-scoped lock (#901).
|
|
57
|
+
*
|
|
58
|
+
* The per-issue lock from #625 is keyed on issue number, so two sessions
|
|
59
|
+
* working *different* issues take different lock files and never contend —
|
|
60
|
+
* yet `git checkout`, `reset`, `rebase` and `merge` are global to a working
|
|
61
|
+
* tree. This lock represents the tree itself.
|
|
62
|
+
*
|
|
63
|
+
* Extends `LockFileSchema` with:
|
|
64
|
+
* - `issue` — which issue the holder is working on, so a refusal can name it
|
|
65
|
+
* (AC-2) and point the loser at the right worktree (AC-3).
|
|
66
|
+
* - `sessionId` — Claude Code's per-session id, read from the PreToolUse
|
|
67
|
+
* stdin envelope. Optional: it is the *preferred* holder identity because a
|
|
68
|
+
* skill shell's PID dies immediately after acquire, but the hook falls back
|
|
69
|
+
* to `pid`+`hostname` when the envelope does not carry one, so nothing
|
|
70
|
+
* depends on it being present.
|
|
71
|
+
*/
|
|
72
|
+
export const CheckoutLockFileSchema = LockFileSchema.extend({
|
|
73
|
+
issue: z.number().int().positive(),
|
|
74
|
+
sessionId: z.string().optional(),
|
|
75
|
+
});
|
|
@@ -6,6 +6,21 @@
|
|
|
6
6
|
*/
|
|
7
7
|
/** Path to the project-level MCP config file used by Claude Code */
|
|
8
8
|
export declare const PROJECT_MCP_JSON = ".mcp.json";
|
|
9
|
+
/**
|
|
10
|
+
* npm package specifier used to launch the MCP server (#793).
|
|
11
|
+
*
|
|
12
|
+
* We pin to the installed version rather than `@latest` so that `npx` does not
|
|
13
|
+
* re-resolve and reinstall the package on the first MCP reconnect after every
|
|
14
|
+
* sequant release — the reinstall was surfacing as an opaque
|
|
15
|
+
* `Failed to reconnect to sequant: -32000` when npx's cache was corrupted
|
|
16
|
+
* (npm ENOTEMPTY). `setup`/`update` (re)write this pin, so the MCP server
|
|
17
|
+
* tracks new releases when the user runs `sequant update` — matching how the
|
|
18
|
+
* plugin cache already pins. See docs/troubleshooting.md ("MCP Server Issues").
|
|
19
|
+
*
|
|
20
|
+
* Falls back to `latest` if the installed version cannot be resolved, so a
|
|
21
|
+
* failed lookup never writes a bogus `sequant@0.0.0` pin into `.mcp.json`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getSequantPackageSpec(): string;
|
|
9
24
|
export type McpClientType = "claude-desktop" | "cursor" | "vscode-continue";
|
|
10
25
|
export interface McpClientInfo {
|
|
11
26
|
name: string;
|
|
@@ -52,3 +67,33 @@ export interface ProjectMcpJsonResult {
|
|
|
52
67
|
* because Claude Code runs from the project root.
|
|
53
68
|
*/
|
|
54
69
|
export declare function createProjectMcpJson(projectDir?: string): ProjectMcpJsonResult;
|
|
70
|
+
export interface SyncMcpPinResult {
|
|
71
|
+
updated: boolean;
|
|
72
|
+
/** Why no rewrite happened, when updated === false. */
|
|
73
|
+
reason?: "no-file" | "no-entry" | "no-pin" | "already-current";
|
|
74
|
+
/** Previous package spec, when updated === true (e.g. "sequant@latest"). */
|
|
75
|
+
from?: string;
|
|
76
|
+
/** New package spec, when updated === true (e.g. "sequant@2.9.0"). */
|
|
77
|
+
to?: string;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Re-pin an existing project `.mcp.json` sequant entry to the installed version (#793).
|
|
81
|
+
*
|
|
82
|
+
* Unlike {@link createProjectMcpJson} (which skips when a sequant entry already
|
|
83
|
+
* exists), this is the `update`/`sync` path: it refreshes the version pin so the
|
|
84
|
+
* MCP server tracks the release the user just updated to. It only rewrites the
|
|
85
|
+
* `sequant@<version>` token inside `args` and leaves everything else untouched.
|
|
86
|
+
*
|
|
87
|
+
* No-ops (returns `updated: false`) when:
|
|
88
|
+
* - `.mcp.json` doesn't exist (`no-file`) — we never create it here; that's init's job
|
|
89
|
+
* - there's no sequant server entry (`no-entry`)
|
|
90
|
+
* - the entry uses a local-binary form with no `sequant@…` arg (`no-pin`) — a
|
|
91
|
+
* deliberate contributor override we must not clobber
|
|
92
|
+
* - the pin already matches the installed version (`already-current`)
|
|
93
|
+
*
|
|
94
|
+
* With `opts.dryRun`, computes `from`/`to` and returns `updated: true` for a
|
|
95
|
+
* pending change but does not write the file — the caller reports it as a preview.
|
|
96
|
+
*/
|
|
97
|
+
export declare function syncSequantMcpPin(projectDir?: string, opts?: {
|
|
98
|
+
dryRun?: boolean;
|
|
99
|
+
}): SyncMcpPinResult;
|
|
@@ -7,8 +7,29 @@
|
|
|
7
7
|
import * as fs from "fs";
|
|
8
8
|
import * as os from "os";
|
|
9
9
|
import * as path from "path";
|
|
10
|
+
import { getVersion } from "./version.js";
|
|
10
11
|
/** Path to the project-level MCP config file used by Claude Code */
|
|
11
12
|
export const PROJECT_MCP_JSON = ".mcp.json";
|
|
13
|
+
/**
|
|
14
|
+
* npm package specifier used to launch the MCP server (#793).
|
|
15
|
+
*
|
|
16
|
+
* We pin to the installed version rather than `@latest` so that `npx` does not
|
|
17
|
+
* re-resolve and reinstall the package on the first MCP reconnect after every
|
|
18
|
+
* sequant release — the reinstall was surfacing as an opaque
|
|
19
|
+
* `Failed to reconnect to sequant: -32000` when npx's cache was corrupted
|
|
20
|
+
* (npm ENOTEMPTY). `setup`/`update` (re)write this pin, so the MCP server
|
|
21
|
+
* tracks new releases when the user runs `sequant update` — matching how the
|
|
22
|
+
* plugin cache already pins. See docs/troubleshooting.md ("MCP Server Issues").
|
|
23
|
+
*
|
|
24
|
+
* Falls back to `latest` if the installed version cannot be resolved, so a
|
|
25
|
+
* failed lookup never writes a bogus `sequant@0.0.0` pin into `.mcp.json`.
|
|
26
|
+
*/
|
|
27
|
+
export function getSequantPackageSpec() {
|
|
28
|
+
const version = getVersion();
|
|
29
|
+
return version && version !== "0.0.0"
|
|
30
|
+
? `sequant@${version}`
|
|
31
|
+
: "sequant@latest";
|
|
32
|
+
}
|
|
12
33
|
/**
|
|
13
34
|
* Clients that need an explicit cwd because they don't run from the project directory.
|
|
14
35
|
*/
|
|
@@ -25,7 +46,7 @@ const CLIENTS_NEEDING_CWD = new Set([
|
|
|
25
46
|
export function getSequantMcpConfig(options) {
|
|
26
47
|
const config = {
|
|
27
48
|
command: "npx",
|
|
28
|
-
args: ["-y",
|
|
49
|
+
args: ["-y", getSequantPackageSpec(), "serve"],
|
|
29
50
|
};
|
|
30
51
|
// Add cwd for clients that don't run from the project directory
|
|
31
52
|
if (options?.clientType && CLIENTS_NEEDING_CWD.has(options.clientType)) {
|
|
@@ -170,3 +191,58 @@ export function createProjectMcpJson(projectDir) {
|
|
|
170
191
|
}
|
|
171
192
|
return { created: true, merged: false, skipped: false };
|
|
172
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* Re-pin an existing project `.mcp.json` sequant entry to the installed version (#793).
|
|
196
|
+
*
|
|
197
|
+
* Unlike {@link createProjectMcpJson} (which skips when a sequant entry already
|
|
198
|
+
* exists), this is the `update`/`sync` path: it refreshes the version pin so the
|
|
199
|
+
* MCP server tracks the release the user just updated to. It only rewrites the
|
|
200
|
+
* `sequant@<version>` token inside `args` and leaves everything else untouched.
|
|
201
|
+
*
|
|
202
|
+
* No-ops (returns `updated: false`) when:
|
|
203
|
+
* - `.mcp.json` doesn't exist (`no-file`) — we never create it here; that's init's job
|
|
204
|
+
* - there's no sequant server entry (`no-entry`)
|
|
205
|
+
* - the entry uses a local-binary form with no `sequant@…` arg (`no-pin`) — a
|
|
206
|
+
* deliberate contributor override we must not clobber
|
|
207
|
+
* - the pin already matches the installed version (`already-current`)
|
|
208
|
+
*
|
|
209
|
+
* With `opts.dryRun`, computes `from`/`to` and returns `updated: true` for a
|
|
210
|
+
* pending change but does not write the file — the caller reports it as a preview.
|
|
211
|
+
*/
|
|
212
|
+
export function syncSequantMcpPin(projectDir, opts) {
|
|
213
|
+
const mcpJsonPath = path.resolve(projectDir ?? ".", PROJECT_MCP_JSON);
|
|
214
|
+
if (!fs.existsSync(mcpJsonPath)) {
|
|
215
|
+
return { updated: false, reason: "no-file" };
|
|
216
|
+
}
|
|
217
|
+
let config;
|
|
218
|
+
try {
|
|
219
|
+
config = JSON.parse(fs.readFileSync(mcpJsonPath, "utf-8"));
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// Corrupt file — leave it alone rather than overwriting user content.
|
|
223
|
+
return { updated: false, reason: "no-file" };
|
|
224
|
+
}
|
|
225
|
+
const servers = config?.mcpServers;
|
|
226
|
+
const sequant = servers?.sequant;
|
|
227
|
+
if (!sequant) {
|
|
228
|
+
return { updated: false, reason: "no-entry" };
|
|
229
|
+
}
|
|
230
|
+
const args = sequant.args;
|
|
231
|
+
if (!Array.isArray(args)) {
|
|
232
|
+
return { updated: false, reason: "no-pin" };
|
|
233
|
+
}
|
|
234
|
+
const pinIndex = args.findIndex((a) => typeof a === "string" && a.startsWith("sequant@"));
|
|
235
|
+
if (pinIndex === -1) {
|
|
236
|
+
return { updated: false, reason: "no-pin" };
|
|
237
|
+
}
|
|
238
|
+
const from = args[pinIndex];
|
|
239
|
+
const to = getSequantPackageSpec();
|
|
240
|
+
if (from === to) {
|
|
241
|
+
return { updated: false, reason: "already-current" };
|
|
242
|
+
}
|
|
243
|
+
if (!opts?.dryRun) {
|
|
244
|
+
args[pinIndex] = to;
|
|
245
|
+
fs.writeFileSync(mcpJsonPath, JSON.stringify(config, null, 2) + "\n");
|
|
246
|
+
}
|
|
247
|
+
return { updated: true, from, to };
|
|
248
|
+
}
|
|
@@ -2,11 +2,30 @@
|
|
|
2
2
|
* Combined branch testing (AC-1)
|
|
3
3
|
*
|
|
4
4
|
* Creates a temporary branch merging all feature branches from a run batch,
|
|
5
|
-
*
|
|
5
|
+
* reinstalls dependencies when the merge moved a lockfile, then runs the
|
|
6
|
+
* project's test and build scripts on the combined state and reports results.
|
|
6
7
|
*/
|
|
7
8
|
import type { BranchInfo, CheckResult, BranchCheckResult, CheckFinding } from "./types.js";
|
|
9
|
+
import { resolveFailureReason, type CommandResult } from "./command-result.js";
|
|
10
|
+
export { resolveFailureReason, type CommandResult };
|
|
8
11
|
/**
|
|
9
|
-
*
|
|
12
|
+
* Run a package-manager command line (e.g. "npm ci", "pnpm run build").
|
|
13
|
+
*
|
|
14
|
+
* @param command Full command line, as stored in PM_CONFIG
|
|
15
|
+
* @param cwd Working directory
|
|
16
|
+
* @param timeoutMs Kill the command after this long
|
|
17
|
+
* @internal Exported for testing
|
|
18
|
+
*/
|
|
19
|
+
export declare function runPackageManagerCommand(command: string, cwd: string, timeoutMs: number): CommandResult;
|
|
20
|
+
/**
|
|
21
|
+
* Whether any lockfile differs between `baseRef` and the current HEAD.
|
|
22
|
+
*
|
|
23
|
+
* @internal Exported for testing
|
|
24
|
+
*/
|
|
25
|
+
export declare function lockfileChanged(repoRoot: string, baseRef: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Create temp branch, merge all feature branches, reinstall dependencies if the
|
|
28
|
+
* lockfile moved, then run tests and build.
|
|
10
29
|
*
|
|
11
30
|
* @param branches - Feature branches to merge
|
|
12
31
|
* @param repoRoot - Path to the git repository root
|
|
@@ -2,10 +2,37 @@
|
|
|
2
2
|
* Combined branch testing (AC-1)
|
|
3
3
|
*
|
|
4
4
|
* Creates a temporary branch merging all feature branches from a run batch,
|
|
5
|
-
*
|
|
5
|
+
* reinstalls dependencies when the merge moved a lockfile, then runs the
|
|
6
|
+
* project's test and build scripts on the combined state and reports results.
|
|
6
7
|
*/
|
|
7
8
|
import { spawnSync } from "child_process";
|
|
8
9
|
import { getBranchRef } from "./types.js";
|
|
10
|
+
import { detectPackageManagerSync, resolvePackageManagerConfig, } from "../stacks.js";
|
|
11
|
+
import { toCommandResult, resolveFailureReason, } from "./command-result.js";
|
|
12
|
+
// Re-exported for the existing test suite and any downstream consumer that
|
|
13
|
+
// imported these from here before they moved to ./command-result.js.
|
|
14
|
+
export { resolveFailureReason };
|
|
15
|
+
/**
|
|
16
|
+
* Lockfile names for the JS package managers we support. Kept in sync with the
|
|
17
|
+
* equivalent list in `workflow/worktree-manager.ts`.
|
|
18
|
+
*/
|
|
19
|
+
const LOCKFILES = [
|
|
20
|
+
"package-lock.json",
|
|
21
|
+
"pnpm-lock.yaml",
|
|
22
|
+
"bun.lockb",
|
|
23
|
+
"bun.lock",
|
|
24
|
+
"yarn.lock",
|
|
25
|
+
];
|
|
26
|
+
/**
|
|
27
|
+
* Per-step command timeouts. A dependency install and a full test suite are
|
|
28
|
+
* both slower than the previous flat 2-minute budget allowed. An expired
|
|
29
|
+
* timeout kills the process with `status: null` and no captured output, which
|
|
30
|
+
* showed up as a BLOCKED verdict with no stated reason (#803).
|
|
31
|
+
*/
|
|
32
|
+
const INSTALL_TIMEOUT_MS = 300_000; // 5 min
|
|
33
|
+
const TEST_BUILD_TIMEOUT_MS = 600_000; // 10 min
|
|
34
|
+
/** Ref the combined state is built on top of, and compared against. */
|
|
35
|
+
const BASE_REF = "origin/main";
|
|
9
36
|
/**
|
|
10
37
|
* Run a git command and return the result
|
|
11
38
|
*/
|
|
@@ -22,23 +49,39 @@ function git(args, cwd) {
|
|
|
22
49
|
};
|
|
23
50
|
}
|
|
24
51
|
/**
|
|
25
|
-
* Run
|
|
52
|
+
* Run a package-manager command line (e.g. "npm ci", "pnpm run build").
|
|
53
|
+
*
|
|
54
|
+
* @param command Full command line, as stored in PM_CONFIG
|
|
55
|
+
* @param cwd Working directory
|
|
56
|
+
* @param timeoutMs Kill the command after this long
|
|
57
|
+
* @internal Exported for testing
|
|
26
58
|
*/
|
|
27
|
-
function
|
|
28
|
-
const
|
|
59
|
+
export function runPackageManagerCommand(command, cwd, timeoutMs) {
|
|
60
|
+
const [bin, ...args] = command.split(" ");
|
|
61
|
+
const result = spawnSync(bin, args, {
|
|
29
62
|
cwd,
|
|
30
63
|
stdio: "pipe",
|
|
31
64
|
encoding: "utf-8",
|
|
32
|
-
timeout:
|
|
65
|
+
timeout: timeoutMs,
|
|
66
|
+
// On Windows the package managers are `.cmd` shims, which CreateProcess
|
|
67
|
+
// will not resolve from a bare "npm"/"pnpm". `command` is always a constant
|
|
68
|
+
// from PM_CONFIG — never user input — so there is nothing to inject here.
|
|
69
|
+
shell: process.platform === "win32",
|
|
33
70
|
});
|
|
34
|
-
return
|
|
35
|
-
ok: result.status === 0,
|
|
36
|
-
stdout: result.stdout?.trim() ?? "",
|
|
37
|
-
stderr: result.stderr?.trim() ?? "",
|
|
38
|
-
};
|
|
71
|
+
return toCommandResult(result);
|
|
39
72
|
}
|
|
40
73
|
/**
|
|
41
|
-
*
|
|
74
|
+
* Whether any lockfile differs between `baseRef` and the current HEAD.
|
|
75
|
+
*
|
|
76
|
+
* @internal Exported for testing
|
|
77
|
+
*/
|
|
78
|
+
export function lockfileChanged(repoRoot, baseRef) {
|
|
79
|
+
const result = git(["diff", "--name-only", baseRef, "HEAD", "--", ...LOCKFILES], repoRoot);
|
|
80
|
+
return result.ok && result.stdout.length > 0;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Create temp branch, merge all feature branches, reinstall dependencies if the
|
|
84
|
+
* lockfile moved, then run tests and build.
|
|
42
85
|
*
|
|
43
86
|
* @param branches - Feature branches to merge
|
|
44
87
|
* @param repoRoot - Path to the git repository root
|
|
@@ -49,123 +92,203 @@ export function runCombinedBranchTest(branches, repoRoot) {
|
|
|
49
92
|
const tempBranch = `merge-check/temp-${Date.now()}`;
|
|
50
93
|
const branchResults = [];
|
|
51
94
|
const batchFindings = [];
|
|
52
|
-
const
|
|
53
|
-
//
|
|
95
|
+
const pm = detectPackageManagerSync(repoRoot);
|
|
96
|
+
// Resolved against the repo rather than read straight off PM_CONFIG: yarn's
|
|
97
|
+
// frozen install differs between classic and berry, and `pm` is "yarn" for
|
|
98
|
+
// both (#871).
|
|
99
|
+
//
|
|
100
|
+
// This resolution describes the PRE-MERGE tree, which is what the restore
|
|
101
|
+
// install in the `finally` below runs against — it fires after
|
|
102
|
+
// `git checkout restoreBranch`, so the original branch's yarn major is the
|
|
103
|
+
// right one there. `runChecks` re-resolves for the combined state, where a
|
|
104
|
+
// merged branch may have changed the major.
|
|
105
|
+
const pmConfig = resolvePackageManagerConfig(pm, repoRoot);
|
|
106
|
+
// Flipped the moment we start installing against the combined lockfile, so
|
|
107
|
+
// the caller's node_modules can be put back afterwards. A mutable holder
|
|
108
|
+
// rather than a return value: if runChecks throws, a return value never
|
|
109
|
+
// arrives and the restore would be skipped precisely when the tree is most
|
|
110
|
+
// likely to be inconsistent.
|
|
111
|
+
const installState = { installedCombinedDeps: false };
|
|
112
|
+
// Save current branch to restore in the cleanup step
|
|
54
113
|
const originalBranch = git(["rev-parse", "--abbrev-ref", "HEAD"], repoRoot);
|
|
55
114
|
try {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
issueNumber: branch.issueNumber,
|
|
79
|
-
verdict: "PASS",
|
|
80
|
-
findings: [
|
|
81
|
-
{
|
|
82
|
-
check: "combined-branch-test",
|
|
83
|
-
severity: "info",
|
|
84
|
-
message: `Branch merged cleanly into combined state`,
|
|
85
|
-
issueNumber: branch.issueNumber,
|
|
86
|
-
},
|
|
87
|
-
],
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
// Get conflicting files
|
|
92
|
-
const conflictResult = git(["diff", "--name-only", "--diff-filter=U"], repoRoot);
|
|
93
|
-
const conflictFiles = conflictResult.stdout
|
|
94
|
-
? conflictResult.stdout.split("\n")
|
|
95
|
-
: [];
|
|
96
|
-
mergeAttempts.push({
|
|
97
|
-
issueNumber: branch.issueNumber,
|
|
98
|
-
branch: branch.branch,
|
|
99
|
-
success: false,
|
|
100
|
-
conflictFiles,
|
|
101
|
-
error: mergeResult.stderr,
|
|
102
|
-
});
|
|
103
|
-
branchResults.push({
|
|
104
|
-
issueNumber: branch.issueNumber,
|
|
105
|
-
verdict: "FAIL",
|
|
106
|
-
findings: [
|
|
107
|
-
{
|
|
108
|
-
check: "combined-branch-test",
|
|
109
|
-
severity: "error",
|
|
110
|
-
message: `Merge conflict with ${conflictFiles.length} file(s): ${conflictFiles.join(", ")}`,
|
|
111
|
-
issueNumber: branch.issueNumber,
|
|
112
|
-
},
|
|
113
|
-
],
|
|
115
|
+
runChecks(branches, repoRoot, tempBranch, pm, branchResults, batchFindings, installState);
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
// Restore original branch and delete temp branch
|
|
119
|
+
const restoreBranch = originalBranch.ok ? originalBranch.stdout : "main";
|
|
120
|
+
git(["checkout", restoreBranch], repoRoot);
|
|
121
|
+
git(["branch", "-D", tempBranch], repoRoot);
|
|
122
|
+
// We installed the combined state's dependency tree into the caller's
|
|
123
|
+
// node_modules; put it back so the restored branch is not left running
|
|
124
|
+
// against another branch's dependencies.
|
|
125
|
+
//
|
|
126
|
+
// Frozen again, for the same reason as the forward install: a plain
|
|
127
|
+
// `npm install` normalizes and rewrites the lockfile, which would leave
|
|
128
|
+
// the user's restored branch with a dirty working tree that this check
|
|
129
|
+
// never asked them for.
|
|
130
|
+
if (installState.installedCombinedDeps) {
|
|
131
|
+
const restoreInstall = runPackageManagerCommand(pmConfig.ciInstall, repoRoot, INSTALL_TIMEOUT_MS);
|
|
132
|
+
if (!restoreInstall.ok) {
|
|
133
|
+
batchFindings.push({
|
|
134
|
+
check: "combined-branch-test",
|
|
135
|
+
severity: "warning",
|
|
136
|
+
message: `Dependencies were reinstalled for the combined state but could not be restored for \`${restoreBranch}\`. Run \`${pmConfig.ciInstall}\` to resync node_modules: ${resolveFailureReason(restoreInstall)}`,
|
|
114
137
|
});
|
|
115
|
-
// Abort the failed merge and continue
|
|
116
|
-
git(["merge", "--abort"], repoRoot);
|
|
117
138
|
}
|
|
118
139
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
140
|
+
}
|
|
141
|
+
// Built after cleanup so findings pushed during cleanup are reflected in the
|
|
142
|
+
// verdict rather than silently arriving too late to affect `passed`.
|
|
143
|
+
return buildResult(branchResults, batchFindings, startTime);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Assemble the combined state and run install/test/build against it.
|
|
147
|
+
*
|
|
148
|
+
* Mutates `branchResults`, `batchFindings`, and `installState` — the last so
|
|
149
|
+
* the caller's cleanup can restore node_modules even if this function throws.
|
|
150
|
+
*/
|
|
151
|
+
function runChecks(branches, repoRoot, tempBranch, pm, branchResults, batchFindings, installState) {
|
|
152
|
+
const mergeAttempts = [];
|
|
153
|
+
// Fetch latest from remote
|
|
154
|
+
git(["fetch", "origin"], repoRoot);
|
|
155
|
+
// Create temp branch from main
|
|
156
|
+
const createResult = git(["checkout", "-b", tempBranch, BASE_REF], repoRoot);
|
|
157
|
+
if (!createResult.ok) {
|
|
158
|
+
batchFindings.push({
|
|
159
|
+
check: "combined-branch-test",
|
|
160
|
+
severity: "error",
|
|
161
|
+
message: `Failed to create temp branch: ${createResult.stderr}`,
|
|
162
|
+
});
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
// Merge each feature branch
|
|
166
|
+
for (const branch of branches) {
|
|
167
|
+
const mergeResult = git(["merge", "--no-ff", "--no-edit", getBranchRef(branch)], repoRoot);
|
|
168
|
+
if (mergeResult.ok) {
|
|
169
|
+
mergeAttempts.push({
|
|
170
|
+
issueNumber: branch.issueNumber,
|
|
171
|
+
branch: branch.branch,
|
|
172
|
+
success: true,
|
|
126
173
|
});
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
174
|
+
branchResults.push({
|
|
175
|
+
issueNumber: branch.issueNumber,
|
|
176
|
+
verdict: "PASS",
|
|
177
|
+
findings: [
|
|
178
|
+
{
|
|
179
|
+
check: "combined-branch-test",
|
|
180
|
+
severity: "info",
|
|
181
|
+
message: `Branch merged cleanly into combined state`,
|
|
182
|
+
issueNumber: branch.issueNumber,
|
|
183
|
+
},
|
|
184
|
+
],
|
|
136
185
|
});
|
|
137
186
|
}
|
|
138
187
|
else {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
188
|
+
// Get conflicting files
|
|
189
|
+
const conflictResult = git(["diff", "--name-only", "--diff-filter=U"], repoRoot);
|
|
190
|
+
const conflictFiles = conflictResult.stdout
|
|
191
|
+
? conflictResult.stdout.split("\n")
|
|
192
|
+
: [];
|
|
193
|
+
mergeAttempts.push({
|
|
194
|
+
issueNumber: branch.issueNumber,
|
|
195
|
+
branch: branch.branch,
|
|
196
|
+
success: false,
|
|
197
|
+
conflictFiles,
|
|
198
|
+
error: mergeResult.stderr,
|
|
143
199
|
});
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
200
|
+
branchResults.push({
|
|
201
|
+
issueNumber: branch.issueNumber,
|
|
202
|
+
verdict: "FAIL",
|
|
203
|
+
findings: [
|
|
204
|
+
{
|
|
205
|
+
check: "combined-branch-test",
|
|
206
|
+
severity: "error",
|
|
207
|
+
message: `Merge conflict with ${conflictFiles.length} file(s): ${conflictFiles.join(", ")}`,
|
|
208
|
+
issueNumber: branch.issueNumber,
|
|
209
|
+
},
|
|
210
|
+
],
|
|
152
211
|
});
|
|
212
|
+
// Abort the failed merge and continue
|
|
213
|
+
git(["merge", "--abort"], repoRoot);
|
|
153
214
|
}
|
|
154
|
-
|
|
215
|
+
}
|
|
216
|
+
// If any merges failed, skip tests but report what we have
|
|
217
|
+
const failedMerges = mergeAttempts.filter((m) => !m.success);
|
|
218
|
+
if (failedMerges.length > 0) {
|
|
219
|
+
batchFindings.push({
|
|
220
|
+
check: "combined-branch-test",
|
|
221
|
+
severity: "error",
|
|
222
|
+
message: `${failedMerges.length}/${branches.length} branches had merge conflicts — skipping test/build`,
|
|
223
|
+
});
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
// Resolved HERE, after the merges landed, not from the caller's pre-merge
|
|
227
|
+
// resolution: the commands below run against the combined tree, and a merged
|
|
228
|
+
// branch can move the yarn major (a `packageManager` bump, a new
|
|
229
|
+
// `.yarnrc.yml`). Installing the combined state with the pre-merge major's
|
|
230
|
+
// frozen-install flag would fail on an unknown option and report a false
|
|
231
|
+
// BLOCKED — the exact failure class #803 added this install to prevent (#871).
|
|
232
|
+
const pmConfig = resolvePackageManagerConfig(pm, repoRoot);
|
|
233
|
+
// Reinstall dependencies when the merged branches moved the lockfile (#803).
|
|
234
|
+
// Without this, test/build run against the node_modules of whatever branch
|
|
235
|
+
// the user happened to be on, so a batch that merely added a dependency fails
|
|
236
|
+
// with module-not-found errors — a false BLOCKED on a healthy stack.
|
|
237
|
+
//
|
|
238
|
+
// A frozen install is used rather than a plain one: it mirrors what CI does,
|
|
239
|
+
// it will not rewrite the lockfile (which would dirty the temp branch and
|
|
240
|
+
// break the checkout during cleanup), and it fails loudly on a lockfile that
|
|
241
|
+
// is inconsistent with package.json.
|
|
242
|
+
if (lockfileChanged(repoRoot, BASE_REF)) {
|
|
243
|
+
// Marked before the install runs, not after: a frozen install deletes
|
|
244
|
+
// node_modules up front, so even a failed or interrupted one leaves the
|
|
245
|
+
// caller's tree needing a restore.
|
|
246
|
+
installState.installedCombinedDeps = true;
|
|
247
|
+
const installResult = runPackageManagerCommand(pmConfig.ciInstall, repoRoot, INSTALL_TIMEOUT_MS);
|
|
248
|
+
if (!installResult.ok) {
|
|
249
|
+
// Surface install failures on their own terms rather than letting them
|
|
250
|
+
// resurface downstream as an unexplained test failure.
|
|
155
251
|
batchFindings.push({
|
|
156
252
|
check: "combined-branch-test",
|
|
157
253
|
severity: "error",
|
|
158
|
-
message: `
|
|
254
|
+
message: `Dependency install failed on combined state (\`${pmConfig.ciInstall}\`) — skipping test/build. The merged lockfile may be inconsistent with package.json: ${resolveFailureReason(installResult)}`,
|
|
159
255
|
});
|
|
256
|
+
return;
|
|
160
257
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
git(["checkout", restoreBranch], repoRoot);
|
|
167
|
-
git(["branch", "-D", tempBranch], repoRoot);
|
|
258
|
+
batchFindings.push({
|
|
259
|
+
check: "combined-branch-test",
|
|
260
|
+
severity: "info",
|
|
261
|
+
message: `Lockfile changed in the combined state — reinstalled dependencies with \`${pmConfig.ciInstall}\``,
|
|
262
|
+
});
|
|
168
263
|
}
|
|
264
|
+
// Run the test suite
|
|
265
|
+
const testCommand = `${pmConfig.run} test`;
|
|
266
|
+
const testResult = runPackageManagerCommand(testCommand, repoRoot, TEST_BUILD_TIMEOUT_MS);
|
|
267
|
+
batchFindings.push(testResult.ok
|
|
268
|
+
? {
|
|
269
|
+
check: "combined-branch-test",
|
|
270
|
+
severity: "info",
|
|
271
|
+
message: `\`${testCommand}\` passed on combined state`,
|
|
272
|
+
}
|
|
273
|
+
: {
|
|
274
|
+
check: "combined-branch-test",
|
|
275
|
+
severity: "error",
|
|
276
|
+
message: `\`${testCommand}\` failed on combined state: ${resolveFailureReason(testResult)}`,
|
|
277
|
+
});
|
|
278
|
+
// Run the build
|
|
279
|
+
const buildCommand = `${pmConfig.run} build`;
|
|
280
|
+
const buildOutcome = runPackageManagerCommand(buildCommand, repoRoot, TEST_BUILD_TIMEOUT_MS);
|
|
281
|
+
batchFindings.push(buildOutcome.ok
|
|
282
|
+
? {
|
|
283
|
+
check: "combined-branch-test",
|
|
284
|
+
severity: "info",
|
|
285
|
+
message: `\`${buildCommand}\` passed on combined state`,
|
|
286
|
+
}
|
|
287
|
+
: {
|
|
288
|
+
check: "combined-branch-test",
|
|
289
|
+
severity: "error",
|
|
290
|
+
message: `\`${buildCommand}\` failed on combined state: ${resolveFailureReason(buildOutcome)}`,
|
|
291
|
+
});
|
|
169
292
|
}
|
|
170
293
|
export function buildResult(branchResults, batchFindings, startTime) {
|
|
171
294
|
const hasErrors = batchFindings.some((f) => f.severity === "error");
|