opencode-swarm 7.24.0 → 7.25.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/dist/cli/index.js +343 -65
- package/dist/hooks/hive-promoter.d.ts +12 -0
- package/dist/index.js +391 -107
- package/dist/mutation/engine.d.ts +4 -0
- package/dist/tools/knowledge-query.d.ts +6 -0
- package/dist/tools/test-runner.d.ts +10 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -34,7 +34,7 @@ var package_default;
|
|
|
34
34
|
var init_package = __esm(() => {
|
|
35
35
|
package_default = {
|
|
36
36
|
name: "opencode-swarm",
|
|
37
|
-
version: "7.
|
|
37
|
+
version: "7.25.0",
|
|
38
38
|
description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
39
39
|
main: "dist/index.js",
|
|
40
40
|
types: "dist/index.d.ts",
|
|
@@ -35267,6 +35267,20 @@ function normalizeEntry(raw) {
|
|
|
35267
35267
|
ro.failed_after_shown_count = typeof ro.failed_after_count === "number" ? ro.failed_after_count : 0;
|
|
35268
35268
|
}
|
|
35269
35269
|
}
|
|
35270
|
+
try {
|
|
35271
|
+
if (typeof obj.encounter_score !== "number" || Number.isNaN(obj.encounter_score)) {
|
|
35272
|
+
obj.encounter_score = 0;
|
|
35273
|
+
}
|
|
35274
|
+
} catch {
|
|
35275
|
+
try {
|
|
35276
|
+
Object.defineProperty(obj, "encounter_score", {
|
|
35277
|
+
value: 0,
|
|
35278
|
+
writable: true,
|
|
35279
|
+
configurable: true,
|
|
35280
|
+
enumerable: true
|
|
35281
|
+
});
|
|
35282
|
+
} catch {}
|
|
35283
|
+
}
|
|
35270
35284
|
const arrayFields = [
|
|
35271
35285
|
"triggers",
|
|
35272
35286
|
"required_actions",
|
|
@@ -35826,12 +35840,26 @@ import path12 from "path";
|
|
|
35826
35840
|
function isAlreadyInHive(entry, hiveEntries, threshold) {
|
|
35827
35841
|
return findNearDuplicate(entry.lesson, hiveEntries, threshold) !== undefined;
|
|
35828
35842
|
}
|
|
35829
|
-
function
|
|
35843
|
+
function isHiveEligible(entry, autoPromoteDays) {
|
|
35830
35844
|
const phaseNumbers = new Set;
|
|
35831
|
-
for (const record3 of
|
|
35832
|
-
|
|
35845
|
+
for (const record3 of entry.confirmed_by ?? []) {
|
|
35846
|
+
if (record3 && typeof record3.phase_number === "number") {
|
|
35847
|
+
phaseNumbers.add(record3.phase_number);
|
|
35848
|
+
}
|
|
35849
|
+
}
|
|
35850
|
+
if (entry.hive_eligible === true && phaseNumbers.size >= 3) {
|
|
35851
|
+
return true;
|
|
35852
|
+
}
|
|
35853
|
+
if ((entry.tags ?? []).includes("hive-fast-track")) {
|
|
35854
|
+
return true;
|
|
35855
|
+
}
|
|
35856
|
+
const createdMs = Date.parse(entry.created_at);
|
|
35857
|
+
const ageMs = Number.isFinite(createdMs) ? Date.now() - createdMs : 0;
|
|
35858
|
+
const ageThresholdMs = autoPromoteDays * 86400000;
|
|
35859
|
+
if (ageMs >= ageThresholdMs) {
|
|
35860
|
+
return true;
|
|
35833
35861
|
}
|
|
35834
|
-
return
|
|
35862
|
+
return false;
|
|
35835
35863
|
}
|
|
35836
35864
|
function countDistinctProjects(confirmedBy) {
|
|
35837
35865
|
const projectNames = new Set;
|
|
@@ -35849,12 +35877,6 @@ function calculateEncounterScore(currentScore, isSameProject, config3) {
|
|
|
35849
35877
|
const newScore = currentScore + increment;
|
|
35850
35878
|
return Math.min(Math.max(newScore, config3.min_encounter_score), config3.max_encounter_score);
|
|
35851
35879
|
}
|
|
35852
|
-
function getEntryAgeMs(createdAt) {
|
|
35853
|
-
const createdTime = new Date(createdAt).getTime();
|
|
35854
|
-
if (Number.isNaN(createdTime))
|
|
35855
|
-
return 0;
|
|
35856
|
-
return Date.now() - createdTime;
|
|
35857
|
-
}
|
|
35858
35880
|
async function checkHivePromotions(swarmEntries, config3) {
|
|
35859
35881
|
let newPromotions = 0;
|
|
35860
35882
|
let encountersIncremented = 0;
|
|
@@ -35873,19 +35895,7 @@ async function checkHivePromotions(swarmEntries, config3) {
|
|
|
35873
35895
|
if (isAlreadyInHive(swarmEntry, hiveEntries, config3.dedup_threshold)) {
|
|
35874
35896
|
continue;
|
|
35875
35897
|
}
|
|
35876
|
-
|
|
35877
|
-
if (swarmEntry.hive_eligible === true && countDistinctPhases(swarmEntry.confirmed_by) >= 3) {
|
|
35878
|
-
shouldPromote = true;
|
|
35879
|
-
}
|
|
35880
|
-
if (swarmEntry.tags.includes("hive-fast-track")) {
|
|
35881
|
-
shouldPromote = true;
|
|
35882
|
-
}
|
|
35883
|
-
const ageMs = getEntryAgeMs(swarmEntry.created_at);
|
|
35884
|
-
const ageThresholdMs = config3.auto_promote_days * 86400000;
|
|
35885
|
-
if (ageMs >= ageThresholdMs) {
|
|
35886
|
-
shouldPromote = true;
|
|
35887
|
-
}
|
|
35888
|
-
if (!shouldPromote) {
|
|
35898
|
+
if (!isHiveEligible(swarmEntry, config3.auto_promote_days)) {
|
|
35889
35899
|
continue;
|
|
35890
35900
|
}
|
|
35891
35901
|
const validationResult = validateLesson(swarmEntry.lesson, hiveEntries.map((e) => e.lesson), {
|
|
@@ -37803,12 +37813,14 @@ async function handleCloseCommand(directory, args, options = {}) {
|
|
|
37803
37813
|
if (planExists) {
|
|
37804
37814
|
planAlreadyDone = phases.length > 0 && phases.every((p) => p.status === "complete" || p.status === "completed" || p.status === "blocked" || p.status === "closed");
|
|
37805
37815
|
}
|
|
37806
|
-
const
|
|
37816
|
+
const { config: loadedConfig } = loadPluginConfigWithMeta(directory);
|
|
37817
|
+
const config3 = KnowledgeConfigSchema.parse(loadedConfig.knowledge ?? {});
|
|
37807
37818
|
const projectName = planData.title ?? "Unknown Project";
|
|
37808
37819
|
const closedPhases = [];
|
|
37809
37820
|
const closedTasks = [];
|
|
37810
37821
|
const warnings = [];
|
|
37811
37822
|
let hivePromoted = 0;
|
|
37823
|
+
let hiveSkipped = 0;
|
|
37812
37824
|
if (!planAlreadyDone) {
|
|
37813
37825
|
for (const phase of inProgressPhases) {
|
|
37814
37826
|
closedPhases.push(phase.id);
|
|
@@ -37931,23 +37943,35 @@ async function handleCloseCommand(directory, args, options = {}) {
|
|
|
37931
37943
|
await fs7.unlink(lessonsFilePath).catch(() => {});
|
|
37932
37944
|
}
|
|
37933
37945
|
if (curationSucceeded) {
|
|
37934
|
-
|
|
37935
|
-
|
|
37936
|
-
|
|
37937
|
-
|
|
37938
|
-
|
|
37939
|
-
|
|
37940
|
-
|
|
37941
|
-
|
|
37942
|
-
|
|
37943
|
-
|
|
37944
|
-
|
|
37946
|
+
if (config3.hive_enabled === false) {} else {
|
|
37947
|
+
try {
|
|
37948
|
+
const knowledgePath = resolveSwarmKnowledgePath(directory);
|
|
37949
|
+
const entries = await readKnowledge(knowledgePath);
|
|
37950
|
+
const autoPromoteDays = config3.auto_promote_days;
|
|
37951
|
+
if (entries.length > 0) {
|
|
37952
|
+
for (const entry of entries) {
|
|
37953
|
+
if (!isHiveEligible(entry, autoPromoteDays)) {
|
|
37954
|
+
hiveSkipped++;
|
|
37955
|
+
continue;
|
|
37956
|
+
}
|
|
37957
|
+
try {
|
|
37958
|
+
const result = await promoteToHive(directory, entry.lesson, entry.category);
|
|
37959
|
+
if (!result.includes("already exists")) {
|
|
37960
|
+
hivePromoted++;
|
|
37961
|
+
}
|
|
37962
|
+
} catch (promotionErr) {
|
|
37963
|
+
const msg = promotionErr instanceof Error ? promotionErr.message : String(promotionErr);
|
|
37964
|
+
warnings.push(`Hive promotion skipped for lesson: ${msg}`);
|
|
37965
|
+
}
|
|
37966
|
+
}
|
|
37967
|
+
if (hiveSkipped > 0) {
|
|
37968
|
+
warnings.push(`${hiveSkipped} swarm knowledge entr${hiveSkipped === 1 ? "y" : "ies"} not eligible for hive promotion`);
|
|
37945
37969
|
}
|
|
37946
37970
|
}
|
|
37971
|
+
} catch (hiveErr) {
|
|
37972
|
+
const msg = hiveErr instanceof Error ? hiveErr.message : String(hiveErr);
|
|
37973
|
+
warnings.push(`Hive promotion failed: ${msg}`);
|
|
37947
37974
|
}
|
|
37948
|
-
} catch (hiveErr) {
|
|
37949
|
-
const msg = hiveErr instanceof Error ? hiveErr.message : String(hiveErr);
|
|
37950
|
-
warnings.push(`Hive promotion failed: ${msg}`);
|
|
37951
37975
|
}
|
|
37952
37976
|
}
|
|
37953
37977
|
const fallbackKnowledgeCreated = curationResult?.stored ?? 0;
|
|
@@ -37964,8 +37988,8 @@ async function handleCloseCommand(directory, args, options = {}) {
|
|
|
37964
37988
|
let skillReviewSummary = "";
|
|
37965
37989
|
if (runSkillReview) {
|
|
37966
37990
|
try {
|
|
37967
|
-
const { config:
|
|
37968
|
-
const skillImproverConfig = SkillImproverConfigSchema.parse(
|
|
37991
|
+
const { config: loadedConfig2 } = loadPluginConfigWithMeta(directory);
|
|
37992
|
+
const skillImproverConfig = SkillImproverConfigSchema.parse(loadedConfig2.skill_improver ?? {});
|
|
37969
37993
|
const skillReviewResult = await runAbortableSkillReview({
|
|
37970
37994
|
directory,
|
|
37971
37995
|
config: skillImproverConfig,
|
|
@@ -38330,7 +38354,6 @@ var init_close = __esm(() => {
|
|
|
38330
38354
|
"handoff-prompt.md",
|
|
38331
38355
|
"handoff-consumed.md",
|
|
38332
38356
|
"escalation-report.md",
|
|
38333
|
-
"knowledge.jsonl",
|
|
38334
38357
|
"knowledge-rejected.jsonl",
|
|
38335
38358
|
"repo-graph.json",
|
|
38336
38359
|
"doc-manifest.json",
|
|
@@ -45976,7 +45999,14 @@ function defaultBuildTestCommand(profile, framework, files, dir = ".", opts = {}
|
|
|
45976
45999
|
return args;
|
|
45977
46000
|
}
|
|
45978
46001
|
case "vitest": {
|
|
45979
|
-
const args = [
|
|
46002
|
+
const args = [
|
|
46003
|
+
"npx",
|
|
46004
|
+
"vitest",
|
|
46005
|
+
"run",
|
|
46006
|
+
"--reporter=json",
|
|
46007
|
+
"--outputFile",
|
|
46008
|
+
".swarm/cache/test-runner-vitest.json"
|
|
46009
|
+
];
|
|
45980
46010
|
if (coverage)
|
|
45981
46011
|
args.push("--coverage");
|
|
45982
46012
|
if (scope !== "all" && files.length > 0)
|
|
@@ -45984,7 +46014,7 @@ function defaultBuildTestCommand(profile, framework, files, dir = ".", opts = {}
|
|
|
45984
46014
|
return args;
|
|
45985
46015
|
}
|
|
45986
46016
|
case "jest": {
|
|
45987
|
-
const args = ["npx", "jest"];
|
|
46017
|
+
const args = ["npx", "jest", "--json"];
|
|
45988
46018
|
if (coverage)
|
|
45989
46019
|
args.push("--coverage");
|
|
45990
46020
|
if (scope !== "all" && files.length > 0)
|
|
@@ -47219,6 +47249,42 @@ function sanitizeChangedFiles(changedFiles) {
|
|
|
47219
47249
|
const validFiles = changedFiles.filter((f) => typeof f === "string" && f.length > 0 && !DANGEROUS_PROPERTY_NAMES.has(f));
|
|
47220
47250
|
return validFiles.slice(0, MAX_CHANGED_FILES);
|
|
47221
47251
|
}
|
|
47252
|
+
function isTestRunResult(value) {
|
|
47253
|
+
return value === "pass" || value === "fail" || value === "skip";
|
|
47254
|
+
}
|
|
47255
|
+
function parseStoredRecord(value) {
|
|
47256
|
+
if (typeof value !== "object" || value === null)
|
|
47257
|
+
return null;
|
|
47258
|
+
const record3 = value;
|
|
47259
|
+
if (typeof record3.testFile !== "string" || record3.testFile.length === 0) {
|
|
47260
|
+
return null;
|
|
47261
|
+
}
|
|
47262
|
+
if (typeof record3.testName !== "string" || record3.testName.length === 0) {
|
|
47263
|
+
return null;
|
|
47264
|
+
}
|
|
47265
|
+
if (typeof record3.taskId !== "string" || record3.taskId.length === 0) {
|
|
47266
|
+
return null;
|
|
47267
|
+
}
|
|
47268
|
+
if (!isTestRunResult(record3.result))
|
|
47269
|
+
return null;
|
|
47270
|
+
if (typeof record3.durationMs !== "number" || !Number.isFinite(record3.durationMs)) {
|
|
47271
|
+
return null;
|
|
47272
|
+
}
|
|
47273
|
+
if (typeof record3.timestamp !== "string" || Number.isNaN(Date.parse(record3.timestamp))) {
|
|
47274
|
+
return null;
|
|
47275
|
+
}
|
|
47276
|
+
return {
|
|
47277
|
+
timestamp: record3.timestamp,
|
|
47278
|
+
taskId: record3.taskId,
|
|
47279
|
+
testFile: record3.testFile,
|
|
47280
|
+
testName: record3.testName,
|
|
47281
|
+
result: record3.result,
|
|
47282
|
+
durationMs: Math.max(0, record3.durationMs),
|
|
47283
|
+
errorMessage: typeof record3.errorMessage === "string" ? sanitizeErrorMessage(record3.errorMessage) : undefined,
|
|
47284
|
+
stackPrefix: typeof record3.stackPrefix === "string" ? sanitizeStackPrefix(record3.stackPrefix) : undefined,
|
|
47285
|
+
changedFiles: sanitizeChangedFiles(Array.isArray(record3.changedFiles) ? record3.changedFiles : [])
|
|
47286
|
+
};
|
|
47287
|
+
}
|
|
47222
47288
|
function appendTestRun(record3, workingDir) {
|
|
47223
47289
|
if (typeof record3.testFile !== "string" || record3.testFile.length === 0) {
|
|
47224
47290
|
throw new TypeError("testFile must be a non-empty string");
|
|
@@ -47256,16 +47322,16 @@ function appendTestRun(record3, workingDir) {
|
|
|
47256
47322
|
}
|
|
47257
47323
|
const existingRecords = readAllRecords(historyPath);
|
|
47258
47324
|
existingRecords.push(sanitizedRecord);
|
|
47259
|
-
const
|
|
47325
|
+
const recordsByTest = new Map;
|
|
47260
47326
|
for (const rec of existingRecords) {
|
|
47261
|
-
const
|
|
47262
|
-
if (!
|
|
47263
|
-
|
|
47327
|
+
const normalizedKey = `${rec.testFile.toLowerCase()}|${rec.testName.toLowerCase()}`;
|
|
47328
|
+
if (!recordsByTest.has(normalizedKey)) {
|
|
47329
|
+
recordsByTest.set(normalizedKey, []);
|
|
47264
47330
|
}
|
|
47265
|
-
|
|
47331
|
+
recordsByTest.get(normalizedKey).push(rec);
|
|
47266
47332
|
}
|
|
47267
47333
|
const prunedRecords = [];
|
|
47268
|
-
for (const [, records] of
|
|
47334
|
+
for (const [, records] of recordsByTest) {
|
|
47269
47335
|
records.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
47270
47336
|
const toKeep = records.slice(-MAX_HISTORY_PER_TEST);
|
|
47271
47337
|
prunedRecords.push(...toKeep);
|
|
@@ -47305,8 +47371,9 @@ function readAllRecords(historyPath) {
|
|
|
47305
47371
|
}
|
|
47306
47372
|
try {
|
|
47307
47373
|
const parsed = JSON.parse(trimmed);
|
|
47308
|
-
|
|
47309
|
-
|
|
47374
|
+
const record3 = parseStoredRecord(parsed);
|
|
47375
|
+
if (record3) {
|
|
47376
|
+
records.push(record3);
|
|
47310
47377
|
}
|
|
47311
47378
|
} catch {}
|
|
47312
47379
|
}
|
|
@@ -48345,7 +48412,14 @@ function buildTestCommand2(framework, scope, files, coverage, baseDir) {
|
|
|
48345
48412
|
return args;
|
|
48346
48413
|
}
|
|
48347
48414
|
case "vitest": {
|
|
48348
|
-
const args = [
|
|
48415
|
+
const args = [
|
|
48416
|
+
"npx",
|
|
48417
|
+
"vitest",
|
|
48418
|
+
"run",
|
|
48419
|
+
"--reporter=json",
|
|
48420
|
+
"--outputFile",
|
|
48421
|
+
VITEST_JSON_OUTPUT_RELATIVE_PATH
|
|
48422
|
+
];
|
|
48349
48423
|
if (coverage)
|
|
48350
48424
|
args.push("--coverage");
|
|
48351
48425
|
if (scope !== "all" && files.length > 0) {
|
|
@@ -48354,7 +48428,7 @@ function buildTestCommand2(framework, scope, files, coverage, baseDir) {
|
|
|
48354
48428
|
return args;
|
|
48355
48429
|
}
|
|
48356
48430
|
case "jest": {
|
|
48357
|
-
const args = ["npx", "jest"];
|
|
48431
|
+
const args = ["npx", "jest", "--json"];
|
|
48358
48432
|
if (coverage)
|
|
48359
48433
|
args.push("--coverage");
|
|
48360
48434
|
if (scope !== "all" && files.length > 0) {
|
|
@@ -48450,6 +48524,122 @@ function buildTestCommand2(framework, scope, files, coverage, baseDir) {
|
|
|
48450
48524
|
return null;
|
|
48451
48525
|
}
|
|
48452
48526
|
}
|
|
48527
|
+
function mapFrameworkStatusToResult(status) {
|
|
48528
|
+
if (typeof status !== "string")
|
|
48529
|
+
return null;
|
|
48530
|
+
const normalized = status.toLowerCase();
|
|
48531
|
+
if (normalized === "pass" || normalized === "passed")
|
|
48532
|
+
return "pass";
|
|
48533
|
+
if (normalized === "fail" || normalized === "failed")
|
|
48534
|
+
return "fail";
|
|
48535
|
+
if (normalized === "skip" || normalized === "skipped" || normalized === "pending" || normalized === "todo") {
|
|
48536
|
+
return "skip";
|
|
48537
|
+
}
|
|
48538
|
+
return null;
|
|
48539
|
+
}
|
|
48540
|
+
function firstLine(value) {
|
|
48541
|
+
if (typeof value !== "string")
|
|
48542
|
+
return;
|
|
48543
|
+
const line = value.split(`
|
|
48544
|
+
`).find((part) => part.trim().length > 0)?.trim();
|
|
48545
|
+
return line && line.length > 0 ? line : undefined;
|
|
48546
|
+
}
|
|
48547
|
+
function parseJestLikeJsonTestResults(payload) {
|
|
48548
|
+
if (typeof payload !== "object" || payload === null)
|
|
48549
|
+
return [];
|
|
48550
|
+
const rawSuites = payload.testResults;
|
|
48551
|
+
if (!Array.isArray(rawSuites))
|
|
48552
|
+
return [];
|
|
48553
|
+
const parsed = [];
|
|
48554
|
+
for (const suite of rawSuites) {
|
|
48555
|
+
if (typeof suite !== "object" || suite === null)
|
|
48556
|
+
continue;
|
|
48557
|
+
const suiteObj = suite;
|
|
48558
|
+
const rawFile = typeof suiteObj.name === "string" ? suiteObj.name : typeof suiteObj.testFilePath === "string" ? suiteObj.testFilePath : undefined;
|
|
48559
|
+
if (!rawFile)
|
|
48560
|
+
continue;
|
|
48561
|
+
const testFile = rawFile.replace(/\\/g, "/");
|
|
48562
|
+
const assertionResults = suiteObj.assertionResults;
|
|
48563
|
+
if (!Array.isArray(assertionResults))
|
|
48564
|
+
continue;
|
|
48565
|
+
for (const assertion of assertionResults) {
|
|
48566
|
+
if (typeof assertion !== "object" || assertion === null)
|
|
48567
|
+
continue;
|
|
48568
|
+
const assertionObj = assertion;
|
|
48569
|
+
const result = mapFrameworkStatusToResult(assertionObj.status);
|
|
48570
|
+
const testName = typeof assertionObj.fullName === "string" ? assertionObj.fullName : typeof assertionObj.title === "string" ? assertionObj.title : undefined;
|
|
48571
|
+
if (!result || !testName || testName.length === 0)
|
|
48572
|
+
continue;
|
|
48573
|
+
const failureMessages = Array.isArray(assertionObj.failureMessages) ? assertionObj.failureMessages : [];
|
|
48574
|
+
const firstFailure = failureMessages.find((entry) => typeof entry === "string" && entry.length > 0);
|
|
48575
|
+
const durationMs = typeof assertionObj.duration === "number" && Number.isFinite(assertionObj.duration) ? Math.max(assertionObj.duration, 0) : 0;
|
|
48576
|
+
parsed.push({
|
|
48577
|
+
testFile,
|
|
48578
|
+
testName,
|
|
48579
|
+
result,
|
|
48580
|
+
durationMs,
|
|
48581
|
+
errorMessage: firstLine(firstFailure),
|
|
48582
|
+
stackPrefix: firstLine(firstFailure)
|
|
48583
|
+
});
|
|
48584
|
+
}
|
|
48585
|
+
}
|
|
48586
|
+
return parsed;
|
|
48587
|
+
}
|
|
48588
|
+
function parseBunJsonLines(output) {
|
|
48589
|
+
const parsed = [];
|
|
48590
|
+
for (const line of output.split(`
|
|
48591
|
+
`)) {
|
|
48592
|
+
const trimmed = line.trim();
|
|
48593
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}"))
|
|
48594
|
+
continue;
|
|
48595
|
+
try {
|
|
48596
|
+
const obj = JSON.parse(trimmed);
|
|
48597
|
+
const rawFile = typeof obj.file === "string" ? obj.file : typeof obj.testFile === "string" ? obj.testFile : typeof obj.path === "string" ? obj.path : undefined;
|
|
48598
|
+
const rawName = typeof obj.testName === "string" ? obj.testName : typeof obj.fullName === "string" ? obj.fullName : typeof obj.name === "string" ? obj.name : undefined;
|
|
48599
|
+
const result = mapFrameworkStatusToResult(typeof obj.status === "string" ? obj.status : obj.result);
|
|
48600
|
+
if (!rawFile || !rawName || !result)
|
|
48601
|
+
continue;
|
|
48602
|
+
const errorObj = typeof obj.error === "object" && obj.error !== null ? obj.error : undefined;
|
|
48603
|
+
const durationMs = typeof obj.durationMs === "number" && Number.isFinite(obj.durationMs) ? Math.max(obj.durationMs, 0) : typeof obj.duration === "number" && Number.isFinite(obj.duration) ? Math.max(obj.duration, 0) : 0;
|
|
48604
|
+
parsed.push({
|
|
48605
|
+
testFile: rawFile.replace(/\\/g, "/"),
|
|
48606
|
+
testName: rawName,
|
|
48607
|
+
result,
|
|
48608
|
+
durationMs,
|
|
48609
|
+
errorMessage: firstLine(errorObj?.message ?? obj.errorMessage),
|
|
48610
|
+
stackPrefix: firstLine(errorObj?.stack)
|
|
48611
|
+
});
|
|
48612
|
+
} catch {}
|
|
48613
|
+
}
|
|
48614
|
+
return parsed;
|
|
48615
|
+
}
|
|
48616
|
+
function parseFrameworkJsonTestResults(framework, output) {
|
|
48617
|
+
const jsonMatch = output.match(/\{[\s\S]*"testResults"[\s\S]*\}/);
|
|
48618
|
+
if (jsonMatch) {
|
|
48619
|
+
try {
|
|
48620
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
48621
|
+
const testResults = parseJestLikeJsonTestResults(parsed);
|
|
48622
|
+
if (testResults.length > 0)
|
|
48623
|
+
return testResults;
|
|
48624
|
+
} catch {}
|
|
48625
|
+
}
|
|
48626
|
+
for (const line of output.split(`
|
|
48627
|
+
`)) {
|
|
48628
|
+
const trimmed = line.trim();
|
|
48629
|
+
if (!trimmed.startsWith("{") || !trimmed.endsWith("}"))
|
|
48630
|
+
continue;
|
|
48631
|
+
try {
|
|
48632
|
+
const parsed = JSON.parse(trimmed);
|
|
48633
|
+
const testResults = parseJestLikeJsonTestResults(parsed);
|
|
48634
|
+
if (testResults.length > 0)
|
|
48635
|
+
return testResults;
|
|
48636
|
+
} catch {}
|
|
48637
|
+
}
|
|
48638
|
+
if (framework === "bun") {
|
|
48639
|
+
return parseBunJsonLines(output);
|
|
48640
|
+
}
|
|
48641
|
+
return [];
|
|
48642
|
+
}
|
|
48453
48643
|
function parseTestOutput2(framework, output) {
|
|
48454
48644
|
const totals = {
|
|
48455
48645
|
passed: 0,
|
|
@@ -48737,7 +48927,16 @@ async function runTests(framework, scope, files, coverage, timeout_ms, cwd) {
|
|
|
48737
48927
|
};
|
|
48738
48928
|
}
|
|
48739
48929
|
const startTime = Date.now();
|
|
48930
|
+
const vitestJsonOutputPath = framework === "vitest" ? path39.join(cwd, ".swarm", "cache", "test-runner-vitest.json") : undefined;
|
|
48740
48931
|
try {
|
|
48932
|
+
if (vitestJsonOutputPath) {
|
|
48933
|
+
try {
|
|
48934
|
+
fs22.mkdirSync(path39.dirname(vitestJsonOutputPath), { recursive: true });
|
|
48935
|
+
if (fs22.existsSync(vitestJsonOutputPath)) {
|
|
48936
|
+
fs22.unlinkSync(vitestJsonOutputPath);
|
|
48937
|
+
}
|
|
48938
|
+
} catch {}
|
|
48939
|
+
}
|
|
48741
48940
|
const proc = bunSpawn(command, {
|
|
48742
48941
|
stdout: "pipe",
|
|
48743
48942
|
stderr: "pipe",
|
|
@@ -48758,13 +48957,37 @@ async function runTests(framework, scope, files, coverage, timeout_ms, cwd) {
|
|
|
48758
48957
|
output += (output ? `
|
|
48759
48958
|
` : "") + stderrResult.text;
|
|
48760
48959
|
}
|
|
48960
|
+
if (vitestJsonOutputPath) {
|
|
48961
|
+
try {
|
|
48962
|
+
if (fs22.existsSync(vitestJsonOutputPath)) {
|
|
48963
|
+
const vitestJsonOutput = fs22.readFileSync(vitestJsonOutputPath, "utf-8");
|
|
48964
|
+
if (vitestJsonOutput.trim().length > 0) {
|
|
48965
|
+
output += (output ? `
|
|
48966
|
+
` : "") + vitestJsonOutput;
|
|
48967
|
+
}
|
|
48968
|
+
}
|
|
48969
|
+
} catch {}
|
|
48970
|
+
}
|
|
48761
48971
|
if (stdoutResult.truncated || stderrResult.truncated) {
|
|
48762
48972
|
output += `
|
|
48763
48973
|
... (output truncated at stream read limit)`;
|
|
48764
48974
|
}
|
|
48765
48975
|
const useDispatchParse = process.env.SWARM_LANG_BACKEND !== "legacy";
|
|
48766
48976
|
const parsed = useDispatchParse ? await parseTestOutputViaDispatch(framework, output, cwd) ?? parseTestOutput2(framework, output) : parseTestOutput2(framework, output);
|
|
48767
|
-
const
|
|
48977
|
+
const parsedTestCases = parseFrameworkJsonTestResults(framework, output);
|
|
48978
|
+
const totals = { ...parsed.totals };
|
|
48979
|
+
const { coveragePercent } = parsed;
|
|
48980
|
+
if (totals.total === 0 && parsedTestCases.length > 0) {
|
|
48981
|
+
for (const entry of parsedTestCases) {
|
|
48982
|
+
if (entry.result === "pass")
|
|
48983
|
+
totals.passed++;
|
|
48984
|
+
else if (entry.result === "fail")
|
|
48985
|
+
totals.failed++;
|
|
48986
|
+
else
|
|
48987
|
+
totals.skipped++;
|
|
48988
|
+
}
|
|
48989
|
+
totals.total = parsedTestCases.length;
|
|
48990
|
+
}
|
|
48768
48991
|
const isTimeout = exitCode === -1;
|
|
48769
48992
|
const testPassed = exitCode === 0 && totals.failed === 0;
|
|
48770
48993
|
if (testPassed) {
|
|
@@ -48777,7 +49000,8 @@ async function runTests(framework, scope, files, coverage, timeout_ms, cwd) {
|
|
|
48777
49000
|
duration_ms,
|
|
48778
49001
|
totals,
|
|
48779
49002
|
rawOutput: output,
|
|
48780
|
-
outcome: "pass"
|
|
49003
|
+
outcome: "pass",
|
|
49004
|
+
testCases: parsedTestCases
|
|
48781
49005
|
};
|
|
48782
49006
|
if (coveragePercent !== undefined) {
|
|
48783
49007
|
result.coveragePercent = coveragePercent;
|
|
@@ -48799,7 +49023,8 @@ async function runTests(framework, scope, files, coverage, timeout_ms, cwd) {
|
|
|
48799
49023
|
rawOutput: output,
|
|
48800
49024
|
error: isTimeout ? `Tests timed out after ${timeout_ms}ms` : `Tests failed with ${totals.failed} failures`,
|
|
48801
49025
|
message: isTimeout ? `${framework} tests timed out after ${timeout_ms}ms` : `${framework} tests failed (${totals.failed}/${totals.total} failed)`,
|
|
48802
|
-
outcome: isTimeout ? "error" : "regression"
|
|
49026
|
+
outcome: isTimeout ? "error" : "regression",
|
|
49027
|
+
testCases: parsedTestCases
|
|
48803
49028
|
};
|
|
48804
49029
|
if (coveragePercent !== undefined) {
|
|
48805
49030
|
result.coveragePercent = coveragePercent;
|
|
@@ -48820,25 +49045,78 @@ async function runTests(framework, scope, files, coverage, timeout_ms, cwd) {
|
|
|
48820
49045
|
};
|
|
48821
49046
|
}
|
|
48822
49047
|
}
|
|
48823
|
-
function
|
|
49048
|
+
function normalizeHistoryTestFile(testFile, workingDir) {
|
|
49049
|
+
const normalized = testFile.replace(/\\/g, "/");
|
|
49050
|
+
if (!path39.isAbsolute(testFile))
|
|
49051
|
+
return normalized;
|
|
49052
|
+
const relative8 = path39.relative(workingDir, testFile);
|
|
49053
|
+
if (relative8.startsWith("..") || path39.isAbsolute(relative8)) {
|
|
49054
|
+
return normalized;
|
|
49055
|
+
}
|
|
49056
|
+
return relative8.replace(/\\/g, "/");
|
|
49057
|
+
}
|
|
49058
|
+
function combineAggregateResult(current, next) {
|
|
49059
|
+
if (current === "fail" || next === "fail")
|
|
49060
|
+
return "fail";
|
|
49061
|
+
if (current === "pass" || next === "pass")
|
|
49062
|
+
return "pass";
|
|
49063
|
+
return "skip";
|
|
49064
|
+
}
|
|
49065
|
+
function recordAndAnalyzeResults(result, testFiles, workingDir, sourceFiles, parsedTestCases) {
|
|
48824
49066
|
if (!result.totals || result.totals.total === 0)
|
|
48825
49067
|
return;
|
|
48826
49068
|
const now = new Date().toISOString();
|
|
48827
49069
|
const changedFiles = (sourceFiles && sourceFiles.length > 0 ? sourceFiles : testFiles).map((f) => f.replace(/\\/g, "/"));
|
|
48828
|
-
|
|
49070
|
+
const aggregateResultsByFile = new Map;
|
|
49071
|
+
const validParsedCases = parsedTestCases?.filter((parsedCase) => parsedCase.testFile.length > 0 && parsedCase.testName.length > 0) ?? [];
|
|
49072
|
+
for (const parsedCase of validParsedCases) {
|
|
49073
|
+
const normalizedTestFile = normalizeHistoryTestFile(parsedCase.testFile, workingDir);
|
|
49074
|
+
try {
|
|
49075
|
+
appendTestRun({
|
|
49076
|
+
timestamp: now,
|
|
49077
|
+
taskId: "auto",
|
|
49078
|
+
testFile: normalizedTestFile,
|
|
49079
|
+
testName: parsedCase.testName,
|
|
49080
|
+
result: parsedCase.result,
|
|
49081
|
+
durationMs: parsedCase.durationMs,
|
|
49082
|
+
errorMessage: parsedCase.errorMessage,
|
|
49083
|
+
stackPrefix: parsedCase.stackPrefix,
|
|
49084
|
+
changedFiles
|
|
49085
|
+
}, workingDir);
|
|
49086
|
+
} catch {}
|
|
49087
|
+
aggregateResultsByFile.set(normalizedTestFile, combineAggregateResult(aggregateResultsByFile.get(normalizedTestFile), parsedCase.result));
|
|
49088
|
+
}
|
|
49089
|
+
if (aggregateResultsByFile.size === 0) {
|
|
49090
|
+
const aggregateResult = result.success ? "pass" : "fail";
|
|
49091
|
+
for (const testFile of testFiles) {
|
|
49092
|
+
aggregateResultsByFile.set(testFile.replace(/\\/g, "/"), aggregateResult);
|
|
49093
|
+
}
|
|
49094
|
+
}
|
|
49095
|
+
for (const [testFile, aggregateResult] of aggregateResultsByFile) {
|
|
48829
49096
|
try {
|
|
48830
49097
|
appendTestRun({
|
|
48831
49098
|
timestamp: now,
|
|
48832
49099
|
taskId: "auto",
|
|
48833
|
-
testFile
|
|
48834
|
-
testName:
|
|
48835
|
-
result:
|
|
49100
|
+
testFile,
|
|
49101
|
+
testName: AGGREGATE_TEST_NAME,
|
|
49102
|
+
result: aggregateResult,
|
|
48836
49103
|
durationMs: result.duration_ms || 0,
|
|
48837
49104
|
changedFiles
|
|
48838
49105
|
}, workingDir);
|
|
48839
49106
|
} catch {}
|
|
48840
49107
|
}
|
|
48841
49108
|
}
|
|
49109
|
+
function selectHistoryForAnalysis(history) {
|
|
49110
|
+
const filesWithIndividualRecords = new Set;
|
|
49111
|
+
for (const record3 of history) {
|
|
49112
|
+
if (record3.testName !== AGGREGATE_TEST_NAME) {
|
|
49113
|
+
filesWithIndividualRecords.add(record3.testFile.toLowerCase());
|
|
49114
|
+
}
|
|
49115
|
+
}
|
|
49116
|
+
if (filesWithIndividualRecords.size === 0)
|
|
49117
|
+
return history;
|
|
49118
|
+
return history.filter((record3) => record3.testName !== AGGREGATE_TEST_NAME || !filesWithIndividualRecords.has(record3.testFile.toLowerCase()));
|
|
49119
|
+
}
|
|
48842
49120
|
function analyzeFailures(workingDir) {
|
|
48843
49121
|
const report = {
|
|
48844
49122
|
flakyTests: [],
|
|
@@ -48846,7 +49124,7 @@ function analyzeFailures(workingDir) {
|
|
|
48846
49124
|
quarantinedFailures: []
|
|
48847
49125
|
};
|
|
48848
49126
|
try {
|
|
48849
|
-
const history = getAllHistory(workingDir);
|
|
49127
|
+
const history = selectHistoryForAnalysis(getAllHistory(workingDir));
|
|
48850
49128
|
if (history.length === 0)
|
|
48851
49129
|
return report;
|
|
48852
49130
|
report.flakyTests = detectFlakyTests(history);
|
|
@@ -48867,7 +49145,7 @@ function analyzeFailures(workingDir) {
|
|
|
48867
49145
|
} catch {}
|
|
48868
49146
|
return report;
|
|
48869
49147
|
}
|
|
48870
|
-
var MAX_OUTPUT_BYTES3 = 512000, MAX_COMMAND_LENGTH2 = 500, DEFAULT_TIMEOUT_MS = 60000, MAX_TIMEOUT_MS = 300000, MAX_SAFE_TEST_FILES = 50, MAX_SAFE_SOURCE_FILES = 1, POWERSHELL_METACHARACTERS, DISPATCH_FRAMEWORK_MAP, COMPOUND_TEST_EXTENSIONS, TEST_DIRECTORY_NAMES, SOURCE_EXTENSIONS, SKIP_DIRECTORIES, test_runner;
|
|
49148
|
+
var MAX_OUTPUT_BYTES3 = 512000, MAX_COMMAND_LENGTH2 = 500, DEFAULT_TIMEOUT_MS = 60000, MAX_TIMEOUT_MS = 300000, MAX_SAFE_TEST_FILES = 50, MAX_SAFE_SOURCE_FILES = 1, AGGREGATE_TEST_NAME = "(aggregate)", VITEST_JSON_OUTPUT_RELATIVE_PATH = ".swarm/cache/test-runner-vitest.json", POWERSHELL_METACHARACTERS, DISPATCH_FRAMEWORK_MAP, COMPOUND_TEST_EXTENSIONS, TEST_DIRECTORY_NAMES, SOURCE_EXTENSIONS, SKIP_DIRECTORIES, test_runner;
|
|
48871
49149
|
var init_test_runner = __esm(() => {
|
|
48872
49150
|
init_zod();
|
|
48873
49151
|
init_discovery();
|
|
@@ -49313,7 +49591,7 @@ var init_test_runner = __esm(() => {
|
|
|
49313
49591
|
return JSON.stringify(errorResult, null, 2);
|
|
49314
49592
|
}
|
|
49315
49593
|
const result = await runTests(framework, effectiveScope, testFiles, coverage, timeout_ms, workingDir);
|
|
49316
|
-
recordAndAnalyzeResults(result, testFiles, workingDir, _files.length > 0 ? _files : undefined);
|
|
49594
|
+
recordAndAnalyzeResults(result, testFiles, workingDir, _files.length > 0 ? _files : undefined, result.testCases);
|
|
49317
49595
|
let historyReport;
|
|
49318
49596
|
if (!result.success && result.totals && result.totals.failed > 0) {
|
|
49319
49597
|
historyReport = analyzeFailures(workingDir);
|
|
@@ -8,6 +8,18 @@ export interface HivePromotionSummary {
|
|
|
8
8
|
advancements: number;
|
|
9
9
|
total_hive_entries: number;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Check whether a swarm knowledge entry is eligible for hive promotion.
|
|
13
|
+
* Three routes to eligibility:
|
|
14
|
+
* Route 1: hive_eligible flag + 3+ distinct phases
|
|
15
|
+
* Route 2: 'hive-fast-track' tag
|
|
16
|
+
* Route 3: age exceeds auto_promote_days threshold
|
|
17
|
+
*
|
|
18
|
+
* @param entry - The swarm knowledge entry to check
|
|
19
|
+
* @param autoPromoteDays - Number of days before age-based promotion kicks in
|
|
20
|
+
* @returns true if the entry is eligible for hive promotion
|
|
21
|
+
*/
|
|
22
|
+
export declare function isHiveEligible(entry: SwarmKnowledgeEntry, autoPromoteDays: number): boolean;
|
|
11
23
|
/**
|
|
12
24
|
* Main promotion logic: checks swarm entries and promotes eligible ones to hive.
|
|
13
25
|
* Also updates existing hive entries with new project confirmations.
|