@codyswann/lisa 4.33.8 → 4.33.10
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/README.md +22 -0
- package/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +44 -1
- package/all/copy-overwrite/scripts/lisa-work-item.mjs +361 -10
- package/dist/configs/vitest/base.d.ts +76 -11
- package/dist/configs/vitest/base.d.ts.map +1 -1
- package/dist/configs/vitest/base.js +121 -14
- package/dist/configs/vitest/base.js.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +12 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/nightly-e2e-guard-behavior-certificate.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +12 -9
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/migrations/ensure-lisa-postinstall.d.ts +26 -15
- package/dist/migrations/ensure-lisa-postinstall.d.ts.map +1 -1
- package/dist/migrations/ensure-lisa-postinstall.js +43 -20
- package/dist/migrations/ensure-lisa-postinstall.js.map +1 -1
- package/dist/opencode/plugin-templates/parity-safety-net.sh +44 -1
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-drive-pr-to-merge/SKILL.md +17 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-github-build-intake/SKILL.md +1 -1
- package/plugins/lisa/hooks/parity-safety-net.sh +44 -1
- package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +17 -2
- package/plugins/lisa/skills/lisa-github-build-intake/SKILL.md +1 -1
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +44 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +17 -2
- package/plugins/lisa-agy/skills/lisa-github-build-intake/SKILL.md +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/hooks/parity-safety-net.sh +44 -1
- package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +17 -2
- package/plugins/lisa-copilot/skills/lisa-github-build-intake/SKILL.md +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +44 -1
- package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +17 -2
- package/plugins/lisa-cursor/skills/lisa-github-build-intake/SKILL.md +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/hooks/parity-safety-net.sh +44 -1
- package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +17 -2
- package/plugins/src/base/skills/lisa-github-build-intake/SKILL.md +1 -1
- package/scripts/install-claude-plugins.sh +22 -4
- package/scripts/two-channel-couplings.json +1 -1
- package/typescript/copy-overwrite/scripts/lisa-mutation.mjs +96 -12
- package/typescript/copy-overwrite/scripts/reconcile-nightly-e2e-tracking.mjs +3 -2
package/README.md
CHANGED
|
@@ -264,6 +264,28 @@ Lisa can watch a queue of ready work and dispatch each item through the lifecycl
|
|
|
264
264
|
> **Prompt for your coding agent**
|
|
265
265
|
> "Which commands let this Lisa scan a work queue, dispatch ready items, repair stuck ones, and report on scheduled-automation health? Show me how I'd point one at my queue and what it expects in configuration."
|
|
266
266
|
|
|
267
|
+
### Running several agents on one machine
|
|
268
|
+
|
|
269
|
+
Test runners size themselves to the machine, not to what else is running on it. Left alone, each run claims roughly one worker per core, so *k* agents working at once claim *k* × cores. That is not merely slow: past a certain point the machine starts terminating processes, and a gate that was killed looks exactly like a gate that failed — so an agent "fixes" a phantom, retries, and adds the load that kills the next run.
|
|
270
|
+
|
|
271
|
+
Lisa caps the pool for you, in three layers:
|
|
272
|
+
|
|
273
|
+
| layer | what it does | when it applies |
|
|
274
|
+
|---|---|---|
|
|
275
|
+
| **Floor** | Half the machine's cores | Always, when this run is the only one |
|
|
276
|
+
| **Divisor** | Floor ÷ concurrent runs, never below 2 workers | Whenever other Lisa runs are live |
|
|
277
|
+
| **Override** | Exactly the number you give it | Whenever you set it |
|
|
278
|
+
|
|
279
|
+
You do not have to configure any of this. A run discovers how many sibling runs are live by looking at the shared scratch namespace, so the divisor applies to a fleet nobody told it about. Two variables are available when you want to steer it:
|
|
280
|
+
|
|
281
|
+
- **`LISA_FLEET_CONCURRENCY`** — state how many runs are coming, rather than letting the run count what has already started. Useful when you are about to launch six agents and want the first one sized for six.
|
|
282
|
+
- **`LISA_VITEST_MAX_WORKERS`** — set the pool size outright. Wins over both other layers, in both directions: raise it above the floor as readily as you lower it.
|
|
283
|
+
|
|
284
|
+
A smaller pool is not automatically safer, which is why the floor is a proportion rather than a small constant and why the divisor stops at two workers. Below that a suite serialises, every file waits behind every other file, and per-test time budgets start expiring even as machine load falls — trading a visible failure for a subtler one.
|
|
285
|
+
|
|
286
|
+
> **Prompt for your coding agent**
|
|
287
|
+
> "Show me how this project's test worker pool is sized — find the resolver, tell me what each of the three layers would give on this machine right now, and how many sibling runs it currently detects."
|
|
288
|
+
|
|
267
289
|
## Working across trackers and sources
|
|
268
290
|
|
|
269
291
|
Lisa is deliberately vendor-neutral. The lifecycle runs the same whether your tickets live in one tracker or another and whether your product specs originate in one document tool or another — a thin dispatch layer selects the right integration from configuration, so the workflow you learn transfers.
|
|
@@ -1690,7 +1690,50 @@ fi
|
|
|
1690
1690
|
|
|
1691
1691
|
# 13. Dropping or truncating a database / schema / table (Lisa-only guard —
|
|
1692
1692
|
# upstream has no SQL protection; keep).
|
|
1693
|
-
|
|
1693
|
+
#
|
|
1694
|
+
# The two arms used to be asymmetric, and only the lopsided one produced false
|
|
1695
|
+
# positives (issue #3530). The `drop` arm requires a following keyword —
|
|
1696
|
+
# `drop database|schema|table` — so `drop the ball` never matched it. The
|
|
1697
|
+
# `truncate` arm made that keyword OPTIONAL:
|
|
1698
|
+
#
|
|
1699
|
+
# truncate[[:space:]]+(table[[:space:]]+)?[[:alnum:]_."`]+
|
|
1700
|
+
#
|
|
1701
|
+
# which reduces to "`truncate`, whitespace, any identifier-ish word", so
|
|
1702
|
+
# `truncate the board`, `truncate results` and `truncate output` all matched.
|
|
1703
|
+
# A session was refused for a bug-report TITLE describing a connection that
|
|
1704
|
+
# truncates results — and a title has no escape hatch, because the remedy this
|
|
1705
|
+
# hook prints at the wall (write the text to a file, pass it BY PATH) covers a
|
|
1706
|
+
# body, while the title flag of an issue-filing CLI is necessarily inline. The
|
|
1707
|
+
# only exit left was to reword, which is what trains people to route around a
|
|
1708
|
+
# guard.
|
|
1709
|
+
#
|
|
1710
|
+
# The fix keeps the coverage and drops the English, in three arms.
|
|
1711
|
+
#
|
|
1712
|
+
# ARM 1 — `drop`, byte-identical to before. It was already correct, and a
|
|
1713
|
+
# rewrite that unified the arms could only make it permissive.
|
|
1714
|
+
readonly SQL_DROP='\bdrop[[:space:]]+(database|schema|table)\b'
|
|
1715
|
+
# ARM 2 — `truncate` carrying its own evidence that it is a statement rather
|
|
1716
|
+
# than a verb: the TABLE keyword (what the drop arm demands), a
|
|
1717
|
+
# schema-qualified name, a quoted identifier, a trailing `;`, or one of the
|
|
1718
|
+
# tail keywords that only ever follow a TRUNCATE. English has none of these.
|
|
1719
|
+
readonly SQL_TRUNCATE_TAIL='(cascade|restrict|restart[[:space:]]+identity|continue[[:space:]]+identity)([^[:alnum:]_]|$)'
|
|
1720
|
+
readonly SQL_TRUNCATE_EVIDENCE='\btruncate[[:space:]]+(table[[:space:]]+[[:alnum:]_."`]+|(only[[:space:]]+)?([[:alnum:]_]+\.[[:alnum:]_."`]+|["`][[:alnum:]_. ]+["`]|[[:alnum:]_]+[[:space:]]*(;|'"$SQL_TRUNCATE_TAIL"')))'
|
|
1721
|
+
# ARM 3 — the permissive-dialect `TRUNCATE <name>` with none of that evidence.
|
|
1722
|
+
# Deleting this shape is what a lazy fix does: it passes every prose scenario
|
|
1723
|
+
# while silently removing the coverage a regression fixture already asserts
|
|
1724
|
+
# (`mysql -e 'TRUNCATE sessions'`). So it is recognized by CONTEXT instead —
|
|
1725
|
+
# the same command must also invoke a command-line database client. Prose in a
|
|
1726
|
+
# tracker filing or a commit message invokes none, which is exactly the
|
|
1727
|
+
# difference the text scan could not see on its own.
|
|
1728
|
+
readonly SQL_CLIENT='(^|[^[:alnum:]_./-])([[:alnum:]_./-]*/)?(psql|pgcli|mysql|mysqladmin|mysqlsh|mariadb|mycli|sqlite3|sqlcmd|cqlsh|clickhouse-client|cockroach|mongosh|duckdb|usql)([[:space:]]|$)'
|
|
1729
|
+
readonly SQL_TRUNCATE_BARE='\btruncate[[:space:]]+(only[[:space:]]+)?[[:alnum:]_."`]'
|
|
1730
|
+
# Residual, accepted and stated: a bare `TRUNCATE <name>` with no terminator,
|
|
1731
|
+
# no qualification and no recognized client in the same command goes unmatched
|
|
1732
|
+
# — the same trade the `drop` arm has always made for `drop the ball`. No DROP
|
|
1733
|
+
# shape is weakened, and the guard still fails in the safe direction.
|
|
1734
|
+
if matches "$SQL_DROP" \
|
|
1735
|
+
|| matches "$SQL_TRUNCATE_EVIDENCE" \
|
|
1736
|
+
|| { matches "$SQL_CLIENT" && matches "$SQL_TRUNCATE_BARE"; }; then
|
|
1694
1737
|
block "destructive SQL (DROP/TRUNCATE) detected"
|
|
1695
1738
|
fi
|
|
1696
1739
|
|
|
@@ -25,6 +25,14 @@ const RELEASE_SUBJECT =
|
|
|
25
25
|
/^chore\(release\): \d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)? \[skip ci\]$/;
|
|
26
26
|
const ZERO_OID = /^0+$/;
|
|
27
27
|
const MARKER = "[lisa-pr-link]";
|
|
28
|
+
/**
|
|
29
|
+
* The deploy environment whose done role is terminal — the only one that
|
|
30
|
+
* closes a work item. Named once because three separate readers compare
|
|
31
|
+
* against it, and a literal repeated three times is three places to drift.
|
|
32
|
+
*/
|
|
33
|
+
const PRODUCTION = "production";
|
|
34
|
+
/** How a base branch that maps to no deploy environment is described. */
|
|
35
|
+
const NOT_A_DEPLOY_BRANCH = "not a deploy branch";
|
|
28
36
|
/**
|
|
29
37
|
* The command that establishes the ticket-side backlink.
|
|
30
38
|
*
|
|
@@ -513,8 +521,11 @@ function lifecycleContract(config, provider) {
|
|
|
513
521
|
// state is called `Done` — a message that sends someone looking for a state
|
|
514
522
|
// that is not what they configured and not what Linear shows them.
|
|
515
523
|
const terminalName = String(terminal ?? "");
|
|
524
|
+
const byEnvironment = doneRolesByEnvironment(roles.done, terminalName);
|
|
516
525
|
return {
|
|
517
526
|
claimed: requireString(roles.claimed, `${provider} claimed lifecycle role`),
|
|
527
|
+
done: byEnvironment,
|
|
528
|
+
productionEnvironment: productionEnvironmentOf(byEnvironment, terminalName),
|
|
518
529
|
ready: requireString(roles.ready, `${provider} ready lifecycle role`),
|
|
519
530
|
roles: lifecycleRoleSet(roles, done),
|
|
520
531
|
terminal: terminalName.toLowerCase(),
|
|
@@ -522,6 +533,55 @@ function lifecycleContract(config, provider) {
|
|
|
522
533
|
};
|
|
523
534
|
}
|
|
524
535
|
|
|
536
|
+
/**
|
|
537
|
+
* The done roles keyed by deploy environment, in configured order.
|
|
538
|
+
*
|
|
539
|
+
* The single-string form (`done: "Done"`) is a project that names one done
|
|
540
|
+
* role for every environment, so it is read as the production one — that is
|
|
541
|
+
* the only environment whose role closes anything.
|
|
542
|
+
* @param {unknown} done The configured `done` role or role map.
|
|
543
|
+
* @param {string} terminalName The resolved terminal role's spelling.
|
|
544
|
+
* @returns {[string, string][]} `[environment, role]` pairs, order preserved.
|
|
545
|
+
*/
|
|
546
|
+
function doneRolesByEnvironment(done, terminalName) {
|
|
547
|
+
const entries =
|
|
548
|
+
typeof done === "string"
|
|
549
|
+
? [[PRODUCTION, done]]
|
|
550
|
+
: done && typeof done === "object"
|
|
551
|
+
? Object.entries(done)
|
|
552
|
+
: [];
|
|
553
|
+
const mapped = entries
|
|
554
|
+
.map(([environment, role]) => [
|
|
555
|
+
String(environment).trim().toLowerCase(),
|
|
556
|
+
typeof role === "string" ? role.trim() : "",
|
|
557
|
+
])
|
|
558
|
+
.filter(([environment, role]) => environment !== "" && role !== "");
|
|
559
|
+
// A shape this reader could not decompose must never leave the writer with
|
|
560
|
+
// NO environment to apply, which would refuse every completion in a project
|
|
561
|
+
// whose `done` nests. The resolved terminal is always an answer.
|
|
562
|
+
return mapped.length > 0 ? mapped : [[PRODUCTION, terminalName]];
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Which of the configured environments is the production one.
|
|
567
|
+
*
|
|
568
|
+
* Found by matching the already-resolved TERMINAL ROLE, not by position. The
|
|
569
|
+
* terminal role is the single existing answer to "which role closes an item",
|
|
570
|
+
* so deriving the environment from it means the two can never disagree — and
|
|
571
|
+
* it avoids deriving anything a second, independent way from JSON key order,
|
|
572
|
+
* which is not semantically meaningful and which no schema constrains.
|
|
573
|
+
* @param {[string, string][]} byEnvironment `[environment, role]` pairs.
|
|
574
|
+
* @param {string} terminalName The resolved terminal role's spelling.
|
|
575
|
+
* @returns {string} The production environment's name.
|
|
576
|
+
*/
|
|
577
|
+
function productionEnvironmentOf(byEnvironment, terminalName) {
|
|
578
|
+
const folded = terminalName.trim().toLowerCase();
|
|
579
|
+
const matched = byEnvironment.find(
|
|
580
|
+
([, role]) => role.toLowerCase() === folded
|
|
581
|
+
);
|
|
582
|
+
return matched?.[0] ?? PRODUCTION;
|
|
583
|
+
}
|
|
584
|
+
|
|
525
585
|
/**
|
|
526
586
|
* Every lifecycle role a project has configured, in the project's own spelling.
|
|
527
587
|
*
|
|
@@ -670,6 +730,46 @@ function verifyLevel(config) {
|
|
|
670
730
|
return value;
|
|
671
731
|
}
|
|
672
732
|
|
|
733
|
+
/**
|
|
734
|
+
* Deploy branches reversed: branch name -> the environment it deploys.
|
|
735
|
+
*
|
|
736
|
+
* `deploy.branches` is written env-first (`production: main`) because that is
|
|
737
|
+
* how a human thinks about it. Every reader here starts from a branch — a
|
|
738
|
+
* merged pull request's base — and needs the environment, so the map is
|
|
739
|
+
* inverted once, here, rather than searched at each use.
|
|
740
|
+
*
|
|
741
|
+
* **The fallback is deliberate and narrow.** A project that configures no
|
|
742
|
+
* deploy branches at all is not saying "nothing ever deploys"; it is saying
|
|
743
|
+
* nothing unusual happens, and its default branch is its production branch.
|
|
744
|
+
* Treating the absent map as "no branch deploys anything" would refuse every
|
|
745
|
+
* completion in every project that never wrote the key — a fix that stops
|
|
746
|
+
* completing anything is not a fix. What the fallback does NOT do is invent an
|
|
747
|
+
* environment for a branch that is simply not in a map the project DID write:
|
|
748
|
+
* that is the stacked-integration case, and it is exactly the one that must
|
|
749
|
+
* not be read as production.
|
|
750
|
+
* @param {object} config The resolved Lisa configuration.
|
|
751
|
+
* @returns {Map<string, string>} Branch name to deploy environment.
|
|
752
|
+
*/
|
|
753
|
+
export function deployBranchEnvironments(config) {
|
|
754
|
+
const configured = config?.deploy?.branches;
|
|
755
|
+
const entries =
|
|
756
|
+
configured && typeof configured === "object"
|
|
757
|
+
? Object.entries(configured)
|
|
758
|
+
: [];
|
|
759
|
+
const mapped = new Map();
|
|
760
|
+
for (const [environment, branch] of entries) {
|
|
761
|
+
const name = typeof branch === "string" ? branch.trim() : "";
|
|
762
|
+
if (name !== "") mapped.set(name, String(environment).trim().toLowerCase());
|
|
763
|
+
}
|
|
764
|
+
if (mapped.size > 0) return mapped;
|
|
765
|
+
const fallback = config?.policy?.repository?.default_branch;
|
|
766
|
+
const branch =
|
|
767
|
+
typeof fallback === "string" && fallback.trim() !== ""
|
|
768
|
+
? fallback.trim()
|
|
769
|
+
: "main";
|
|
770
|
+
return new Map([[branch, PRODUCTION]]);
|
|
771
|
+
}
|
|
772
|
+
|
|
673
773
|
function trackerContract(config = readConfig()) {
|
|
674
774
|
const provider = requireString(config.tracker, "tracker").toLowerCase();
|
|
675
775
|
const identityRepo = currentRepoIdentity(config);
|
|
@@ -686,6 +786,7 @@ function trackerContract(config = readConfig()) {
|
|
|
686
786
|
provider,
|
|
687
787
|
repository,
|
|
688
788
|
identityRepo,
|
|
789
|
+
deployBranches: deployBranchEnvironments(config),
|
|
689
790
|
verify: verifyLevel(config),
|
|
690
791
|
lifecycle: lifecycleContract(config, provider),
|
|
691
792
|
repositoryIsIdentity:
|
|
@@ -3127,9 +3228,193 @@ function githubTimeline(ref) {
|
|
|
3127
3228
|
}
|
|
3128
3229
|
|
|
3129
3230
|
/**
|
|
3130
|
-
*
|
|
3231
|
+
* The branch a merged pull request was merged INTO.
|
|
3131
3232
|
*
|
|
3132
|
-
*
|
|
3233
|
+
* A second call per pull request, because the timeline cannot answer it: a
|
|
3234
|
+
* `cross-referenced` event carries the referencing item in ISSUE shape, and an
|
|
3235
|
+
* issue has no base branch. Reading the base is the whole point — without it
|
|
3236
|
+
* "a merged pull request references this item" is the only evidence there is,
|
|
3237
|
+
* and that sentence is true of a merge into an integration branch that shipped
|
|
3238
|
+
* nothing.
|
|
3239
|
+
* @param {string} repository `owner/name` the pull request belongs to.
|
|
3240
|
+
* @param {number} number The pull-request number.
|
|
3241
|
+
* @returns {string} The base branch name, empty when it could not be read.
|
|
3242
|
+
*/
|
|
3243
|
+
function pullRequestBaseBranch(repository, number) {
|
|
3244
|
+
const result = run(
|
|
3245
|
+
"gh",
|
|
3246
|
+
["api", `repos/${repository}/pulls/${number}`, "--jq", ".base.ref"],
|
|
3247
|
+
{ allowFailure: true }
|
|
3248
|
+
);
|
|
3249
|
+
if (result.status !== 0)
|
|
3250
|
+
throw githubFailure(result, `${repository}#${number}`);
|
|
3251
|
+
return result.stdout.trim();
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
/**
|
|
3255
|
+
* Decide which lifecycle role a set of merged pull requests has earned.
|
|
3256
|
+
*
|
|
3257
|
+
* The defect this exists to end: completion read "is there a merged pull
|
|
3258
|
+
* request referencing this item" and never asked which branch it merged into,
|
|
3259
|
+
* so a stacked pull request landing on an integration branch stamped the
|
|
3260
|
+
* PRODUCTION terminal role and closed the item. Nothing had reached the deploy
|
|
3261
|
+
* branch and nothing had deployed, but every recovery flow — sweep, repair
|
|
3262
|
+
* intake, the rollup reconcilers — reads a closed item carrying the terminal
|
|
3263
|
+
* role as finished, so the corruption is in the direction that stops recovery
|
|
3264
|
+
* from firing.
|
|
3265
|
+
*
|
|
3266
|
+
* **Production is recognised, never ranked.** An earlier draft of this
|
|
3267
|
+
* function ordered environments by their POSITION in the configured `done`
|
|
3268
|
+
* map, which makes correctness depend on JSON key order — not semantically
|
|
3269
|
+
* meaningful, constrained by no schema, and able to fail in the direction of
|
|
3270
|
+
* writing the WRONG TERMINAL ROLE rather than merely refusing, which is the
|
|
3271
|
+
* original defect with extra steps. So the only distinction drawn here is
|
|
3272
|
+
* production versus not: a merge into the production environment is terminal,
|
|
3273
|
+
* a single non-production environment records its own role, and several
|
|
3274
|
+
* different non-production environments are reported rather than ranked. That
|
|
3275
|
+
* is enough, because the role this ticket is about is the terminal one.
|
|
3276
|
+
*
|
|
3277
|
+
* Pure and exported so the decision can be asserted without a network.
|
|
3278
|
+
* @param {{base: string, number: number}[]} pullRequests Merged pull requests.
|
|
3279
|
+
* @param {Map<string, string>} branchEnvironments Branch to deploy environment.
|
|
3280
|
+
* @param {{done: [string, string][], productionEnvironment: string}} lifecycle
|
|
3281
|
+
* The resolved lifecycle contract.
|
|
3282
|
+
* @returns {{ambiguous: boolean, environment: string|null, evidence: {base: string, environment: string|null, number: number}[], role: string|null, terminal: boolean}}
|
|
3283
|
+
* The role earned, or a null role when nothing was earned or nothing ranks.
|
|
3284
|
+
*/
|
|
3285
|
+
export function mergedBaseDecision(
|
|
3286
|
+
pullRequests,
|
|
3287
|
+
branchEnvironments,
|
|
3288
|
+
lifecycle
|
|
3289
|
+
) {
|
|
3290
|
+
const roles = new Map(lifecycle?.done ?? []);
|
|
3291
|
+
const production = lifecycle?.productionEnvironment;
|
|
3292
|
+
const evidence = (pullRequests ?? []).map(request => {
|
|
3293
|
+
const base = String(request?.base ?? "");
|
|
3294
|
+
return {
|
|
3295
|
+
base,
|
|
3296
|
+
environment: branchEnvironments?.get(base) ?? null,
|
|
3297
|
+
number: request?.number,
|
|
3298
|
+
};
|
|
3299
|
+
});
|
|
3300
|
+
const reached = [
|
|
3301
|
+
...new Set(
|
|
3302
|
+
evidence
|
|
3303
|
+
.map(entry => entry.environment)
|
|
3304
|
+
.filter(environment => roles.has(environment))
|
|
3305
|
+
),
|
|
3306
|
+
];
|
|
3307
|
+
if (reached.includes(production)) {
|
|
3308
|
+
return {
|
|
3309
|
+
ambiguous: false,
|
|
3310
|
+
environment: production,
|
|
3311
|
+
evidence,
|
|
3312
|
+
role: roles.get(production),
|
|
3313
|
+
terminal: true,
|
|
3314
|
+
};
|
|
3315
|
+
}
|
|
3316
|
+
// Exactly one non-production environment is unambiguous. Two different ones
|
|
3317
|
+
// would need an ordering the configuration does not state, so they are
|
|
3318
|
+
// reported instead of guessed at.
|
|
3319
|
+
if (reached.length !== 1) {
|
|
3320
|
+
return {
|
|
3321
|
+
ambiguous: reached.length > 1,
|
|
3322
|
+
environment: null,
|
|
3323
|
+
evidence,
|
|
3324
|
+
role: null,
|
|
3325
|
+
terminal: false,
|
|
3326
|
+
};
|
|
3327
|
+
}
|
|
3328
|
+
return {
|
|
3329
|
+
ambiguous: false,
|
|
3330
|
+
environment: reached[0],
|
|
3331
|
+
evidence,
|
|
3332
|
+
role: roles.get(reached[0]),
|
|
3333
|
+
terminal: false,
|
|
3334
|
+
};
|
|
3335
|
+
}
|
|
3336
|
+
|
|
3337
|
+
/**
|
|
3338
|
+
* Name every merged pull request and the branch it landed on.
|
|
3339
|
+
*
|
|
3340
|
+
* An operator reading a run that completed nothing has to be able to tell WHY
|
|
3341
|
+
* without opening the code, and "which branch did it actually merge into" is
|
|
3342
|
+
* the whole answer.
|
|
3343
|
+
* @param {{base: string, environment: string|null, number: number}[]} evidence
|
|
3344
|
+
* What each merged pull request was observed to target.
|
|
3345
|
+
* @returns {string} A readable one-line summary.
|
|
3346
|
+
*/
|
|
3347
|
+
function describeMergeBases(evidence) {
|
|
3348
|
+
return evidence
|
|
3349
|
+
.map(
|
|
3350
|
+
entry =>
|
|
3351
|
+
`#${entry.number} -> ${entry.base || "(unknown base)"} [${entry.environment ?? NOT_A_DEPLOY_BRANCH}]`
|
|
3352
|
+
)
|
|
3353
|
+
.join(", ");
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
/**
|
|
3357
|
+
* Name the branches this project does deploy, and which one it took to be
|
|
3358
|
+
* production.
|
|
3359
|
+
*
|
|
3360
|
+
* Naming the production branch explicitly is what makes a wrong ASSUMPTION
|
|
3361
|
+
* visible. A project that never wrote `deploy.branches` has its default branch
|
|
3362
|
+
* read as production; if that is not its real production branch, every
|
|
3363
|
+
* completion refuses and the operator has to be able to see the assumption in
|
|
3364
|
+
* the refusal rather than infer it from a list.
|
|
3365
|
+
* @param {Map<string, string>} branchEnvironments Branch to deploy environment.
|
|
3366
|
+
* @param {string} production The production environment's name.
|
|
3367
|
+
* @returns {string} A readable list, with the production branch called out.
|
|
3368
|
+
*/
|
|
3369
|
+
function describeDeployBranches(branchEnvironments, production) {
|
|
3370
|
+
const listed =
|
|
3371
|
+
[...branchEnvironments]
|
|
3372
|
+
.map(([branch, environment]) => `${branch} [${environment}]`)
|
|
3373
|
+
.join(", ") || "(none configured)";
|
|
3374
|
+
const [productionBranch] =
|
|
3375
|
+
[...branchEnvironments].find(
|
|
3376
|
+
([, environment]) => environment === production
|
|
3377
|
+
) ?? [];
|
|
3378
|
+
return productionBranch === undefined
|
|
3379
|
+
? `${listed}; no branch maps to "${production}"`
|
|
3380
|
+
: `${listed}; production is taken to be "${productionBranch}"`;
|
|
3381
|
+
}
|
|
3382
|
+
|
|
3383
|
+
/**
|
|
3384
|
+
* Record a deploy role that is NOT the production terminal.
|
|
3385
|
+
*
|
|
3386
|
+
* Adds the environment's role and stops. It deliberately does not close the
|
|
3387
|
+
* item and does not retire the claimed role: the work is genuinely still in
|
|
3388
|
+
* flight until it reaches the production branch, and native closure fires only
|
|
3389
|
+
* at the production terminal.
|
|
3390
|
+
* @param {string} ref Canonical work-item reference.
|
|
3391
|
+
* @param {{labels: string[]}} before The pre-write tracker state.
|
|
3392
|
+
* @param {string} role The environment's configured done role.
|
|
3393
|
+
* @returns {void}
|
|
3394
|
+
*/
|
|
3395
|
+
function recordDeployRole(ref, before, role) {
|
|
3396
|
+
if (carriesLabel(before.labels, role)) return;
|
|
3397
|
+
const [repository, number] = ref.split("#");
|
|
3398
|
+
const edited = run(
|
|
3399
|
+
"gh",
|
|
3400
|
+
[
|
|
3401
|
+
"issue",
|
|
3402
|
+
"edit",
|
|
3403
|
+
String(number),
|
|
3404
|
+
"--repo",
|
|
3405
|
+
repository,
|
|
3406
|
+
"--add-label",
|
|
3407
|
+
role,
|
|
3408
|
+
],
|
|
3409
|
+
{ allowFailure: true }
|
|
3410
|
+
);
|
|
3411
|
+
if (edited.status !== 0) throw githubFailure(edited, ref);
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3414
|
+
/**
|
|
3415
|
+
* Move a work item to the lifecycle role its merges have actually earned.
|
|
3416
|
+
*
|
|
3417
|
+
* The role is RESOLVED from configuration, never assumed. Lisa's own
|
|
3133
3418
|
* repository maps `production` to `status:done`, but the map is environment
|
|
3134
3419
|
* aware — a project whose target is `dev` has a different terminal role, and a
|
|
3135
3420
|
* hardcoded label would silently apply the wrong one there while looking
|
|
@@ -3139,9 +3424,18 @@ function githubTimeline(ref) {
|
|
|
3139
3424
|
* is pointed at is a way to make unfinished work disappear, which is the same
|
|
3140
3425
|
* defect class as a gate that passes because it found nothing. Evidence is a
|
|
3141
3426
|
* merged pull request in this repository.
|
|
3427
|
+
*
|
|
3428
|
+
* **And a merged pull request is not evidence on its own.** The base branch it
|
|
3429
|
+
* merged into decides which role it earned: only a merge into the production
|
|
3430
|
+
* deploy branch is terminal. A merge into an integration branch records the
|
|
3431
|
+
* environment's role at most, and a merge into a branch this project does not
|
|
3432
|
+
* deploy at all records nothing and closes nothing — reported, never silent,
|
|
3433
|
+
* because a completion that quietly did not happen is the same failure as one
|
|
3434
|
+
* that wrongly did.
|
|
3142
3435
|
* @param {string} ref Canonical work-item reference.
|
|
3143
3436
|
* @param {object} contract The resolved tracker contract.
|
|
3144
|
-
* @returns {{merged: number[], terminal: string}}
|
|
3437
|
+
* @returns {{applied: boolean, merged: number[], report: string, terminal: string|null}}
|
|
3438
|
+
* What was applied, and why.
|
|
3145
3439
|
*/
|
|
3146
3440
|
function completeGithubWorkItem(ref, contract) {
|
|
3147
3441
|
const [repository] = ref.split("#");
|
|
@@ -3154,11 +3448,51 @@ function completeGithubWorkItem(ref, contract) {
|
|
|
3154
3448
|
`from a real one afterwards. If the work shipped some other way, say so on the item and close it deliberately.`
|
|
3155
3449
|
);
|
|
3156
3450
|
}
|
|
3451
|
+
const decision = mergedBaseDecision(
|
|
3452
|
+
merged.map(number => ({
|
|
3453
|
+
base: pullRequestBaseBranch(repository, number),
|
|
3454
|
+
number,
|
|
3455
|
+
})),
|
|
3456
|
+
contract.deployBranches,
|
|
3457
|
+
contract.lifecycle
|
|
3458
|
+
);
|
|
3459
|
+
const evidence = merged.map(number => `#${number}`).join(", ");
|
|
3460
|
+
const bases = describeMergeBases(decision.evidence);
|
|
3461
|
+
if (decision.role === null) {
|
|
3462
|
+
const why = decision.ambiguous
|
|
3463
|
+
? `Several different non-production deploy environments were reached, and the configuration states\n` +
|
|
3464
|
+
`no ordering between them, so this writer reports them rather than guessing which is furthest.`
|
|
3465
|
+
: `No merged pull request reached a branch this project deploys, so no lifecycle role was\n` +
|
|
3466
|
+
`applied and ${ref} stays open. It completes when the change reaches a deploy branch.`;
|
|
3467
|
+
return {
|
|
3468
|
+
applied: false,
|
|
3469
|
+
bases,
|
|
3470
|
+
merged,
|
|
3471
|
+
report:
|
|
3472
|
+
`work-item NOT completed: ${ref} (merged: ${evidence}) bases: ${bases}\n` +
|
|
3473
|
+
`${why}\n` +
|
|
3474
|
+
`Deploy branches: ${describeDeployBranches(contract.deployBranches, contract.lifecycle.productionEnvironment)}.\n` +
|
|
3475
|
+
`If that production branch is wrong, "deploy.branches" in .lisa.config.json is what to fix.`,
|
|
3476
|
+
terminal: null,
|
|
3477
|
+
};
|
|
3478
|
+
}
|
|
3157
3479
|
const before = githubLifecycleState(
|
|
3158
3480
|
ref,
|
|
3159
3481
|
`GitHub issue ${ref} completion read`
|
|
3160
3482
|
);
|
|
3161
3483
|
assertNotAbandoned(ref, before);
|
|
3484
|
+
if (!decision.terminal) {
|
|
3485
|
+
recordDeployRole(ref, before, decision.role);
|
|
3486
|
+
return {
|
|
3487
|
+
applied: true,
|
|
3488
|
+
bases,
|
|
3489
|
+
merged,
|
|
3490
|
+
report:
|
|
3491
|
+
`work-item advanced: ${ref} -> ${decision.role} (merged: ${evidence}) bases: ${bases}\n` +
|
|
3492
|
+
`${decision.environment} is not the production environment, so ${ref} stays open.`,
|
|
3493
|
+
terminal: decision.role,
|
|
3494
|
+
};
|
|
3495
|
+
}
|
|
3162
3496
|
reconcileGithubLifecycle(ref, before, contract);
|
|
3163
3497
|
// A SECOND read, deliberately. The edit and the close each reported their own
|
|
3164
3498
|
// success, and a writer that trusts those is asserting about tracker state it
|
|
@@ -3168,7 +3502,13 @@ function completeGithubWorkItem(ref, contract) {
|
|
|
3168
3502
|
`GitHub issue ${ref} completion readback`
|
|
3169
3503
|
);
|
|
3170
3504
|
assertCompletionReadback(ref, after, contract);
|
|
3171
|
-
return {
|
|
3505
|
+
return {
|
|
3506
|
+
applied: true,
|
|
3507
|
+
bases,
|
|
3508
|
+
merged,
|
|
3509
|
+
report: `work-item completed: ${ref} -> ${contract.lifecycle.terminalName} (merged: ${evidence}) bases: ${bases}`,
|
|
3510
|
+
terminal: contract.lifecycle.terminalName,
|
|
3511
|
+
};
|
|
3172
3512
|
}
|
|
3173
3513
|
|
|
3174
3514
|
/**
|
|
@@ -3582,11 +3922,15 @@ function complete(args) {
|
|
|
3582
3922
|
}
|
|
3583
3923
|
const ref = canonicalizeRef(supplied ?? bound, contract);
|
|
3584
3924
|
const prUrl = pullRequestUrlOption(args);
|
|
3585
|
-
const
|
|
3925
|
+
const outcome = completeWorkItem(ref, contract, prUrl);
|
|
3926
|
+
// The writer composes its own sentence when the base branch shaped the
|
|
3927
|
+
// outcome, because only the writer knows which bases it read. The Linear
|
|
3928
|
+
// path has no such decision to report, so it keeps the original line.
|
|
3586
3929
|
console.log(
|
|
3587
|
-
|
|
3588
|
-
.
|
|
3589
|
-
|
|
3930
|
+
outcome.report ??
|
|
3931
|
+
`work-item completed: ${ref} -> ${outcome.terminal} (merged: ${outcome.merged
|
|
3932
|
+
.map(number => `#${number}`)
|
|
3933
|
+
.join(", ")})`
|
|
3590
3934
|
);
|
|
3591
3935
|
}
|
|
3592
3936
|
|
|
@@ -3645,8 +3989,15 @@ function sweep(args) {
|
|
|
3645
3989
|
console.log(`DRIFT ${ref} merged: ${evidence} ${issue.title}`);
|
|
3646
3990
|
continue;
|
|
3647
3991
|
}
|
|
3648
|
-
const
|
|
3649
|
-
|
|
3992
|
+
const outcome = completeWorkItem(ref, contract);
|
|
3993
|
+
// A sweep must not abort on the first item whose merges have not reached a
|
|
3994
|
+
// deploy branch. It reports that item and carries on, which is what makes
|
|
3995
|
+
// the backstop safe to run over a whole queue.
|
|
3996
|
+
console.log(
|
|
3997
|
+
outcome.applied
|
|
3998
|
+
? `completed ${ref} -> ${outcome.terminal} (merged: ${evidence}) bases: ${outcome.bases}`
|
|
3999
|
+
: outcome.report
|
|
4000
|
+
);
|
|
3650
4001
|
}
|
|
3651
4002
|
if (drifted === 0) {
|
|
3652
4003
|
console.log(
|
|
@@ -93,11 +93,18 @@ export declare function worktreeExclusions(): readonly string[];
|
|
|
93
93
|
*/
|
|
94
94
|
export declare const MAX_WORKERS_OVERRIDE_VAR = "LISA_VITEST_MAX_WORKERS";
|
|
95
95
|
/**
|
|
96
|
-
* Environment variable
|
|
96
|
+
* Environment variable stating how many concurrent runs share this machine.
|
|
97
97
|
*
|
|
98
|
-
*
|
|
99
|
-
* which is
|
|
100
|
-
*
|
|
98
|
+
* Optional, and it is a statement of INTENT rather than an observation: "six
|
|
99
|
+
* runs are coming", which is knowledge a count of what is live right now cannot
|
|
100
|
+
* have. So it outranks {@link discoverFleetConcurrency} when present.
|
|
101
|
+
*
|
|
102
|
+
* It is no longer the only way the divisor is reached, and that correction is
|
|
103
|
+
* the whole of #3665. Shipped as the sole mechanism, it was set by nothing —
|
|
104
|
+
* one occurrence in the tree, its own declaration — so the divisor was inert
|
|
105
|
+
* for every consumer and the cap arrived as floor-only. Discovery is the layer
|
|
106
|
+
* that reaches a run nobody configured; this variable is now the way to
|
|
107
|
+
* override what discovery sees.
|
|
101
108
|
*/
|
|
102
109
|
export declare const FLEET_CONCURRENCY_VAR = "LISA_FLEET_CONCURRENCY";
|
|
103
110
|
/**
|
|
@@ -122,6 +129,56 @@ export declare const DEFAULT_MAX_WORKERS = "50%";
|
|
|
122
129
|
* a visible kill for an invisible timeout has not helped anyone.
|
|
123
130
|
*/
|
|
124
131
|
export declare const MIN_FLEET_WORKERS = 2;
|
|
132
|
+
/**
|
|
133
|
+
* How many Lisa test runs are live on this machine right now, including this one.
|
|
134
|
+
*
|
|
135
|
+
* ## Why this is discovered rather than declared
|
|
136
|
+
*
|
|
137
|
+
* The divisor shipped as an explicit signal, and for a release nothing set it:
|
|
138
|
+
* `LISA_FLEET_CONCURRENCY` occurred exactly once in the tree, at its own
|
|
139
|
+
* declaration. So the cap reached every consumer as floor-only, and the layer
|
|
140
|
+
* that addresses multi-agent contention was inert by default
|
|
141
|
+
* (CodySwannGT/lisa#3665).
|
|
142
|
+
*
|
|
143
|
+
* The obvious repair — pick a component and have it export the variable — fails
|
|
144
|
+
* its own test. **Nothing knows how many runs it is about to start.** Agents are
|
|
145
|
+
* launched independently, each in its own worktree, by whoever happens to be
|
|
146
|
+
* driving; there is no scheduler holding that number. A setter would have to
|
|
147
|
+
* guess, and a guessed fleet size that goes stale the moment the fleet resizes
|
|
148
|
+
* is a worse contract than no setter at all.
|
|
149
|
+
*
|
|
150
|
+
* What every run *can* do is look. Each supervised run already registers a
|
|
151
|
+
* `run-<pid>-<epoch>-<suffix>` root inside one namespace under `os.tmpdir()`,
|
|
152
|
+
* which on this platform is per-user and therefore shared by every checkout on
|
|
153
|
+
* the machine. Counting the roots whose owning process is still alive answers
|
|
154
|
+
* the question at the only moment it matters — when this run is about to size
|
|
155
|
+
* its pool — without anybody having to know the answer in advance.
|
|
156
|
+
*
|
|
157
|
+
* ## Fail open, never closed
|
|
158
|
+
*
|
|
159
|
+
* This runs inside a Vitest config factory, so a throw here does not degrade a
|
|
160
|
+
* test run, it prevents one. Every failure path returns 1 (no fleet detected),
|
|
161
|
+
* which yields the floor — the behaviour that shipped. An unreadable namespace,
|
|
162
|
+
* a permission error, a platform without `process.kill`, an absent directory on
|
|
163
|
+
* the very first run: all of them mean "no evidence of siblings", never "assume
|
|
164
|
+
* the worst and throttle".
|
|
165
|
+
*
|
|
166
|
+
* Our own pid is excluded rather than subtracted, because the config may load
|
|
167
|
+
* before or after this run registers its own root, and both orders must give
|
|
168
|
+
* the same count. The result adds one back for this run.
|
|
169
|
+
* @param deps - Injectable seams so tests state a fleet instead of spawning one.
|
|
170
|
+
* @param deps.namespaceDir - Directory holding run roots.
|
|
171
|
+
* @param deps.readDir - Directory lister.
|
|
172
|
+
* @param deps.isAlive - Process-liveness probe.
|
|
173
|
+
* @param deps.self - This process's id.
|
|
174
|
+
* @returns Live run count including this one; 1 when nothing else is detected.
|
|
175
|
+
*/
|
|
176
|
+
export declare function discoverFleetConcurrency(deps?: {
|
|
177
|
+
namespaceDir?: () => string;
|
|
178
|
+
readDir?: (dir: string) => readonly string[];
|
|
179
|
+
isAlive?: (pid: number) => boolean;
|
|
180
|
+
self?: number;
|
|
181
|
+
}): number;
|
|
125
182
|
/**
|
|
126
183
|
* Resolves the Vitest worker-pool cap for this run.
|
|
127
184
|
*
|
|
@@ -142,14 +199,21 @@ export declare const MIN_FLEET_WORKERS = 2;
|
|
|
142
199
|
* recorded, not hypothesised — see {@link MIN_FLEET_WORKERS}. So the resolution
|
|
143
200
|
* has three layers, and only the middle one knows about the fleet:
|
|
144
201
|
*
|
|
145
|
-
* 1. **Floor.** {@link DEFAULT_MAX_WORKERS},
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* {@link
|
|
202
|
+
* 1. **Floor.** {@link DEFAULT_MAX_WORKERS}, when this run is the only one.
|
|
203
|
+
* 2. **Divisor.** The floor divided by the number of concurrent runs, so the
|
|
204
|
+
* fleet's total pool is bounded by the machine rather than multiplied by its
|
|
205
|
+
* own size — never below {@link MIN_FLEET_WORKERS}. That number comes from
|
|
206
|
+
* {@link FLEET_CONCURRENCY_VAR} when something states it, and otherwise from
|
|
207
|
+
* {@link discoverFleetConcurrency}, which counts live sibling runs.
|
|
208
|
+
* Discovery is what makes this layer reach a run nobody configured; as an
|
|
209
|
+
* opt-in signal it reached nothing for a release (#3665).
|
|
151
210
|
* 3. **Override.** {@link MAX_WORKERS_OVERRIDE_VAR} replaces both, upward or
|
|
152
211
|
* downward.
|
|
212
|
+
*
|
|
213
|
+
* A stated signal outranks discovery rather than being reconciled with it. An
|
|
214
|
+
* operator who says "we are six" is describing an intent — six runs are coming,
|
|
215
|
+
* even if only two have started — and a count of what happens to be live right
|
|
216
|
+
* now must not quietly contradict it.
|
|
153
217
|
* ## Why the environment is imported rather than reached for
|
|
154
218
|
*
|
|
155
219
|
* The default comes from `node:process`'s `env` binding, not from the ambient
|
|
@@ -162,9 +226,10 @@ export declare const MIN_FLEET_WORKERS = 2;
|
|
|
162
226
|
* parameter injectable so tests state an environment rather than mutating one.
|
|
163
227
|
* @param environment - Environment to read; defaults to this process's.
|
|
164
228
|
* @param cores - Logical cores available; defaults to this machine's count.
|
|
229
|
+
* @param discover - Fleet-size discovery; defaults to counting live run roots.
|
|
165
230
|
* @returns A value for Vitest's `maxWorkers` — a worker count, or a percentage.
|
|
166
231
|
*/
|
|
167
|
-
export declare function resolveMaxWorkers(environment?: NodeJS.ProcessEnv, cores?: number): number | string;
|
|
232
|
+
export declare function resolveMaxWorkers(environment?: NodeJS.ProcessEnv, cores?: number, discover?: () => number): number | string;
|
|
168
233
|
/**
|
|
169
234
|
* Setup files that bound and reclaim the suite's temporary directories.
|
|
170
235
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../src/configs/vitest/base.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../src/configs/vitest/base.ts"],"names":[],"mappings":"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"}
|