opencode-swarm 6.47.0 → 6.47.1
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/index.js +125 -60
- 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>;
|
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");
|
|
@@ -49150,7 +49215,7 @@ init_manager();
|
|
|
49150
49215
|
init_utils2();
|
|
49151
49216
|
init_manager2();
|
|
49152
49217
|
import * as child_process3 from "child_process";
|
|
49153
|
-
import { existsSync as
|
|
49218
|
+
import { existsSync as existsSync11, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as statSync5 } from "fs";
|
|
49154
49219
|
import path22 from "path";
|
|
49155
49220
|
import { fileURLToPath } from "url";
|
|
49156
49221
|
function validateTaskDag(plan) {
|
|
@@ -49384,7 +49449,7 @@ async function checkConfigBackups(directory) {
|
|
|
49384
49449
|
}
|
|
49385
49450
|
async function checkGitRepository(directory) {
|
|
49386
49451
|
try {
|
|
49387
|
-
if (!
|
|
49452
|
+
if (!existsSync11(directory) || !statSync5(directory).isDirectory()) {
|
|
49388
49453
|
return {
|
|
49389
49454
|
name: "Git Repository",
|
|
49390
49455
|
status: "\u274C",
|
|
@@ -49449,7 +49514,7 @@ async function checkSpecStaleness(directory, plan) {
|
|
|
49449
49514
|
}
|
|
49450
49515
|
async function checkConfigParseability(directory) {
|
|
49451
49516
|
const configPath = path22.join(directory, ".opencode/opencode-swarm.json");
|
|
49452
|
-
if (!
|
|
49517
|
+
if (!existsSync11(configPath)) {
|
|
49453
49518
|
return {
|
|
49454
49519
|
name: "Config Parseability",
|
|
49455
49520
|
status: "\u2705",
|
|
@@ -49499,11 +49564,11 @@ async function checkGrammarWasmFiles() {
|
|
|
49499
49564
|
const isSource = thisDir.replace(/\\/g, "/").endsWith("/src/services");
|
|
49500
49565
|
const grammarDir = isSource ? path22.join(thisDir, "..", "lang", "grammars") : path22.join(thisDir, "lang", "grammars");
|
|
49501
49566
|
const missing = [];
|
|
49502
|
-
if (!
|
|
49567
|
+
if (!existsSync11(path22.join(grammarDir, "tree-sitter.wasm"))) {
|
|
49503
49568
|
missing.push("tree-sitter.wasm (core runtime)");
|
|
49504
49569
|
}
|
|
49505
49570
|
for (const file3 of grammarFiles) {
|
|
49506
|
-
if (!
|
|
49571
|
+
if (!existsSync11(path22.join(grammarDir, file3))) {
|
|
49507
49572
|
missing.push(file3);
|
|
49508
49573
|
}
|
|
49509
49574
|
}
|
|
@@ -49522,7 +49587,7 @@ async function checkGrammarWasmFiles() {
|
|
|
49522
49587
|
}
|
|
49523
49588
|
async function checkCheckpointManifest(directory) {
|
|
49524
49589
|
const manifestPath = path22.join(directory, ".swarm/checkpoints.json");
|
|
49525
|
-
if (!
|
|
49590
|
+
if (!existsSync11(manifestPath)) {
|
|
49526
49591
|
return {
|
|
49527
49592
|
name: "Checkpoint Manifest",
|
|
49528
49593
|
status: "\u2705",
|
|
@@ -49574,7 +49639,7 @@ async function checkCheckpointManifest(directory) {
|
|
|
49574
49639
|
}
|
|
49575
49640
|
async function checkEventStreamIntegrity(directory) {
|
|
49576
49641
|
const eventsPath = path22.join(directory, ".swarm/events.jsonl");
|
|
49577
|
-
if (!
|
|
49642
|
+
if (!existsSync11(eventsPath)) {
|
|
49578
49643
|
return {
|
|
49579
49644
|
name: "Event Stream",
|
|
49580
49645
|
status: "\u2705",
|
|
@@ -49615,7 +49680,7 @@ async function checkEventStreamIntegrity(directory) {
|
|
|
49615
49680
|
}
|
|
49616
49681
|
async function checkSteeringDirectives(directory) {
|
|
49617
49682
|
const eventsPath = path22.join(directory, ".swarm/events.jsonl");
|
|
49618
|
-
if (!
|
|
49683
|
+
if (!existsSync11(eventsPath)) {
|
|
49619
49684
|
return {
|
|
49620
49685
|
name: "Steering Directives",
|
|
49621
49686
|
status: "\u2705",
|
|
@@ -49671,7 +49736,7 @@ async function checkCurator(directory) {
|
|
|
49671
49736
|
};
|
|
49672
49737
|
}
|
|
49673
49738
|
const summaryPath = path22.join(directory, ".swarm/curator-summary.json");
|
|
49674
|
-
if (!
|
|
49739
|
+
if (!existsSync11(summaryPath)) {
|
|
49675
49740
|
return {
|
|
49676
49741
|
name: "Curator",
|
|
49677
49742
|
status: "\u2705",
|
|
@@ -50570,14 +50635,14 @@ init_schema();
|
|
|
50570
50635
|
// src/hooks/knowledge-migrator.ts
|
|
50571
50636
|
init_knowledge_store();
|
|
50572
50637
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
50573
|
-
import { existsSync as
|
|
50638
|
+
import { existsSync as existsSync13, readFileSync as readFileSync9 } from "fs";
|
|
50574
50639
|
import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
|
|
50575
50640
|
import * as path24 from "path";
|
|
50576
50641
|
async function migrateContextToKnowledge(directory, config3) {
|
|
50577
50642
|
const sentinelPath = path24.join(directory, ".swarm", ".knowledge-migrated");
|
|
50578
50643
|
const contextPath = path24.join(directory, ".swarm", "context.md");
|
|
50579
50644
|
const knowledgePath = resolveSwarmKnowledgePath(directory);
|
|
50580
|
-
if (
|
|
50645
|
+
if (existsSync13(sentinelPath)) {
|
|
50581
50646
|
return {
|
|
50582
50647
|
migrated: false,
|
|
50583
50648
|
entriesMigrated: 0,
|
|
@@ -50586,7 +50651,7 @@ async function migrateContextToKnowledge(directory, config3) {
|
|
|
50586
50651
|
skippedReason: "sentinel-exists"
|
|
50587
50652
|
};
|
|
50588
50653
|
}
|
|
50589
|
-
if (!
|
|
50654
|
+
if (!existsSync13(contextPath)) {
|
|
50590
50655
|
return {
|
|
50591
50656
|
migrated: false,
|
|
50592
50657
|
entriesMigrated: 0,
|
|
@@ -50772,7 +50837,7 @@ function truncateLesson(text) {
|
|
|
50772
50837
|
}
|
|
50773
50838
|
function inferProjectName(directory) {
|
|
50774
50839
|
const packageJsonPath = path24.join(directory, "package.json");
|
|
50775
|
-
if (
|
|
50840
|
+
if (existsSync13(packageJsonPath)) {
|
|
50776
50841
|
try {
|
|
50777
50842
|
const pkg = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
|
|
50778
50843
|
if (pkg.name && typeof pkg.name === "string") {
|
|
@@ -62573,7 +62638,7 @@ init_dist();
|
|
|
62573
62638
|
init_config();
|
|
62574
62639
|
init_knowledge_store();
|
|
62575
62640
|
init_create_tool();
|
|
62576
|
-
import { existsSync as
|
|
62641
|
+
import { existsSync as existsSync37 } from "fs";
|
|
62577
62642
|
var DEFAULT_LIMIT = 10;
|
|
62578
62643
|
var MAX_LESSON_LENGTH = 200;
|
|
62579
62644
|
var VALID_CATEGORIES3 = [
|
|
@@ -62642,14 +62707,14 @@ function validateLimit(limit) {
|
|
|
62642
62707
|
}
|
|
62643
62708
|
async function readSwarmKnowledge(directory) {
|
|
62644
62709
|
const swarmPath = resolveSwarmKnowledgePath(directory);
|
|
62645
|
-
if (!
|
|
62710
|
+
if (!existsSync37(swarmPath)) {
|
|
62646
62711
|
return [];
|
|
62647
62712
|
}
|
|
62648
62713
|
return readKnowledge(swarmPath);
|
|
62649
62714
|
}
|
|
62650
62715
|
async function readHiveKnowledge() {
|
|
62651
62716
|
const hivePath = resolveHiveKnowledgePath();
|
|
62652
|
-
if (!
|
|
62717
|
+
if (!existsSync37(hivePath)) {
|
|
62653
62718
|
return [];
|
|
62654
62719
|
}
|
|
62655
62720
|
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.1",
|
|
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",
|