opencode-swarm 6.47.0 → 6.47.2
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 +111 -47
- package/dist/commands/sync-plan.d.ts +3 -1
- package/dist/hooks/curator-types.d.ts +3 -0
- package/dist/hooks/curator.d.ts +1 -1
- package/dist/index.js +171 -72
- package/dist/plan/ledger.d.ts +2 -2
- package/dist/plan/manager.d.ts +12 -0
- package/dist/plan/manager.loadplan-validation-guard.test.d.ts +13 -0
- package/dist/plan/migration-revert.regression.test.d.ts +8 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -16208,20 +16208,22 @@ async function readLedgerEvents(directory) {
|
|
|
16208
16208
|
return [];
|
|
16209
16209
|
}
|
|
16210
16210
|
}
|
|
16211
|
-
async function initLedger(directory, planId) {
|
|
16211
|
+
async function initLedger(directory, planId, initialPlanHash) {
|
|
16212
16212
|
const ledgerPath = getLedgerPath(directory);
|
|
16213
16213
|
const planJsonPath = getPlanJsonPath(directory);
|
|
16214
16214
|
if (fs4.existsSync(ledgerPath)) {
|
|
16215
16215
|
throw new Error("Ledger already initialized. Use appendLedgerEvent to add events.");
|
|
16216
16216
|
}
|
|
16217
|
-
let planHashAfter = "";
|
|
16218
|
-
|
|
16219
|
-
|
|
16220
|
-
|
|
16221
|
-
|
|
16222
|
-
|
|
16223
|
-
|
|
16224
|
-
|
|
16217
|
+
let planHashAfter = initialPlanHash ?? "";
|
|
16218
|
+
if (!initialPlanHash) {
|
|
16219
|
+
try {
|
|
16220
|
+
if (fs4.existsSync(planJsonPath)) {
|
|
16221
|
+
const content = fs4.readFileSync(planJsonPath, "utf8");
|
|
16222
|
+
const plan = JSON.parse(content);
|
|
16223
|
+
planHashAfter = computePlanHash(plan);
|
|
16224
|
+
}
|
|
16225
|
+
} catch {}
|
|
16226
|
+
}
|
|
16225
16227
|
const event = {
|
|
16226
16228
|
seq: 1,
|
|
16227
16229
|
timestamp: new Date().toISOString(),
|
|
@@ -16233,7 +16235,7 @@ async function initLedger(directory, planId) {
|
|
|
16233
16235
|
schema_version: LEDGER_SCHEMA_VERSION
|
|
16234
16236
|
};
|
|
16235
16237
|
fs4.mkdirSync(path7.join(directory, ".swarm"), { recursive: true });
|
|
16236
|
-
const tempPath = `${ledgerPath}.tmp`;
|
|
16238
|
+
const tempPath = `${ledgerPath}.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`;
|
|
16237
16239
|
const line = `${JSON.stringify(event)}
|
|
16238
16240
|
`;
|
|
16239
16241
|
fs4.writeFileSync(tempPath, line, "utf8");
|
|
@@ -16260,7 +16262,7 @@ async function appendLedgerEvent(directory, eventInput, options) {
|
|
|
16260
16262
|
schema_version: LEDGER_SCHEMA_VERSION
|
|
16261
16263
|
};
|
|
16262
16264
|
fs4.mkdirSync(path7.join(directory, ".swarm"), { recursive: true });
|
|
16263
|
-
const tempPath = `${ledgerPath}.tmp`;
|
|
16265
|
+
const tempPath = `${ledgerPath}.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`;
|
|
16264
16266
|
const line = `${JSON.stringify(event)}
|
|
16265
16267
|
`;
|
|
16266
16268
|
if (fs4.existsSync(ledgerPath)) {
|
|
@@ -16278,10 +16280,11 @@ async function takeSnapshotEvent(directory, plan, options) {
|
|
|
16278
16280
|
plan,
|
|
16279
16281
|
payload_hash: payloadHash
|
|
16280
16282
|
};
|
|
16283
|
+
const planId = `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
16281
16284
|
return appendLedgerEvent(directory, {
|
|
16282
16285
|
event_type: "snapshot",
|
|
16283
16286
|
source: "takeSnapshotEvent",
|
|
16284
|
-
plan_id:
|
|
16287
|
+
plan_id: planId,
|
|
16285
16288
|
payload: snapshotPayload
|
|
16286
16289
|
}, options);
|
|
16287
16290
|
}
|
|
@@ -16290,13 +16293,15 @@ async function replayFromLedger(directory, options) {
|
|
|
16290
16293
|
if (events.length === 0) {
|
|
16291
16294
|
return null;
|
|
16292
16295
|
}
|
|
16296
|
+
const targetPlanId = events[0].plan_id;
|
|
16297
|
+
const relevantEvents = events.filter((e) => e.plan_id === targetPlanId);
|
|
16293
16298
|
{
|
|
16294
|
-
const snapshotEvents =
|
|
16299
|
+
const snapshotEvents = relevantEvents.filter((e) => e.event_type === "snapshot");
|
|
16295
16300
|
if (snapshotEvents.length > 0) {
|
|
16296
16301
|
const latestSnapshotEvent = snapshotEvents[snapshotEvents.length - 1];
|
|
16297
16302
|
const snapshotPayload = latestSnapshotEvent.payload;
|
|
16298
16303
|
let plan2 = snapshotPayload.plan;
|
|
16299
|
-
const eventsAfterSnapshot =
|
|
16304
|
+
const eventsAfterSnapshot = relevantEvents.filter((e) => e.seq > latestSnapshotEvent.seq);
|
|
16300
16305
|
for (const event of eventsAfterSnapshot) {
|
|
16301
16306
|
plan2 = applyEventToPlan(plan2, event);
|
|
16302
16307
|
if (plan2 === null) {
|
|
@@ -16317,7 +16322,7 @@ async function replayFromLedger(directory, options) {
|
|
|
16317
16322
|
} catch {
|
|
16318
16323
|
return null;
|
|
16319
16324
|
}
|
|
16320
|
-
for (const event of
|
|
16325
|
+
for (const event of relevantEvents) {
|
|
16321
16326
|
if (plan === null) {
|
|
16322
16327
|
return null;
|
|
16323
16328
|
}
|
|
@@ -16331,10 +16336,14 @@ function applyEventToPlan(plan, event) {
|
|
|
16331
16336
|
return plan;
|
|
16332
16337
|
case "task_status_changed":
|
|
16333
16338
|
if (event.task_id && event.to_status) {
|
|
16339
|
+
const parseResult = TaskStatusSchema.safeParse(event.to_status);
|
|
16340
|
+
if (!parseResult.success) {
|
|
16341
|
+
return plan;
|
|
16342
|
+
}
|
|
16334
16343
|
for (const phase of plan.phases) {
|
|
16335
16344
|
const task = phase.tasks.find((t) => t.id === event.task_id);
|
|
16336
16345
|
if (task) {
|
|
16337
|
-
task.status =
|
|
16346
|
+
task.status = parseResult.data;
|
|
16338
16347
|
break;
|
|
16339
16348
|
}
|
|
16340
16349
|
}
|
|
@@ -16368,6 +16377,7 @@ function applyEventToPlan(plan, event) {
|
|
|
16368
16377
|
}
|
|
16369
16378
|
var LEDGER_SCHEMA_VERSION = "1.0.0", LEDGER_FILENAME = "plan-ledger.jsonl", PLAN_JSON_FILENAME = "plan.json", LedgerStaleWriterError;
|
|
16370
16379
|
var init_ledger = __esm(() => {
|
|
16380
|
+
init_plan_schema();
|
|
16371
16381
|
LedgerStaleWriterError = class LedgerStaleWriterError extends Error {
|
|
16372
16382
|
constructor(message) {
|
|
16373
16383
|
super(message);
|
|
@@ -16377,7 +16387,7 @@ var init_ledger = __esm(() => {
|
|
|
16377
16387
|
});
|
|
16378
16388
|
|
|
16379
16389
|
// src/plan/manager.ts
|
|
16380
|
-
import { renameSync as renameSync3, unlinkSync } from "fs";
|
|
16390
|
+
import { existsSync as existsSync5, renameSync as renameSync3, unlinkSync } from "fs";
|
|
16381
16391
|
import * as path8 from "path";
|
|
16382
16392
|
async function loadPlanJsonOnly(directory) {
|
|
16383
16393
|
const planJsonContent = await readSwarmFileAsync(directory, "plan.json");
|
|
@@ -16508,28 +16518,49 @@ async function loadPlan(directory) {
|
|
|
16508
16518
|
const planHash = computePlanHash(validated);
|
|
16509
16519
|
const ledgerHash = await getLatestLedgerHash(directory);
|
|
16510
16520
|
if (ledgerHash !== "" && planHash !== ledgerHash) {
|
|
16511
|
-
|
|
16512
|
-
|
|
16513
|
-
|
|
16514
|
-
|
|
16515
|
-
|
|
16516
|
-
|
|
16517
|
-
|
|
16521
|
+
const currentPlanId = `${validated.swarm}-${validated.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
16522
|
+
const ledgerEvents = await readLedgerEvents(directory);
|
|
16523
|
+
const firstEvent = ledgerEvents.length > 0 ? ledgerEvents[0] : null;
|
|
16524
|
+
if (firstEvent && firstEvent.plan_id !== currentPlanId) {
|
|
16525
|
+
warn(`[loadPlan] Ledger identity mismatch (ledger: ${firstEvent.plan_id}, plan: ${currentPlanId}) \u2014 skipping ledger rebuild (migration detected). Use /swarm reset-session to reinitialize the ledger.`);
|
|
16526
|
+
} else {
|
|
16527
|
+
warn("[loadPlan] plan.json is stale (hash mismatch with ledger) \u2014 rebuilding from ledger. If this recurs, run /swarm reset-session to clear stale session state.");
|
|
16528
|
+
try {
|
|
16529
|
+
const rebuilt = await replayFromLedger(directory);
|
|
16530
|
+
if (rebuilt) {
|
|
16531
|
+
await rebuildPlan(directory, rebuilt);
|
|
16532
|
+
warn("[loadPlan] Rebuilt plan from ledger. Checkpoint available at SWARM_PLAN.md if it exists.");
|
|
16533
|
+
return rebuilt;
|
|
16534
|
+
}
|
|
16535
|
+
} catch (replayError) {
|
|
16536
|
+
warn(`[loadPlan] Ledger replay failed during hash-mismatch rebuild: ${replayError instanceof Error ? replayError.message : String(replayError)}. Returning stale plan.json. To recover: check SWARM_PLAN.md for a checkpoint, or run /swarm reset-session.`);
|
|
16518
16537
|
}
|
|
16519
|
-
} catch (replayError) {
|
|
16520
|
-
warn(`[loadPlan] Ledger replay failed during hash-mismatch rebuild: ${replayError instanceof Error ? replayError.message : String(replayError)}. Returning stale plan.json. To recover: check SWARM_PLAN.md for a checkpoint, or run /swarm reset-session.`);
|
|
16521
16538
|
}
|
|
16522
16539
|
}
|
|
16523
16540
|
}
|
|
16524
16541
|
return validated;
|
|
16525
16542
|
} catch (error93) {
|
|
16526
16543
|
warn(`[loadPlan] plan.json validation failed: ${error93 instanceof Error ? error93.message : String(error93)}. Attempting rebuild from ledger. If rebuild fails, check SWARM_PLAN.md for a checkpoint.`);
|
|
16544
|
+
let rawPlanId = null;
|
|
16545
|
+
try {
|
|
16546
|
+
const rawParsed = JSON.parse(planJsonContent);
|
|
16547
|
+
if (typeof rawParsed?.swarm === "string" && typeof rawParsed?.title === "string") {
|
|
16548
|
+
rawPlanId = `${rawParsed.swarm}-${rawParsed.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
16549
|
+
}
|
|
16550
|
+
} catch {}
|
|
16527
16551
|
if (await ledgerExists(directory)) {
|
|
16528
|
-
const
|
|
16529
|
-
|
|
16530
|
-
|
|
16531
|
-
|
|
16532
|
-
|
|
16552
|
+
const ledgerEventsForCatch = await readLedgerEvents(directory);
|
|
16553
|
+
const catchFirstEvent = ledgerEventsForCatch.length > 0 ? ledgerEventsForCatch[0] : null;
|
|
16554
|
+
const identityMatch = rawPlanId === null || catchFirstEvent === null || catchFirstEvent.plan_id === rawPlanId;
|
|
16555
|
+
if (!identityMatch) {
|
|
16556
|
+
warn(`[loadPlan] Ledger identity mismatch in validation-failure path (ledger: ${catchFirstEvent?.plan_id}, plan: ${rawPlanId}) \u2014 skipping ledger rebuild (migration detected).`);
|
|
16557
|
+
} else if (catchFirstEvent !== null && rawPlanId !== null) {
|
|
16558
|
+
const rebuilt = await replayFromLedger(directory);
|
|
16559
|
+
if (rebuilt) {
|
|
16560
|
+
await rebuildPlan(directory, rebuilt);
|
|
16561
|
+
warn("[loadPlan] Rebuilt plan from ledger after validation failure. Projection was stale.");
|
|
16562
|
+
return rebuilt;
|
|
16563
|
+
}
|
|
16533
16564
|
}
|
|
16534
16565
|
}
|
|
16535
16566
|
const planMdContent2 = await readSwarmFileAsync(directory, "plan.md");
|
|
@@ -16597,9 +16628,28 @@ async function savePlan(directory, plan, options) {
|
|
|
16597
16628
|
}
|
|
16598
16629
|
}
|
|
16599
16630
|
const currentPlan = await loadPlanJsonOnly(directory);
|
|
16631
|
+
const planId = `${validated.swarm}-${validated.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
16632
|
+
const planHashForInit = computePlanHash(validated);
|
|
16600
16633
|
if (!await ledgerExists(directory)) {
|
|
16601
|
-
|
|
16602
|
-
|
|
16634
|
+
await initLedger(directory, planId, planHashForInit);
|
|
16635
|
+
} else {
|
|
16636
|
+
const existingEvents = await readLedgerEvents(directory);
|
|
16637
|
+
if (existingEvents.length > 0 && existingEvents[0].plan_id !== planId) {
|
|
16638
|
+
const swarmDir2 = path8.resolve(directory, ".swarm");
|
|
16639
|
+
const oldLedgerPath = path8.join(swarmDir2, "plan-ledger.jsonl");
|
|
16640
|
+
const archivePath = path8.join(swarmDir2, `plan-ledger.archived-${Date.now()}-${Math.floor(Math.random() * 1e9)}.jsonl`);
|
|
16641
|
+
if (existsSync5(oldLedgerPath)) {
|
|
16642
|
+
renameSync3(oldLedgerPath, archivePath);
|
|
16643
|
+
warn(`[savePlan] Ledger identity mismatch (was "${existingEvents[0].plan_id}", now "${planId}") \u2014 archived old ledger to ${archivePath} and reinitializing.`);
|
|
16644
|
+
}
|
|
16645
|
+
try {
|
|
16646
|
+
await initLedger(directory, planId, planHashForInit);
|
|
16647
|
+
} catch (initErr) {
|
|
16648
|
+
if (!(initErr instanceof Error && initErr.message.includes("already initialized"))) {
|
|
16649
|
+
throw initErr;
|
|
16650
|
+
}
|
|
16651
|
+
}
|
|
16652
|
+
}
|
|
16603
16653
|
}
|
|
16604
16654
|
const currentHash = computeCurrentPlanHash(directory);
|
|
16605
16655
|
const hashAfter = computePlanHash(validated);
|
|
@@ -16692,10 +16742,24 @@ async function rebuildPlan(directory, plan) {
|
|
|
16692
16742
|
const tempPlanPath = path8.join(swarmDir, `plan.json.rebuild.${Date.now()}`);
|
|
16693
16743
|
await Bun.write(tempPlanPath, JSON.stringify(targetPlan, null, 2));
|
|
16694
16744
|
renameSync3(tempPlanPath, planPath);
|
|
16745
|
+
const contentHash = computePlanContentHash(targetPlan);
|
|
16695
16746
|
const markdown = derivePlanMarkdown(targetPlan);
|
|
16747
|
+
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
16748
|
+
${markdown}`;
|
|
16696
16749
|
const tempMdPath = path8.join(swarmDir, `plan.md.rebuild.${Date.now()}`);
|
|
16697
|
-
await Bun.write(tempMdPath,
|
|
16750
|
+
await Bun.write(tempMdPath, markdownWithHash);
|
|
16698
16751
|
renameSync3(tempMdPath, mdPath);
|
|
16752
|
+
try {
|
|
16753
|
+
const markerPath = path8.join(swarmDir, ".plan-write-marker");
|
|
16754
|
+
const tasksCount = targetPlan.phases.reduce((sum, phase) => sum + phase.tasks.length, 0);
|
|
16755
|
+
const marker = JSON.stringify({
|
|
16756
|
+
source: "plan_manager",
|
|
16757
|
+
timestamp: new Date().toISOString(),
|
|
16758
|
+
phases_count: targetPlan.phases.length,
|
|
16759
|
+
tasks_count: tasksCount
|
|
16760
|
+
});
|
|
16761
|
+
await Bun.write(markerPath, marker);
|
|
16762
|
+
} catch {}
|
|
16699
16763
|
return targetPlan;
|
|
16700
16764
|
}
|
|
16701
16765
|
function derivePlanMarkdown(plan) {
|
|
@@ -33996,7 +34060,7 @@ async function handleDarkMatterCommand(directory, args) {
|
|
|
33996
34060
|
|
|
33997
34061
|
// src/services/diagnose-service.ts
|
|
33998
34062
|
import * as child_process3 from "child_process";
|
|
33999
|
-
import { existsSync as
|
|
34063
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
|
|
34000
34064
|
import path15 from "path";
|
|
34001
34065
|
import { fileURLToPath } from "url";
|
|
34002
34066
|
init_manager();
|
|
@@ -34233,7 +34297,7 @@ async function checkConfigBackups(directory) {
|
|
|
34233
34297
|
}
|
|
34234
34298
|
async function checkGitRepository(directory) {
|
|
34235
34299
|
try {
|
|
34236
|
-
if (!
|
|
34300
|
+
if (!existsSync6(directory) || !statSync3(directory).isDirectory()) {
|
|
34237
34301
|
return {
|
|
34238
34302
|
name: "Git Repository",
|
|
34239
34303
|
status: "\u274C",
|
|
@@ -34298,7 +34362,7 @@ async function checkSpecStaleness(directory, plan) {
|
|
|
34298
34362
|
}
|
|
34299
34363
|
async function checkConfigParseability(directory) {
|
|
34300
34364
|
const configPath = path15.join(directory, ".opencode/opencode-swarm.json");
|
|
34301
|
-
if (!
|
|
34365
|
+
if (!existsSync6(configPath)) {
|
|
34302
34366
|
return {
|
|
34303
34367
|
name: "Config Parseability",
|
|
34304
34368
|
status: "\u2705",
|
|
@@ -34348,11 +34412,11 @@ async function checkGrammarWasmFiles() {
|
|
|
34348
34412
|
const isSource = thisDir.replace(/\\/g, "/").endsWith("/src/services");
|
|
34349
34413
|
const grammarDir = isSource ? path15.join(thisDir, "..", "lang", "grammars") : path15.join(thisDir, "lang", "grammars");
|
|
34350
34414
|
const missing = [];
|
|
34351
|
-
if (!
|
|
34415
|
+
if (!existsSync6(path15.join(grammarDir, "tree-sitter.wasm"))) {
|
|
34352
34416
|
missing.push("tree-sitter.wasm (core runtime)");
|
|
34353
34417
|
}
|
|
34354
34418
|
for (const file3 of grammarFiles) {
|
|
34355
|
-
if (!
|
|
34419
|
+
if (!existsSync6(path15.join(grammarDir, file3))) {
|
|
34356
34420
|
missing.push(file3);
|
|
34357
34421
|
}
|
|
34358
34422
|
}
|
|
@@ -34371,7 +34435,7 @@ async function checkGrammarWasmFiles() {
|
|
|
34371
34435
|
}
|
|
34372
34436
|
async function checkCheckpointManifest(directory) {
|
|
34373
34437
|
const manifestPath = path15.join(directory, ".swarm/checkpoints.json");
|
|
34374
|
-
if (!
|
|
34438
|
+
if (!existsSync6(manifestPath)) {
|
|
34375
34439
|
return {
|
|
34376
34440
|
name: "Checkpoint Manifest",
|
|
34377
34441
|
status: "\u2705",
|
|
@@ -34423,7 +34487,7 @@ async function checkCheckpointManifest(directory) {
|
|
|
34423
34487
|
}
|
|
34424
34488
|
async function checkEventStreamIntegrity(directory) {
|
|
34425
34489
|
const eventsPath = path15.join(directory, ".swarm/events.jsonl");
|
|
34426
|
-
if (!
|
|
34490
|
+
if (!existsSync6(eventsPath)) {
|
|
34427
34491
|
return {
|
|
34428
34492
|
name: "Event Stream",
|
|
34429
34493
|
status: "\u2705",
|
|
@@ -34464,7 +34528,7 @@ async function checkEventStreamIntegrity(directory) {
|
|
|
34464
34528
|
}
|
|
34465
34529
|
async function checkSteeringDirectives(directory) {
|
|
34466
34530
|
const eventsPath = path15.join(directory, ".swarm/events.jsonl");
|
|
34467
|
-
if (!
|
|
34531
|
+
if (!existsSync6(eventsPath)) {
|
|
34468
34532
|
return {
|
|
34469
34533
|
name: "Steering Directives",
|
|
34470
34534
|
status: "\u2705",
|
|
@@ -34520,7 +34584,7 @@ async function checkCurator(directory) {
|
|
|
34520
34584
|
};
|
|
34521
34585
|
}
|
|
34522
34586
|
const summaryPath = path15.join(directory, ".swarm/curator-summary.json");
|
|
34523
|
-
if (!
|
|
34587
|
+
if (!existsSync6(summaryPath)) {
|
|
34524
34588
|
return {
|
|
34525
34589
|
name: "Curator",
|
|
34526
34590
|
status: "\u2705",
|
|
@@ -35414,14 +35478,14 @@ async function handleHistoryCommand(directory, _args) {
|
|
|
35414
35478
|
}
|
|
35415
35479
|
// src/hooks/knowledge-migrator.ts
|
|
35416
35480
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
35417
|
-
import { existsSync as
|
|
35481
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
|
|
35418
35482
|
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
35419
35483
|
import * as path17 from "path";
|
|
35420
35484
|
async function migrateContextToKnowledge(directory, config3) {
|
|
35421
35485
|
const sentinelPath = path17.join(directory, ".swarm", ".knowledge-migrated");
|
|
35422
35486
|
const contextPath = path17.join(directory, ".swarm", "context.md");
|
|
35423
35487
|
const knowledgePath = resolveSwarmKnowledgePath(directory);
|
|
35424
|
-
if (
|
|
35488
|
+
if (existsSync8(sentinelPath)) {
|
|
35425
35489
|
return {
|
|
35426
35490
|
migrated: false,
|
|
35427
35491
|
entriesMigrated: 0,
|
|
@@ -35430,7 +35494,7 @@ async function migrateContextToKnowledge(directory, config3) {
|
|
|
35430
35494
|
skippedReason: "sentinel-exists"
|
|
35431
35495
|
};
|
|
35432
35496
|
}
|
|
35433
|
-
if (!
|
|
35497
|
+
if (!existsSync8(contextPath)) {
|
|
35434
35498
|
return {
|
|
35435
35499
|
migrated: false,
|
|
35436
35500
|
entriesMigrated: 0,
|
|
@@ -35616,7 +35680,7 @@ function truncateLesson(text) {
|
|
|
35616
35680
|
}
|
|
35617
35681
|
function inferProjectName(directory) {
|
|
35618
35682
|
const packageJsonPath = path17.join(directory, "package.json");
|
|
35619
|
-
if (
|
|
35683
|
+
if (existsSync8(packageJsonPath)) {
|
|
35620
35684
|
try {
|
|
35621
35685
|
const pkg = JSON.parse(readFileSync7(packageJsonPath, "utf-8"));
|
|
35622
35686
|
if (pkg.name && typeof pkg.name === "string") {
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Maps to: plan service (loadPlan which triggers auto-heal/sync)
|
|
4
4
|
*
|
|
5
5
|
* This command ensures plan.json and plan.md are in sync.
|
|
6
|
-
*
|
|
6
|
+
* loadPlan() is safe here: the migration-aware ledger guard in loadPlan()
|
|
7
|
+
* now prevents false reverts caused by swarm identity changes, so the
|
|
8
|
+
* full auto-heal path (including legacy plan.md migration) is correct.
|
|
7
9
|
*/
|
|
8
10
|
export declare function handleSyncPlanCommand(directory: string, _args: string[]): Promise<string>;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Curator types — phase context consolidation and drift detection.
|
|
3
3
|
* No runtime logic. Types only.
|
|
4
4
|
*/
|
|
5
|
+
import type { KnowledgeCategory } from './knowledge-types.js';
|
|
5
6
|
/** Curator summary — anchored iterative format. Persisted to .swarm/curator-summary.json */
|
|
6
7
|
export interface CuratorSummary {
|
|
7
8
|
schema_version: 1;
|
|
@@ -39,6 +40,8 @@ export interface KnowledgeRecommendation {
|
|
|
39
40
|
entry_id?: string;
|
|
40
41
|
lesson: string;
|
|
41
42
|
reason: string;
|
|
43
|
+
category?: KnowledgeCategory;
|
|
44
|
+
confidence?: number;
|
|
42
45
|
}
|
|
43
46
|
/** Drift report — produced by critic after curator phase run */
|
|
44
47
|
export interface DriftReport {
|
package/dist/hooks/curator.d.ts
CHANGED
|
@@ -83,7 +83,7 @@ export declare function runCuratorPhase(directory: string, phase: number, agents
|
|
|
83
83
|
* @param knowledgeConfig - Knowledge configuration (for path resolution)
|
|
84
84
|
* @returns Counts of applied and skipped recommendations
|
|
85
85
|
*/
|
|
86
|
-
export declare function applyCuratorKnowledgeUpdates(directory: string, recommendations: KnowledgeRecommendation[],
|
|
86
|
+
export declare function applyCuratorKnowledgeUpdates(directory: string, recommendations: KnowledgeRecommendation[], knowledgeConfig: KnowledgeConfig): Promise<{
|
|
87
87
|
applied: number;
|
|
88
88
|
skipped: number;
|
|
89
89
|
}>;
|
package/dist/index.js
CHANGED
|
@@ -16046,20 +16046,22 @@ async function readLedgerEvents(directory) {
|
|
|
16046
16046
|
return [];
|
|
16047
16047
|
}
|
|
16048
16048
|
}
|
|
16049
|
-
async function initLedger(directory, planId) {
|
|
16049
|
+
async function initLedger(directory, planId, initialPlanHash) {
|
|
16050
16050
|
const ledgerPath = getLedgerPath(directory);
|
|
16051
16051
|
const planJsonPath = getPlanJsonPath(directory);
|
|
16052
16052
|
if (fs6.existsSync(ledgerPath)) {
|
|
16053
16053
|
throw new Error("Ledger already initialized. Use appendLedgerEvent to add events.");
|
|
16054
16054
|
}
|
|
16055
|
-
let planHashAfter = "";
|
|
16056
|
-
|
|
16057
|
-
|
|
16058
|
-
|
|
16059
|
-
|
|
16060
|
-
|
|
16061
|
-
|
|
16062
|
-
|
|
16055
|
+
let planHashAfter = initialPlanHash ?? "";
|
|
16056
|
+
if (!initialPlanHash) {
|
|
16057
|
+
try {
|
|
16058
|
+
if (fs6.existsSync(planJsonPath)) {
|
|
16059
|
+
const content = fs6.readFileSync(planJsonPath, "utf8");
|
|
16060
|
+
const plan = JSON.parse(content);
|
|
16061
|
+
planHashAfter = computePlanHash(plan);
|
|
16062
|
+
}
|
|
16063
|
+
} catch {}
|
|
16064
|
+
}
|
|
16063
16065
|
const event = {
|
|
16064
16066
|
seq: 1,
|
|
16065
16067
|
timestamp: new Date().toISOString(),
|
|
@@ -16071,7 +16073,7 @@ async function initLedger(directory, planId) {
|
|
|
16071
16073
|
schema_version: LEDGER_SCHEMA_VERSION
|
|
16072
16074
|
};
|
|
16073
16075
|
fs6.mkdirSync(path6.join(directory, ".swarm"), { recursive: true });
|
|
16074
|
-
const tempPath = `${ledgerPath}.tmp`;
|
|
16076
|
+
const tempPath = `${ledgerPath}.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`;
|
|
16075
16077
|
const line = `${JSON.stringify(event)}
|
|
16076
16078
|
`;
|
|
16077
16079
|
fs6.writeFileSync(tempPath, line, "utf8");
|
|
@@ -16098,7 +16100,7 @@ async function appendLedgerEvent(directory, eventInput, options) {
|
|
|
16098
16100
|
schema_version: LEDGER_SCHEMA_VERSION
|
|
16099
16101
|
};
|
|
16100
16102
|
fs6.mkdirSync(path6.join(directory, ".swarm"), { recursive: true });
|
|
16101
|
-
const tempPath = `${ledgerPath}.tmp`;
|
|
16103
|
+
const tempPath = `${ledgerPath}.tmp.${Date.now()}.${Math.floor(Math.random() * 1e9)}`;
|
|
16102
16104
|
const line = `${JSON.stringify(event)}
|
|
16103
16105
|
`;
|
|
16104
16106
|
if (fs6.existsSync(ledgerPath)) {
|
|
@@ -16116,10 +16118,11 @@ async function takeSnapshotEvent(directory, plan, options) {
|
|
|
16116
16118
|
plan,
|
|
16117
16119
|
payload_hash: payloadHash
|
|
16118
16120
|
};
|
|
16121
|
+
const planId = `${plan.swarm}-${plan.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
16119
16122
|
return appendLedgerEvent(directory, {
|
|
16120
16123
|
event_type: "snapshot",
|
|
16121
16124
|
source: "takeSnapshotEvent",
|
|
16122
|
-
plan_id:
|
|
16125
|
+
plan_id: planId,
|
|
16123
16126
|
payload: snapshotPayload
|
|
16124
16127
|
}, options);
|
|
16125
16128
|
}
|
|
@@ -16128,13 +16131,15 @@ async function replayFromLedger(directory, options) {
|
|
|
16128
16131
|
if (events.length === 0) {
|
|
16129
16132
|
return null;
|
|
16130
16133
|
}
|
|
16134
|
+
const targetPlanId = events[0].plan_id;
|
|
16135
|
+
const relevantEvents = events.filter((e) => e.plan_id === targetPlanId);
|
|
16131
16136
|
{
|
|
16132
|
-
const snapshotEvents =
|
|
16137
|
+
const snapshotEvents = relevantEvents.filter((e) => e.event_type === "snapshot");
|
|
16133
16138
|
if (snapshotEvents.length > 0) {
|
|
16134
16139
|
const latestSnapshotEvent = snapshotEvents[snapshotEvents.length - 1];
|
|
16135
16140
|
const snapshotPayload = latestSnapshotEvent.payload;
|
|
16136
16141
|
let plan2 = snapshotPayload.plan;
|
|
16137
|
-
const eventsAfterSnapshot =
|
|
16142
|
+
const eventsAfterSnapshot = relevantEvents.filter((e) => e.seq > latestSnapshotEvent.seq);
|
|
16138
16143
|
for (const event of eventsAfterSnapshot) {
|
|
16139
16144
|
plan2 = applyEventToPlan(plan2, event);
|
|
16140
16145
|
if (plan2 === null) {
|
|
@@ -16155,7 +16160,7 @@ async function replayFromLedger(directory, options) {
|
|
|
16155
16160
|
} catch {
|
|
16156
16161
|
return null;
|
|
16157
16162
|
}
|
|
16158
|
-
for (const event of
|
|
16163
|
+
for (const event of relevantEvents) {
|
|
16159
16164
|
if (plan === null) {
|
|
16160
16165
|
return null;
|
|
16161
16166
|
}
|
|
@@ -16169,10 +16174,14 @@ function applyEventToPlan(plan, event) {
|
|
|
16169
16174
|
return plan;
|
|
16170
16175
|
case "task_status_changed":
|
|
16171
16176
|
if (event.task_id && event.to_status) {
|
|
16177
|
+
const parseResult = TaskStatusSchema.safeParse(event.to_status);
|
|
16178
|
+
if (!parseResult.success) {
|
|
16179
|
+
return plan;
|
|
16180
|
+
}
|
|
16172
16181
|
for (const phase of plan.phases) {
|
|
16173
16182
|
const task = phase.tasks.find((t) => t.id === event.task_id);
|
|
16174
16183
|
if (task) {
|
|
16175
|
-
task.status =
|
|
16184
|
+
task.status = parseResult.data;
|
|
16176
16185
|
break;
|
|
16177
16186
|
}
|
|
16178
16187
|
}
|
|
@@ -16206,6 +16215,7 @@ function applyEventToPlan(plan, event) {
|
|
|
16206
16215
|
}
|
|
16207
16216
|
var LEDGER_SCHEMA_VERSION = "1.0.0", LEDGER_FILENAME = "plan-ledger.jsonl", PLAN_JSON_FILENAME = "plan.json", LedgerStaleWriterError;
|
|
16208
16217
|
var init_ledger = __esm(() => {
|
|
16218
|
+
init_plan_schema();
|
|
16209
16219
|
LedgerStaleWriterError = class LedgerStaleWriterError extends Error {
|
|
16210
16220
|
constructor(message) {
|
|
16211
16221
|
super(message);
|
|
@@ -16215,7 +16225,7 @@ var init_ledger = __esm(() => {
|
|
|
16215
16225
|
});
|
|
16216
16226
|
|
|
16217
16227
|
// src/plan/manager.ts
|
|
16218
|
-
import { renameSync as renameSync3, unlinkSync } from "fs";
|
|
16228
|
+
import { existsSync as existsSync4, renameSync as renameSync3, unlinkSync } from "fs";
|
|
16219
16229
|
import * as path7 from "path";
|
|
16220
16230
|
async function loadPlanJsonOnly(directory) {
|
|
16221
16231
|
const planJsonContent = await readSwarmFileAsync(directory, "plan.json");
|
|
@@ -16346,28 +16356,49 @@ async function loadPlan(directory) {
|
|
|
16346
16356
|
const planHash = computePlanHash(validated);
|
|
16347
16357
|
const ledgerHash = await getLatestLedgerHash(directory);
|
|
16348
16358
|
if (ledgerHash !== "" && planHash !== ledgerHash) {
|
|
16349
|
-
|
|
16350
|
-
|
|
16351
|
-
|
|
16352
|
-
|
|
16353
|
-
|
|
16354
|
-
|
|
16355
|
-
|
|
16359
|
+
const currentPlanId = `${validated.swarm}-${validated.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
16360
|
+
const ledgerEvents = await readLedgerEvents(directory);
|
|
16361
|
+
const firstEvent = ledgerEvents.length > 0 ? ledgerEvents[0] : null;
|
|
16362
|
+
if (firstEvent && firstEvent.plan_id !== currentPlanId) {
|
|
16363
|
+
warn(`[loadPlan] Ledger identity mismatch (ledger: ${firstEvent.plan_id}, plan: ${currentPlanId}) \u2014 skipping ledger rebuild (migration detected). Use /swarm reset-session to reinitialize the ledger.`);
|
|
16364
|
+
} else {
|
|
16365
|
+
warn("[loadPlan] plan.json is stale (hash mismatch with ledger) \u2014 rebuilding from ledger. If this recurs, run /swarm reset-session to clear stale session state.");
|
|
16366
|
+
try {
|
|
16367
|
+
const rebuilt = await replayFromLedger(directory);
|
|
16368
|
+
if (rebuilt) {
|
|
16369
|
+
await rebuildPlan(directory, rebuilt);
|
|
16370
|
+
warn("[loadPlan] Rebuilt plan from ledger. Checkpoint available at SWARM_PLAN.md if it exists.");
|
|
16371
|
+
return rebuilt;
|
|
16372
|
+
}
|
|
16373
|
+
} catch (replayError) {
|
|
16374
|
+
warn(`[loadPlan] Ledger replay failed during hash-mismatch rebuild: ${replayError instanceof Error ? replayError.message : String(replayError)}. Returning stale plan.json. To recover: check SWARM_PLAN.md for a checkpoint, or run /swarm reset-session.`);
|
|
16356
16375
|
}
|
|
16357
|
-
} catch (replayError) {
|
|
16358
|
-
warn(`[loadPlan] Ledger replay failed during hash-mismatch rebuild: ${replayError instanceof Error ? replayError.message : String(replayError)}. Returning stale plan.json. To recover: check SWARM_PLAN.md for a checkpoint, or run /swarm reset-session.`);
|
|
16359
16376
|
}
|
|
16360
16377
|
}
|
|
16361
16378
|
}
|
|
16362
16379
|
return validated;
|
|
16363
16380
|
} catch (error49) {
|
|
16364
16381
|
warn(`[loadPlan] plan.json validation failed: ${error49 instanceof Error ? error49.message : String(error49)}. Attempting rebuild from ledger. If rebuild fails, check SWARM_PLAN.md for a checkpoint.`);
|
|
16382
|
+
let rawPlanId = null;
|
|
16383
|
+
try {
|
|
16384
|
+
const rawParsed = JSON.parse(planJsonContent);
|
|
16385
|
+
if (typeof rawParsed?.swarm === "string" && typeof rawParsed?.title === "string") {
|
|
16386
|
+
rawPlanId = `${rawParsed.swarm}-${rawParsed.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
16387
|
+
}
|
|
16388
|
+
} catch {}
|
|
16365
16389
|
if (await ledgerExists(directory)) {
|
|
16366
|
-
const
|
|
16367
|
-
|
|
16368
|
-
|
|
16369
|
-
|
|
16370
|
-
|
|
16390
|
+
const ledgerEventsForCatch = await readLedgerEvents(directory);
|
|
16391
|
+
const catchFirstEvent = ledgerEventsForCatch.length > 0 ? ledgerEventsForCatch[0] : null;
|
|
16392
|
+
const identityMatch = rawPlanId === null || catchFirstEvent === null || catchFirstEvent.plan_id === rawPlanId;
|
|
16393
|
+
if (!identityMatch) {
|
|
16394
|
+
warn(`[loadPlan] Ledger identity mismatch in validation-failure path (ledger: ${catchFirstEvent?.plan_id}, plan: ${rawPlanId}) \u2014 skipping ledger rebuild (migration detected).`);
|
|
16395
|
+
} else if (catchFirstEvent !== null && rawPlanId !== null) {
|
|
16396
|
+
const rebuilt = await replayFromLedger(directory);
|
|
16397
|
+
if (rebuilt) {
|
|
16398
|
+
await rebuildPlan(directory, rebuilt);
|
|
16399
|
+
warn("[loadPlan] Rebuilt plan from ledger after validation failure. Projection was stale.");
|
|
16400
|
+
return rebuilt;
|
|
16401
|
+
}
|
|
16371
16402
|
}
|
|
16372
16403
|
}
|
|
16373
16404
|
const planMdContent2 = await readSwarmFileAsync(directory, "plan.md");
|
|
@@ -16435,9 +16466,28 @@ async function savePlan(directory, plan, options) {
|
|
|
16435
16466
|
}
|
|
16436
16467
|
}
|
|
16437
16468
|
const currentPlan = await loadPlanJsonOnly(directory);
|
|
16469
|
+
const planId = `${validated.swarm}-${validated.title}`.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
16470
|
+
const planHashForInit = computePlanHash(validated);
|
|
16438
16471
|
if (!await ledgerExists(directory)) {
|
|
16439
|
-
|
|
16440
|
-
|
|
16472
|
+
await initLedger(directory, planId, planHashForInit);
|
|
16473
|
+
} else {
|
|
16474
|
+
const existingEvents = await readLedgerEvents(directory);
|
|
16475
|
+
if (existingEvents.length > 0 && existingEvents[0].plan_id !== planId) {
|
|
16476
|
+
const swarmDir2 = path7.resolve(directory, ".swarm");
|
|
16477
|
+
const oldLedgerPath = path7.join(swarmDir2, "plan-ledger.jsonl");
|
|
16478
|
+
const archivePath = path7.join(swarmDir2, `plan-ledger.archived-${Date.now()}-${Math.floor(Math.random() * 1e9)}.jsonl`);
|
|
16479
|
+
if (existsSync4(oldLedgerPath)) {
|
|
16480
|
+
renameSync3(oldLedgerPath, archivePath);
|
|
16481
|
+
warn(`[savePlan] Ledger identity mismatch (was "${existingEvents[0].plan_id}", now "${planId}") \u2014 archived old ledger to ${archivePath} and reinitializing.`);
|
|
16482
|
+
}
|
|
16483
|
+
try {
|
|
16484
|
+
await initLedger(directory, planId, planHashForInit);
|
|
16485
|
+
} catch (initErr) {
|
|
16486
|
+
if (!(initErr instanceof Error && initErr.message.includes("already initialized"))) {
|
|
16487
|
+
throw initErr;
|
|
16488
|
+
}
|
|
16489
|
+
}
|
|
16490
|
+
}
|
|
16441
16491
|
}
|
|
16442
16492
|
const currentHash = computeCurrentPlanHash(directory);
|
|
16443
16493
|
const hashAfter = computePlanHash(validated);
|
|
@@ -16530,10 +16580,24 @@ async function rebuildPlan(directory, plan) {
|
|
|
16530
16580
|
const tempPlanPath = path7.join(swarmDir, `plan.json.rebuild.${Date.now()}`);
|
|
16531
16581
|
await Bun.write(tempPlanPath, JSON.stringify(targetPlan, null, 2));
|
|
16532
16582
|
renameSync3(tempPlanPath, planPath);
|
|
16583
|
+
const contentHash = computePlanContentHash(targetPlan);
|
|
16533
16584
|
const markdown = derivePlanMarkdown(targetPlan);
|
|
16585
|
+
const markdownWithHash = `<!-- PLAN_HASH: ${contentHash} -->
|
|
16586
|
+
${markdown}`;
|
|
16534
16587
|
const tempMdPath = path7.join(swarmDir, `plan.md.rebuild.${Date.now()}`);
|
|
16535
|
-
await Bun.write(tempMdPath,
|
|
16588
|
+
await Bun.write(tempMdPath, markdownWithHash);
|
|
16536
16589
|
renameSync3(tempMdPath, mdPath);
|
|
16590
|
+
try {
|
|
16591
|
+
const markerPath = path7.join(swarmDir, ".plan-write-marker");
|
|
16592
|
+
const tasksCount = targetPlan.phases.reduce((sum, phase) => sum + phase.tasks.length, 0);
|
|
16593
|
+
const marker = JSON.stringify({
|
|
16594
|
+
source: "plan_manager",
|
|
16595
|
+
timestamp: new Date().toISOString(),
|
|
16596
|
+
phases_count: targetPlan.phases.length,
|
|
16597
|
+
tasks_count: tasksCount
|
|
16598
|
+
});
|
|
16599
|
+
await Bun.write(markerPath, marker);
|
|
16600
|
+
} catch {}
|
|
16537
16601
|
return targetPlan;
|
|
16538
16602
|
}
|
|
16539
16603
|
async function updateTaskStatus(directory, taskId, status) {
|
|
@@ -17134,11 +17198,11 @@ __export(exports_evidence_summary_integration, {
|
|
|
17134
17198
|
createEvidenceSummaryIntegration: () => createEvidenceSummaryIntegration,
|
|
17135
17199
|
EvidenceSummaryIntegration: () => EvidenceSummaryIntegration
|
|
17136
17200
|
});
|
|
17137
|
-
import { existsSync as
|
|
17201
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
17138
17202
|
import * as path8 from "path";
|
|
17139
17203
|
function persistSummary(swarmDir, artifact, filename) {
|
|
17140
17204
|
const swarmPath = path8.join(swarmDir, ".swarm");
|
|
17141
|
-
if (!
|
|
17205
|
+
if (!existsSync5(swarmPath)) {
|
|
17142
17206
|
mkdirSync4(swarmPath, { recursive: true });
|
|
17143
17207
|
}
|
|
17144
17208
|
const artifactPath = path8.join(swarmPath, filename);
|
|
@@ -32356,7 +32420,7 @@ var require_proper_lockfile = __commonJS((exports, module2) => {
|
|
|
32356
32420
|
});
|
|
32357
32421
|
|
|
32358
32422
|
// src/hooks/knowledge-store.ts
|
|
32359
|
-
import { existsSync as
|
|
32423
|
+
import { existsSync as existsSync9 } from "fs";
|
|
32360
32424
|
import { appendFile, mkdir, readFile as readFile2, writeFile } from "fs/promises";
|
|
32361
32425
|
import * as os3 from "os";
|
|
32362
32426
|
import * as path12 from "path";
|
|
@@ -32384,7 +32448,7 @@ function resolveHiveRejectedPath() {
|
|
|
32384
32448
|
return path12.join(path12.dirname(hivePath), "shared-learnings-rejected.jsonl");
|
|
32385
32449
|
}
|
|
32386
32450
|
async function readKnowledge(filePath) {
|
|
32387
|
-
if (!
|
|
32451
|
+
if (!existsSync9(filePath))
|
|
32388
32452
|
return [];
|
|
32389
32453
|
const content = await readFile2(filePath, "utf-8");
|
|
32390
32454
|
const results = [];
|
|
@@ -41867,8 +41931,8 @@ async function loadGrammar(languageId) {
|
|
|
41867
41931
|
const parser = new Parser;
|
|
41868
41932
|
const wasmFileName = getWasmFileName(normalizedId);
|
|
41869
41933
|
const wasmPath = path56.join(getGrammarsDirAbsolute(), wasmFileName);
|
|
41870
|
-
const { existsSync:
|
|
41871
|
-
if (!
|
|
41934
|
+
const { existsSync: existsSync34 } = await import("fs");
|
|
41935
|
+
if (!existsSync34(wasmPath)) {
|
|
41872
41936
|
throw new Error(`Grammar file not found for ${languageId}: ${wasmPath}
|
|
41873
41937
|
Make sure to run 'bun run build' to copy grammar files to dist/lang/grammars/`);
|
|
41874
41938
|
}
|
|
@@ -46021,8 +46085,9 @@ class PlanSyncWorker {
|
|
|
46021
46085
|
try {
|
|
46022
46086
|
log("[PlanSyncWorker] Syncing plan...");
|
|
46023
46087
|
this.checkForUnauthorizedWrite();
|
|
46024
|
-
const plan = await this.withTimeout(
|
|
46088
|
+
const plan = await this.withTimeout(loadPlanJsonOnly(this.directory), this.syncTimeoutMs, "Sync operation timed out");
|
|
46025
46089
|
if (plan) {
|
|
46090
|
+
await regeneratePlanMarkdown(this.directory, plan);
|
|
46026
46091
|
log("[PlanSyncWorker] Sync complete", {
|
|
46027
46092
|
title: plan.title,
|
|
46028
46093
|
phase: plan.current_phase
|
|
@@ -46686,7 +46751,7 @@ import path17 from "path";
|
|
|
46686
46751
|
|
|
46687
46752
|
// src/hooks/knowledge-reader.ts
|
|
46688
46753
|
init_knowledge_store();
|
|
46689
|
-
import { existsSync as
|
|
46754
|
+
import { existsSync as existsSync10 } from "fs";
|
|
46690
46755
|
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
46691
46756
|
import * as path13 from "path";
|
|
46692
46757
|
var JACCARD_THRESHOLD = 0.6;
|
|
@@ -46739,7 +46804,7 @@ async function recordLessonsShown(directory, lessonIds, currentPhase) {
|
|
|
46739
46804
|
const shownFile = path13.join(directory, ".swarm", ".knowledge-shown.json");
|
|
46740
46805
|
try {
|
|
46741
46806
|
let shownData = {};
|
|
46742
|
-
if (
|
|
46807
|
+
if (existsSync10(shownFile)) {
|
|
46743
46808
|
const content = await readFile3(shownFile, "utf-8");
|
|
46744
46809
|
shownData = JSON.parse(content);
|
|
46745
46810
|
}
|
|
@@ -46845,7 +46910,7 @@ async function readMergedKnowledge(directory, config3, context) {
|
|
|
46845
46910
|
async function updateRetrievalOutcome(directory, phaseInfo, phaseSucceeded) {
|
|
46846
46911
|
const shownFile = path13.join(directory, ".swarm", ".knowledge-shown.json");
|
|
46847
46912
|
try {
|
|
46848
|
-
if (!
|
|
46913
|
+
if (!existsSync10(shownFile)) {
|
|
46849
46914
|
return;
|
|
46850
46915
|
}
|
|
46851
46916
|
const content = await readFile3(shownFile, "utf-8");
|
|
@@ -48791,7 +48856,7 @@ ${phaseDigest.summary}`,
|
|
|
48791
48856
|
};
|
|
48792
48857
|
}
|
|
48793
48858
|
}
|
|
48794
|
-
async function applyCuratorKnowledgeUpdates(directory, recommendations,
|
|
48859
|
+
async function applyCuratorKnowledgeUpdates(directory, recommendations, knowledgeConfig) {
|
|
48795
48860
|
let applied = 0;
|
|
48796
48861
|
let skipped = 0;
|
|
48797
48862
|
if (recommendations.length === 0) {
|
|
@@ -48853,6 +48918,7 @@ async function applyCuratorKnowledgeUpdates(directory, recommendations, _knowled
|
|
|
48853
48918
|
if (modified) {
|
|
48854
48919
|
await rewriteKnowledge(knowledgePath, updatedEntries);
|
|
48855
48920
|
}
|
|
48921
|
+
const existingLessons = entries.map((e) => e.lesson);
|
|
48856
48922
|
for (const rec of recommendations) {
|
|
48857
48923
|
if (rec.entry_id !== undefined)
|
|
48858
48924
|
continue;
|
|
@@ -48860,20 +48926,35 @@ async function applyCuratorKnowledgeUpdates(directory, recommendations, _knowled
|
|
|
48860
48926
|
skipped++;
|
|
48861
48927
|
continue;
|
|
48862
48928
|
}
|
|
48863
|
-
const lesson = rec.lesson?.trim() ?? "";
|
|
48929
|
+
const lesson = (rec.lesson?.trim() ?? "").slice(0, 280);
|
|
48864
48930
|
if (lesson.length < 15) {
|
|
48865
48931
|
skipped++;
|
|
48866
48932
|
continue;
|
|
48867
48933
|
}
|
|
48934
|
+
if (existingLessons.some((el) => el.toLowerCase() === lesson.toLowerCase())) {
|
|
48935
|
+
skipped++;
|
|
48936
|
+
continue;
|
|
48937
|
+
}
|
|
48938
|
+
if (knowledgeConfig.validation_enabled !== false) {
|
|
48939
|
+
const validation = validateLesson(lesson, existingLessons, {
|
|
48940
|
+
category: rec.category ?? "other",
|
|
48941
|
+
scope: "global",
|
|
48942
|
+
confidence: rec.confidence ?? 0.5
|
|
48943
|
+
});
|
|
48944
|
+
if (!validation.valid) {
|
|
48945
|
+
skipped++;
|
|
48946
|
+
continue;
|
|
48947
|
+
}
|
|
48948
|
+
}
|
|
48868
48949
|
const now = new Date().toISOString();
|
|
48869
48950
|
const newEntry = {
|
|
48870
48951
|
id: randomUUID(),
|
|
48871
48952
|
tier: "swarm",
|
|
48872
|
-
lesson
|
|
48873
|
-
category: "other",
|
|
48953
|
+
lesson,
|
|
48954
|
+
category: rec.category ?? "other",
|
|
48874
48955
|
tags: [],
|
|
48875
48956
|
scope: "global",
|
|
48876
|
-
confidence: 0.5,
|
|
48957
|
+
confidence: rec.confidence ?? 0.5,
|
|
48877
48958
|
status: "candidate",
|
|
48878
48959
|
confirmed_by: [],
|
|
48879
48960
|
retrieval_outcomes: {
|
|
@@ -48889,6 +48970,7 @@ async function applyCuratorKnowledgeUpdates(directory, recommendations, _knowled
|
|
|
48889
48970
|
};
|
|
48890
48971
|
await appendKnowledge(knowledgePath, newEntry);
|
|
48891
48972
|
applied++;
|
|
48973
|
+
existingLessons.push(lesson);
|
|
48892
48974
|
}
|
|
48893
48975
|
return { applied, skipped };
|
|
48894
48976
|
}
|
|
@@ -49150,7 +49232,7 @@ init_manager();
|
|
|
49150
49232
|
init_utils2();
|
|
49151
49233
|
init_manager2();
|
|
49152
49234
|
import * as child_process3 from "child_process";
|
|
49153
|
-
import { existsSync as
|
|
49235
|
+
import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
|
|
49154
49236
|
import path22 from "path";
|
|
49155
49237
|
import { fileURLToPath } from "url";
|
|
49156
49238
|
function validateTaskDag(plan) {
|
|
@@ -49384,7 +49466,7 @@ async function checkConfigBackups(directory) {
|
|
|
49384
49466
|
}
|
|
49385
49467
|
async function checkGitRepository(directory) {
|
|
49386
49468
|
try {
|
|
49387
|
-
if (!
|
|
49469
|
+
if (!existsSync11(directory) || !statSync5(directory).isDirectory()) {
|
|
49388
49470
|
return {
|
|
49389
49471
|
name: "Git Repository",
|
|
49390
49472
|
status: "\u274C",
|
|
@@ -49449,7 +49531,7 @@ async function checkSpecStaleness(directory, plan) {
|
|
|
49449
49531
|
}
|
|
49450
49532
|
async function checkConfigParseability(directory) {
|
|
49451
49533
|
const configPath = path22.join(directory, ".opencode/opencode-swarm.json");
|
|
49452
|
-
if (!
|
|
49534
|
+
if (!existsSync11(configPath)) {
|
|
49453
49535
|
return {
|
|
49454
49536
|
name: "Config Parseability",
|
|
49455
49537
|
status: "\u2705",
|
|
@@ -49499,11 +49581,11 @@ async function checkGrammarWasmFiles() {
|
|
|
49499
49581
|
const isSource = thisDir.replace(/\\/g, "/").endsWith("/src/services");
|
|
49500
49582
|
const grammarDir = isSource ? path22.join(thisDir, "..", "lang", "grammars") : path22.join(thisDir, "lang", "grammars");
|
|
49501
49583
|
const missing = [];
|
|
49502
|
-
if (!
|
|
49584
|
+
if (!existsSync11(path22.join(grammarDir, "tree-sitter.wasm"))) {
|
|
49503
49585
|
missing.push("tree-sitter.wasm (core runtime)");
|
|
49504
49586
|
}
|
|
49505
49587
|
for (const file3 of grammarFiles) {
|
|
49506
|
-
if (!
|
|
49588
|
+
if (!existsSync11(path22.join(grammarDir, file3))) {
|
|
49507
49589
|
missing.push(file3);
|
|
49508
49590
|
}
|
|
49509
49591
|
}
|
|
@@ -49522,7 +49604,7 @@ async function checkGrammarWasmFiles() {
|
|
|
49522
49604
|
}
|
|
49523
49605
|
async function checkCheckpointManifest(directory) {
|
|
49524
49606
|
const manifestPath = path22.join(directory, ".swarm/checkpoints.json");
|
|
49525
|
-
if (!
|
|
49607
|
+
if (!existsSync11(manifestPath)) {
|
|
49526
49608
|
return {
|
|
49527
49609
|
name: "Checkpoint Manifest",
|
|
49528
49610
|
status: "\u2705",
|
|
@@ -49574,7 +49656,7 @@ async function checkCheckpointManifest(directory) {
|
|
|
49574
49656
|
}
|
|
49575
49657
|
async function checkEventStreamIntegrity(directory) {
|
|
49576
49658
|
const eventsPath = path22.join(directory, ".swarm/events.jsonl");
|
|
49577
|
-
if (!
|
|
49659
|
+
if (!existsSync11(eventsPath)) {
|
|
49578
49660
|
return {
|
|
49579
49661
|
name: "Event Stream",
|
|
49580
49662
|
status: "\u2705",
|
|
@@ -49615,7 +49697,7 @@ async function checkEventStreamIntegrity(directory) {
|
|
|
49615
49697
|
}
|
|
49616
49698
|
async function checkSteeringDirectives(directory) {
|
|
49617
49699
|
const eventsPath = path22.join(directory, ".swarm/events.jsonl");
|
|
49618
|
-
if (!
|
|
49700
|
+
if (!existsSync11(eventsPath)) {
|
|
49619
49701
|
return {
|
|
49620
49702
|
name: "Steering Directives",
|
|
49621
49703
|
status: "\u2705",
|
|
@@ -49671,7 +49753,7 @@ async function checkCurator(directory) {
|
|
|
49671
49753
|
};
|
|
49672
49754
|
}
|
|
49673
49755
|
const summaryPath = path22.join(directory, ".swarm/curator-summary.json");
|
|
49674
|
-
if (!
|
|
49756
|
+
if (!existsSync11(summaryPath)) {
|
|
49675
49757
|
return {
|
|
49676
49758
|
name: "Curator",
|
|
49677
49759
|
status: "\u2705",
|
|
@@ -50570,14 +50652,14 @@ init_schema();
|
|
|
50570
50652
|
// src/hooks/knowledge-migrator.ts
|
|
50571
50653
|
init_knowledge_store();
|
|
50572
50654
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
50573
|
-
import { existsSync as
|
|
50655
|
+
import { existsSync as existsSync13, readFileSync as readFileSync9 } from "fs";
|
|
50574
50656
|
import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
|
|
50575
50657
|
import * as path24 from "path";
|
|
50576
50658
|
async function migrateContextToKnowledge(directory, config3) {
|
|
50577
50659
|
const sentinelPath = path24.join(directory, ".swarm", ".knowledge-migrated");
|
|
50578
50660
|
const contextPath = path24.join(directory, ".swarm", "context.md");
|
|
50579
50661
|
const knowledgePath = resolveSwarmKnowledgePath(directory);
|
|
50580
|
-
if (
|
|
50662
|
+
if (existsSync13(sentinelPath)) {
|
|
50581
50663
|
return {
|
|
50582
50664
|
migrated: false,
|
|
50583
50665
|
entriesMigrated: 0,
|
|
@@ -50586,7 +50668,7 @@ async function migrateContextToKnowledge(directory, config3) {
|
|
|
50586
50668
|
skippedReason: "sentinel-exists"
|
|
50587
50669
|
};
|
|
50588
50670
|
}
|
|
50589
|
-
if (!
|
|
50671
|
+
if (!existsSync13(contextPath)) {
|
|
50590
50672
|
return {
|
|
50591
50673
|
migrated: false,
|
|
50592
50674
|
entriesMigrated: 0,
|
|
@@ -50772,7 +50854,7 @@ function truncateLesson(text) {
|
|
|
50772
50854
|
}
|
|
50773
50855
|
function inferProjectName(directory) {
|
|
50774
50856
|
const packageJsonPath = path24.join(directory, "package.json");
|
|
50775
|
-
if (
|
|
50857
|
+
if (existsSync13(packageJsonPath)) {
|
|
50776
50858
|
try {
|
|
50777
50859
|
const pkg = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
|
|
50778
50860
|
if (pkg.name && typeof pkg.name === "string") {
|
|
@@ -60698,7 +60780,19 @@ var curator_analyze = createSwarmTool({
|
|
|
60698
60780
|
]),
|
|
60699
60781
|
entry_id: tool.schema.string().optional(),
|
|
60700
60782
|
lesson: tool.schema.string(),
|
|
60701
|
-
reason: tool.schema.string()
|
|
60783
|
+
reason: tool.schema.string(),
|
|
60784
|
+
category: tool.schema.enum([
|
|
60785
|
+
"process",
|
|
60786
|
+
"architecture",
|
|
60787
|
+
"tooling",
|
|
60788
|
+
"security",
|
|
60789
|
+
"testing",
|
|
60790
|
+
"debugging",
|
|
60791
|
+
"performance",
|
|
60792
|
+
"integration",
|
|
60793
|
+
"other"
|
|
60794
|
+
]).optional(),
|
|
60795
|
+
confidence: tool.schema.number().min(0).max(1).optional()
|
|
60702
60796
|
})).optional().describe("Knowledge recommendations to apply. If omitted, only collects digest data.")
|
|
60703
60797
|
},
|
|
60704
60798
|
execute: async (args2, directory, ctx) => {
|
|
@@ -60717,6 +60811,16 @@ var curator_analyze = createSwarmTool({
|
|
|
60717
60811
|
}
|
|
60718
60812
|
}
|
|
60719
60813
|
}
|
|
60814
|
+
if (typedArgs.recommendations) {
|
|
60815
|
+
const UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
60816
|
+
for (const rec of typedArgs.recommendations) {
|
|
60817
|
+
if (rec.entry_id !== undefined && !UUID_V4.test(rec.entry_id)) {
|
|
60818
|
+
return JSON.stringify({
|
|
60819
|
+
error: `Invalid entry_id '${rec.entry_id}': must be a UUID v4 or omitted. ` + `Use undefined/omit entry_id to create a new entry.`
|
|
60820
|
+
}, null, 2);
|
|
60821
|
+
}
|
|
60822
|
+
}
|
|
60823
|
+
}
|
|
60720
60824
|
const { config: config3 } = loadPluginConfigWithMeta(directory);
|
|
60721
60825
|
const curatorConfig = CuratorConfigSchema.parse(config3.curator ?? {});
|
|
60722
60826
|
const knowledgeConfig = KnowledgeConfigSchema.parse(config3.knowledge ?? {});
|
|
@@ -60757,12 +60861,7 @@ var curator_analyze = createSwarmTool({
|
|
|
60757
60861
|
let applied = 0;
|
|
60758
60862
|
let skipped = 0;
|
|
60759
60863
|
if (typedArgs.recommendations && typedArgs.recommendations.length > 0) {
|
|
60760
|
-
const
|
|
60761
|
-
const sanitizedRecs = typedArgs.recommendations.map((rec) => ({
|
|
60762
|
-
...rec,
|
|
60763
|
-
entry_id: rec.entry_id === undefined || UUID_V4.test(rec.entry_id) ? rec.entry_id : undefined
|
|
60764
|
-
}));
|
|
60765
|
-
const result = await applyCuratorKnowledgeUpdates(directory, sanitizedRecs, knowledgeConfig);
|
|
60864
|
+
const result = await applyCuratorKnowledgeUpdates(directory, typedArgs.recommendations, knowledgeConfig);
|
|
60766
60865
|
applied = result.applied;
|
|
60767
60866
|
skipped = result.skipped;
|
|
60768
60867
|
}
|
|
@@ -62573,7 +62672,7 @@ init_dist();
|
|
|
62573
62672
|
init_config();
|
|
62574
62673
|
init_knowledge_store();
|
|
62575
62674
|
init_create_tool();
|
|
62576
|
-
import { existsSync as
|
|
62675
|
+
import { existsSync as existsSync37 } from "fs";
|
|
62577
62676
|
var DEFAULT_LIMIT = 10;
|
|
62578
62677
|
var MAX_LESSON_LENGTH = 200;
|
|
62579
62678
|
var VALID_CATEGORIES3 = [
|
|
@@ -62642,14 +62741,14 @@ function validateLimit(limit) {
|
|
|
62642
62741
|
}
|
|
62643
62742
|
async function readSwarmKnowledge(directory) {
|
|
62644
62743
|
const swarmPath = resolveSwarmKnowledgePath(directory);
|
|
62645
|
-
if (!
|
|
62744
|
+
if (!existsSync37(swarmPath)) {
|
|
62646
62745
|
return [];
|
|
62647
62746
|
}
|
|
62648
62747
|
return readKnowledge(swarmPath);
|
|
62649
62748
|
}
|
|
62650
62749
|
async function readHiveKnowledge() {
|
|
62651
62750
|
const hivePath = resolveHiveKnowledgePath();
|
|
62652
|
-
if (!
|
|
62751
|
+
if (!existsSync37(hivePath)) {
|
|
62653
62752
|
return [];
|
|
62654
62753
|
}
|
|
62655
62754
|
return readKnowledge(hivePath);
|
package/dist/plan/ledger.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Provides durable, immutable audit trail of plan evolution events.
|
|
5
5
|
* Each event is written as a JSON line to .swarm/plan-ledger.jsonl
|
|
6
6
|
*/
|
|
7
|
-
import type
|
|
7
|
+
import { type Plan } from '../config/plan-schema';
|
|
8
8
|
/**
|
|
9
9
|
* Ledger schema version
|
|
10
10
|
*/
|
|
@@ -108,7 +108,7 @@ export declare function readLedgerEvents(directory: string): Promise<LedgerEvent
|
|
|
108
108
|
* @param directory - The working directory
|
|
109
109
|
* @param planId - Unique identifier for the plan
|
|
110
110
|
*/
|
|
111
|
-
export declare function initLedger(directory: string, planId: string): Promise<void>;
|
|
111
|
+
export declare function initLedger(directory: string, planId: string, initialPlanHash?: string): Promise<void>;
|
|
112
112
|
/**
|
|
113
113
|
* Append a new event to the ledger.
|
|
114
114
|
* Uses atomic write: write to temp file then rename.
|
package/dist/plan/manager.d.ts
CHANGED
|
@@ -5,6 +5,10 @@ import { type Plan, type TaskStatus } from '../config/plan-schema';
|
|
|
5
5
|
* Use this when you want to check for structured plans without triggering migration.
|
|
6
6
|
*/
|
|
7
7
|
export declare function loadPlanJsonOnly(directory: string): Promise<Plan | null>;
|
|
8
|
+
/**
|
|
9
|
+
* Regenerate plan.md from valid plan.json (auto-heal case 1).
|
|
10
|
+
*/
|
|
11
|
+
export declare function regeneratePlanMarkdown(directory: string, plan: Plan): Promise<void>;
|
|
8
12
|
/**
|
|
9
13
|
* Load and validate plan from .swarm/plan.json with auto-heal sync.
|
|
10
14
|
*
|
|
@@ -38,6 +42,14 @@ export declare function rebuildPlan(directory: string, plan?: Plan): Promise<Pla
|
|
|
38
42
|
/**
|
|
39
43
|
* Load plan → find task by ID → update status → save → return updated plan.
|
|
40
44
|
* Throw if plan not found or task not found.
|
|
45
|
+
*
|
|
46
|
+
* Uses loadPlan() (not loadPlanJsonOnly) so that legitimate same-identity ledger
|
|
47
|
+
* drift is detected and healed before the status update is applied. Without this,
|
|
48
|
+
* a stale plan.json would silently overwrite ledger-ahead task state with only the
|
|
49
|
+
* one targeted status change applied on top.
|
|
50
|
+
*
|
|
51
|
+
* The migration guard in loadPlan() (plan_id identity check) prevents destructive
|
|
52
|
+
* revert after a swarm rename — so this is safe even in post-migration scenarios.
|
|
41
53
|
*/
|
|
42
54
|
export declare function updateTaskStatus(directory: string, taskId: string, status: TaskStatus): Promise<Plan>;
|
|
43
55
|
/**
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the migration-aware identity guard in loadPlan()'s validation-failure
|
|
3
|
+
* catch path (lines ~299-323 of manager.ts).
|
|
4
|
+
*
|
|
5
|
+
* When plan.json fails schema validation, the old code unconditionally called
|
|
6
|
+
* replayFromLedger(). This allowed a post-migration ledger (old identity) to
|
|
7
|
+
* overwrite a schema-invalid but correctly migrated plan.json.
|
|
8
|
+
*
|
|
9
|
+
* The fix: extract swarm+title from the raw JSON (even if schema validation
|
|
10
|
+
* fails), compare against the first ledger event's plan_id, and only replay
|
|
11
|
+
* when identities match.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression tests for GitHub issues #383/#384:
|
|
3
|
+
* PlanSyncWorker Aggressively Reverts Plan Files
|
|
4
|
+
*
|
|
5
|
+
* These tests verify that the fixes introduced in the debug-issues-383-384 branch
|
|
6
|
+
* prevent the destructive revert behavior and related edge cases.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-swarm",
|
|
3
|
-
"version": "6.47.
|
|
3
|
+
"version": "6.47.2",
|
|
4
4
|
"description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|