alphacouncil-agent 0.9.2 → 0.9.4
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/commands/alpha.md +22 -3
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.grok/commands/alpha.md +22 -3
- package/.opencode/command/alpha.md +22 -3
- package/AGENTS.md +27 -4
- package/CHANGELOG.md +67 -0
- package/CLAUDE.md +41 -7
- package/README.ja.md +21 -11
- package/README.md +35 -11
- package/README.zh-CN.md +31 -11
- package/commands/alpha.md +22 -3
- package/data/build-profile.v1.json +1 -1
- package/data/master-selector-method-locales.v1.mjs +286 -0
- package/docs/INSTALL.md +43 -6
- package/docs/releases/v0.9.3.md +86 -0
- package/docs/releases/v0.9.4.md +79 -0
- package/docs/report-contract.md +50 -1
- package/knowledge/ai-assisted-solo/experiments/runs/a.json +4 -4
- package/knowledge/ai-assisted-solo/experiments/runs/b.json +2 -2
- package/knowledge/ai-assisted-solo/experiments/runs/c.json +2 -2
- package/knowledge/ai-assisted-solo/experiments/runs/d13.json +16 -16
- package/knowledge/ai-assisted-solo/experiments/runs/d26.json +29 -29
- package/knowledge/ai-assisted-solo/experiments/runs/e-d13.json +19 -19
- package/knowledge/ai-assisted-solo/experiments/runs/e-d26.json +32 -32
- package/knowledge/ai-assisted-solo/experiments/runs/h_ai_reference.json +33 -33
- package/knowledge/ai-assisted-solo/experiments/simulation-input.json +79 -79
- package/knowledge/ai-assisted-solo/experiments/simulation-manifest.json +19 -19
- package/knowledge/solo-test/masters/master_ackman/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_aschenbrenner/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_asness/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_buffett/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_burry/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_cathie_wood/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_dalio/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_damodaran/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_druckenmiller/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_duan_yongping/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_fisher/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_forensic_short/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_graham/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_jhunjhunwala/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_klarman/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_li_lu/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_lynch/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_marks/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_munger/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_natenberg/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_pabrai/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_simons/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_sinclair/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_soros/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_taleb/manifest.json +14 -4
- package/knowledge/solo-test/masters/master_thorp/manifest.json +14 -4
- package/mcp/lib/codex.mjs +3 -3
- package/mcp/lib/constants.mjs +61 -25
- package/mcp/lib/council-options.mjs +95 -40
- package/mcp/lib/council-selection.mjs +95 -9
- package/mcp/lib/gates.mjs +78 -11
- package/mcp/lib/lang.mjs +109 -2
- package/mcp/lib/markdown.mjs +444 -135
- package/mcp/lib/master-catalog.mjs +28 -3
- package/mcp/lib/orchestrator.mjs +952 -265
- package/mcp/lib/packets.mjs +173 -20
- package/mcp/lib/personas/engine.mjs +30 -30
- package/mcp/lib/personas-v3/compiler.mjs +2 -0
- package/mcp/lib/personas-v3/loader.mjs +10 -7
- package/mcp/lib/personas-v3/source-acquisition.mjs +10 -3
- package/mcp/lib/prompts.mjs +33 -0
- package/mcp/lib/rpc.mjs +75 -33
- package/mcp/lib/run-store.mjs +23 -0
- package/package.json +2 -2
- package/schemas/persona-v3.schema.json +37 -2
- package/scripts/lib/packaged-host-parity.mjs +54 -5
- package/scripts/lib/persona-v3-solo-test-packs.mjs +31 -5
- package/scripts/package-smoke.mjs +31 -12
- package/scripts/run-tests.mjs +23 -2
- package/skills/alphacouncil-agent/SKILL.md +50 -7
package/mcp/lib/rpc.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import readline from "node:readline";
|
|
|
4
4
|
import { COUNCIL_MODES, LIMITS, MASTER_STANCES, OUTPUT_MODES, QUICK_TASKS, SERVER_NAME, VERSION } from "./constants.mjs";
|
|
5
5
|
import { RpcCode, methodNotFound, invalidParams, toRpcError } from "./errors.mjs";
|
|
6
6
|
import { readJson, readJsonl, writeJson } from "./fsutil.mjs";
|
|
7
|
-
import { resolveLanguage } from "./lang.mjs";
|
|
7
|
+
import { localized, resolveLanguage } from "./lang.mjs";
|
|
8
8
|
import { sweepStaleOutputs } from "./codex.mjs";
|
|
9
9
|
import { completenessStatus, sourceManifest } from "./gates.mjs";
|
|
10
10
|
import { artifactPaths, existingDebate, runId, runPath, safeSymbol, saveRun } from "./run-store.mjs";
|
|
@@ -76,7 +76,14 @@ export function recordAck(run, extra = {}) {
|
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
function renderSelectionCatalog(data
|
|
79
|
+
function renderSelectionCatalog(data) {
|
|
80
|
+
const copy = (messages) => localized(data.language, messages);
|
|
81
|
+
const labels = copy({
|
|
82
|
+
en: { identity: "Identity", method: "Method", bestFor: "Best for", maturity: "Maturity", pack: "Pack format", preselected: "preselected" },
|
|
83
|
+
zh: { identity: "身份", method: "方法", bestFor: "适合", maturity: "成熟度", pack: "物理格式", preselected: "已预选" },
|
|
84
|
+
ja: { identity: "人物像", method: "手法", bestFor: "適した対象", maturity: "成熟度", pack: "パック形式", preselected: "事前選択済み" },
|
|
85
|
+
ko: { identity: "정체성", method: "방법", bestFor: "적합 대상", maturity: "성숙도", pack: "팩 형식", preselected: "사전 선택" },
|
|
86
|
+
});
|
|
80
87
|
const preselected = new Set(data.preselected_master_ids || []);
|
|
81
88
|
// Some MCP hosts expose only text content even when the server also returns
|
|
82
89
|
// structuredContent. Keep the selection handshake usable on those hosts by
|
|
@@ -91,31 +98,45 @@ function renderSelectionCatalog(data, zh) {
|
|
|
91
98
|
council_mode: data.council_mode,
|
|
92
99
|
})}`;
|
|
93
100
|
const cards = data.masters.map((master) => [
|
|
94
|
-
`${master.index}. ${master.title} [${master.id}]${preselected.has(master.id) ?
|
|
95
|
-
`${
|
|
96
|
-
`${
|
|
97
|
-
`${
|
|
98
|
-
`${
|
|
99
|
-
`${
|
|
101
|
+
`${master.index}. ${master.title} [${master.id}]${preselected.has(master.id) ? ` [${labels.preselected}]` : ""}`,
|
|
102
|
+
`${labels.identity}: ${master.identity}`,
|
|
103
|
+
`${labels.method}: ${master.method}`,
|
|
104
|
+
`${labels.bestFor}: ${master.best_for}`,
|
|
105
|
+
`${labels.maturity}: ${master.maturity_label} (${master.maturity})`,
|
|
106
|
+
`${labels.pack}: ${master.pack_format} (${master.admission_level})`,
|
|
100
107
|
].join("\n ")).join("\n\n");
|
|
101
108
|
const quick = data.council_mode === "quick";
|
|
102
|
-
const instructions =
|
|
103
|
-
?
|
|
104
|
-
|
|
105
|
-
:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
const instructions = quick
|
|
110
|
+
? copy({
|
|
111
|
+
en: `Quick mode: choose 1 to ${data.maximum} masters. Submit numbers, ranges, or stable IDs, for example: 1 / 1,3,8 / 1-4 / master_buffett. Selecting all is not supported.`,
|
|
112
|
+
zh: `Quick 模式请选择 1 至 ${data.maximum} 位大师。回复编号、范围或稳定 ID,例如:1 / 1,3,8 / 1-4 / master_buffett;不支持 all 全选。`,
|
|
113
|
+
ja: `Quick モードでは1席から${data.maximum}席を選んでください。番号、範囲、stable ID(例:1 / 1,3,8 / 1-4 / master_buffett)を送信してください。all は使用できません。`,
|
|
114
|
+
ko: `Quick 모드에서는 1개에서 ${data.maximum}개 마스터 좌석을 선택하십시오. 번호, 범위 또는 stable ID(예: 1 / 1,3,8 / 1-4 / master_buffett)를 제출하십시오. all은 지원하지 않습니다.`,
|
|
115
|
+
})
|
|
116
|
+
: copy({
|
|
117
|
+
en: `Choose 1 to ${data.maximum} masters. Submit numbers, ranges, stable IDs, or all, for example: 1 / 1,3,8 / 1-5 / master_buffett / all.`,
|
|
118
|
+
zh: `请选择 1 至 ${data.maximum} 位大师。回复编号、范围、稳定 ID,或 all 全选,例如:1 / 1,3,8 / 1-5 / master_buffett / all。`,
|
|
119
|
+
ja: `1席から${data.maximum}席のマスターを選んでください。番号、範囲、stable ID、または all(例:1 / 1,3,8 / 1-5 / master_buffett / all)を送信してください。`,
|
|
120
|
+
ko: `1개에서 ${data.maximum}개 마스터 좌석을 선택하십시오. 번호, 범위, stable ID 또는 all(예: 1 / 1,3,8 / 1-5 / master_buffett / all)을 제출하십시오.`,
|
|
121
|
+
});
|
|
109
122
|
return [
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
:
|
|
123
|
+
copy({
|
|
124
|
+
en: `Master selection (${data.masters.length} in catalog; choose up to ${data.maximum})`,
|
|
125
|
+
zh: `大师选择(目录共 ${data.masters.length} 席;最多选择 ${data.maximum} 席)`,
|
|
126
|
+
ja: `マスター選択(全${data.masters.length}席、最大${data.maximum}席まで)`,
|
|
127
|
+
ko: `마스터 선택(전체 ${data.masters.length}개 좌석, 최대 ${data.maximum}개 선택)`,
|
|
128
|
+
}),
|
|
113
129
|
fallbackContext,
|
|
114
130
|
"",
|
|
115
131
|
cards,
|
|
116
132
|
"",
|
|
117
133
|
instructions,
|
|
118
|
-
|
|
134
|
+
copy({
|
|
135
|
+
en: "Research starts only after this selection is submitted.",
|
|
136
|
+
zh: "提交选择后才会开始研究。",
|
|
137
|
+
ja: "選択を送信するまで調査は開始されません。",
|
|
138
|
+
ko: "선택을 제출하기 전에는 조사를 시작하지 않습니다.",
|
|
139
|
+
}),
|
|
119
140
|
].join("\n");
|
|
120
141
|
}
|
|
121
142
|
|
|
@@ -176,6 +197,12 @@ function startValidation(args, entryTool) {
|
|
|
176
197
|
maximum_ms: LIMITS.QUICK_HARD_MAX_MS,
|
|
177
198
|
});
|
|
178
199
|
}
|
|
200
|
+
if (mode === "full" && args.total_timeout_ms > LIMITS.FULL_HARD_MAX_MS) {
|
|
201
|
+
throw invalidParams(`Full council total_timeout_ms cannot exceed ${LIMITS.FULL_HARD_MAX_MS}.`, {
|
|
202
|
+
reason: "FULL_TOTAL_TIMEOUT_EXCEEDS_MAX",
|
|
203
|
+
maximum_ms: LIMITS.FULL_HARD_MAX_MS,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
179
206
|
if (mode === "quick" && args.synthesis === false) {
|
|
180
207
|
throw invalidParams("Quick council requires its one-round bull/bear and portfolio-manager synthesis.", {
|
|
181
208
|
reason: "QUICK_SYNTHESIS_REQUIRED",
|
|
@@ -230,7 +257,9 @@ function selectedRunArgs(args = {}, entryTool) {
|
|
|
230
257
|
selection_receipt: args.selection_receipt,
|
|
231
258
|
symbol,
|
|
232
259
|
run_id: id,
|
|
233
|
-
|
|
260
|
+
// Preserve omission so consumeCouncilSelection can infer the same locale
|
|
261
|
+
// from the prompt that begin_council_selection bound into the receipt.
|
|
262
|
+
language: args.language,
|
|
234
263
|
prompt: typeof args.prompt === "string" ? args.prompt : "",
|
|
235
264
|
council_mode: args.council_mode || "full",
|
|
236
265
|
});
|
|
@@ -373,9 +402,8 @@ export function tools() {
|
|
|
373
402
|
synthesis_timeout_ms: { type: "number", default: LIMITS.CODEX_TIMEOUT_MS },
|
|
374
403
|
total_timeout_ms: {
|
|
375
404
|
type: "number",
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
description: "Hard end-to-end wall-clock budget for council_mode=quick, including grounding wait, retries and synthesis. It may be lowered but never raised above ten minutes. Full mode has no implicit global deadline.",
|
|
405
|
+
maximum: LIMITS.FULL_HARD_MAX_MS,
|
|
406
|
+
description: "Hard queue-to-persistence wall-clock budget. quick defaults to and cannot exceed ten minutes; full defaults to and cannot exceed thirty minutes. Callers may lower the applicable ceiling, never raise it.",
|
|
379
407
|
},
|
|
380
408
|
output_mode: { type: "string", enum: OUTPUT_MODES, default: "public_equity", description: "Final synthesis target shape." },
|
|
381
409
|
selection_receipt: { type: "string", description: "One-run receipt returned by confirm_master_selection. Required for every council run and consumed exactly once." },
|
|
@@ -435,7 +463,7 @@ export function tools() {
|
|
|
435
463
|
},
|
|
436
464
|
required: ["symbol", "selection_receipt"],
|
|
437
465
|
}),
|
|
438
|
-
tool("record_visible_packet", "MANDATORY sequential step (not optional): record one completed visible evidence agent packet into a planned visible run. Every
|
|
466
|
+
tool("record_visible_packet", "MANDATORY sequential step (not optional): record one completed visible evidence agent packet into a planned visible run. Every reader-facing field must use the run language or the packet is rejected without changing run state. Every planned evidence task MUST be recorded before debate completion and portfolio_manager.", {
|
|
439
467
|
type: "object",
|
|
440
468
|
properties: {
|
|
441
469
|
run_id: { type: "string" },
|
|
@@ -446,16 +474,27 @@ export function tools() {
|
|
|
446
474
|
},
|
|
447
475
|
required: ["run_id", "task", "packet"],
|
|
448
476
|
}),
|
|
449
|
-
tool("record_visible_decision", "Record one
|
|
477
|
+
tool("record_visible_decision", "Record exactly one visible decision step. Full visible bull_researcher and bear_researcher calls MUST supply round=1, then round=2, then round=3; both sides of the prior round are required before advancing. Round 2 asks exactly three questions; Round 3 preserves its own questions and answers the opponent's questions with exact bindings. Packets are persisted by role+round: an identical replay is idempotent and conflicting content is rejected. portfolio_manager accepts no round and is rejected until all evidence, selected masters, both three-round sides, and the exact Q&A gate are complete. Every reader-facing field must use the run language.", {
|
|
450
478
|
type: "object",
|
|
451
479
|
properties: {
|
|
452
480
|
run_id: { type: "string" },
|
|
453
481
|
role: { type: "string", enum: debateIds },
|
|
482
|
+
round: { type: "integer", minimum: 1, maximum: 3, description: "Required for bull_researcher and bear_researcher. Full visible runs require 1, 2, and 3 in order. Omit for portfolio_manager." },
|
|
454
483
|
packet: { type: "object" },
|
|
455
484
|
thread_id: { type: "string" },
|
|
456
485
|
thread_title: { type: "string" },
|
|
457
486
|
},
|
|
458
487
|
required: ["run_id", "role", "packet"],
|
|
488
|
+
allOf: [
|
|
489
|
+
{
|
|
490
|
+
if: { properties: { role: { enum: ["bull_researcher", "bear_researcher"] } }, required: ["role"] },
|
|
491
|
+
then: { required: ["round"] },
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
if: { properties: { role: { const: "portfolio_manager" } }, required: ["role"] },
|
|
495
|
+
then: { not: { required: ["round"] } },
|
|
496
|
+
},
|
|
497
|
+
],
|
|
459
498
|
}),
|
|
460
499
|
tool("collect_evidence", "Launch Codex subagents and save shared JSON evidence packets. Use dry_run=true only for planning/self-tests.", {
|
|
461
500
|
type: "object",
|
|
@@ -504,7 +543,7 @@ export function tools() {
|
|
|
504
543
|
type: "object",
|
|
505
544
|
properties: { language: common.language },
|
|
506
545
|
}, { readOnlyHint: true, destructiveHint: false, openWorldHint: false }),
|
|
507
|
-
tool("record_master_opinion", "Record one completed selected master-seat opinion into a planned visible run. Masters run AFTER every evidence packet is recorded and BEFORE the bull/bear debate, so the debate has their disagreements to argue with. A master may return stance=out_of_scope, which is a recorded conclusion rather than an abstention. Every selected seat must either report or be settled deterministically before the run can be complete.", {
|
|
546
|
+
tool("record_master_opinion", "Record one completed selected master-seat opinion into a planned visible run. Every reader-facing field must use the run language or the opinion is rejected without changing run state. Masters run AFTER every evidence packet is recorded and BEFORE the bull/bear debate, so the debate has their disagreements to argue with. A master may return stance=out_of_scope, which is a recorded conclusion rather than an abstention. Every selected seat must either report or be settled deterministically before the run can be complete.", {
|
|
508
547
|
type: "object",
|
|
509
548
|
properties: {
|
|
510
549
|
run_id: { type: "string" },
|
|
@@ -692,8 +731,7 @@ export async function handleToolCall(id, params) {
|
|
|
692
731
|
const args = params?.arguments || {};
|
|
693
732
|
if (name === "begin_council_selection") {
|
|
694
733
|
const data = beginCouncilSelection(args);
|
|
695
|
-
|
|
696
|
-
sendResult(id, jsonContent(renderSelectionCatalog(data, zh), data));
|
|
734
|
+
sendResult(id, jsonContent(renderSelectionCatalog(data), data));
|
|
697
735
|
return;
|
|
698
736
|
}
|
|
699
737
|
if (name === "confirm_master_selection") {
|
|
@@ -705,10 +743,13 @@ export async function handleToolCall(id, params) {
|
|
|
705
743
|
intent_hash: data.intent_hash,
|
|
706
744
|
council_mode: data.council_mode,
|
|
707
745
|
})}`;
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
data
|
|
711
|
-
|
|
746
|
+
const confirmation = localized(data.language, {
|
|
747
|
+
en: `Confirmed ${data.selected_count} master seat(s) for ${data.symbol}. Use the one-time selection_receipt to start this run.`,
|
|
748
|
+
zh: `已为 ${data.symbol} 确认 ${data.selected_count} 个大师席位。请使用一次性 selection_receipt 启动本轮运行。`,
|
|
749
|
+
ja: `${data.symbol} について ${data.selected_count} 席のマスターを確定しました。今回の実行を開始するには1回限りの selection_receipt を使用してください。`,
|
|
750
|
+
ko: `${data.symbol}에 대해 마스터 ${data.selected_count}개 좌석을 확정했습니다. 이번 실행을 시작하려면 일회용 selection_receipt를 사용하십시오.`,
|
|
751
|
+
});
|
|
752
|
+
sendResult(id, jsonContent(`${fallbackContext}\n${confirmation}`, data));
|
|
712
753
|
return;
|
|
713
754
|
}
|
|
714
755
|
if (name === "plan_visible_run") {
|
|
@@ -766,11 +807,12 @@ export async function handleToolCall(id, params) {
|
|
|
766
807
|
if (name === "record_visible_decision") {
|
|
767
808
|
const result = recordVisibleDecision(args);
|
|
768
809
|
sendResult(id, jsonContent(
|
|
769
|
-
`Recorded visible decision ${args.role} for ${result.run.symbol}: ${result.run.run_id}`,
|
|
810
|
+
`Recorded visible decision ${args.role}${args.round ? ` round ${args.round}` : ""} for ${result.run.symbol}: ${result.run.run_id}`,
|
|
770
811
|
// decision and opinion are small and are what a caller reads back; only the full
|
|
771
812
|
// run object is dropped.
|
|
772
813
|
recordAck(result.run, {
|
|
773
814
|
decision: result.decision,
|
|
815
|
+
idempotent_replay: result.idempotent_replay === true,
|
|
774
816
|
report_quality: result.report_quality?.status,
|
|
775
817
|
missing_report_items: result.report_quality?.missing || [],
|
|
776
818
|
}),
|
package/mcp/lib/run-store.mjs
CHANGED
|
@@ -54,6 +54,13 @@ export function statusSnapshot(run) {
|
|
|
54
54
|
const recordedMasters = (run.master_opinions || []).map((opinion) => opinion.master);
|
|
55
55
|
const recordedSet = new Set(recordedMasters);
|
|
56
56
|
const pendingMasters = selectedMasters.filter((master) => !recordedSet.has(master));
|
|
57
|
+
const visibleDebate = run.execution_mode === "visible_host_threads" ? run.visible_debate : null;
|
|
58
|
+
const visibleDebateRounds = visibleDebate
|
|
59
|
+
? Object.fromEntries(["bull_researcher", "bear_researcher"].map((role) => [
|
|
60
|
+
role,
|
|
61
|
+
Object.keys(visibleDebate.rounds?.[role] || {}).map(Number).filter(Number.isInteger).sort((a, b) => a - b),
|
|
62
|
+
]))
|
|
63
|
+
: null;
|
|
57
64
|
return {
|
|
58
65
|
run_id: run.run_id,
|
|
59
66
|
symbol: run.symbol,
|
|
@@ -62,13 +69,29 @@ export function statusSnapshot(run) {
|
|
|
62
69
|
execution_mode: run.execution_mode,
|
|
63
70
|
council_mode: run.council_mode || "full",
|
|
64
71
|
debate_format: run.debate_format || "three_round_cross_exam",
|
|
72
|
+
visible_debate_contract: visibleDebate?.contract || null,
|
|
73
|
+
visible_debate_rounds_expected: visibleDebate?.rounds_expected || null,
|
|
74
|
+
visible_debate_rounds_recorded: visibleDebateRounds,
|
|
75
|
+
visible_debate_qna_gate: visibleDebate?.qna_gate?.status || null,
|
|
65
76
|
report_contract: run.council_mode === "quick" ? "quick_v1" : "full_v2",
|
|
66
77
|
full_council_equivalent: run.council_mode !== "quick",
|
|
78
|
+
master_worker_contract: run.execution_mode === "background_codex_exec"
|
|
79
|
+
? "one_isolated_worker_per_selected_method_v1"
|
|
80
|
+
: run.execution_mode === "dry_run"
|
|
81
|
+
? "planned_not_executed"
|
|
82
|
+
: "host_managed_not_plugin_enforced",
|
|
83
|
+
deadline_enforced: run.deadline_enforced === true,
|
|
67
84
|
time_budget_ms: run.time_budget_ms || null,
|
|
68
85
|
deadline_at: run.deadline_at || null,
|
|
69
86
|
remaining_budget_ms: run.deadline_at
|
|
70
87
|
? Math.max(0, Date.parse(run.deadline_at) - Date.now())
|
|
71
88
|
: null,
|
|
89
|
+
elapsed_ms: run.started_at
|
|
90
|
+
? Math.max(0, Date.parse(run.completed_at || new Date().toISOString()) - Date.parse(run.started_at))
|
|
91
|
+
: null,
|
|
92
|
+
deadline_met: run.deadline_at && run.completed_at
|
|
93
|
+
? Date.parse(run.completed_at) <= Date.parse(run.deadline_at)
|
|
94
|
+
: null,
|
|
72
95
|
visibility_required: run.visibility_required,
|
|
73
96
|
dry_run: run.dry_run,
|
|
74
97
|
status: run.status,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alphacouncil-agent",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
4
4
|
"description": "Multi-agent public-equity research workflow plugin for Codex & Claude Code: sourced evidence packets, bull/bear debate, and a portfolio-manager Buy/Overweight/Hold/Underweight/Sell decision.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"check": "node scripts/selfcheck.mjs",
|
|
34
34
|
"test": "node scripts/run-tests.mjs",
|
|
35
35
|
"test:source": "node --test",
|
|
36
|
-
"test:package": "node scripts/
|
|
36
|
+
"test:package": "node scripts/check-packaged-host-parity.mjs --check",
|
|
37
37
|
"sync": "node scripts/sync-personas.mjs",
|
|
38
38
|
"persona:corpus": "node scripts/report-persona-corpus-gaps.mjs",
|
|
39
39
|
"persona:build-specs": "node scripts/report-persona-v3-build-specs.mjs",
|
|
@@ -19,6 +19,31 @@
|
|
|
19
19
|
"components"
|
|
20
20
|
],
|
|
21
21
|
"additionalProperties": false,
|
|
22
|
+
"allOf": [
|
|
23
|
+
{
|
|
24
|
+
"if": {
|
|
25
|
+
"required": ["build_profile"],
|
|
26
|
+
"properties": { "build_profile": { "const": "solo_test" } }
|
|
27
|
+
},
|
|
28
|
+
"then": {
|
|
29
|
+
"properties": {
|
|
30
|
+
"identity": {
|
|
31
|
+
"properties": {
|
|
32
|
+
"public_label": { "$ref": "#/$defs/localizedTextFourLocales" },
|
|
33
|
+
"operator_label": { "$ref": "#/$defs/localizedTextFourLocales" }
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"selection": {
|
|
37
|
+
"properties": {
|
|
38
|
+
"identity": { "$ref": "#/$defs/localizedTextFourLocales" },
|
|
39
|
+
"method": { "$ref": "#/$defs/localizedTextFourLocales" },
|
|
40
|
+
"best_for": { "$ref": "#/$defs/localizedTextFourLocales" }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
],
|
|
22
47
|
"properties": {
|
|
23
48
|
"schema_version": { "const": 3 },
|
|
24
49
|
"build_profile": {
|
|
@@ -38,7 +63,9 @@
|
|
|
38
63
|
"additionalProperties": false,
|
|
39
64
|
"properties": {
|
|
40
65
|
"en": { "type": "string", "minLength": 3 },
|
|
41
|
-
"zh": { "type": "string", "minLength": 2 }
|
|
66
|
+
"zh": { "type": "string", "minLength": 2 },
|
|
67
|
+
"ja": { "type": "string", "minLength": 2 },
|
|
68
|
+
"ko": { "type": "string", "minLength": 2 }
|
|
42
69
|
}
|
|
43
70
|
},
|
|
44
71
|
"operator_label": {
|
|
@@ -192,9 +219,17 @@
|
|
|
192
219
|
"additionalProperties": false,
|
|
193
220
|
"properties": {
|
|
194
221
|
"en": { "type": "string", "minLength": 3 },
|
|
195
|
-
"zh": { "type": "string", "minLength": 2 }
|
|
222
|
+
"zh": { "type": "string", "minLength": 2 },
|
|
223
|
+
"ja": { "type": "string", "minLength": 2 },
|
|
224
|
+
"ko": { "type": "string", "minLength": 2 }
|
|
196
225
|
}
|
|
197
226
|
},
|
|
227
|
+
"localizedTextFourLocales": {
|
|
228
|
+
"allOf": [
|
|
229
|
+
{ "$ref": "#/$defs/localizedText" },
|
|
230
|
+
{ "required": ["en", "zh", "ja", "ko"] }
|
|
231
|
+
]
|
|
232
|
+
},
|
|
198
233
|
"strings": {
|
|
199
234
|
"type": "array",
|
|
200
235
|
"items": { "type": "string", "minLength": 1 },
|
|
@@ -29,7 +29,12 @@ import { isDeepStrictEqual } from "node:util";
|
|
|
29
29
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
30
30
|
export const PACKAGED_PARITY_REPO_ROOT = resolve(HERE, "../..");
|
|
31
31
|
export const PACKAGED_HOST_IDS = Object.freeze(["claude_code", "codex", "opencode", "grok"]);
|
|
32
|
-
export const PACKAGED_SELECTION_INPUT =
|
|
32
|
+
export const PACKAGED_SELECTION_INPUT = Object.freeze([
|
|
33
|
+
"master_buffett",
|
|
34
|
+
"master_damodaran",
|
|
35
|
+
"master_taleb",
|
|
36
|
+
"master_sinclair",
|
|
37
|
+
]);
|
|
33
38
|
|
|
34
39
|
const TEMP_PREFIX = "alphacouncil-packaged-parity-";
|
|
35
40
|
const SYMBOL = "NOK";
|
|
@@ -39,6 +44,13 @@ const PROMPT = "packaged adapter parity fixture";
|
|
|
39
44
|
const TASKS = Object.freeze(["market_data"]);
|
|
40
45
|
const REQUEST_TIMEOUT_MS = 30_000;
|
|
41
46
|
const PROCESS_TIMEOUT_MS = 120_000;
|
|
47
|
+
const EXPECTED_TOOL_COUNT = 31;
|
|
48
|
+
const INSTALLED_LOCALE_CASES = Object.freeze([
|
|
49
|
+
Object.freeze({ language: "en-US", prompt: "installed package locale smoke", key: "en", script: /[A-Za-z]/u }),
|
|
50
|
+
Object.freeze({ language: "zh-CN", prompt: "安装包语言烟雾测试", key: "zh", script: /\p{Script=Han}/u }),
|
|
51
|
+
Object.freeze({ language: "ja-JP", prompt: "インストール済みパッケージの言語スモークテスト", key: "ja", script: /[\p{Script=Hiragana}\p{Script=Katakana}]/u }),
|
|
52
|
+
Object.freeze({ language: "ko-KR", prompt: "설치 패키지 언어 스모크 테스트", key: "ko", script: /\p{Script=Hangul}/u }),
|
|
53
|
+
]);
|
|
42
54
|
const REQUIRED_PACKAGE_FILES = Object.freeze([
|
|
43
55
|
"mcp/server.mjs",
|
|
44
56
|
"data/host-capabilities.v1.json",
|
|
@@ -575,16 +587,28 @@ function assertCompleteCatalog(opened, text, hostId) {
|
|
|
575
587
|
for (const [offset, master] of opened.masters.entries()) {
|
|
576
588
|
if (master.index !== offset + 1 || seen.has(master.id)) fail(`${hostId}: catalog order or IDs are invalid`);
|
|
577
589
|
seen.add(master.id);
|
|
578
|
-
for (const field of ["identity", "method", "best_for", "maturity", "pack_hash"]) {
|
|
590
|
+
for (const field of ["identity", "method", "best_for", "maturity", "maturity_label", "pack_hash"]) {
|
|
579
591
|
if (typeof master[field] !== "string" || !master[field]) fail(`${hostId}: catalog seat ${master.id} lacks ${field}`);
|
|
580
592
|
}
|
|
581
593
|
if (!/^sha256:[a-f0-9]{64}$/u.test(master.pack_hash)) fail(`${hostId}: ${master.id} pack hash is invalid`);
|
|
582
|
-
for (const visible of [`${master.index}. `, `[${master.id}]`, master.identity, master.method, master.best_for, master.maturity]) {
|
|
594
|
+
for (const visible of [`${master.index}. `, `[${master.id}]`, master.identity, master.method, master.best_for, master.maturity, master.maturity_label]) {
|
|
583
595
|
if (!text.includes(visible)) fail(`${hostId}: returned text did not display ${master.id} field ${visible}`);
|
|
584
596
|
}
|
|
585
597
|
}
|
|
586
598
|
}
|
|
587
599
|
|
|
600
|
+
function assertLocalizedCatalog(opened, text, locale) {
|
|
601
|
+
assertCompleteCatalog(opened, text, `installed-${locale.key}`);
|
|
602
|
+
for (const master of opened.masters) {
|
|
603
|
+
for (const field of ["identity", "method", "best_for", "maturity_label"]) {
|
|
604
|
+
if (!locale.script.test(master[field])) fail(`installed-${locale.key}: ${master.id}.${field} lacks requested-locale prose`);
|
|
605
|
+
if (locale.key !== "en" && /\b[a-z]+(?:_[a-z0-9]+)+\b/u.test(master[field])) {
|
|
606
|
+
fail(`installed-${locale.key}: ${master.id}.${field} leaked a machine domain id into reader-facing prose`);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
588
612
|
function normalizedReceiptBinding(record) {
|
|
589
613
|
return {
|
|
590
614
|
symbol: record.symbol,
|
|
@@ -624,7 +648,27 @@ async function executePackagedAdapters({ pack, surfaces, tempRoot }) {
|
|
|
624
648
|
networkSentinel,
|
|
625
649
|
});
|
|
626
650
|
await server.request("initialize", {});
|
|
627
|
-
|
|
651
|
+
const listed = await server.request("tools/list", {});
|
|
652
|
+
const tools = listed.result?.tools || [];
|
|
653
|
+
if (tools.length !== EXPECTED_TOOL_COUNT) fail(`${hostId}: installed server exposed ${tools.length} tools, expected ${EXPECTED_TOOL_COUNT}`);
|
|
654
|
+
sessions.push({ hostId, dataDir, server, toolCount: tools.length });
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const localeCases = process.env.ALPHACOUNCIL_PERSONA_BUILD_PROFILE === "production"
|
|
658
|
+
? INSTALLED_LOCALE_CASES.slice(0, 1)
|
|
659
|
+
: INSTALLED_LOCALE_CASES;
|
|
660
|
+
const localeCatalogHashes = {};
|
|
661
|
+
for (const locale of localeCases) {
|
|
662
|
+
const response = await sessions[0].server.callTool("begin_council_selection", {
|
|
663
|
+
symbol: SYMBOL,
|
|
664
|
+
as_of: AS_OF,
|
|
665
|
+
language: locale.language,
|
|
666
|
+
prompt: locale.prompt,
|
|
667
|
+
host: "installed-package-locale-smoke",
|
|
668
|
+
});
|
|
669
|
+
const opened = structured(response, `installed ${locale.language} begin_council_selection`);
|
|
670
|
+
assertLocalizedCatalog(opened, response.result.content?.[0]?.text || "", locale);
|
|
671
|
+
localeCatalogHashes[locale.key] = opened.catalog_hash;
|
|
628
672
|
}
|
|
629
673
|
|
|
630
674
|
for (const session of sessions) {
|
|
@@ -642,7 +686,7 @@ async function executePackagedAdapters({ pack, surfaces, tempRoot }) {
|
|
|
642
686
|
selection_id: opened.selection_id,
|
|
643
687
|
catalog_hash: opened.catalog_hash,
|
|
644
688
|
display_ack: true,
|
|
645
|
-
|
|
689
|
+
selected_master_ids: PACKAGED_SELECTION_INPUT,
|
|
646
690
|
}), `${session.hostId} confirm_master_selection`);
|
|
647
691
|
session.opened = opened;
|
|
648
692
|
session.confirmed = confirmed;
|
|
@@ -744,11 +788,15 @@ async function executePackagedAdapters({ pack, surfaces, tempRoot }) {
|
|
|
744
788
|
evidence_scope: "installed_npm_tarball_mcp_stdio_adapters_only",
|
|
745
789
|
host_order: PACKAGED_HOST_IDS,
|
|
746
790
|
host_count: sessions.length,
|
|
791
|
+
tool_count: EXPECTED_TOOL_COUNT,
|
|
792
|
+
locale_count: localeCases.length,
|
|
793
|
+
locale_catalog_hashes: localeCatalogHashes,
|
|
747
794
|
catalog_count: baseline.catalog.length,
|
|
748
795
|
catalog_hash: baseline.catalog_hash,
|
|
749
796
|
catalog_order_hash: sha256(baseline.catalog),
|
|
750
797
|
maturity_counts: maturityCounts,
|
|
751
798
|
selection_input: PACKAGED_SELECTION_INPUT,
|
|
799
|
+
selection_input_type: "stable_ids",
|
|
752
800
|
selected_master_ids: baseline.selected_master_ids,
|
|
753
801
|
selected_master_pack_hashes: baseline.selected_master_pack_hashes,
|
|
754
802
|
receipt_binding_hash: sha256(baseline.receipt_binding),
|
|
@@ -774,6 +822,7 @@ async function executePackagedAdapters({ pack, surfaces, tempRoot }) {
|
|
|
774
822
|
host_id: session.hostId,
|
|
775
823
|
server_entry: "mcp/server.mjs",
|
|
776
824
|
adapter_status: "passed",
|
|
825
|
+
tool_count: session.toolCount,
|
|
777
826
|
catalog_count: session.catalog.length,
|
|
778
827
|
catalog_hash: session.opened.catalog_hash,
|
|
779
828
|
selected_master_ids: session.confirmed.selected_master_ids,
|
|
@@ -21,6 +21,11 @@ import { fileURLToPath } from "node:url";
|
|
|
21
21
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
22
22
|
|
|
23
23
|
import buildInventory from "../../data/persona-v3-build-specs.v1.mjs";
|
|
24
|
+
import {
|
|
25
|
+
selectorBestForLocale,
|
|
26
|
+
selectorMethodLocale,
|
|
27
|
+
} from "../../data/master-selector-method-locales.v1.mjs";
|
|
28
|
+
import { selectorCard } from "../../mcp/lib/master-catalog.mjs";
|
|
24
29
|
import { inspectPersonaAdmission } from "../../mcp/lib/personas-v3/admission.mjs";
|
|
25
30
|
import {
|
|
26
31
|
canonicalValue,
|
|
@@ -381,14 +386,26 @@ function buildDocuments({ seat, blueprint, rawTools, packVersion, formulaManifes
|
|
|
381
386
|
if (policyErrors.length) fail(`${seat.persona_id}: generated deterministic policy is invalid`, { errors: policyErrors });
|
|
382
387
|
|
|
383
388
|
const publicTitle = blueprint.canonical_title;
|
|
389
|
+
const selectorPersona = { id: seat.persona_id, title: publicTitle };
|
|
390
|
+
const chineseMethod = selectorCard(selectorPersona, "zh-CN").method;
|
|
384
391
|
const manifest = canonicalValue({
|
|
385
392
|
schema_version: 3,
|
|
386
393
|
build_profile: "solo_test",
|
|
387
394
|
pack_version: packVersion,
|
|
388
395
|
identity: {
|
|
389
396
|
persona_id: seat.persona_id,
|
|
390
|
-
public_label: {
|
|
391
|
-
|
|
397
|
+
public_label: {
|
|
398
|
+
en: `${publicTitle.en} Solo Test`,
|
|
399
|
+
zh: `${publicTitle.zh}单人测试`,
|
|
400
|
+
ja: `${publicTitle.en} 単独テスト`,
|
|
401
|
+
ko: `${publicTitle.en} 단독 테스트`,
|
|
402
|
+
},
|
|
403
|
+
operator_label: {
|
|
404
|
+
en: `${publicTitle.en} Provisional Operator Lens`,
|
|
405
|
+
zh: `${publicTitle.zh}临时操作视角`,
|
|
406
|
+
ja: `${publicTitle.en} 暫定オペレーター・レンズ`,
|
|
407
|
+
ko: `${publicTitle.en} 임시 오퍼레이터 렌즈`,
|
|
408
|
+
},
|
|
392
409
|
maturity: "operator_lens",
|
|
393
410
|
source_cutoff: SOURCE_DATE,
|
|
394
411
|
},
|
|
@@ -396,11 +413,20 @@ function buildDocuments({ seat, blueprint, rawTools, packVersion, formulaManifes
|
|
|
396
413
|
identity: {
|
|
397
414
|
en: `${publicTitle.en}; project-derived and not human reviewed`,
|
|
398
415
|
zh: `${publicTitle.zh};项目派生、未经人工审定`,
|
|
416
|
+
ja: `${publicTitle.en}。プロジェクト派生で、方法帰属について人による審査を受けていない暫定オペレーター・レンズ。本人の発言や現在の見解ではない。`,
|
|
417
|
+
ko: `${publicTitle.en}. 프로젝트에서 파생되었고 방법 귀속에 대한 인적 심사를 거치지 않은 임시 오퍼레이터 렌즈다. 본인의 발언이나 현재 견해가 아니다.`,
|
|
418
|
+
},
|
|
419
|
+
method: {
|
|
420
|
+
en: seat.method_scope.planning_hypothesis,
|
|
421
|
+
zh: chineseMethod,
|
|
422
|
+
ja: selectorMethodLocale(seat.persona_id, "ja"),
|
|
423
|
+
ko: selectorMethodLocale(seat.persona_id, "ko"),
|
|
399
424
|
},
|
|
400
|
-
method: { en: seat.method_scope.planning_hypothesis, zh: seat.method_scope.planning_hypothesis },
|
|
401
425
|
best_for: {
|
|
402
|
-
en:
|
|
403
|
-
zh:
|
|
426
|
+
en: selectorBestForLocale(seat.persona_id, "en"),
|
|
427
|
+
zh: selectorBestForLocale(seat.persona_id, "zh"),
|
|
428
|
+
ja: selectorBestForLocale(seat.persona_id, "ja"),
|
|
429
|
+
ko: selectorBestForLocale(seat.persona_id, "ko"),
|
|
404
430
|
},
|
|
405
431
|
},
|
|
406
432
|
capability: {
|
|
@@ -107,19 +107,38 @@ export async function runPackageSmoke() {
|
|
|
107
107
|
|
|
108
108
|
const listed = await server.request("tools/list", {});
|
|
109
109
|
const tools = listed.result?.tools || [];
|
|
110
|
+
assert.equal(tools.length, 31, "installed server must expose exactly 31 MCP tools");
|
|
110
111
|
const names = new Set(tools.map((tool) => tool.name));
|
|
111
112
|
for (const required of ["begin_council_selection", "confirm_master_selection", "plan_visible_run", "analyze_symbol"]) {
|
|
112
113
|
assert.ok(names.has(required), `installed server is missing ${required}`);
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
language: "
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
116
|
+
const localeCases = [
|
|
117
|
+
{ language: "en-US", prompt: "bounded installed-package smoke", script: /[A-Za-z]/u },
|
|
118
|
+
{ language: "zh-CN", prompt: "安装包有界烟雾测试", script: /\p{Script=Han}/u },
|
|
119
|
+
{ language: "ja-JP", prompt: "インストール済みパッケージの有界スモークテスト", script: /[\p{Script=Hiragana}\p{Script=Katakana}]/u },
|
|
120
|
+
{ language: "ko-KR", prompt: "설치 패키지의 제한된 스모크 테스트", script: /\p{Script=Hangul}/u },
|
|
121
|
+
];
|
|
122
|
+
let opened;
|
|
123
|
+
let prompt;
|
|
124
|
+
for (const locale of localeCases) {
|
|
125
|
+
const catalog = structured(await server.callTool("begin_council_selection", {
|
|
126
|
+
symbol: "AAPL",
|
|
127
|
+
language: locale.language,
|
|
128
|
+
host: "package-smoke",
|
|
129
|
+
prompt: locale.prompt,
|
|
130
|
+
}));
|
|
131
|
+
assert.equal(catalog.masters.length, 26, `${locale.language} installed catalog must contain exactly 26 seats`);
|
|
132
|
+
for (const master of catalog.masters) {
|
|
133
|
+
for (const field of ["identity", "method", "best_for", "maturity_label"]) {
|
|
134
|
+
assert.match(master[field], locale.script, `${locale.language} ${master.id}.${field}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (locale.language === "zh-CN") {
|
|
138
|
+
opened = catalog;
|
|
139
|
+
prompt = locale.prompt;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
123
142
|
|
|
124
143
|
const confirmed = structured(await server.callTool("confirm_master_selection", {
|
|
125
144
|
selection_id: opened.selection_id,
|
|
@@ -132,7 +151,7 @@ export async function runPackageSmoke() {
|
|
|
132
151
|
const runId = `PACKAGE-SMOKE-${process.pid}`;
|
|
133
152
|
const planned = structured(await server.callTool("plan_visible_run", {
|
|
134
153
|
symbol: "AAPL",
|
|
135
|
-
language: "
|
|
154
|
+
language: "zh-CN",
|
|
136
155
|
prompt,
|
|
137
156
|
run_id: runId,
|
|
138
157
|
tasks: ["market_data"],
|
|
@@ -143,7 +162,7 @@ export async function runPackageSmoke() {
|
|
|
143
162
|
|
|
144
163
|
const replay = await server.callTool("plan_visible_run", {
|
|
145
164
|
symbol: "AAPL",
|
|
146
|
-
language: "
|
|
165
|
+
language: "zh-CN",
|
|
147
166
|
prompt,
|
|
148
167
|
run_id: `${runId}-REPLAY`,
|
|
149
168
|
tasks: ["market_data"],
|
|
@@ -152,8 +171,8 @@ export async function runPackageSmoke() {
|
|
|
152
171
|
});
|
|
153
172
|
assert.equal(replay.error?.data?.reason, "MASTER_SELECTION_REPLAYED");
|
|
154
173
|
|
|
155
|
-
process.stdout.write(`package-smoke: passed tools=${tools.length} catalog=26 selected=1 replay_rejected=true\n`);
|
|
156
|
-
return { tools: tools.length, catalog: 26, selected: 1, replay_rejected: true };
|
|
174
|
+
process.stdout.write(`package-smoke: passed tools=${tools.length} catalog=26 locales=4 selected=1 replay_rejected=true\n`);
|
|
175
|
+
return { tools: tools.length, catalog: 26, locales: 4, selected: 1, replay_rejected: true };
|
|
157
176
|
} finally {
|
|
158
177
|
if (!server.child.killed) await server.close().catch(() => server.child.kill());
|
|
159
178
|
rmSync(dataDir, { recursive: true, force: true });
|
package/scripts/run-tests.mjs
CHANGED
|
@@ -32,6 +32,23 @@ export const SOURCE_PORTABLE_EXCLUDED_TESTS = Object.freeze([
|
|
|
32
32
|
]);
|
|
33
33
|
|
|
34
34
|
export const SOURCE_PORTABLE_EXCLUDED_TEST_COUNT = 14;
|
|
35
|
+
// The suite contains several package-install and hash-audit files that each spawn
|
|
36
|
+
// their own child processes. Node's CPU-count default can oversubscribe large CI
|
|
37
|
+
// hosts and turn deterministic sub-second fixtures into platform-specific timeouts.
|
|
38
|
+
export const SOURCE_TEST_CONCURRENCY = 4;
|
|
39
|
+
|
|
40
|
+
export function sourceTestConcurrencyArg(concurrency = SOURCE_TEST_CONCURRENCY, nodeVersion = process.versions.node) {
|
|
41
|
+
const [major = 0, minor = 0] = String(nodeVersion).split(".").map(Number);
|
|
42
|
+
const supported = (major === 18 && minor >= 19)
|
|
43
|
+
|| (major === 20 && minor >= 10)
|
|
44
|
+
|| major >= 21;
|
|
45
|
+
return supported ? `--test-concurrency=${concurrency}` : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function sourceTestArgs(files = []) {
|
|
49
|
+
const concurrencyArg = sourceTestConcurrencyArg();
|
|
50
|
+
return Object.freeze(["--test", ...(concurrencyArg ? [concurrencyArg] : []), ...files]);
|
|
51
|
+
}
|
|
35
52
|
|
|
36
53
|
function listMjsFiles(root, directory = join(root, "test")) {
|
|
37
54
|
if (!existsSync(directory)) return [];
|
|
@@ -81,7 +98,11 @@ export function buildTestPlan(root = repoRoot) {
|
|
|
81
98
|
return Object.freeze({ mode: "installed_package", excluded: Object.freeze([]), args: Object.freeze(["scripts/package-smoke.mjs"]) });
|
|
82
99
|
}
|
|
83
100
|
if (staging === "present") {
|
|
84
|
-
return Object.freeze({
|
|
101
|
+
return Object.freeze({
|
|
102
|
+
mode: "source_with_staging",
|
|
103
|
+
excluded: Object.freeze([]),
|
|
104
|
+
args: sourceTestArgs(),
|
|
105
|
+
});
|
|
85
106
|
}
|
|
86
107
|
|
|
87
108
|
const excluded = new Set(SOURCE_PORTABLE_EXCLUDED_TESTS);
|
|
@@ -91,7 +112,7 @@ export function buildTestPlan(root = repoRoot) {
|
|
|
91
112
|
return Object.freeze({
|
|
92
113
|
mode: "source_portable",
|
|
93
114
|
excluded: SOURCE_PORTABLE_EXCLUDED_TESTS,
|
|
94
|
-
args:
|
|
115
|
+
args: sourceTestArgs(selected),
|
|
95
116
|
});
|
|
96
117
|
}
|
|
97
118
|
|