opencode-swarm 7.15.0 → 7.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -34,7 +34,7 @@ var package_default;
34
34
  var init_package = __esm(() => {
35
35
  package_default = {
36
36
  name: "opencode-swarm",
37
- version: "7.15.0",
37
+ version: "7.16.0",
38
38
  description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
39
39
  main: "dist/index.js",
40
40
  types: "dist/index.d.ts",
@@ -35365,6 +35365,20 @@ function validateLesson(candidate, existingLessons, meta3) {
35365
35365
  severity: null
35366
35366
  };
35367
35367
  }
35368
+ function validateSkillPath(p) {
35369
+ if (typeof p !== "string")
35370
+ return false;
35371
+ if (p.length === 0 || p.length > 256)
35372
+ return false;
35373
+ if (p.includes("\x00"))
35374
+ return false;
35375
+ if (path11.isAbsolute(p))
35376
+ return false;
35377
+ if (p.includes(".."))
35378
+ return false;
35379
+ const norm = p.replace(/\\/g, "/");
35380
+ return ALLOWED_SKILL_PATH_PREFIXES.some((prefix) => norm.startsWith(prefix));
35381
+ }
35368
35382
  async function quarantineEntry(directory, entryId, reason, reportedBy) {
35369
35383
  if (!directory || directory.includes("..")) {
35370
35384
  warn("[knowledge-validator] quarantineEntry: directory traversal attempt blocked");
@@ -35475,7 +35489,7 @@ async function restoreEntry(directory, entryId) {
35475
35489
  }
35476
35490
  }
35477
35491
  }
35478
- var import_proper_lockfile4, DANGEROUS_COMMAND_PATTERNS, SECURITY_DEGRADING_PATTERNS, INVISIBLE_FORMAT_CHARS, INJECTION_PATTERNS, VALID_CATEGORIES, TECH_REFERENCE_WORDS, ACTION_VERB_WORDS, NEGATION_PAIRS, VALID_DIRECTIVE_PRIORITIES;
35492
+ var import_proper_lockfile4, DANGEROUS_COMMAND_PATTERNS, SECURITY_DEGRADING_PATTERNS, INVISIBLE_FORMAT_CHARS, INJECTION_PATTERNS, VALID_CATEGORIES, TECH_REFERENCE_WORDS, ACTION_VERB_WORDS, NEGATION_PAIRS, ALLOWED_SKILL_PATH_PREFIXES, VALID_DIRECTIVE_PRIORITIES;
35479
35493
  var init_knowledge_validator = __esm(() => {
35480
35494
  init_logger();
35481
35495
  init_knowledge_store();
@@ -35581,6 +35595,10 @@ var init_knowledge_validator = __esm(() => {
35581
35595
  ["use", "don't use"],
35582
35596
  ["recommended", "not recommended"]
35583
35597
  ];
35598
+ ALLOWED_SKILL_PATH_PREFIXES = [
35599
+ ".opencode/skills/generated/",
35600
+ ".swarm/skills/proposals/"
35601
+ ];
35584
35602
  VALID_DIRECTIVE_PRIORITIES = new Set([
35585
35603
  "low",
35586
35604
  "medium",
@@ -36241,6 +36259,904 @@ var init_knowledge_curator = __esm(() => {
36241
36259
  };
36242
36260
  });
36243
36261
 
36262
+ // src/hooks/skill-improver-llm-factory.ts
36263
+ function resolveSkillImproverAgentName(sessionId) {
36264
+ const suffix = "skill_improver";
36265
+ const registeredNames = swarmState.skillImproverAgentNames;
36266
+ if (registeredNames.length === 1)
36267
+ return registeredNames[0];
36268
+ if (registeredNames.length === 0)
36269
+ return suffix;
36270
+ const prefixMap = new Map;
36271
+ for (const name of registeredNames) {
36272
+ const prefix = name.endsWith(suffix) ? name.slice(0, name.length - suffix.length) : "";
36273
+ prefixMap.set(prefix, name);
36274
+ }
36275
+ const matchForAgent = (agentName) => {
36276
+ let bestPrefix = "";
36277
+ let bestName = "";
36278
+ for (const [prefix, name] of prefixMap) {
36279
+ if (prefix && agentName.startsWith(prefix)) {
36280
+ if (prefix.length > bestPrefix.length) {
36281
+ bestPrefix = prefix;
36282
+ bestName = name;
36283
+ }
36284
+ }
36285
+ }
36286
+ return bestName;
36287
+ };
36288
+ if (sessionId) {
36289
+ const callingAgent = swarmState.activeAgent.get(sessionId);
36290
+ if (callingAgent) {
36291
+ const match = matchForAgent(callingAgent);
36292
+ if (match)
36293
+ return match;
36294
+ const defaultAgent = prefixMap.get("");
36295
+ if (defaultAgent)
36296
+ return defaultAgent;
36297
+ }
36298
+ }
36299
+ for (const activeAgentName of swarmState.activeAgent.values()) {
36300
+ const match = matchForAgent(activeAgentName);
36301
+ if (match)
36302
+ return match;
36303
+ }
36304
+ return prefixMap.get("") ?? registeredNames[0];
36305
+ }
36306
+ function createSkillImproverLLMDelegate(directory, sessionId) {
36307
+ const client = swarmState.opencodeClient;
36308
+ if (!client)
36309
+ return;
36310
+ return async (systemPrompt, userInput, signal) => {
36311
+ let ephemeralSessionId;
36312
+ const cleanup = () => {
36313
+ if (ephemeralSessionId) {
36314
+ const id = ephemeralSessionId;
36315
+ ephemeralSessionId = undefined;
36316
+ client.session.delete({ path: { id } }).catch(() => {});
36317
+ }
36318
+ };
36319
+ if (signal?.aborted) {
36320
+ cleanup();
36321
+ throw new Error("SKILL_IMPROVER_LLM_TIMEOUT");
36322
+ }
36323
+ signal?.addEventListener("abort", cleanup, { once: true });
36324
+ try {
36325
+ const createResult = await client.session.create({
36326
+ query: { directory }
36327
+ });
36328
+ if (!createResult.data) {
36329
+ throw new Error(`Failed to create skill_improver session: ${JSON.stringify(createResult.error)}`);
36330
+ }
36331
+ ephemeralSessionId = createResult.data.id;
36332
+ if (signal?.aborted)
36333
+ throw new Error("SKILL_IMPROVER_LLM_TIMEOUT");
36334
+ const agentName = resolveSkillImproverAgentName(sessionId);
36335
+ let promptResult;
36336
+ try {
36337
+ const prelude = systemPrompt ? `${systemPrompt}
36338
+
36339
+ ---
36340
+
36341
+ ${userInput}` : userInput;
36342
+ promptResult = await client.session.prompt({
36343
+ path: { id: ephemeralSessionId },
36344
+ body: {
36345
+ agent: agentName,
36346
+ tools: { write: false, edit: false, patch: false },
36347
+ parts: [{ type: "text", text: prelude }]
36348
+ }
36349
+ });
36350
+ } catch (err) {
36351
+ if (signal?.aborted)
36352
+ throw new Error("SKILL_IMPROVER_LLM_TIMEOUT");
36353
+ throw err;
36354
+ }
36355
+ if (!promptResult.data) {
36356
+ throw new Error(`skill_improver LLM prompt failed: ${JSON.stringify(promptResult.error)}`);
36357
+ }
36358
+ const textParts = promptResult.data.parts.filter((p) => p.type === "text");
36359
+ return textParts.map((p) => p.text).join(`
36360
+ `);
36361
+ } finally {
36362
+ signal?.removeEventListener("abort", cleanup);
36363
+ cleanup();
36364
+ }
36365
+ };
36366
+ }
36367
+ var init_skill_improver_llm_factory = __esm(() => {
36368
+ init_state();
36369
+ });
36370
+
36371
+ // src/services/skill-generator.ts
36372
+ import { existsSync as existsSync9 } from "fs";
36373
+ import { mkdir as mkdir5, readFile as readFile5, rename as rename3, writeFile as writeFile6 } from "fs/promises";
36374
+ import * as path14 from "path";
36375
+ function sanitizeSlug(input) {
36376
+ const lc = input.toLowerCase().trim();
36377
+ const mapped = lc.replace(/[^a-z0-9-]+/g, "-").replace(/-+/g, "-");
36378
+ const trimmed = mapped.replace(/^-+|-+$/g, "");
36379
+ return trimmed.slice(0, 64);
36380
+ }
36381
+ function isValidSlug(slug) {
36382
+ return SLUG_PATTERN.test(slug);
36383
+ }
36384
+ function proposalPath(directory, slug) {
36385
+ return path14.join(directory, ".swarm", "skills", "proposals", `${slug}.md`);
36386
+ }
36387
+ function activePath(directory, slug) {
36388
+ return path14.join(directory, ".opencode", "skills", "generated", slug, "SKILL.md");
36389
+ }
36390
+ function activeRepoRelativePath(slug) {
36391
+ return `.opencode/skills/generated/${slug}/SKILL.md`;
36392
+ }
36393
+ async function selectCandidateEntries(directory, opts) {
36394
+ const swarm = await readKnowledge(resolveSwarmKnowledgePath(directory));
36395
+ const hivePath = resolveHiveKnowledgePath();
36396
+ const hive = existsSync9(hivePath) ? await readKnowledge(hivePath) : [];
36397
+ const all = [...swarm, ...hive];
36398
+ return all.filter((e) => {
36399
+ if (e.status === "archived")
36400
+ return false;
36401
+ if (e.confidence < opts.minConfidence)
36402
+ return false;
36403
+ const confirmations = (e.confirmed_by ?? []).length;
36404
+ if (confirmations < opts.minConfirmations)
36405
+ return false;
36406
+ if (e.generated_skill_slug)
36407
+ return false;
36408
+ return true;
36409
+ });
36410
+ }
36411
+ function clusterKey(e) {
36412
+ const t = (e.triggers ?? []).map((s) => s.toLowerCase()).sort().join("|");
36413
+ if (t)
36414
+ return `trigger:${t}`;
36415
+ const tools = (e.applies_to_tools ?? []).map((s) => s.toLowerCase()).sort();
36416
+ const agents = (e.applies_to_agents ?? []).map((s) => s.toLowerCase()).sort();
36417
+ if (tools.length > 0 || agents.length > 0) {
36418
+ return `tool-agent:${tools.join("+")}::${agents.join("+")}`;
36419
+ }
36420
+ const tagSig = e.tags.slice(0, 3).map((s) => s.toLowerCase()).sort().join(",");
36421
+ return `cat:${e.category}:${tagSig}`;
36422
+ }
36423
+ function clusterEntries(entries) {
36424
+ const groups = new Map;
36425
+ for (const e of entries) {
36426
+ const k = clusterKey(e);
36427
+ const arr = groups.get(k) ?? [];
36428
+ arr.push(e);
36429
+ groups.set(k, arr);
36430
+ }
36431
+ const clusters = [];
36432
+ for (const [key, arr] of groups) {
36433
+ const triggers = uniqueStrings(arr.flatMap((e) => e.triggers ?? []));
36434
+ const required3 = uniqueStrings(arr.flatMap((e) => e.required_actions ?? []));
36435
+ const forbidden = uniqueStrings(arr.flatMap((e) => e.forbidden_actions ?? []));
36436
+ const agents = uniqueStrings(arr.flatMap((e) => e.applies_to_agents ?? []));
36437
+ const checks5 = uniqueStrings(arr.flatMap((e) => e.verification_checks ?? []));
36438
+ const avgConf = arr.reduce((s, e) => s + e.confidence, 0) / Math.max(1, arr.length);
36439
+ const slugSeed = triggers[0] ?? required3[0] ?? arr[0]?.tags?.[0] ?? arr[0]?.category ?? "lesson";
36440
+ const slug = sanitizeSlug(slugSeed);
36441
+ const title = triggers[0] ?? required3[0] ?? `Lessons: ${arr[0]?.category ?? "general"} (${arr.length})`;
36442
+ clusters.push({
36443
+ slug: isValidSlug(slug) ? slug : sanitizeSlug(`cluster-${key.slice(0, 12)}`),
36444
+ title,
36445
+ entries: arr,
36446
+ triggers,
36447
+ required_actions: required3,
36448
+ forbidden_actions: forbidden,
36449
+ target_agents: agents,
36450
+ verification_checks: checks5,
36451
+ avgConfidence: avgConf
36452
+ });
36453
+ }
36454
+ clusters.sort((a, b) => b.entries.length - a.entries.length || b.avgConfidence - a.avgConfidence || a.slug.localeCompare(b.slug));
36455
+ return clusters;
36456
+ }
36457
+ function uniqueStrings(arr) {
36458
+ return [...new Set(arr.filter((s) => typeof s === "string" && s.length > 0))];
36459
+ }
36460
+ function renderSkillMarkdown(cluster, mode = "active") {
36461
+ const description = cluster.title.length > 200 ? `${cluster.title.slice(0, 197)}\u2026` : cluster.title;
36462
+ const ids = cluster.entries.map((e) => ` - ${e.id}`).join(`
36463
+ `);
36464
+ const lines = [];
36465
+ lines.push("---");
36466
+ lines.push(`name: ${cluster.slug}`);
36467
+ lines.push(`description: ${escapeYaml(description)}`);
36468
+ lines.push("generated_from_knowledge:");
36469
+ lines.push(ids);
36470
+ lines.push(`confidence: ${cluster.avgConfidence.toFixed(2)}`);
36471
+ lines.push(`status: ${mode === "active" ? "active" : "draft"}`);
36472
+ lines.push("---");
36473
+ lines.push("");
36474
+ lines.push("<!-- generated by opencode-swarm skill-generator. Do not edit by hand; edits will be preserved on regeneration only with controlled update mode. -->");
36475
+ lines.push("");
36476
+ lines.push(`# ${escapeMarkdown(cluster.title)}`);
36477
+ lines.push("");
36478
+ lines.push("## Trigger");
36479
+ lines.push("");
36480
+ for (const t of cluster.triggers.length > 0 ? cluster.triggers : ["(no explicit trigger metadata; cluster derived from category/tags)"]) {
36481
+ lines.push(`- ${escapeMarkdown(t)}`);
36482
+ }
36483
+ lines.push("");
36484
+ lines.push("## Required Procedure");
36485
+ lines.push("");
36486
+ if (cluster.required_actions.length > 0) {
36487
+ for (const r of cluster.required_actions)
36488
+ lines.push(`- ${escapeMarkdown(r)}`);
36489
+ } else {
36490
+ lines.push("- Apply the lessons listed under Source Knowledge IDs.");
36491
+ }
36492
+ lines.push("");
36493
+ lines.push("## Forbidden Shortcuts");
36494
+ lines.push("");
36495
+ if (cluster.forbidden_actions.length > 0) {
36496
+ for (const f of cluster.forbidden_actions)
36497
+ lines.push(`- ${escapeMarkdown(f)}`);
36498
+ } else {
36499
+ lines.push("- (none recorded)");
36500
+ }
36501
+ lines.push("");
36502
+ lines.push("## Delegation Template");
36503
+ lines.push("");
36504
+ lines.push("When delegating a task affected by this skill, include:");
36505
+ lines.push("");
36506
+ lines.push("```");
36507
+ lines.push(`SKILLS: file:.opencode/skills/generated/${cluster.slug}/SKILL.md`);
36508
+ lines.push("```");
36509
+ lines.push("");
36510
+ lines.push("## Reviewer Checks");
36511
+ lines.push("");
36512
+ if (cluster.verification_checks.length > 0) {
36513
+ for (const c of cluster.verification_checks)
36514
+ lines.push(`- ${escapeMarkdown(c)}`);
36515
+ } else {
36516
+ lines.push("- Verify each required action above appears in the diff.");
36517
+ }
36518
+ lines.push("");
36519
+ const needsTestEng = cluster.entries.some((e) => e.category === "testing" || (e.tags ?? []).includes("testing"));
36520
+ if (needsTestEng) {
36521
+ lines.push("## Test Engineer Checks");
36522
+ lines.push("");
36523
+ lines.push("- Add or update tests covering the trigger condition and the forbidden shortcut.");
36524
+ lines.push("");
36525
+ }
36526
+ lines.push("## Source Knowledge IDs");
36527
+ lines.push("");
36528
+ for (const e of cluster.entries)
36529
+ lines.push(`- ${e.id} \u2014 ${escapeMarkdown(e.lesson)}`);
36530
+ lines.push("");
36531
+ return lines.join(`
36532
+ `);
36533
+ }
36534
+ function escapeYaml(s) {
36535
+ if (/[:#\n\r"']/.test(s)) {
36536
+ return JSON.stringify(s);
36537
+ }
36538
+ return s;
36539
+ }
36540
+ function escapeMarkdown(s) {
36541
+ return s.replace(/[\r\n]+/g, " ").slice(0, 280);
36542
+ }
36543
+ async function atomicWrite(p, content) {
36544
+ await mkdir5(path14.dirname(p), { recursive: true });
36545
+ const tmp = `${p}.tmp-${process.pid}-${Date.now()}`;
36546
+ await writeFile6(tmp, content, "utf-8");
36547
+ await rename3(tmp, p);
36548
+ }
36549
+ async function generateSkills(req) {
36550
+ const minConfidence = req.minConfidence ?? 0.85;
36551
+ const minConfirmations = req.minConfirmations ?? 2;
36552
+ const candidates = await selectCandidateEntries(req.directory, {
36553
+ minConfidence,
36554
+ minConfirmations
36555
+ });
36556
+ let pool;
36557
+ if (req.sourceKnowledgeIds && req.sourceKnowledgeIds.length > 0) {
36558
+ const idSet = new Set(req.sourceKnowledgeIds);
36559
+ const swarm = await readKnowledge(resolveSwarmKnowledgePath(req.directory));
36560
+ const hivePath = resolveHiveKnowledgePath();
36561
+ const hive = existsSync9(hivePath) ? await readKnowledge(hivePath) : [];
36562
+ pool = [...swarm, ...hive].filter((e) => idSet.has(e.id) && e.status !== "archived");
36563
+ } else {
36564
+ pool = candidates;
36565
+ }
36566
+ const clusters = clusterEntries(pool);
36567
+ const result = { written: [], skipped: [] };
36568
+ for (let i = 0;i < clusters.length; i++) {
36569
+ const cluster = clusters[i];
36570
+ if (req.slug && i === 0) {
36571
+ const overridden = sanitizeSlug(req.slug);
36572
+ if (!isValidSlug(overridden)) {
36573
+ result.skipped.push({
36574
+ slug: req.slug,
36575
+ reason: "slug rejected by sanitizer (path traversal or invalid chars)"
36576
+ });
36577
+ continue;
36578
+ }
36579
+ cluster.slug = overridden;
36580
+ }
36581
+ if (!isValidSlug(cluster.slug)) {
36582
+ result.skipped.push({
36583
+ slug: cluster.slug,
36584
+ reason: "computed slug invalid"
36585
+ });
36586
+ continue;
36587
+ }
36588
+ const targetPath = req.mode === "active" ? activePath(req.directory, cluster.slug) : proposalPath(req.directory, cluster.slug);
36589
+ const repoRel = path14.relative(req.directory, targetPath).replace(/\\/g, "/");
36590
+ if (!validateSkillPath(repoRel)) {
36591
+ result.skipped.push({
36592
+ slug: cluster.slug,
36593
+ reason: `target path ${repoRel} not under allowed prefixes (${ALLOWED_SKILL_PATH_PREFIXES.join(", ")})`
36594
+ });
36595
+ continue;
36596
+ }
36597
+ let preserved = false;
36598
+ if (req.mode === "active" && existsSync9(targetPath) && !req.force) {
36599
+ const existing = await readFile5(targetPath, "utf-8");
36600
+ if (!existing.includes("generated by opencode-swarm skill-generator")) {
36601
+ preserved = true;
36602
+ result.skipped.push({
36603
+ slug: cluster.slug,
36604
+ reason: "manually edited skill exists at target path; rerun with force=true to overwrite"
36605
+ });
36606
+ continue;
36607
+ }
36608
+ }
36609
+ const content = renderSkillMarkdown(cluster, req.mode);
36610
+ await atomicWrite(targetPath, content);
36611
+ if (req.mode === "active") {
36612
+ await stampSourceEntries(req.directory, cluster.slug, cluster.entries.map((e) => e.id));
36613
+ }
36614
+ result.written.push({
36615
+ slug: cluster.slug,
36616
+ path: targetPath,
36617
+ mode: req.mode,
36618
+ sourceKnowledgeIds: cluster.entries.map((e) => e.id),
36619
+ preserved
36620
+ });
36621
+ }
36622
+ return result;
36623
+ }
36624
+ async function stampSourceEntries(directory, slug, ids) {
36625
+ if (!ids || ids.length === 0)
36626
+ return;
36627
+ const swarmPath = resolveSwarmKnowledgePath(directory);
36628
+ const swarm = await readKnowledge(swarmPath);
36629
+ const idSet = new Set(ids);
36630
+ let touched = false;
36631
+ const repoRel = activeRepoRelativePath(slug);
36632
+ for (const e of swarm) {
36633
+ if (!idSet.has(e.id))
36634
+ continue;
36635
+ e.generated_skill_slug = slug;
36636
+ e.generated_skill_path = repoRel;
36637
+ e.updated_at = new Date().toISOString();
36638
+ touched = true;
36639
+ }
36640
+ if (touched)
36641
+ await rewriteKnowledge(swarmPath, swarm);
36642
+ const hivePath = resolveHiveKnowledgePath();
36643
+ if (!existsSync9(hivePath))
36644
+ return;
36645
+ const hive = await readKnowledge(hivePath);
36646
+ let touchedHive = false;
36647
+ for (const e of hive) {
36648
+ if (!idSet.has(e.id))
36649
+ continue;
36650
+ e.generated_skill_slug = slug;
36651
+ e.generated_skill_path = repoRel;
36652
+ e.updated_at = new Date().toISOString();
36653
+ touchedHive = true;
36654
+ }
36655
+ if (touchedHive)
36656
+ await rewriteKnowledge(hivePath, hive);
36657
+ }
36658
+ async function listSkills(directory) {
36659
+ const result = {
36660
+ proposals: [],
36661
+ active: []
36662
+ };
36663
+ const proposalsDir = path14.join(directory, ".swarm", "skills", "proposals");
36664
+ const activeDir = path14.join(directory, ".opencode", "skills", "generated");
36665
+ const fs7 = await import("fs/promises");
36666
+ if (existsSync9(proposalsDir)) {
36667
+ const entries = await fs7.readdir(proposalsDir);
36668
+ for (const f of entries) {
36669
+ if (!f.endsWith(".md"))
36670
+ continue;
36671
+ const slug = f.replace(/\.md$/, "");
36672
+ result.proposals.push({
36673
+ slug,
36674
+ path: path14.join(proposalsDir, f)
36675
+ });
36676
+ }
36677
+ }
36678
+ if (existsSync9(activeDir)) {
36679
+ const entries = await fs7.readdir(activeDir, { withFileTypes: true });
36680
+ for (const e of entries) {
36681
+ if (!e.isDirectory())
36682
+ continue;
36683
+ const skillPath = path14.join(activeDir, e.name, "SKILL.md");
36684
+ if (existsSync9(skillPath)) {
36685
+ result.active.push({
36686
+ slug: e.name,
36687
+ path: skillPath
36688
+ });
36689
+ }
36690
+ }
36691
+ }
36692
+ return result;
36693
+ }
36694
+ var SLUG_PATTERN;
36695
+ var init_skill_generator = __esm(() => {
36696
+ init_knowledge_store();
36697
+ init_knowledge_validator();
36698
+ init_logger();
36699
+ SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
36700
+ });
36701
+
36702
+ // src/services/skill-improver-quota.ts
36703
+ import { existsSync as existsSync10 } from "fs";
36704
+ import { mkdir as mkdir6, readFile as readFile6, rename as rename4, writeFile as writeFile7 } from "fs/promises";
36705
+ import * as path15 from "path";
36706
+ async function acquireLock(dir) {
36707
+ const acquire = import_proper_lockfile5.default.lock(dir, LOCK_RETRY_OPTS);
36708
+ let timer;
36709
+ const timeout = new Promise((_, reject) => {
36710
+ timer = setTimeout(() => {
36711
+ reject(new Error(`SKILL_IMPROVER_QUOTA_LOCK_TIMEOUT: failed to acquire lock on ${dir} within ${LOCK_ACQUIRE_TIMEOUT_MS}ms`));
36712
+ }, LOCK_ACQUIRE_TIMEOUT_MS);
36713
+ });
36714
+ try {
36715
+ const release = await Promise.race([acquire, timeout]);
36716
+ return release;
36717
+ } finally {
36718
+ if (timer)
36719
+ clearTimeout(timer);
36720
+ }
36721
+ }
36722
+ function resolveQuotaPath(directory) {
36723
+ return path15.join(directory, ".swarm", "skill-improver-quota.json");
36724
+ }
36725
+ function todayKey(window, now = new Date) {
36726
+ if (window === "utc") {
36727
+ return now.toISOString().slice(0, 10);
36728
+ }
36729
+ const yr = now.getFullYear();
36730
+ const m = String(now.getMonth() + 1).padStart(2, "0");
36731
+ const d = String(now.getDate()).padStart(2, "0");
36732
+ return `${yr}-${m}-${d}`;
36733
+ }
36734
+ async function readState(filePath) {
36735
+ if (!existsSync10(filePath))
36736
+ return null;
36737
+ try {
36738
+ const raw = await readFile6(filePath, "utf-8");
36739
+ const parsed = JSON.parse(raw);
36740
+ if (typeof parsed.date !== "string" || typeof parsed.calls_used !== "number" || typeof parsed.max_calls !== "number" || parsed.window !== "utc" && parsed.window !== "local") {
36741
+ return null;
36742
+ }
36743
+ return parsed;
36744
+ } catch {
36745
+ return null;
36746
+ }
36747
+ }
36748
+ async function writeState(filePath, state) {
36749
+ await mkdir6(path15.dirname(filePath), { recursive: true });
36750
+ const tmp = `${filePath}.tmp-${process.pid}`;
36751
+ await writeFile7(tmp, JSON.stringify(state, null, 2), "utf-8");
36752
+ await rename4(tmp, filePath);
36753
+ }
36754
+ async function getQuotaState(directory, opts) {
36755
+ const filePath = resolveQuotaPath(directory);
36756
+ const today = todayKey(opts.window, opts.now);
36757
+ const existing = await readState(filePath);
36758
+ if (!existing || existing.date !== today || existing.window !== opts.window) {
36759
+ const fresh = {
36760
+ date: today,
36761
+ calls_used: 0,
36762
+ max_calls: opts.maxCalls,
36763
+ window: opts.window
36764
+ };
36765
+ await writeState(filePath, fresh);
36766
+ return fresh;
36767
+ }
36768
+ return { ...existing, max_calls: opts.maxCalls };
36769
+ }
36770
+ async function reserveQuota(directory, opts) {
36771
+ const filePath = resolveQuotaPath(directory);
36772
+ await mkdir6(path15.dirname(filePath), { recursive: true });
36773
+ let release = null;
36774
+ try {
36775
+ release = await acquireLock(path15.dirname(filePath));
36776
+ const state = await getQuotaState(directory, opts);
36777
+ if (state.calls_used + opts.nCalls > opts.maxCalls) {
36778
+ return {
36779
+ allowed: false,
36780
+ state,
36781
+ reason: `daily quota exhausted: used=${state.calls_used} requested=${opts.nCalls} max=${opts.maxCalls}`
36782
+ };
36783
+ }
36784
+ const next = {
36785
+ ...state,
36786
+ calls_used: state.calls_used + opts.nCalls,
36787
+ max_calls: opts.maxCalls,
36788
+ last_run_at: (opts.now ?? new Date).toISOString()
36789
+ };
36790
+ await writeState(filePath, next);
36791
+ return { allowed: true, state: next };
36792
+ } finally {
36793
+ if (release) {
36794
+ try {
36795
+ await release();
36796
+ } catch {}
36797
+ }
36798
+ }
36799
+ }
36800
+ async function releaseQuota(directory, opts) {
36801
+ const filePath = resolveQuotaPath(directory);
36802
+ await mkdir6(path15.dirname(filePath), { recursive: true });
36803
+ let release = null;
36804
+ try {
36805
+ release = await acquireLock(path15.dirname(filePath));
36806
+ const state = await getQuotaState(directory, opts);
36807
+ const next = {
36808
+ ...state,
36809
+ calls_used: Math.max(0, state.calls_used - opts.nCalls),
36810
+ max_calls: opts.maxCalls
36811
+ };
36812
+ await writeState(filePath, next);
36813
+ return next;
36814
+ } finally {
36815
+ if (release) {
36816
+ try {
36817
+ await release();
36818
+ } catch {}
36819
+ }
36820
+ }
36821
+ }
36822
+ var import_proper_lockfile5, LOCK_ACQUIRE_TIMEOUT_MS = 1e4, LOCK_RETRY_OPTS;
36823
+ var init_skill_improver_quota = __esm(() => {
36824
+ import_proper_lockfile5 = __toESM(require_proper_lockfile(), 1);
36825
+ LOCK_RETRY_OPTS = {
36826
+ retries: {
36827
+ retries: 30,
36828
+ minTimeout: 50,
36829
+ maxTimeout: 200,
36830
+ factor: 1.5
36831
+ },
36832
+ stale: 5000
36833
+ };
36834
+ });
36835
+
36836
+ // src/services/skill-improver.ts
36837
+ import { existsSync as existsSync11 } from "fs";
36838
+ import { mkdir as mkdir7, rename as rename5, writeFile as writeFile8 } from "fs/promises";
36839
+ import * as path16 from "path";
36840
+ function timestampSlug(d) {
36841
+ return d.toISOString().replace(/[:.]/g, "-");
36842
+ }
36843
+ async function atomicWrite2(p, content) {
36844
+ await mkdir7(path16.dirname(p), { recursive: true });
36845
+ const tmp = `${p}.tmp-${process.pid}-${Date.now()}`;
36846
+ await writeFile8(tmp, content, "utf-8");
36847
+ await rename5(tmp, p);
36848
+ }
36849
+ async function gatherInventory(directory) {
36850
+ const swarm = await readKnowledge(resolveSwarmKnowledgePath(directory));
36851
+ const hivePath = resolveHiveKnowledgePath();
36852
+ const hive = existsSync11(hivePath) ? await readKnowledge(hivePath) : [];
36853
+ const archived = [...swarm, ...hive].filter((e) => e.status === "archived").length;
36854
+ const skills = await listSkills(directory);
36855
+ const matureCandidates = swarm.concat(hive).filter((e) => e.status !== "archived" && e.confidence >= 0.85 && !e.generated_skill_slug && (e.confirmed_by ?? []).length >= 2);
36856
+ return {
36857
+ knowledge: { swarm: swarm.length, hive: hive.length, archived },
36858
+ skills: {
36859
+ proposals: skills.proposals.length,
36860
+ active: skills.active.length
36861
+ },
36862
+ highConfidenceClusters: matureCandidates.length,
36863
+ matureCandidates
36864
+ };
36865
+ }
36866
+ function buildSystemPrompt(targets, cfg) {
36867
+ return `You are skill_improver. Targets: ${targets.join(", ")}. Mode: ${cfg.write_mode}.
36868
+
36869
+ Output a single complete markdown proposal under 6000 chars. Sections required:
36870
+ 1. ## Inventory snapshot
36871
+ 2. ## Repeated ignored or violated directives (cite knowledge ids)
36872
+ 3. ## Concrete recommendations
36873
+ 4. ## Optional cluster suggestions for new draft skills (slug, title, source ids, target agents) \u2014 leave empty if none qualify
36874
+ 5. ## Risks and known limitations
36875
+
36876
+ Do NOT propose direct source-code edits. Do NOT call any tools. Return only the markdown body.`;
36877
+ }
36878
+ function buildUserPrompt(inv) {
36879
+ const matureRows = inv.matureCandidates.slice(0, 25).map((e) => `- ${e.id} | conf=${e.confidence.toFixed(2)} | ${e.category} | "${e.lesson.slice(0, 140).replace(/\n/g, " ")}"`).join(`
36880
+ `);
36881
+ return `INVENTORY
36882
+ swarm_entries: ${inv.knowledge.swarm}
36883
+ hive_entries: ${inv.knowledge.hive}
36884
+ archived: ${inv.knowledge.archived}
36885
+ draft_skills: ${inv.skills.proposals}
36886
+ active_skills: ${inv.skills.active}
36887
+ mature_uncompiled_clusters: ${inv.highConfidenceClusters}
36888
+
36889
+ TOP MATURE CANDIDATES (first 25):
36890
+ ${matureRows || "(none)"}
36891
+ `;
36892
+ }
36893
+ function isAbortError(err) {
36894
+ return err instanceof Error && (err.name === "AbortError" || err.message === "skill_improver_aborted");
36895
+ }
36896
+ function throwIfAborted(signal) {
36897
+ if (!signal?.aborted)
36898
+ return;
36899
+ const err = new Error("skill_improver_aborted");
36900
+ err.name = "AbortError";
36901
+ throw err;
36902
+ }
36903
+ function buildDeterministicProposal(args) {
36904
+ const lines = [];
36905
+ lines.push("---");
36906
+ lines.push("source: deterministic_fallback");
36907
+ lines.push(`generated_at: ${args.now.toISOString()}`);
36908
+ lines.push(`targets: [${args.targets.join(", ")}]`);
36909
+ lines.push(`model: ${args.model ?? "(none \u2014 deterministic body)"}`);
36910
+ lines.push("---");
36911
+ lines.push("");
36912
+ lines.push("# Skill Improvement Proposal (deterministic fallback)");
36913
+ lines.push("");
36914
+ lines.push("> No OpenCode LLM client was available when this proposal was generated. The body below is a deterministic inventory summary, not an LLM-derived analysis. To produce a real LLM proposal, run `skill_improve` from a session where the OpenCode runtime has wired `swarmState.opencodeClient`, or set `skill_improver.allow_deterministic_fallback: false` to force a hard failure instead of this fallback.");
36915
+ lines.push("");
36916
+ lines.push("## Inventory snapshot");
36917
+ lines.push(`- Knowledge entries: swarm=${args.inventory.knowledge.swarm}, hive=${args.inventory.knowledge.hive}, archived=${args.inventory.knowledge.archived}`);
36918
+ lines.push(`- Generated skills: proposals=${args.inventory.skills.proposals}, active=${args.inventory.skills.active}`);
36919
+ lines.push(`- High-confidence un-skill'd clusters: ${args.inventory.highConfidenceClusters}`);
36920
+ lines.push("");
36921
+ lines.push("## Recommendations");
36922
+ if (args.inventory.highConfidenceClusters > 0) {
36923
+ lines.push(`- Run \`skill_generate { mode: "draft" }\` to emit ${args.inventory.highConfidenceClusters} draft SKILL.md proposal(s).`);
36924
+ }
36925
+ if (args.targets.includes("architect_prompt")) {
36926
+ lines.push("- Manually review architect prompt against current high-priority directives.");
36927
+ }
36928
+ if (args.targets.includes("spec")) {
36929
+ lines.push("- Audit `.swarm/spec.md` for drift.");
36930
+ }
36931
+ if (args.targets.includes("knowledge")) {
36932
+ lines.push("- Archive low-confidence (< 0.3) entries with applied_explicit_count == 0 and shown_count > 5.");
36933
+ }
36934
+ lines.push("");
36935
+ return lines.join(`
36936
+ `);
36937
+ }
36938
+ function buildLLMProposalFrame(args) {
36939
+ const lines = [];
36940
+ lines.push("---");
36941
+ lines.push("source: llm");
36942
+ lines.push(`generated_at: ${args.now.toISOString()}`);
36943
+ lines.push(`targets: [${args.targets.join(", ")}]`);
36944
+ if (args.model)
36945
+ lines.push(`model: ${args.model}`);
36946
+ lines.push("---");
36947
+ lines.push("");
36948
+ lines.push(args.body.trim());
36949
+ lines.push("");
36950
+ return lines.join(`
36951
+ `);
36952
+ }
36953
+ async function runSkillImprover(req) {
36954
+ const cfg = req.config;
36955
+ const now = req.now ?? new Date;
36956
+ const targets = req.targets && req.targets.length > 0 ? req.targets : cfg.targets;
36957
+ const writeMode = req.mode ?? cfg.write_mode;
36958
+ const maxCalls = Math.max(1, Math.min(cfg.max_calls_per_day, req.maxCalls ?? 1));
36959
+ const noQuota = {
36960
+ date: now.toISOString().slice(0, 10),
36961
+ calls_used: 0,
36962
+ max_calls: cfg.max_calls_per_day
36963
+ };
36964
+ if (!cfg.enabled) {
36965
+ return {
36966
+ ran: false,
36967
+ reason: "skill_improver.enabled is false",
36968
+ quota: noQuota
36969
+ };
36970
+ }
36971
+ if (req.signal?.aborted) {
36972
+ return {
36973
+ ran: false,
36974
+ reason: "skill_improver aborted",
36975
+ quota: noQuota
36976
+ };
36977
+ }
36978
+ const delegate = req.delegate ?? createSkillImproverLLMDelegate(req.directory, req.sessionId);
36979
+ if (!delegate && !cfg.allow_deterministic_fallback) {
36980
+ return {
36981
+ ran: false,
36982
+ reason: "no_llm_client: skill_improver.allow_deterministic_fallback is false and no OpenCode client is wired. Run from a session where swarmState.opencodeClient is available, or enable allow_deterministic_fallback to use the offline path.",
36983
+ quota: noQuota
36984
+ };
36985
+ }
36986
+ const reservation = await reserveQuota(req.directory, {
36987
+ nCalls: maxCalls,
36988
+ maxCalls: cfg.max_calls_per_day,
36989
+ window: cfg.quota_window,
36990
+ now
36991
+ });
36992
+ if (!reservation.allowed) {
36993
+ return {
36994
+ ran: false,
36995
+ reason: reservation.reason ?? "quota exhausted",
36996
+ quota: {
36997
+ date: reservation.state.date,
36998
+ calls_used: reservation.state.calls_used,
36999
+ max_calls: reservation.state.max_calls
37000
+ }
37001
+ };
37002
+ }
37003
+ let networkStarted = false;
37004
+ let sideEffectsStarted = false;
37005
+ const reservationQuota = {
37006
+ date: reservation.state.date,
37007
+ calls_used: reservation.state.calls_used,
37008
+ max_calls: reservation.state.max_calls
37009
+ };
37010
+ const releaseReservedQuota = async () => {
37011
+ const released = await releaseQuota(req.directory, {
37012
+ nCalls: maxCalls,
37013
+ maxCalls: cfg.max_calls_per_day,
37014
+ window: cfg.quota_window,
37015
+ now
37016
+ }).catch(() => ({
37017
+ ...reservation.state,
37018
+ calls_used: Math.max(0, reservation.state.calls_used - maxCalls)
37019
+ }));
37020
+ return {
37021
+ date: released.date,
37022
+ calls_used: released.calls_used,
37023
+ max_calls: released.max_calls
37024
+ };
37025
+ };
37026
+ const abortResult = async () => ({
37027
+ ran: false,
37028
+ reason: "skill_improver aborted",
37029
+ quota: networkStarted || sideEffectsStarted ? reservationQuota : await releaseReservedQuota()
37030
+ });
37031
+ let inventory;
37032
+ try {
37033
+ throwIfAborted(req.signal);
37034
+ inventory = await gatherInventory(req.directory);
37035
+ throwIfAborted(req.signal);
37036
+ } catch (err) {
37037
+ if (isAbortError(err)) {
37038
+ return await abortResult();
37039
+ }
37040
+ await releaseQuota(req.directory, {
37041
+ nCalls: maxCalls,
37042
+ maxCalls: cfg.max_calls_per_day,
37043
+ window: cfg.quota_window,
37044
+ now
37045
+ }).catch(() => {});
37046
+ return {
37047
+ ran: false,
37048
+ reason: `inventory_failed: ${err instanceof Error ? err.message : String(err)}`,
37049
+ quota: {
37050
+ date: reservation.state.date,
37051
+ calls_used: reservation.state.calls_used - maxCalls,
37052
+ max_calls: reservation.state.max_calls
37053
+ }
37054
+ };
37055
+ }
37056
+ let body;
37057
+ let source;
37058
+ if (delegate) {
37059
+ try {
37060
+ throwIfAborted(req.signal);
37061
+ networkStarted = true;
37062
+ body = await delegate(buildSystemPrompt(targets, { ...cfg, write_mode: writeMode }), buildUserPrompt(inventory), req.signal);
37063
+ throwIfAborted(req.signal);
37064
+ if (!body || body.trim().length === 0) {
37065
+ throw new Error("empty LLM response");
37066
+ }
37067
+ source = "llm";
37068
+ } catch (err) {
37069
+ if (isAbortError(err)) {
37070
+ return await abortResult();
37071
+ }
37072
+ return {
37073
+ ran: false,
37074
+ reason: `llm_call_failed: ${err instanceof Error ? err.message : String(err)}`,
37075
+ quota: {
37076
+ date: reservation.state.date,
37077
+ calls_used: reservation.state.calls_used,
37078
+ max_calls: reservation.state.max_calls
37079
+ }
37080
+ };
37081
+ }
37082
+ } else {
37083
+ try {
37084
+ throwIfAborted(req.signal);
37085
+ } catch (err) {
37086
+ if (isAbortError(err)) {
37087
+ return await abortResult();
37088
+ }
37089
+ throw err;
37090
+ }
37091
+ body = "";
37092
+ source = "deterministic_fallback";
37093
+ }
37094
+ let draftSkillsWritten;
37095
+ if (writeMode === "draft_skills" && inventory.matureCandidates.length > 0) {
37096
+ try {
37097
+ throwIfAborted(req.signal);
37098
+ } catch (err) {
37099
+ if (isAbortError(err)) {
37100
+ return await abortResult();
37101
+ }
37102
+ throw err;
37103
+ }
37104
+ sideEffectsStarted = true;
37105
+ const gen = await generateSkills({
37106
+ directory: req.directory,
37107
+ mode: "draft",
37108
+ minConfidence: 0.85,
37109
+ minConfirmations: 2
37110
+ });
37111
+ draftSkillsWritten = gen.written.map((w) => ({
37112
+ slug: w.slug,
37113
+ path: w.path,
37114
+ sourceKnowledgeIds: w.sourceKnowledgeIds
37115
+ }));
37116
+ }
37117
+ try {
37118
+ throwIfAborted(req.signal);
37119
+ } catch (err) {
37120
+ if (isAbortError(err)) {
37121
+ return await abortResult();
37122
+ }
37123
+ throw err;
37124
+ }
37125
+ const proposalDir = path16.join(req.directory, ".swarm", "skill-improver", "proposals");
37126
+ const proposalFile = path16.join(proposalDir, `${timestampSlug(now)}.md`);
37127
+ const finalBody = source === "llm" ? buildLLMProposalFrame({
37128
+ body,
37129
+ targets,
37130
+ model: cfg.model,
37131
+ now
37132
+ }) : buildDeterministicProposal({
37133
+ targets,
37134
+ inventory,
37135
+ model: cfg.model,
37136
+ now
37137
+ });
37138
+ sideEffectsStarted = true;
37139
+ await atomicWrite2(proposalFile, finalBody);
37140
+ return {
37141
+ ran: true,
37142
+ proposalPath: proposalFile,
37143
+ source,
37144
+ quota: {
37145
+ date: reservation.state.date,
37146
+ calls_used: reservation.state.calls_used,
37147
+ max_calls: reservation.state.max_calls
37148
+ },
37149
+ draftSkillsWritten,
37150
+ model: cfg.model ?? undefined
37151
+ };
37152
+ }
37153
+ var init_skill_improver = __esm(() => {
37154
+ init_knowledge_store();
37155
+ init_skill_improver_llm_factory();
37156
+ init_skill_generator();
37157
+ init_skill_improver_quota();
37158
+ });
37159
+
36244
37160
  // src/tools/write-retro.ts
36245
37161
  async function executeWriteRetro(args, directory) {
36246
37162
  if (/^(CON|PRN|AUX|NUL|COM[0-9]|LPT[0-9])(:|$)/i.test(directory)) {
@@ -36603,7 +37519,40 @@ var init_write_retro = __esm(() => {
36603
37519
 
36604
37520
  // src/commands/close.ts
36605
37521
  import { promises as fs7 } from "fs";
36606
- import path14 from "path";
37522
+ import path17 from "path";
37523
+ async function runAbortableSkillReview(req, timeoutMs) {
37524
+ const controller = new AbortController;
37525
+ let timeout;
37526
+ const skillReviewPromise = runSkillImprover({
37527
+ ...req,
37528
+ signal: controller.signal
37529
+ });
37530
+ const timeoutPromise = new Promise((_, reject) => {
37531
+ timeout = setTimeout(() => {
37532
+ reject(new Error(`skill_review exceeded ${timeoutMs}ms budget`));
37533
+ controller.abort();
37534
+ }, timeoutMs);
37535
+ });
37536
+ try {
37537
+ return await Promise.race([skillReviewPromise, timeoutPromise]);
37538
+ } finally {
37539
+ if (timeout)
37540
+ clearTimeout(timeout);
37541
+ }
37542
+ }
37543
+ function countSessionKnowledgeEntries(entries, sessionStart, fallbackCount) {
37544
+ if (!sessionStart)
37545
+ return fallbackCount;
37546
+ const sessionStartMs = Date.parse(sessionStart);
37547
+ if (!Number.isFinite(sessionStartMs))
37548
+ return fallbackCount;
37549
+ return entries.filter((entry) => {
37550
+ if (typeof entry.created_at !== "string")
37551
+ return false;
37552
+ const createdAtMs = Date.parse(entry.created_at);
37553
+ return Number.isFinite(createdAtMs) && createdAtMs >= sessionStartMs;
37554
+ }).length;
37555
+ }
36607
37556
  function guaranteeAllPlansComplete(planData) {
36608
37557
  const closedPhaseIds = [];
36609
37558
  const closedTaskIds = [];
@@ -36624,12 +37573,12 @@ function guaranteeAllPlansComplete(planData) {
36624
37573
  }
36625
37574
  return { closedPhaseIds, closedTaskIds };
36626
37575
  }
36627
- async function handleCloseCommand(directory, args) {
37576
+ async function handleCloseCommand(directory, args, options = {}) {
36628
37577
  const planPath = validateSwarmPath(directory, "plan.json");
36629
- const swarmDir = path14.join(directory, ".swarm");
37578
+ const swarmDir = path17.join(directory, ".swarm");
36630
37579
  let planExists = false;
36631
37580
  let planData = {
36632
- title: path14.basename(directory) || "Ad-hoc session",
37581
+ title: path17.basename(directory) || "Ad-hoc session",
36633
37582
  phases: []
36634
37583
  };
36635
37584
  try {
@@ -36648,6 +37597,7 @@ async function handleCloseCommand(directory, args) {
36648
37597
  const phases = planData.phases ?? [];
36649
37598
  const inProgressPhases = phases.filter((p) => p.status === "in_progress");
36650
37599
  const isForced = args.includes("--force");
37600
+ const runSkillReview = args.includes("--skill-review");
36651
37601
  let planAlreadyDone = false;
36652
37602
  if (planExists) {
36653
37603
  planAlreadyDone = phases.length > 0 && phases.every((p) => p.status === "complete" || p.status === "completed" || p.status === "blocked" || p.status === "closed");
@@ -36735,7 +37685,7 @@ async function handleCloseCommand(directory, args) {
36735
37685
  warnings.push(`Session retrospective write threw: ${retroError instanceof Error ? retroError.message : String(retroError)}`);
36736
37686
  }
36737
37687
  }
36738
- const lessonsFilePath = path14.join(swarmDir, "close-lessons.md");
37688
+ const lessonsFilePath = path17.join(swarmDir, "close-lessons.md");
36739
37689
  let explicitLessons = [];
36740
37690
  try {
36741
37691
  const lessonsText = await fs7.readFile(lessonsFilePath, "utf-8");
@@ -36744,11 +37694,11 @@ async function handleCloseCommand(directory, args) {
36744
37694
  } catch {}
36745
37695
  const retroLessons = [];
36746
37696
  try {
36747
- const evidenceDir = path14.join(swarmDir, "evidence");
37697
+ const evidenceDir = path17.join(swarmDir, "evidence");
36748
37698
  const evidenceEntries = await fs7.readdir(evidenceDir);
36749
37699
  const retroDirs = evidenceEntries.filter((e) => e.startsWith("retro-")).sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
36750
37700
  for (const retroDir of retroDirs) {
36751
- const evidencePath = path14.join(evidenceDir, retroDir, "evidence.json");
37701
+ const evidencePath = path17.join(evidenceDir, retroDir, "evidence.json");
36752
37702
  try {
36753
37703
  const content = await fs7.readFile(evidencePath, "utf-8");
36754
37704
  const parsed = JSON.parse(content);
@@ -36767,8 +37717,9 @@ async function handleCloseCommand(directory, args) {
36767
37717
  } catch {}
36768
37718
  const allLessons = [...new Set([...explicitLessons, ...retroLessons])];
36769
37719
  let curationSucceeded = false;
37720
+ let curationResult;
36770
37721
  try {
36771
- await curateAndStoreSwarm(allLessons, projectName, { phase_number: 0 }, directory, config3);
37722
+ curationResult = await curateAndStoreSwarm(allLessons, projectName, { phase_number: 0 }, directory, config3);
36772
37723
  curationSucceeded = true;
36773
37724
  } catch (error93) {
36774
37725
  const msg = error93 instanceof Error ? error93.message : String(error93);
@@ -36798,6 +37749,44 @@ async function handleCloseCommand(directory, args) {
36798
37749
  warnings.push(`Hive promotion failed: ${msg}`);
36799
37750
  }
36800
37751
  }
37752
+ const fallbackKnowledgeCreated = curationResult?.stored ?? 0;
37753
+ let sessionKnowledgeCreated = fallbackKnowledgeCreated;
37754
+ try {
37755
+ const knowledgePath = resolveSwarmKnowledgePath(directory);
37756
+ const entries = await readKnowledge(knowledgePath);
37757
+ sessionKnowledgeCreated = countSessionKnowledgeEntries(entries, sessionStart, fallbackKnowledgeCreated);
37758
+ } catch (knowledgeErr) {
37759
+ const msg = knowledgeErr instanceof Error ? knowledgeErr.message : String(knowledgeErr);
37760
+ warnings.push(`Knowledge session count failed: ${msg}`);
37761
+ }
37762
+ const knowledgeSkillHint = sessionKnowledgeCreated > 0 ? `${sessionKnowledgeCreated} knowledge entries created this session. Consider running skill_improve or skill_generate to compile mature entries into skills.` : "";
37763
+ let skillReviewSummary = "";
37764
+ if (runSkillReview) {
37765
+ try {
37766
+ const { config: loadedConfig } = loadPluginConfigWithMeta(directory);
37767
+ const skillImproverConfig = SkillImproverConfigSchema.parse(loadedConfig.skill_improver ?? {});
37768
+ const skillReviewResult = await runAbortableSkillReview({
37769
+ directory,
37770
+ config: skillImproverConfig,
37771
+ targets: ["skills", "knowledge"],
37772
+ mode: "proposal",
37773
+ sessionId: options.sessionID
37774
+ }, options.skillReviewTimeoutMs ?? CLOSE_SKILL_REVIEW_TIMEOUT_MS);
37775
+ if (skillReviewResult.ran) {
37776
+ const proposal = skillReviewResult.proposalPath ? ` Proposal: ${skillReviewResult.proposalPath}.` : "";
37777
+ const source = skillReviewResult.source ? ` Source: ${skillReviewResult.source}.` : "";
37778
+ skillReviewSummary = `Skill review proposal generated.${proposal}${source}`;
37779
+ } else {
37780
+ const reason = skillReviewResult.reason ?? "unknown reason";
37781
+ skillReviewSummary = `Skill review skipped: ${reason}`;
37782
+ warnings.push(skillReviewSummary);
37783
+ }
37784
+ } catch (skillReviewErr) {
37785
+ const msg = skillReviewErr instanceof Error ? skillReviewErr.message : String(skillReviewErr);
37786
+ skillReviewSummary = `Skill review failed: ${msg}`;
37787
+ warnings.push(skillReviewSummary);
37788
+ }
37789
+ }
36801
37790
  if (planExists) {
36802
37791
  const guaranteeResult = guaranteeAllPlansComplete(planData);
36803
37792
  for (const phaseId of guaranteeResult.closedPhaseIds) {
@@ -36822,7 +37811,7 @@ async function handleCloseCommand(directory, args) {
36822
37811
  }
36823
37812
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
36824
37813
  const suffix = Math.random().toString(36).slice(2, 8);
36825
- const archiveDir = path14.join(swarmDir, "archive", `swarm-${timestamp}-${suffix}`);
37814
+ const archiveDir = path17.join(swarmDir, "archive", `swarm-${timestamp}-${suffix}`);
36826
37815
  let archiveResult = "";
36827
37816
  let archivedFileCount = 0;
36828
37817
  const archivedActiveStateFiles = new Set;
@@ -36830,8 +37819,8 @@ async function handleCloseCommand(directory, args) {
36830
37819
  try {
36831
37820
  await fs7.mkdir(archiveDir, { recursive: true });
36832
37821
  for (const artifact of ARCHIVE_ARTIFACTS) {
36833
- const srcPath = path14.join(swarmDir, artifact);
36834
- const destPath = path14.join(archiveDir, artifact);
37822
+ const srcPath = path17.join(swarmDir, artifact);
37823
+ const destPath = path17.join(archiveDir, artifact);
36835
37824
  try {
36836
37825
  await fs7.copyFile(srcPath, destPath);
36837
37826
  archivedFileCount++;
@@ -36841,22 +37830,22 @@ async function handleCloseCommand(directory, args) {
36841
37830
  } catch {}
36842
37831
  }
36843
37832
  for (const dirName of ACTIVE_STATE_DIRS_TO_CLEAN) {
36844
- const srcDir = path14.join(swarmDir, dirName);
36845
- const destDir = path14.join(archiveDir, dirName);
37833
+ const srcDir = path17.join(swarmDir, dirName);
37834
+ const destDir = path17.join(archiveDir, dirName);
36846
37835
  try {
36847
37836
  const entries = await fs7.readdir(srcDir);
36848
37837
  if (entries.length > 0) {
36849
37838
  await fs7.mkdir(destDir, { recursive: true });
36850
37839
  for (const entry of entries) {
36851
- const srcEntry = path14.join(srcDir, entry);
36852
- const destEntry = path14.join(destDir, entry);
37840
+ const srcEntry = path17.join(srcDir, entry);
37841
+ const destEntry = path17.join(destDir, entry);
36853
37842
  try {
36854
37843
  const stat2 = await fs7.stat(srcEntry);
36855
37844
  if (stat2.isDirectory()) {
36856
37845
  await fs7.mkdir(destEntry, { recursive: true });
36857
37846
  const subEntries = await fs7.readdir(srcEntry);
36858
37847
  for (const sub of subEntries) {
36859
- await fs7.copyFile(path14.join(srcEntry, sub), path14.join(destEntry, sub)).catch(() => {});
37848
+ await fs7.copyFile(path17.join(srcEntry, sub), path17.join(destEntry, sub)).catch(() => {});
36860
37849
  }
36861
37850
  } else {
36862
37851
  await fs7.copyFile(srcEntry, destEntry);
@@ -36888,7 +37877,7 @@ async function handleCloseCommand(directory, args) {
36888
37877
  warnings.push(`Preserved ${artifact} because it was not successfully archived.`);
36889
37878
  continue;
36890
37879
  }
36891
- const filePath = path14.join(swarmDir, artifact);
37880
+ const filePath = path17.join(swarmDir, artifact);
36892
37881
  try {
36893
37882
  await fs7.unlink(filePath);
36894
37883
  cleanedFiles.push(artifact);
@@ -36901,7 +37890,7 @@ async function handleCloseCommand(directory, args) {
36901
37890
  if (!archivedActiveStateDirs.has(dirName)) {
36902
37891
  continue;
36903
37892
  }
36904
- const dirPath = path14.join(swarmDir, dirName);
37893
+ const dirPath = path17.join(swarmDir, dirName);
36905
37894
  try {
36906
37895
  await fs7.rm(dirPath, { recursive: true, force: true });
36907
37896
  cleanedFiles.push(`${dirName}/`);
@@ -36912,23 +37901,23 @@ async function handleCloseCommand(directory, args) {
36912
37901
  const configBackups = swarmFiles.filter((f) => f.startsWith("config-backup-") && f.endsWith(".json"));
36913
37902
  for (const backup of configBackups) {
36914
37903
  try {
36915
- await fs7.unlink(path14.join(swarmDir, backup));
37904
+ await fs7.unlink(path17.join(swarmDir, backup));
36916
37905
  configBackupsRemoved++;
36917
37906
  } catch {}
36918
37907
  }
36919
37908
  const ledgerSiblings = swarmFiles.filter((f) => (f.startsWith("plan-ledger.archived-") || f.startsWith("plan-ledger.backup-")) && f.endsWith(".jsonl"));
36920
37909
  for (const sibling of ledgerSiblings) {
36921
37910
  try {
36922
- await fs7.unlink(path14.join(swarmDir, sibling));
37911
+ await fs7.unlink(path17.join(swarmDir, sibling));
36923
37912
  } catch {}
36924
37913
  }
36925
37914
  } catch {}
36926
37915
  let swarmPlanFilesRemoved = 0;
36927
37916
  const candidates = [
36928
- path14.join(directory, ".swarm", "SWARM_PLAN.json"),
36929
- path14.join(directory, ".swarm", "SWARM_PLAN.md"),
36930
- path14.join(directory, "SWARM_PLAN.json"),
36931
- path14.join(directory, "SWARM_PLAN.md")
37917
+ path17.join(directory, ".swarm", "SWARM_PLAN.json"),
37918
+ path17.join(directory, ".swarm", "SWARM_PLAN.md"),
37919
+ path17.join(directory, "SWARM_PLAN.json"),
37920
+ path17.join(directory, "SWARM_PLAN.md")
36932
37921
  ];
36933
37922
  for (const candidate of candidates) {
36934
37923
  try {
@@ -36936,12 +37925,12 @@ async function handleCloseCommand(directory, args) {
36936
37925
  swarmPlanFilesRemoved++;
36937
37926
  } catch (err) {
36938
37927
  if (err?.code !== "ENOENT") {
36939
- warnings.push(`Failed to remove ${path14.basename(candidate)}: ${err instanceof Error ? err.message : String(err)}`);
37928
+ warnings.push(`Failed to remove ${path17.basename(candidate)}: ${err instanceof Error ? err.message : String(err)}`);
36940
37929
  }
36941
37930
  }
36942
37931
  }
36943
37932
  clearAllScopes(directory);
36944
- const contextPath = path14.join(swarmDir, "context.md");
37933
+ const contextPath = path17.join(swarmDir, "context.md");
36945
37934
  const contextContent = [
36946
37935
  "# Context",
36947
37936
  "",
@@ -37014,6 +38003,12 @@ async function handleCloseCommand(directory, args) {
37014
38003
  "## Lessons Committed",
37015
38004
  allLessons.length > 0 ? `| # | Lesson |` : "_No lessons committed_",
37016
38005
  ...allLessons.length > 0 ? ["| --- | --- |", ...allLessons.map((l, i) => `| ${i + 1} | ${l} |`)] : [],
38006
+ ...knowledgeSkillHint ? ["", knowledgeSkillHint] : [],
38007
+ ...runSkillReview ? [
38008
+ "",
38009
+ "## Skill Review",
38010
+ skillReviewSummary || "Skill review completed without details."
38011
+ ] : [],
37017
38012
  "",
37018
38013
  "## Local Repo State",
37019
38014
  ...gitAlignResult ? [`- **Git:** ${gitAlignResult}`] : ["- Git alignment skipped"],
@@ -37044,11 +38039,13 @@ async function handleCloseCommand(directory, args) {
37044
38039
  const preservedFullAutoFlag = swarmState.fullAutoEnabledInConfig;
37045
38040
  const preservedCuratorInitNames = swarmState.curatorInitAgentNames;
37046
38041
  const preservedCuratorPhaseNames = swarmState.curatorPhaseAgentNames;
38042
+ const preservedSkillImproverAgentNames = swarmState.skillImproverAgentNames;
37047
38043
  resetSwarmState();
37048
38044
  swarmState.opencodeClient = preservedClient;
37049
38045
  swarmState.fullAutoEnabledInConfig = preservedFullAutoFlag;
37050
38046
  swarmState.curatorInitAgentNames = preservedCuratorInitNames;
37051
38047
  swarmState.curatorPhaseAgentNames = preservedCuratorPhaseNames;
38048
+ swarmState.skillImproverAgentNames = preservedSkillImproverAgentNames;
37052
38049
  const retroWarnings = warnings.filter((w) => w.includes("Retrospective write") || w.includes("retrospective write") || w.includes("Session retrospective"));
37053
38050
  const otherWarnings = warnings.filter((w) => !w.includes("Retrospective write") && !w.includes("retrospective write") && !w.includes("Session retrospective"));
37054
38051
  let warningMsg = "";
@@ -37069,19 +38066,26 @@ ${otherWarnings.map((w) => `- ${w}`).join(`
37069
38066
  const lessonSummary = curationSucceeded && allLessons.length > 0 ? `
37070
38067
 
37071
38068
  **Lessons Committed:** ${allLessons.length} lesson(s) committed to knowledge store` : "";
38069
+ const knowledgeHintSummary = knowledgeSkillHint ? `
38070
+
38071
+ **Knowledge Review:** ${knowledgeSkillHint}` : "";
38072
+ const skillReviewOutput = skillReviewSummary ? `
38073
+
38074
+ **Skill Review:** ${skillReviewSummary}` : "";
37072
38075
  if (planAlreadyDone) {
37073
38076
  return `\u2705 Session finalized. Plan was already in a terminal state \u2014 cleanup and archive applied.
37074
38077
 
37075
38078
  **Archive:** ${archiveResult}
37076
- **Git:** ${gitAlignResult}${lessonSummary}${warningMsg}`;
38079
+ **Git:** ${gitAlignResult}${lessonSummary}${knowledgeHintSummary}${skillReviewOutput}${warningMsg}`;
37077
38080
  }
37078
38081
  return `\u2705 Swarm finalized. ${closedPhases.length} phase(s) closed, ${closedTasks.length} incomplete task(s) marked closed.
37079
38082
 
37080
38083
  **Archive:** ${archiveResult}
37081
- **Git:** ${gitAlignResult}${lessonSummary}${warningMsg}`;
38084
+ **Git:** ${gitAlignResult}${lessonSummary}${knowledgeHintSummary}${skillReviewOutput}${warningMsg}`;
37082
38085
  }
37083
- var ARCHIVE_ARTIFACTS, ACTIVE_STATE_TO_CLEAN, ACTIVE_STATE_DIRS_TO_CLEAN;
38086
+ var CLOSE_SKILL_REVIEW_TIMEOUT_MS = 120000, ARCHIVE_ARTIFACTS, ACTIVE_STATE_TO_CLEAN, ACTIVE_STATE_DIRS_TO_CLEAN;
37084
38087
  var init_close = __esm(() => {
38088
+ init_config();
37085
38089
  init_schema();
37086
38090
  init_manager2();
37087
38091
  init_branch();
@@ -37090,6 +38094,7 @@ var init_close = __esm(() => {
37090
38094
  init_knowledge_store();
37091
38095
  init_utils2();
37092
38096
  init_scope_persistence();
38097
+ init_skill_improver();
37093
38098
  init_state();
37094
38099
  init_write_retro();
37095
38100
  ARCHIVE_ARTIFACTS = [
@@ -37145,14 +38150,14 @@ var init_close = __esm(() => {
37145
38150
 
37146
38151
  // src/commands/config.ts
37147
38152
  import * as os4 from "os";
37148
- import * as path15 from "path";
38153
+ import * as path18 from "path";
37149
38154
  function getUserConfigDir2() {
37150
- return process.env.XDG_CONFIG_HOME || path15.join(os4.homedir(), ".config");
38155
+ return process.env.XDG_CONFIG_HOME || path18.join(os4.homedir(), ".config");
37151
38156
  }
37152
38157
  async function handleConfigCommand(directory, _args) {
37153
38158
  const config3 = loadPluginConfig(directory);
37154
- const userConfigPath = path15.join(getUserConfigDir2(), "opencode", "opencode-swarm.json");
37155
- const projectConfigPath = path15.join(directory, ".opencode", "opencode-swarm.json");
38159
+ const userConfigPath = path18.join(getUserConfigDir2(), "opencode", "opencode-swarm.json");
38160
+ const projectConfigPath = path18.join(directory, ".opencode", "opencode-swarm.json");
37156
38161
  const lines = [
37157
38162
  "## Swarm Configuration",
37158
38163
  "",
@@ -37278,8 +38283,8 @@ var init_curate = __esm(() => {
37278
38283
  // src/tools/co-change-analyzer.ts
37279
38284
  import * as child_process3 from "child_process";
37280
38285
  import { randomUUID } from "crypto";
37281
- import { readdir, readFile as readFile5, stat as stat2 } from "fs/promises";
37282
- import * as path16 from "path";
38286
+ import { readdir, readFile as readFile7, stat as stat2 } from "fs/promises";
38287
+ import * as path19 from "path";
37283
38288
  import { promisify } from "util";
37284
38289
  function getExecFileAsync() {
37285
38290
  return promisify(child_process3.execFile);
@@ -37381,7 +38386,7 @@ async function scanSourceFiles(dir) {
37381
38386
  try {
37382
38387
  const entries = await readdir(dir, { withFileTypes: true });
37383
38388
  for (const entry of entries) {
37384
- const fullPath = path16.join(dir, entry.name);
38389
+ const fullPath = path19.join(dir, entry.name);
37385
38390
  if (entry.isDirectory()) {
37386
38391
  if (skipDirs.has(entry.name)) {
37387
38392
  continue;
@@ -37389,7 +38394,7 @@ async function scanSourceFiles(dir) {
37389
38394
  const subFiles = await scanSourceFiles(fullPath);
37390
38395
  results.push(...subFiles);
37391
38396
  } else if (entry.isFile()) {
37392
- const ext = path16.extname(entry.name);
38397
+ const ext = path19.extname(entry.name);
37393
38398
  if ([".ts", ".tsx", ".js", ".jsx", ".mjs"].includes(ext)) {
37394
38399
  results.push(fullPath);
37395
38400
  }
@@ -37403,7 +38408,7 @@ async function getStaticEdges(directory) {
37403
38408
  const sourceFiles = await scanSourceFiles(directory);
37404
38409
  for (const sourceFile of sourceFiles) {
37405
38410
  try {
37406
- const content = await readFile5(sourceFile, "utf-8");
38411
+ const content = await readFile7(sourceFile, "utf-8");
37407
38412
  const importRegex = /(?:import|require)\s*(?:\(?\s*['"`]|.*?from\s+['"`])([^'"`]+)['"`]/g;
37408
38413
  for (let match = importRegex.exec(content);match !== null; match = importRegex.exec(content)) {
37409
38414
  const importPath = match[1].trim();
@@ -37411,8 +38416,8 @@ async function getStaticEdges(directory) {
37411
38416
  continue;
37412
38417
  }
37413
38418
  try {
37414
- const sourceDir = path16.dirname(sourceFile);
37415
- const resolvedPath = path16.resolve(sourceDir, importPath);
38419
+ const sourceDir = path19.dirname(sourceFile);
38420
+ const resolvedPath = path19.resolve(sourceDir, importPath);
37416
38421
  const extensions = [
37417
38422
  "",
37418
38423
  ".ts",
@@ -37437,8 +38442,8 @@ async function getStaticEdges(directory) {
37437
38442
  if (!targetFile) {
37438
38443
  continue;
37439
38444
  }
37440
- const relSource = path16.relative(directory, sourceFile).replace(/\\/g, "/");
37441
- const relTarget = path16.relative(directory, targetFile).replace(/\\/g, "/");
38445
+ const relSource = path19.relative(directory, sourceFile).replace(/\\/g, "/");
38446
+ const relTarget = path19.relative(directory, targetFile).replace(/\\/g, "/");
37442
38447
  const [key] = relSource < relTarget ? [`${relSource}::${relTarget}`, relSource, relTarget] : [`${relTarget}::${relSource}`, relTarget, relSource];
37443
38448
  edges.add(key);
37444
38449
  } catch {}
@@ -37450,7 +38455,7 @@ async function getStaticEdges(directory) {
37450
38455
  function isTestImplementationPair(fileA, fileB) {
37451
38456
  const testPatterns = [".test.ts", ".test.js", ".spec.ts", ".spec.js"];
37452
38457
  const getBaseName = (filePath) => {
37453
- const base = path16.basename(filePath);
38458
+ const base = path19.basename(filePath);
37454
38459
  for (const pattern of testPatterns) {
37455
38460
  if (base.endsWith(pattern)) {
37456
38461
  return base.slice(0, -pattern.length);
@@ -37460,16 +38465,16 @@ function isTestImplementationPair(fileA, fileB) {
37460
38465
  };
37461
38466
  const baseA = getBaseName(fileA);
37462
38467
  const baseB = getBaseName(fileB);
37463
- return baseA === baseB && baseA !== path16.basename(fileA) && baseA !== path16.basename(fileB);
38468
+ return baseA === baseB && baseA !== path19.basename(fileA) && baseA !== path19.basename(fileB);
37464
38469
  }
37465
38470
  function hasSharedPrefix(fileA, fileB) {
37466
- const dirA = path16.dirname(fileA);
37467
- const dirB = path16.dirname(fileB);
38471
+ const dirA = path19.dirname(fileA);
38472
+ const dirB = path19.dirname(fileB);
37468
38473
  if (dirA !== dirB) {
37469
38474
  return false;
37470
38475
  }
37471
- const baseA = path16.basename(fileA).replace(/\.(ts|js|tsx|jsx|mjs)$/, "");
37472
- const baseB = path16.basename(fileB).replace(/\.(ts|js|tsx|jsx|mjs)$/, "");
38476
+ const baseA = path19.basename(fileA).replace(/\.(ts|js|tsx|jsx|mjs)$/, "");
38477
+ const baseB = path19.basename(fileB).replace(/\.(ts|js|tsx|jsx|mjs)$/, "");
37473
38478
  if (baseA.startsWith(baseB) || baseB.startsWith(baseA)) {
37474
38479
  return true;
37475
38480
  }
@@ -37523,8 +38528,8 @@ function darkMatterToKnowledgeEntries(pairs, projectName) {
37523
38528
  const entries = [];
37524
38529
  const now = new Date().toISOString();
37525
38530
  for (const pair of pairs.slice(0, 10)) {
37526
- const baseA = path16.basename(pair.fileA);
37527
- const baseB = path16.basename(pair.fileB);
38531
+ const baseA = path19.basename(pair.fileA);
38532
+ const baseB = path19.basename(pair.fileB);
37528
38533
  let lesson = `Files ${pair.fileA} and ${pair.fileB} co-change with NPMI=${pair.npmi.toFixed(3)} but have no import relationship. This hidden coupling suggests a shared architectural concern \u2014 changes to one likely require changes to the other.`;
37529
38534
  if (lesson.length > 280) {
37530
38535
  lesson = `Files ${baseA} and ${baseB} co-change with NPMI=${pair.npmi.toFixed(3)} but have no import relationship. This hidden coupling suggests a shared architectural concern \u2014 changes to one likely require changes to the other.`;
@@ -37626,7 +38631,7 @@ var init_co_change_analyzer = __esm(() => {
37626
38631
  });
37627
38632
 
37628
38633
  // src/commands/dark-matter.ts
37629
- import path17 from "path";
38634
+ import path20 from "path";
37630
38635
  async function handleDarkMatterCommand(directory, args) {
37631
38636
  const options = {};
37632
38637
  for (let i = 0;i < args.length; i++) {
@@ -37658,7 +38663,7 @@ Ensure this is a git repository with commit history.`;
37658
38663
  const output = formatDarkMatterOutput(pairs);
37659
38664
  if (pairs.length > 0) {
37660
38665
  try {
37661
- const projectName = path17.basename(path17.resolve(directory));
38666
+ const projectName = path20.basename(path20.resolve(directory));
37662
38667
  const entries = darkMatterToKnowledgeEntries(pairs, projectName);
37663
38668
  if (entries.length > 0) {
37664
38669
  const knowledgePath = resolveSwarmKnowledgePath(directory);
@@ -37795,67 +38800,67 @@ var init_deep_dive = __esm(() => {
37795
38800
 
37796
38801
  // src/config/cache-paths.ts
37797
38802
  import * as os5 from "os";
37798
- import * as path18 from "path";
38803
+ import * as path21 from "path";
37799
38804
  function getPluginConfigDir() {
37800
- return path18.join(process.env.XDG_CONFIG_HOME || path18.join(os5.homedir(), ".config"), "opencode");
38805
+ return path21.join(process.env.XDG_CONFIG_HOME || path21.join(os5.homedir(), ".config"), "opencode");
37801
38806
  }
37802
38807
  function getPluginCachePaths() {
37803
- const cacheBase = process.env.XDG_CACHE_HOME || path18.join(os5.homedir(), ".cache");
38808
+ const cacheBase = process.env.XDG_CACHE_HOME || path21.join(os5.homedir(), ".cache");
37804
38809
  const configDir = getPluginConfigDir();
37805
38810
  const paths = [
37806
- path18.join(cacheBase, "opencode", "node_modules", "opencode-swarm"),
37807
- path18.join(cacheBase, "opencode", "packages", "opencode-swarm@latest"),
37808
- path18.join(configDir, "node_modules", "opencode-swarm")
38811
+ path21.join(cacheBase, "opencode", "node_modules", "opencode-swarm"),
38812
+ path21.join(cacheBase, "opencode", "packages", "opencode-swarm@latest"),
38813
+ path21.join(configDir, "node_modules", "opencode-swarm")
37809
38814
  ];
37810
38815
  if (process.platform === "darwin") {
37811
- const libCaches = path18.join(os5.homedir(), "Library", "Caches");
37812
- paths.push(path18.join(libCaches, "opencode", "node_modules", "opencode-swarm"), path18.join(libCaches, "opencode", "packages", "opencode-swarm@latest"));
38816
+ const libCaches = path21.join(os5.homedir(), "Library", "Caches");
38817
+ paths.push(path21.join(libCaches, "opencode", "node_modules", "opencode-swarm"), path21.join(libCaches, "opencode", "packages", "opencode-swarm@latest"));
37813
38818
  }
37814
38819
  if (process.platform === "win32") {
37815
- const localAppData = process.env.LOCALAPPDATA || path18.join(os5.homedir(), "AppData", "Local");
37816
- const appData = process.env.APPDATA || path18.join(os5.homedir(), "AppData", "Roaming");
37817
- paths.push(path18.join(localAppData, "opencode", "node_modules", "opencode-swarm"), path18.join(localAppData, "opencode", "packages", "opencode-swarm@latest"), path18.join(appData, "opencode", "node_modules", "opencode-swarm"));
38820
+ const localAppData = process.env.LOCALAPPDATA || path21.join(os5.homedir(), "AppData", "Local");
38821
+ const appData = process.env.APPDATA || path21.join(os5.homedir(), "AppData", "Roaming");
38822
+ paths.push(path21.join(localAppData, "opencode", "node_modules", "opencode-swarm"), path21.join(localAppData, "opencode", "packages", "opencode-swarm@latest"), path21.join(appData, "opencode", "node_modules", "opencode-swarm"));
37818
38823
  }
37819
38824
  return paths;
37820
38825
  }
37821
38826
  function getPluginLockFilePaths() {
37822
- const cacheBase = process.env.XDG_CACHE_HOME || path18.join(os5.homedir(), ".cache");
38827
+ const cacheBase = process.env.XDG_CACHE_HOME || path21.join(os5.homedir(), ".cache");
37823
38828
  const configDir = getPluginConfigDir();
37824
38829
  const paths = [
37825
- path18.join(cacheBase, "opencode", "bun.lock"),
37826
- path18.join(cacheBase, "opencode", "bun.lockb"),
37827
- path18.join(configDir, "package-lock.json")
38830
+ path21.join(cacheBase, "opencode", "bun.lock"),
38831
+ path21.join(cacheBase, "opencode", "bun.lockb"),
38832
+ path21.join(configDir, "package-lock.json")
37828
38833
  ];
37829
38834
  if (process.platform === "darwin") {
37830
- const libCaches = path18.join(os5.homedir(), "Library", "Caches");
37831
- paths.push(path18.join(libCaches, "opencode", "bun.lock"), path18.join(libCaches, "opencode", "bun.lockb"));
38835
+ const libCaches = path21.join(os5.homedir(), "Library", "Caches");
38836
+ paths.push(path21.join(libCaches, "opencode", "bun.lock"), path21.join(libCaches, "opencode", "bun.lockb"));
37832
38837
  }
37833
38838
  if (process.platform === "win32") {
37834
- const localAppData = process.env.LOCALAPPDATA || path18.join(os5.homedir(), "AppData", "Local");
37835
- paths.push(path18.join(localAppData, "opencode", "bun.lock"), path18.join(localAppData, "opencode", "bun.lockb"));
38839
+ const localAppData = process.env.LOCALAPPDATA || path21.join(os5.homedir(), "AppData", "Local");
38840
+ paths.push(path21.join(localAppData, "opencode", "bun.lock"), path21.join(localAppData, "opencode", "bun.lockb"));
37836
38841
  }
37837
38842
  return paths;
37838
38843
  }
37839
38844
  var init_cache_paths = () => {};
37840
38845
 
37841
38846
  // src/services/version-check.ts
37842
- import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
38847
+ import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
37843
38848
  import { homedir as homedir5 } from "os";
37844
- import { join as join17 } from "path";
38849
+ import { join as join20 } from "path";
37845
38850
  function cacheDir() {
37846
38851
  const xdg = process.env.XDG_CACHE_HOME;
37847
- const base = xdg && xdg.length > 0 ? xdg : join17(homedir5(), ".cache");
37848
- return join17(base, "opencode-swarm");
38852
+ const base = xdg && xdg.length > 0 ? xdg : join20(homedir5(), ".cache");
38853
+ return join20(base, "opencode-swarm");
37849
38854
  }
37850
38855
  function cacheFile() {
37851
- return join17(cacheDir(), "version-check.json");
38856
+ return join20(cacheDir(), "version-check.json");
37852
38857
  }
37853
38858
  function readVersionCache() {
37854
38859
  try {
37855
- const path19 = cacheFile();
37856
- if (!existsSync9(path19))
38860
+ const path22 = cacheFile();
38861
+ if (!existsSync12(path22))
37857
38862
  return null;
37858
- const raw = readFileSync6(path19, "utf-8");
38863
+ const raw = readFileSync6(path22, "utf-8");
37859
38864
  const parsed = JSON.parse(raw);
37860
38865
  if (typeof parsed?.checkedAt !== "number")
37861
38866
  return null;
@@ -37894,8 +38899,8 @@ var init_version_check = __esm(() => {
37894
38899
 
37895
38900
  // src/services/diagnose-service.ts
37896
38901
  import * as child_process4 from "child_process";
37897
- import { existsSync as existsSync10, readdirSync as readdirSync4, readFileSync as readFileSync7, statSync as statSync6 } from "fs";
37898
- import path19 from "path";
38902
+ import { existsSync as existsSync13, readdirSync as readdirSync4, readFileSync as readFileSync7, statSync as statSync6 } from "fs";
38903
+ import path22 from "path";
37899
38904
  import { fileURLToPath } from "url";
37900
38905
  function validateTaskDag(plan) {
37901
38906
  const allTaskIds = new Set;
@@ -38128,7 +39133,7 @@ async function checkConfigBackups(directory) {
38128
39133
  }
38129
39134
  async function checkGitRepository(directory) {
38130
39135
  try {
38131
- if (!existsSync10(directory) || !statSync6(directory).isDirectory()) {
39136
+ if (!existsSync13(directory) || !statSync6(directory).isDirectory()) {
38132
39137
  return {
38133
39138
  name: "Git Repository",
38134
39139
  status: "\u274C",
@@ -38192,8 +39197,8 @@ async function checkSpecStaleness(directory, plan) {
38192
39197
  };
38193
39198
  }
38194
39199
  async function checkConfigParseability(directory) {
38195
- const configPath = path19.join(directory, ".opencode/opencode-swarm.json");
38196
- if (!existsSync10(configPath)) {
39200
+ const configPath = path22.join(directory, ".opencode/opencode-swarm.json");
39201
+ if (!existsSync13(configPath)) {
38197
39202
  return {
38198
39203
  name: "Config Parseability",
38199
39204
  status: "\u2705",
@@ -38221,7 +39226,7 @@ function resolveGrammarDir(thisDir) {
38221
39226
  const normalized = thisDir.replace(/\\/g, "/");
38222
39227
  const isSource = normalized.endsWith("/src/services");
38223
39228
  const isCliBundle = normalized.endsWith("/cli");
38224
- return isSource || isCliBundle ? path19.join(thisDir, "..", "lang", "grammars") : path19.join(thisDir, "lang", "grammars");
39229
+ return isSource || isCliBundle ? path22.join(thisDir, "..", "lang", "grammars") : path22.join(thisDir, "lang", "grammars");
38225
39230
  }
38226
39231
  async function checkGrammarWasmFiles() {
38227
39232
  const grammarFiles = [
@@ -38245,14 +39250,14 @@ async function checkGrammarWasmFiles() {
38245
39250
  "tree-sitter-ini.wasm",
38246
39251
  "tree-sitter-regex.wasm"
38247
39252
  ];
38248
- const thisDir = path19.dirname(fileURLToPath(import.meta.url));
39253
+ const thisDir = path22.dirname(fileURLToPath(import.meta.url));
38249
39254
  const grammarDir = resolveGrammarDir(thisDir);
38250
39255
  const missing = [];
38251
- if (!existsSync10(path19.join(grammarDir, "tree-sitter.wasm"))) {
39256
+ if (!existsSync13(path22.join(grammarDir, "tree-sitter.wasm"))) {
38252
39257
  missing.push("tree-sitter.wasm (core runtime)");
38253
39258
  }
38254
39259
  for (const file3 of grammarFiles) {
38255
- if (!existsSync10(path19.join(grammarDir, file3))) {
39260
+ if (!existsSync13(path22.join(grammarDir, file3))) {
38256
39261
  missing.push(file3);
38257
39262
  }
38258
39263
  }
@@ -38270,8 +39275,8 @@ async function checkGrammarWasmFiles() {
38270
39275
  };
38271
39276
  }
38272
39277
  async function checkCheckpointManifest(directory) {
38273
- const manifestPath = path19.join(directory, ".swarm/checkpoints.json");
38274
- if (!existsSync10(manifestPath)) {
39278
+ const manifestPath = path22.join(directory, ".swarm/checkpoints.json");
39279
+ if (!existsSync13(manifestPath)) {
38275
39280
  return {
38276
39281
  name: "Checkpoint Manifest",
38277
39282
  status: "\u2705",
@@ -38322,8 +39327,8 @@ async function checkCheckpointManifest(directory) {
38322
39327
  }
38323
39328
  }
38324
39329
  async function checkEventStreamIntegrity(directory) {
38325
- const eventsPath = path19.join(directory, ".swarm/events.jsonl");
38326
- if (!existsSync10(eventsPath)) {
39330
+ const eventsPath = path22.join(directory, ".swarm/events.jsonl");
39331
+ if (!existsSync13(eventsPath)) {
38327
39332
  return {
38328
39333
  name: "Event Stream",
38329
39334
  status: "\u2705",
@@ -38363,8 +39368,8 @@ async function checkEventStreamIntegrity(directory) {
38363
39368
  }
38364
39369
  }
38365
39370
  async function checkSteeringDirectives(directory) {
38366
- const eventsPath = path19.join(directory, ".swarm/events.jsonl");
38367
- if (!existsSync10(eventsPath)) {
39371
+ const eventsPath = path22.join(directory, ".swarm/events.jsonl");
39372
+ if (!existsSync13(eventsPath)) {
38368
39373
  return {
38369
39374
  name: "Steering Directives",
38370
39375
  status: "\u2705",
@@ -38419,8 +39424,8 @@ async function checkCurator(directory) {
38419
39424
  detail: "Disabled (enable via curator.enabled)"
38420
39425
  };
38421
39426
  }
38422
- const summaryPath = path19.join(directory, ".swarm/curator-summary.json");
38423
- if (!existsSync10(summaryPath)) {
39427
+ const summaryPath = path22.join(directory, ".swarm/curator-summary.json");
39428
+ if (!existsSync13(summaryPath)) {
38424
39429
  return {
38425
39430
  name: "Curator",
38426
39431
  status: "\u2705",
@@ -38585,8 +39590,8 @@ async function getDiagnoseData(directory) {
38585
39590
  checks5.push(await checkSteeringDirectives(directory));
38586
39591
  checks5.push(await checkCurator(directory));
38587
39592
  try {
38588
- const evidenceDir = path19.join(directory, ".swarm", "evidence");
38589
- const snapshotFiles = existsSync10(evidenceDir) ? readdirSync4(evidenceDir).filter((f) => f.startsWith("agent-tools-") && f.endsWith(".json")) : [];
39593
+ const evidenceDir = path22.join(directory, ".swarm", "evidence");
39594
+ const snapshotFiles = existsSync13(evidenceDir) ? readdirSync4(evidenceDir).filter((f) => f.startsWith("agent-tools-") && f.endsWith(".json")) : [];
38590
39595
  if (snapshotFiles.length > 0) {
38591
39596
  const latest = snapshotFiles.sort().pop();
38592
39597
  checks5.push({
@@ -38619,11 +39624,11 @@ async function getDiagnoseData(directory) {
38619
39624
  const cacheRows = [];
38620
39625
  for (const cachePath of cachePaths) {
38621
39626
  try {
38622
- if (!existsSync10(cachePath)) {
39627
+ if (!existsSync13(cachePath)) {
38623
39628
  cacheRows.push(`\u2B1C ${cachePath} \u2014 absent`);
38624
39629
  continue;
38625
39630
  }
38626
- const pkgJsonPath = path19.join(cachePath, "package.json");
39631
+ const pkgJsonPath = path22.join(cachePath, "package.json");
38627
39632
  try {
38628
39633
  const raw = readFileSync7(pkgJsonPath, "utf-8");
38629
39634
  const parsed = JSON.parse(raw);
@@ -38711,13 +39716,13 @@ __export(exports_config_doctor, {
38711
39716
  import * as crypto3 from "crypto";
38712
39717
  import * as fs8 from "fs";
38713
39718
  import * as os6 from "os";
38714
- import * as path20 from "path";
39719
+ import * as path23 from "path";
38715
39720
  function getUserConfigDir3() {
38716
- return process.env.XDG_CONFIG_HOME || path20.join(os6.homedir(), ".config");
39721
+ return process.env.XDG_CONFIG_HOME || path23.join(os6.homedir(), ".config");
38717
39722
  }
38718
39723
  function getConfigPaths(directory) {
38719
- const userConfigPath = path20.join(getUserConfigDir3(), "opencode", "opencode-swarm.json");
38720
- const projectConfigPath = path20.join(directory, ".opencode", "opencode-swarm.json");
39724
+ const userConfigPath = path23.join(getUserConfigDir3(), "opencode", "opencode-swarm.json");
39725
+ const projectConfigPath = path23.join(directory, ".opencode", "opencode-swarm.json");
38721
39726
  return { userConfigPath, projectConfigPath };
38722
39727
  }
38723
39728
  function computeHash(content) {
@@ -38742,9 +39747,9 @@ function isValidConfigPath(configPath, directory) {
38742
39747
  const normalizedUser = userConfigPath.replace(/\\/g, "/");
38743
39748
  const normalizedProject = projectConfigPath.replace(/\\/g, "/");
38744
39749
  try {
38745
- const resolvedConfig = path20.resolve(configPath);
38746
- const resolvedUser = path20.resolve(normalizedUser);
38747
- const resolvedProject = path20.resolve(normalizedProject);
39750
+ const resolvedConfig = path23.resolve(configPath);
39751
+ const resolvedUser = path23.resolve(normalizedUser);
39752
+ const resolvedProject = path23.resolve(normalizedProject);
38748
39753
  return resolvedConfig === resolvedUser || resolvedConfig === resolvedProject;
38749
39754
  } catch {
38750
39755
  return false;
@@ -38784,12 +39789,12 @@ function createConfigBackup(directory) {
38784
39789
  };
38785
39790
  }
38786
39791
  function writeBackupArtifact(directory, backup) {
38787
- const swarmDir = path20.join(directory, ".swarm");
39792
+ const swarmDir = path23.join(directory, ".swarm");
38788
39793
  if (!fs8.existsSync(swarmDir)) {
38789
39794
  fs8.mkdirSync(swarmDir, { recursive: true });
38790
39795
  }
38791
39796
  const backupFilename = `config-backup-${backup.createdAt}.json`;
38792
- const backupPath = path20.join(swarmDir, backupFilename);
39797
+ const backupPath = path23.join(swarmDir, backupFilename);
38793
39798
  const artifact = {
38794
39799
  createdAt: backup.createdAt,
38795
39800
  configPath: backup.configPath,
@@ -38819,7 +39824,7 @@ function restoreFromBackup(backupPath, directory) {
38819
39824
  return null;
38820
39825
  }
38821
39826
  const targetPath = artifact.configPath;
38822
- const targetDir = path20.dirname(targetPath);
39827
+ const targetDir = path23.dirname(targetPath);
38823
39828
  if (!fs8.existsSync(targetDir)) {
38824
39829
  fs8.mkdirSync(targetDir, { recursive: true });
38825
39830
  }
@@ -38850,9 +39855,9 @@ function readConfigFromFile(directory) {
38850
39855
  return null;
38851
39856
  }
38852
39857
  }
38853
- function validateConfigKey(path21, value, _config) {
39858
+ function validateConfigKey(path24, value, _config) {
38854
39859
  const findings = [];
38855
- switch (path21) {
39860
+ switch (path24) {
38856
39861
  case "agents": {
38857
39862
  if (value !== undefined) {
38858
39863
  findings.push({
@@ -39099,27 +40104,27 @@ function validateConfigKey(path21, value, _config) {
39099
40104
  }
39100
40105
  return findings;
39101
40106
  }
39102
- function walkConfigAndValidate(obj, path21, config3, findings) {
40107
+ function walkConfigAndValidate(obj, path24, config3, findings) {
39103
40108
  if (obj === null || obj === undefined) {
39104
40109
  return;
39105
40110
  }
39106
- if (path21 && typeof obj === "object" && !Array.isArray(obj)) {
39107
- const keyFindings = validateConfigKey(path21, obj, config3);
40111
+ if (path24 && typeof obj === "object" && !Array.isArray(obj)) {
40112
+ const keyFindings = validateConfigKey(path24, obj, config3);
39108
40113
  findings.push(...keyFindings);
39109
40114
  }
39110
40115
  if (typeof obj !== "object") {
39111
- const keyFindings = validateConfigKey(path21, obj, config3);
40116
+ const keyFindings = validateConfigKey(path24, obj, config3);
39112
40117
  findings.push(...keyFindings);
39113
40118
  return;
39114
40119
  }
39115
40120
  if (Array.isArray(obj)) {
39116
40121
  obj.forEach((item, index) => {
39117
- walkConfigAndValidate(item, `${path21}[${index}]`, config3, findings);
40122
+ walkConfigAndValidate(item, `${path24}[${index}]`, config3, findings);
39118
40123
  });
39119
40124
  return;
39120
40125
  }
39121
40126
  for (const [key, value] of Object.entries(obj)) {
39122
- const newPath = path21 ? `${path21}.${key}` : key;
40127
+ const newPath = path24 ? `${path24}.${key}` : key;
39123
40128
  walkConfigAndValidate(value, newPath, config3, findings);
39124
40129
  }
39125
40130
  }
@@ -39239,7 +40244,7 @@ function applySafeAutoFixes(directory, result) {
39239
40244
  }
39240
40245
  }
39241
40246
  if (appliedFixes.length > 0) {
39242
- const configDir = path20.dirname(configPath);
40247
+ const configDir = path23.dirname(configPath);
39243
40248
  if (!fs8.existsSync(configDir)) {
39244
40249
  fs8.mkdirSync(configDir, { recursive: true });
39245
40250
  }
@@ -39249,12 +40254,12 @@ function applySafeAutoFixes(directory, result) {
39249
40254
  return { appliedFixes, updatedConfigPath };
39250
40255
  }
39251
40256
  function writeDoctorArtifact(directory, result) {
39252
- const swarmDir = path20.join(directory, ".swarm");
40257
+ const swarmDir = path23.join(directory, ".swarm");
39253
40258
  if (!fs8.existsSync(swarmDir)) {
39254
40259
  fs8.mkdirSync(swarmDir, { recursive: true });
39255
40260
  }
39256
40261
  const artifactFilename = "config-doctor.json";
39257
- const artifactPath = path20.join(swarmDir, artifactFilename);
40262
+ const artifactPath = path23.join(swarmDir, artifactFilename);
39258
40263
  const guiOutput = {
39259
40264
  timestamp: result.timestamp,
39260
40265
  summary: result.summary,
@@ -40301,7 +41306,7 @@ var init_profiles = __esm(() => {
40301
41306
 
40302
41307
  // src/lang/detector.ts
40303
41308
  import { access as access3, readdir as readdir2 } from "fs/promises";
40304
- import { extname as extname2, join as join19 } from "path";
41309
+ import { extname as extname2, join as join22 } from "path";
40305
41310
  async function detectProjectLanguages(projectDir) {
40306
41311
  const detected = new Set;
40307
41312
  async function scanDir(dir) {
@@ -40317,7 +41322,7 @@ async function detectProjectLanguages(projectDir) {
40317
41322
  if (detectFile.includes("*") || detectFile.includes("?"))
40318
41323
  continue;
40319
41324
  try {
40320
- await access3(join19(dir, detectFile));
41325
+ await access3(join22(dir, detectFile));
40321
41326
  detected.add(profile.id);
40322
41327
  break;
40323
41328
  } catch {}
@@ -40338,7 +41343,7 @@ async function detectProjectLanguages(projectDir) {
40338
41343
  const topEntries = await readdir2(projectDir, { withFileTypes: true });
40339
41344
  for (const entry of topEntries) {
40340
41345
  if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
40341
- await scanDir(join19(projectDir, entry.name));
41346
+ await scanDir(join22(projectDir, entry.name));
40342
41347
  }
40343
41348
  }
40344
41349
  } catch {}
@@ -40357,7 +41362,7 @@ var init_detector = __esm(() => {
40357
41362
 
40358
41363
  // src/build/discovery.ts
40359
41364
  import * as fs9 from "fs";
40360
- import * as path21 from "path";
41365
+ import * as path24 from "path";
40361
41366
  function isCommandAvailable(command) {
40362
41367
  if (toolchainCache.has(command)) {
40363
41368
  return toolchainCache.get(command);
@@ -40389,11 +41394,11 @@ function findBuildFiles(workingDir, patterns) {
40389
41394
  const regex = simpleGlobToRegex(pattern);
40390
41395
  const matches = files.filter((f) => regex.test(f));
40391
41396
  if (matches.length > 0) {
40392
- return path21.join(dir, matches[0]);
41397
+ return path24.join(dir, matches[0]);
40393
41398
  }
40394
41399
  } catch {}
40395
41400
  } else {
40396
- const filePath = path21.join(workingDir, pattern);
41401
+ const filePath = path24.join(workingDir, pattern);
40397
41402
  if (fs9.existsSync(filePath)) {
40398
41403
  return filePath;
40399
41404
  }
@@ -40402,7 +41407,7 @@ function findBuildFiles(workingDir, patterns) {
40402
41407
  return null;
40403
41408
  }
40404
41409
  function getRepoDefinedScripts(workingDir, scripts) {
40405
- const packageJsonPath = path21.join(workingDir, "package.json");
41410
+ const packageJsonPath = path24.join(workingDir, "package.json");
40406
41411
  if (!fs9.existsSync(packageJsonPath)) {
40407
41412
  return [];
40408
41413
  }
@@ -40443,7 +41448,7 @@ function findAllBuildFiles(workingDir) {
40443
41448
  const regex = simpleGlobToRegex(pattern);
40444
41449
  findFilesRecursive(workingDir, regex, allBuildFiles);
40445
41450
  } else {
40446
- const filePath = path21.join(workingDir, pattern);
41451
+ const filePath = path24.join(workingDir, pattern);
40447
41452
  if (fs9.existsSync(filePath)) {
40448
41453
  allBuildFiles.add(filePath);
40449
41454
  }
@@ -40456,7 +41461,7 @@ function findFilesRecursive(dir, regex, results) {
40456
41461
  try {
40457
41462
  const entries = fs9.readdirSync(dir, { withFileTypes: true });
40458
41463
  for (const entry of entries) {
40459
- const fullPath = path21.join(dir, entry.name);
41464
+ const fullPath = path24.join(dir, entry.name);
40460
41465
  if (entry.isDirectory() && !["node_modules", ".git", "dist", "build", "target"].includes(entry.name)) {
40461
41466
  findFilesRecursive(fullPath, regex, results);
40462
41467
  } else if (entry.isFile() && regex.test(entry.name)) {
@@ -40479,7 +41484,7 @@ async function discoverBuildCommandsFromProfiles(workingDir) {
40479
41484
  let foundCommand = false;
40480
41485
  for (const cmd of sortedCommands) {
40481
41486
  if (cmd.detectFile) {
40482
- const detectFilePath = path21.join(workingDir, cmd.detectFile);
41487
+ const detectFilePath = path24.join(workingDir, cmd.detectFile);
40483
41488
  if (!fs9.existsSync(detectFilePath)) {
40484
41489
  continue;
40485
41490
  }
@@ -40719,7 +41724,7 @@ var init_discovery = __esm(() => {
40719
41724
 
40720
41725
  // src/services/tool-doctor.ts
40721
41726
  import * as fs10 from "fs";
40722
- import * as path22 from "path";
41727
+ import * as path25 from "path";
40723
41728
  function extractRegisteredToolKeys(indexPath) {
40724
41729
  const registeredKeys = new Set;
40725
41730
  try {
@@ -40774,8 +41779,8 @@ function checkBinaryReadiness() {
40774
41779
  }
40775
41780
  function runToolDoctor(_directory, pluginRoot) {
40776
41781
  const findings = [];
40777
- const resolvedPluginRoot = pluginRoot ?? path22.resolve(import.meta.dir, "..", "..");
40778
- const indexPath = path22.join(resolvedPluginRoot, "src", "index.ts");
41782
+ const resolvedPluginRoot = pluginRoot ?? path25.resolve(import.meta.dir, "..", "..");
41783
+ const indexPath = path25.join(resolvedPluginRoot, "src", "index.ts");
40779
41784
  if (!fs10.existsSync(indexPath)) {
40780
41785
  return {
40781
41786
  findings: [
@@ -41466,12 +42471,12 @@ var init_export = __esm(() => {
41466
42471
 
41467
42472
  // src/full-auto/state.ts
41468
42473
  import * as fs11 from "fs";
41469
- import * as path23 from "path";
42474
+ import * as path26 from "path";
41470
42475
  function nowISO() {
41471
42476
  return new Date().toISOString();
41472
42477
  }
41473
42478
  function ensureSwarmDir(directory) {
41474
- const swarmDir = path23.resolve(directory, ".swarm");
42479
+ const swarmDir = path26.resolve(directory, ".swarm");
41475
42480
  if (!fs11.existsSync(swarmDir)) {
41476
42481
  fs11.mkdirSync(swarmDir, { recursive: true });
41477
42482
  }
@@ -41516,7 +42521,7 @@ function withStateLock(directory, fn) {
41516
42521
  fs11.writeFileSync(lockTarget, `${JSON.stringify(seed, null, 2)}
41517
42522
  `, "utf-8");
41518
42523
  }
41519
- release = lockfile5.lockSync(lockTarget, {
42524
+ release = lockfile6.lockSync(lockTarget, {
41520
42525
  retries: { retries: 5, minTimeout: 5, maxTimeout: 50 },
41521
42526
  stale: 5000
41522
42527
  });
@@ -41702,12 +42707,12 @@ function terminateFullAutoRun(directory, sessionID, reason) {
41702
42707
  return state;
41703
42708
  });
41704
42709
  }
41705
- var import_proper_lockfile5, lockfile5, STATE_FILE = "full-auto-state.json", stateUnreadable = false, stateUnreadableReason = "";
42710
+ var import_proper_lockfile6, lockfile6, STATE_FILE = "full-auto-state.json", stateUnreadable = false, stateUnreadableReason = "";
41706
42711
  var init_state2 = __esm(() => {
41707
42712
  init_utils2();
41708
42713
  init_logger();
41709
- import_proper_lockfile5 = __toESM(require_proper_lockfile(), 1);
41710
- lockfile5 = import_proper_lockfile5.default;
42714
+ import_proper_lockfile6 = __toESM(require_proper_lockfile(), 1);
42715
+ lockfile6 = import_proper_lockfile6.default;
41711
42716
  });
41712
42717
 
41713
42718
  // src/commands/full-auto.ts
@@ -42172,7 +43177,7 @@ var init_handoff_service = __esm(() => {
42172
43177
 
42173
43178
  // src/session/snapshot-writer.ts
42174
43179
  import { mkdirSync as mkdirSync10, renameSync as renameSync6 } from "fs";
42175
- import * as path24 from "path";
43180
+ import * as path27 from "path";
42176
43181
  function serializeAgentSession(s) {
42177
43182
  const gateLog = {};
42178
43183
  const rawGateLog = s.gateLog ?? new Map;
@@ -42262,7 +43267,7 @@ async function writeSnapshot(directory, state) {
42262
43267
  }
42263
43268
  const content = JSON.stringify(snapshot, null, 2);
42264
43269
  const resolvedPath = validateSwarmPath(directory, "session/state.json");
42265
- const dir = path24.dirname(resolvedPath);
43270
+ const dir = path27.dirname(resolvedPath);
42266
43271
  mkdirSync10(dir, { recursive: true });
42267
43272
  const tempPath = `${resolvedPath}.tmp.${Date.now()}.${Math.random().toString(36).slice(2)}`;
42268
43273
  await bunWrite(tempPath, content);
@@ -42711,9 +43716,9 @@ var init_issue = __esm(() => {
42711
43716
 
42712
43717
  // src/hooks/knowledge-migrator.ts
42713
43718
  import { randomUUID as randomUUID2 } from "crypto";
42714
- import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
42715
- import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
42716
- import * as path25 from "path";
43719
+ import { existsSync as existsSync18, readFileSync as readFileSync12 } from "fs";
43720
+ import { mkdir as mkdir8, readFile as readFile8, writeFile as writeFile9 } from "fs/promises";
43721
+ import * as path28 from "path";
42717
43722
  async function migrateKnowledgeToExternal(_directory, _config) {
42718
43723
  return {
42719
43724
  migrated: false,
@@ -42724,10 +43729,10 @@ async function migrateKnowledgeToExternal(_directory, _config) {
42724
43729
  };
42725
43730
  }
42726
43731
  async function migrateContextToKnowledge(directory, config3) {
42727
- const sentinelPath = path25.join(directory, ".swarm", ".knowledge-migrated");
42728
- const contextPath = path25.join(directory, ".swarm", "context.md");
43732
+ const sentinelPath = path28.join(directory, ".swarm", ".knowledge-migrated");
43733
+ const contextPath = path28.join(directory, ".swarm", "context.md");
42729
43734
  const knowledgePath = resolveSwarmKnowledgePath(directory);
42730
- if (existsSync15(sentinelPath)) {
43735
+ if (existsSync18(sentinelPath)) {
42731
43736
  return {
42732
43737
  migrated: false,
42733
43738
  entriesMigrated: 0,
@@ -42736,7 +43741,7 @@ async function migrateContextToKnowledge(directory, config3) {
42736
43741
  skippedReason: "sentinel-exists"
42737
43742
  };
42738
43743
  }
42739
- if (!existsSync15(contextPath)) {
43744
+ if (!existsSync18(contextPath)) {
42740
43745
  return {
42741
43746
  migrated: false,
42742
43747
  entriesMigrated: 0,
@@ -42745,7 +43750,7 @@ async function migrateContextToKnowledge(directory, config3) {
42745
43750
  skippedReason: "no-context-file"
42746
43751
  };
42747
43752
  }
42748
- const contextContent = await readFile6(contextPath, "utf-8");
43753
+ const contextContent = await readFile8(contextPath, "utf-8");
42749
43754
  if (contextContent.trim().length === 0) {
42750
43755
  return {
42751
43756
  migrated: false,
@@ -42921,8 +43926,8 @@ function truncateLesson(text) {
42921
43926
  return `${text.slice(0, 277)}...`;
42922
43927
  }
42923
43928
  function inferProjectName(directory) {
42924
- const packageJsonPath = path25.join(directory, "package.json");
42925
- if (existsSync15(packageJsonPath)) {
43929
+ const packageJsonPath = path28.join(directory, "package.json");
43930
+ if (existsSync18(packageJsonPath)) {
42926
43931
  try {
42927
43932
  const pkg = JSON.parse(readFileSync12(packageJsonPath, "utf-8"));
42928
43933
  if (pkg.name && typeof pkg.name === "string") {
@@ -42930,7 +43935,7 @@ function inferProjectName(directory) {
42930
43935
  }
42931
43936
  } catch {}
42932
43937
  }
42933
- return path25.basename(directory);
43938
+ return path28.basename(directory);
42934
43939
  }
42935
43940
  async function writeSentinel(sentinelPath, migrated, dropped) {
42936
43941
  const sentinel = {
@@ -42942,8 +43947,8 @@ async function writeSentinel(sentinelPath, migrated, dropped) {
42942
43947
  schema_version: 1,
42943
43948
  migration_tool: "knowledge-migrator.ts"
42944
43949
  };
42945
- await mkdir5(path25.dirname(sentinelPath), { recursive: true });
42946
- await writeFile6(sentinelPath, JSON.stringify(sentinel, null, 2), "utf-8");
43950
+ await mkdir8(path28.dirname(sentinelPath), { recursive: true });
43951
+ await writeFile9(sentinelPath, JSON.stringify(sentinel, null, 2), "utf-8");
42947
43952
  }
42948
43953
  var _internals15;
42949
43954
  var init_knowledge_migrator = __esm(() => {
@@ -42964,7 +43969,7 @@ var init_knowledge_migrator = __esm(() => {
42964
43969
  });
42965
43970
 
42966
43971
  // src/commands/knowledge.ts
42967
- import { join as join23 } from "path";
43972
+ import { join as join26 } from "path";
42968
43973
  function resolveEntryByPrefix(entries, inputId) {
42969
43974
  const exact = entries.find((e) => e.id === inputId);
42970
43975
  if (exact)
@@ -43015,7 +44020,7 @@ async function handleKnowledgeRestoreCommand(directory, args) {
43015
44020
  return "Invalid entry ID. IDs must be 1-64 characters: letters, digits, hyphens, underscores only.";
43016
44021
  }
43017
44022
  try {
43018
- const quarantinePath = join23(directory, ".swarm", "knowledge-quarantined.jsonl");
44023
+ const quarantinePath = join26(directory, ".swarm", "knowledge-quarantined.jsonl");
43019
44024
  const entries = await readKnowledge(quarantinePath);
43020
44025
  const resolved = resolveEntryByPrefix(entries, inputId);
43021
44026
  if ("error" in resolved) {
@@ -43464,7 +44469,7 @@ var init_path_security = () => {};
43464
44469
 
43465
44470
  // src/tools/lint.ts
43466
44471
  import * as fs12 from "fs";
43467
- import * as path26 from "path";
44472
+ import * as path29 from "path";
43468
44473
  function validateArgs(args) {
43469
44474
  if (typeof args !== "object" || args === null)
43470
44475
  return false;
@@ -43475,9 +44480,9 @@ function validateArgs(args) {
43475
44480
  }
43476
44481
  function getLinterCommand(linter, mode, projectDir) {
43477
44482
  const isWindows = process.platform === "win32";
43478
- const binDir = path26.join(projectDir, "node_modules", ".bin");
43479
- const biomeBin = isWindows ? path26.join(binDir, "biome.EXE") : path26.join(binDir, "biome");
43480
- const eslintBin = isWindows ? path26.join(binDir, "eslint.cmd") : path26.join(binDir, "eslint");
44483
+ const binDir = path29.join(projectDir, "node_modules", ".bin");
44484
+ const biomeBin = isWindows ? path29.join(binDir, "biome.EXE") : path29.join(binDir, "biome");
44485
+ const eslintBin = isWindows ? path29.join(binDir, "eslint.cmd") : path29.join(binDir, "eslint");
43481
44486
  switch (linter) {
43482
44487
  case "biome":
43483
44488
  if (mode === "fix") {
@@ -43493,7 +44498,7 @@ function getLinterCommand(linter, mode, projectDir) {
43493
44498
  }
43494
44499
  function getAdditionalLinterCommand(linter, mode, cwd) {
43495
44500
  const gradlewName = process.platform === "win32" ? "gradlew.bat" : "gradlew";
43496
- const gradlew = fs12.existsSync(path26.join(cwd, gradlewName)) ? path26.join(cwd, gradlewName) : null;
44501
+ const gradlew = fs12.existsSync(path29.join(cwd, gradlewName)) ? path29.join(cwd, gradlewName) : null;
43497
44502
  switch (linter) {
43498
44503
  case "ruff":
43499
44504
  return mode === "fix" ? ["ruff", "check", "--fix", "."] : ["ruff", "check", "."];
@@ -43527,10 +44532,10 @@ function getAdditionalLinterCommand(linter, mode, cwd) {
43527
44532
  }
43528
44533
  }
43529
44534
  function detectRuff(cwd) {
43530
- if (fs12.existsSync(path26.join(cwd, "ruff.toml")))
44535
+ if (fs12.existsSync(path29.join(cwd, "ruff.toml")))
43531
44536
  return isCommandAvailable("ruff");
43532
44537
  try {
43533
- const pyproject = path26.join(cwd, "pyproject.toml");
44538
+ const pyproject = path29.join(cwd, "pyproject.toml");
43534
44539
  if (fs12.existsSync(pyproject)) {
43535
44540
  const content = fs12.readFileSync(pyproject, "utf-8");
43536
44541
  if (content.includes("[tool.ruff]"))
@@ -43540,19 +44545,19 @@ function detectRuff(cwd) {
43540
44545
  return false;
43541
44546
  }
43542
44547
  function detectClippy(cwd) {
43543
- return fs12.existsSync(path26.join(cwd, "Cargo.toml")) && isCommandAvailable("cargo");
44548
+ return fs12.existsSync(path29.join(cwd, "Cargo.toml")) && isCommandAvailable("cargo");
43544
44549
  }
43545
44550
  function detectGolangciLint(cwd) {
43546
- return fs12.existsSync(path26.join(cwd, "go.mod")) && isCommandAvailable("golangci-lint");
44551
+ return fs12.existsSync(path29.join(cwd, "go.mod")) && isCommandAvailable("golangci-lint");
43547
44552
  }
43548
44553
  function detectCheckstyle(cwd) {
43549
- const hasMaven = fs12.existsSync(path26.join(cwd, "pom.xml"));
43550
- const hasGradle = fs12.existsSync(path26.join(cwd, "build.gradle")) || fs12.existsSync(path26.join(cwd, "build.gradle.kts"));
43551
- const hasBinary = hasMaven && isCommandAvailable("mvn") || hasGradle && (fs12.existsSync(path26.join(cwd, "gradlew")) || isCommandAvailable("gradle"));
44554
+ const hasMaven = fs12.existsSync(path29.join(cwd, "pom.xml"));
44555
+ const hasGradle = fs12.existsSync(path29.join(cwd, "build.gradle")) || fs12.existsSync(path29.join(cwd, "build.gradle.kts"));
44556
+ const hasBinary = hasMaven && isCommandAvailable("mvn") || hasGradle && (fs12.existsSync(path29.join(cwd, "gradlew")) || isCommandAvailable("gradle"));
43552
44557
  return (hasMaven || hasGradle) && hasBinary;
43553
44558
  }
43554
44559
  function detectKtlint(cwd) {
43555
- const hasKotlin = fs12.existsSync(path26.join(cwd, "build.gradle.kts")) || fs12.existsSync(path26.join(cwd, "build.gradle")) || (() => {
44560
+ const hasKotlin = fs12.existsSync(path29.join(cwd, "build.gradle.kts")) || fs12.existsSync(path29.join(cwd, "build.gradle")) || (() => {
43556
44561
  try {
43557
44562
  return fs12.readdirSync(cwd).some((f) => f.endsWith(".kt") || f.endsWith(".kts"));
43558
44563
  } catch {
@@ -43571,11 +44576,11 @@ function detectDotnetFormat(cwd) {
43571
44576
  }
43572
44577
  }
43573
44578
  function detectCppcheck(cwd) {
43574
- if (fs12.existsSync(path26.join(cwd, "CMakeLists.txt"))) {
44579
+ if (fs12.existsSync(path29.join(cwd, "CMakeLists.txt"))) {
43575
44580
  return isCommandAvailable("cppcheck");
43576
44581
  }
43577
44582
  try {
43578
- const dirsToCheck = [cwd, path26.join(cwd, "src")];
44583
+ const dirsToCheck = [cwd, path29.join(cwd, "src")];
43579
44584
  const hasCpp = dirsToCheck.some((dir) => {
43580
44585
  try {
43581
44586
  return fs12.readdirSync(dir).some((f) => /\.(c|cpp|cc|cxx|h|hpp)$/.test(f));
@@ -43589,13 +44594,13 @@ function detectCppcheck(cwd) {
43589
44594
  }
43590
44595
  }
43591
44596
  function detectSwiftlint(cwd) {
43592
- return fs12.existsSync(path26.join(cwd, "Package.swift")) && isCommandAvailable("swiftlint");
44597
+ return fs12.existsSync(path29.join(cwd, "Package.swift")) && isCommandAvailable("swiftlint");
43593
44598
  }
43594
44599
  function detectDartAnalyze(cwd) {
43595
- return fs12.existsSync(path26.join(cwd, "pubspec.yaml")) && (isCommandAvailable("dart") || isCommandAvailable("flutter"));
44600
+ return fs12.existsSync(path29.join(cwd, "pubspec.yaml")) && (isCommandAvailable("dart") || isCommandAvailable("flutter"));
43596
44601
  }
43597
44602
  function detectRubocop(cwd) {
43598
- return (fs12.existsSync(path26.join(cwd, "Gemfile")) || fs12.existsSync(path26.join(cwd, "gems.rb")) || fs12.existsSync(path26.join(cwd, ".rubocop.yml"))) && (isCommandAvailable("rubocop") || isCommandAvailable("bundle"));
44603
+ return (fs12.existsSync(path29.join(cwd, "Gemfile")) || fs12.existsSync(path29.join(cwd, "gems.rb")) || fs12.existsSync(path29.join(cwd, ".rubocop.yml"))) && (isCommandAvailable("rubocop") || isCommandAvailable("bundle"));
43599
44604
  }
43600
44605
  function detectAdditionalLinter(cwd) {
43601
44606
  if (detectRuff(cwd))
@@ -43623,10 +44628,10 @@ function detectAdditionalLinter(cwd) {
43623
44628
  function findBinInAncestors(startDir, binName) {
43624
44629
  let dir = startDir;
43625
44630
  while (true) {
43626
- const candidate = path26.join(dir, "node_modules", ".bin", binName);
44631
+ const candidate = path29.join(dir, "node_modules", ".bin", binName);
43627
44632
  if (fs12.existsSync(candidate))
43628
44633
  return candidate;
43629
- const parent = path26.dirname(dir);
44634
+ const parent = path29.dirname(dir);
43630
44635
  if (parent === dir)
43631
44636
  break;
43632
44637
  dir = parent;
@@ -43635,10 +44640,10 @@ function findBinInAncestors(startDir, binName) {
43635
44640
  }
43636
44641
  function findBinInEnvPath(binName) {
43637
44642
  const searchPath = process.env.PATH ?? "";
43638
- for (const dir of searchPath.split(path26.delimiter)) {
44643
+ for (const dir of searchPath.split(path29.delimiter)) {
43639
44644
  if (!dir)
43640
44645
  continue;
43641
- const candidate = path26.join(dir, binName);
44646
+ const candidate = path29.join(dir, binName);
43642
44647
  if (fs12.existsSync(candidate))
43643
44648
  return candidate;
43644
44649
  }
@@ -43651,13 +44656,13 @@ async function detectAvailableLinter(directory) {
43651
44656
  return null;
43652
44657
  const projectDir = directory;
43653
44658
  const isWindows = process.platform === "win32";
43654
- const biomeBin = isWindows ? path26.join(projectDir, "node_modules", ".bin", "biome.EXE") : path26.join(projectDir, "node_modules", ".bin", "biome");
43655
- const eslintBin = isWindows ? path26.join(projectDir, "node_modules", ".bin", "eslint.cmd") : path26.join(projectDir, "node_modules", ".bin", "eslint");
44659
+ const biomeBin = isWindows ? path29.join(projectDir, "node_modules", ".bin", "biome.EXE") : path29.join(projectDir, "node_modules", ".bin", "biome");
44660
+ const eslintBin = isWindows ? path29.join(projectDir, "node_modules", ".bin", "eslint.cmd") : path29.join(projectDir, "node_modules", ".bin", "eslint");
43656
44661
  const localResult = await _detectAvailableLinter(projectDir, biomeBin, eslintBin);
43657
44662
  if (localResult)
43658
44663
  return localResult;
43659
- const biomeAncestor = findBinInAncestors(path26.dirname(projectDir), isWindows ? "biome.EXE" : "biome");
43660
- const eslintAncestor = findBinInAncestors(path26.dirname(projectDir), isWindows ? "eslint.cmd" : "eslint");
44664
+ const biomeAncestor = findBinInAncestors(path29.dirname(projectDir), isWindows ? "biome.EXE" : "biome");
44665
+ const eslintAncestor = findBinInAncestors(path29.dirname(projectDir), isWindows ? "eslint.cmd" : "eslint");
43661
44666
  if (biomeAncestor || eslintAncestor) {
43662
44667
  return _detectAvailableLinter(projectDir, biomeAncestor ?? biomeBin, eslintAncestor ?? eslintBin);
43663
44668
  }
@@ -43880,7 +44885,7 @@ For Rust: rustup component add clippy`
43880
44885
 
43881
44886
  // src/tools/secretscan.ts
43882
44887
  import * as fs13 from "fs";
43883
- import * as path27 from "path";
44888
+ import * as path30 from "path";
43884
44889
  function calculateShannonEntropy(str) {
43885
44890
  if (str.length === 0)
43886
44891
  return 0;
@@ -43928,7 +44933,7 @@ function isGlobOrPathPattern(pattern) {
43928
44933
  return pattern.includes("/") || pattern.includes("\\") || /[*?[\]{}]/.test(pattern);
43929
44934
  }
43930
44935
  function loadSecretScanIgnore(scanDir) {
43931
- const ignorePath = path27.join(scanDir, ".secretscanignore");
44936
+ const ignorePath = path30.join(scanDir, ".secretscanignore");
43932
44937
  try {
43933
44938
  if (!fs13.existsSync(ignorePath))
43934
44939
  return [];
@@ -43951,7 +44956,7 @@ function isExcluded(entry, relPath, exactNames, globPatterns) {
43951
44956
  if (exactNames.has(entry))
43952
44957
  return true;
43953
44958
  for (const pattern of globPatterns) {
43954
- if (path27.matchesGlob(relPath, pattern))
44959
+ if (path30.matchesGlob(relPath, pattern))
43955
44960
  return true;
43956
44961
  }
43957
44962
  return false;
@@ -43972,7 +44977,7 @@ function validateDirectoryInput(dir) {
43972
44977
  return null;
43973
44978
  }
43974
44979
  function isBinaryFile(filePath, buffer) {
43975
- const ext = path27.extname(filePath).toLowerCase();
44980
+ const ext = path30.extname(filePath).toLowerCase();
43976
44981
  if (DEFAULT_EXCLUDE_EXTENSIONS.has(ext)) {
43977
44982
  return true;
43978
44983
  }
@@ -44108,9 +45113,9 @@ function isSymlinkLoop(realPath, visited) {
44108
45113
  return false;
44109
45114
  }
44110
45115
  function isPathWithinScope(realPath, scanDir) {
44111
- const resolvedScanDir = path27.resolve(scanDir);
44112
- const resolvedRealPath = path27.resolve(realPath);
44113
- return resolvedRealPath === resolvedScanDir || resolvedRealPath.startsWith(resolvedScanDir + path27.sep) || resolvedRealPath.startsWith(`${resolvedScanDir}/`) || resolvedRealPath.startsWith(`${resolvedScanDir}\\`);
45116
+ const resolvedScanDir = path30.resolve(scanDir);
45117
+ const resolvedRealPath = path30.resolve(realPath);
45118
+ return resolvedRealPath === resolvedScanDir || resolvedRealPath.startsWith(resolvedScanDir + path30.sep) || resolvedRealPath.startsWith(`${resolvedScanDir}/`) || resolvedRealPath.startsWith(`${resolvedScanDir}\\`);
44114
45119
  }
44115
45120
  function findScannableFiles(dir, excludeExact, excludeGlobs, scanDir, visited, stats = {
44116
45121
  skippedDirs: 0,
@@ -44136,8 +45141,8 @@ function findScannableFiles(dir, excludeExact, excludeGlobs, scanDir, visited, s
44136
45141
  return a.localeCompare(b);
44137
45142
  });
44138
45143
  for (const entry of entries) {
44139
- const fullPath = path27.join(dir, entry);
44140
- const relPath = path27.relative(scanDir, fullPath).replace(/\\/g, "/");
45144
+ const fullPath = path30.join(dir, entry);
45145
+ const relPath = path30.relative(scanDir, fullPath).replace(/\\/g, "/");
44141
45146
  if (isExcluded(entry, relPath, excludeExact, excludeGlobs)) {
44142
45147
  stats.skippedDirs++;
44143
45148
  continue;
@@ -44172,7 +45177,7 @@ function findScannableFiles(dir, excludeExact, excludeGlobs, scanDir, visited, s
44172
45177
  const subFiles = findScannableFiles(fullPath, excludeExact, excludeGlobs, scanDir, visited, stats);
44173
45178
  files.push(...subFiles);
44174
45179
  } else if (lstat.isFile()) {
44175
- const ext = path27.extname(fullPath).toLowerCase();
45180
+ const ext = path30.extname(fullPath).toLowerCase();
44176
45181
  if (!DEFAULT_EXCLUDE_EXTENSIONS.has(ext)) {
44177
45182
  files.push(fullPath);
44178
45183
  } else {
@@ -44432,7 +45437,7 @@ var init_secretscan = __esm(() => {
44432
45437
  }
44433
45438
  }
44434
45439
  try {
44435
- const _scanDirRaw = path27.resolve(directory);
45440
+ const _scanDirRaw = path30.resolve(directory);
44436
45441
  const scanDir = (() => {
44437
45442
  try {
44438
45443
  return fs13.realpathSync(_scanDirRaw);
@@ -44579,7 +45584,7 @@ var init_secretscan = __esm(() => {
44579
45584
 
44580
45585
  // src/test-impact/analyzer.ts
44581
45586
  import fs14 from "fs";
44582
- import path28 from "path";
45587
+ import path31 from "path";
44583
45588
  function normalizePath(p) {
44584
45589
  return p.replace(/\\/g, "/");
44585
45590
  }
@@ -44600,8 +45605,8 @@ function resolveRelativeImport(fromDir, importPath) {
44600
45605
  if (!importPath.startsWith(".")) {
44601
45606
  return null;
44602
45607
  }
44603
- const resolved = path28.resolve(fromDir, importPath);
44604
- if (path28.extname(resolved)) {
45608
+ const resolved = path31.resolve(fromDir, importPath);
45609
+ if (path31.extname(resolved)) {
44605
45610
  if (fs14.existsSync(resolved) && fs14.statSync(resolved).isFile()) {
44606
45611
  return normalizePath(resolved);
44607
45612
  }
@@ -44646,12 +45651,12 @@ function findTestFilesSync(cwd) {
44646
45651
  for (const entry of entries) {
44647
45652
  if (entry.isDirectory()) {
44648
45653
  if (!skipDirs.has(entry.name)) {
44649
- walk(path28.join(dir, entry.name), visitedInodes);
45654
+ walk(path31.join(dir, entry.name), visitedInodes);
44650
45655
  }
44651
45656
  } else if (entry.isFile()) {
44652
45657
  const name = entry.name;
44653
45658
  if (/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(name) || dir.includes("__tests__") && /\.(ts|tsx|js|jsx)$/.test(name)) {
44654
- testFiles.push(normalizePath(path28.join(dir, entry.name)));
45659
+ testFiles.push(normalizePath(path31.join(dir, entry.name)));
44655
45660
  }
44656
45661
  }
44657
45662
  }
@@ -44689,7 +45694,7 @@ async function buildImpactMapInternal(cwd) {
44689
45694
  continue;
44690
45695
  }
44691
45696
  const imports = extractImports(content);
44692
- const testDir = path28.dirname(testFile);
45697
+ const testDir = path31.dirname(testFile);
44693
45698
  for (const importPath of imports) {
44694
45699
  const resolvedSource = resolveRelativeImport(testDir, importPath);
44695
45700
  if (resolvedSource === null) {
@@ -44711,7 +45716,7 @@ async function buildImpactMap(cwd) {
44711
45716
  return impactMap;
44712
45717
  }
44713
45718
  async function loadImpactMap(cwd) {
44714
- const cachePath = path28.join(cwd, ".swarm", "cache", "impact-map.json");
45719
+ const cachePath = path31.join(cwd, ".swarm", "cache", "impact-map.json");
44715
45720
  if (fs14.existsSync(cachePath)) {
44716
45721
  try {
44717
45722
  const content = fs14.readFileSync(cachePath, "utf-8");
@@ -44726,8 +45731,8 @@ async function loadImpactMap(cwd) {
44726
45731
  return _internals18.buildImpactMap(cwd);
44727
45732
  }
44728
45733
  async function saveImpactMap(cwd, impactMap) {
44729
- const cacheDir2 = path28.join(cwd, ".swarm", "cache");
44730
- const cachePath = path28.join(cacheDir2, "impact-map.json");
45734
+ const cacheDir2 = path31.join(cwd, ".swarm", "cache");
45735
+ const cachePath = path31.join(cacheDir2, "impact-map.json");
44731
45736
  if (!fs14.existsSync(cacheDir2)) {
44732
45737
  fs14.mkdirSync(cacheDir2, { recursive: true });
44733
45738
  }
@@ -44753,7 +45758,7 @@ async function analyzeImpact(changedFiles, cwd) {
44753
45758
  const impactedTestsSet = new Set;
44754
45759
  const untestedFiles = [];
44755
45760
  for (const changedFile of validFiles) {
44756
- const normalizedChanged = normalizePath(path28.resolve(changedFile));
45761
+ const normalizedChanged = normalizePath(path31.resolve(changedFile));
44757
45762
  const tests = impactMap[normalizedChanged];
44758
45763
  if (tests && tests.length > 0) {
44759
45764
  for (const test of tests) {
@@ -45017,9 +46022,9 @@ var FLAKY_THRESHOLD = 0.3, MIN_RUNS_FOR_QUARANTINE = 5, MAX_HISTORY_RUNS = 20;
45017
46022
 
45018
46023
  // src/test-impact/history-store.ts
45019
46024
  import fs15 from "fs";
45020
- import path29 from "path";
46025
+ import path32 from "path";
45021
46026
  function getHistoryPath(workingDir) {
45022
- return path29.join(workingDir || process.cwd(), ".swarm", "cache", "test-history.jsonl");
46027
+ return path32.join(workingDir || process.cwd(), ".swarm", "cache", "test-history.jsonl");
45023
46028
  }
45024
46029
  function sanitizeErrorMessage(errorMessage) {
45025
46030
  if (errorMessage === undefined) {
@@ -45074,7 +46079,7 @@ function appendTestRun(record3, workingDir) {
45074
46079
  changedFiles: sanitizeChangedFiles(record3.changedFiles || [])
45075
46080
  };
45076
46081
  const historyPath = getHistoryPath(workingDir);
45077
- const historyDir = path29.dirname(historyPath);
46082
+ const historyDir = path32.dirname(historyPath);
45078
46083
  if (!fs15.existsSync(historyDir)) {
45079
46084
  fs15.mkdirSync(historyDir, { recursive: true });
45080
46085
  }
@@ -45156,7 +46161,7 @@ var init_history_store = __esm(() => {
45156
46161
 
45157
46162
  // src/tools/resolve-working-directory.ts
45158
46163
  import * as fs16 from "fs";
45159
- import * as path30 from "path";
46164
+ import * as path33 from "path";
45160
46165
  function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
45161
46166
  if (workingDirectory == null || workingDirectory === "") {
45162
46167
  return { success: true, directory: fallbackDirectory };
@@ -45176,15 +46181,15 @@ function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
45176
46181
  };
45177
46182
  }
45178
46183
  }
45179
- const normalizedDir = path30.normalize(workingDirectory);
45180
- const pathParts = normalizedDir.split(path30.sep);
46184
+ const normalizedDir = path33.normalize(workingDirectory);
46185
+ const pathParts = normalizedDir.split(path33.sep);
45181
46186
  if (pathParts.includes("..")) {
45182
46187
  return {
45183
46188
  success: false,
45184
46189
  message: "Invalid working_directory: path traversal sequences (..) are not allowed"
45185
46190
  };
45186
46191
  }
45187
- const resolvedDir = path30.resolve(normalizedDir);
46192
+ const resolvedDir = path33.resolve(normalizedDir);
45188
46193
  let statResult;
45189
46194
  try {
45190
46195
  statResult = fs16.statSync(resolvedDir);
@@ -45200,7 +46205,7 @@ function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
45200
46205
  message: `Invalid working_directory: path "${resolvedDir}" is not a directory`
45201
46206
  };
45202
46207
  }
45203
- const resolvedFallback = path30.resolve(fallbackDirectory);
46208
+ const resolvedFallback = path33.resolve(fallbackDirectory);
45204
46209
  let fallbackExists = false;
45205
46210
  try {
45206
46211
  fs16.statSync(resolvedFallback);
@@ -45210,7 +46215,7 @@ function resolveWorkingDirectory(workingDirectory, fallbackDirectory) {
45210
46215
  }
45211
46216
  if (workingDirectory != null && workingDirectory !== "") {
45212
46217
  if (fallbackExists) {
45213
- const isSubdirectory = resolvedDir.startsWith(resolvedFallback + path30.sep);
46218
+ const isSubdirectory = resolvedDir.startsWith(resolvedFallback + path33.sep);
45214
46219
  if (isSubdirectory) {
45215
46220
  return {
45216
46221
  success: false,
@@ -45232,7 +46237,7 @@ var init_resolve_working_directory = () => {};
45232
46237
 
45233
46238
  // src/tools/test-runner.ts
45234
46239
  import * as fs17 from "fs";
45235
- import * as path31 from "path";
46240
+ import * as path34 from "path";
45236
46241
  function isAbsolutePath(str) {
45237
46242
  if (str.startsWith("/"))
45238
46243
  return true;
@@ -45297,14 +46302,14 @@ function hasDevDependency(devDeps, ...patterns) {
45297
46302
  return hasPackageJsonDependency(devDeps, ...patterns);
45298
46303
  }
45299
46304
  function detectGoTest(cwd) {
45300
- return fs17.existsSync(path31.join(cwd, "go.mod")) && isCommandAvailable("go");
46305
+ return fs17.existsSync(path34.join(cwd, "go.mod")) && isCommandAvailable("go");
45301
46306
  }
45302
46307
  function detectJavaMaven(cwd) {
45303
- return fs17.existsSync(path31.join(cwd, "pom.xml")) && isCommandAvailable("mvn");
46308
+ return fs17.existsSync(path34.join(cwd, "pom.xml")) && isCommandAvailable("mvn");
45304
46309
  }
45305
46310
  function detectGradle(cwd) {
45306
- const hasBuildFile = fs17.existsSync(path31.join(cwd, "build.gradle")) || fs17.existsSync(path31.join(cwd, "build.gradle.kts"));
45307
- const hasGradlew = fs17.existsSync(path31.join(cwd, "gradlew")) || fs17.existsSync(path31.join(cwd, "gradlew.bat"));
46311
+ const hasBuildFile = fs17.existsSync(path34.join(cwd, "build.gradle")) || fs17.existsSync(path34.join(cwd, "build.gradle.kts"));
46312
+ const hasGradlew = fs17.existsSync(path34.join(cwd, "gradlew")) || fs17.existsSync(path34.join(cwd, "gradlew.bat"));
45308
46313
  return hasBuildFile && (hasGradlew || isCommandAvailable("gradle"));
45309
46314
  }
45310
46315
  function detectDotnetTest(cwd) {
@@ -45317,30 +46322,30 @@ function detectDotnetTest(cwd) {
45317
46322
  }
45318
46323
  }
45319
46324
  function detectCTest(cwd) {
45320
- const hasSource = fs17.existsSync(path31.join(cwd, "CMakeLists.txt"));
45321
- const hasBuildCache = fs17.existsSync(path31.join(cwd, "CMakeCache.txt")) || fs17.existsSync(path31.join(cwd, "build", "CMakeCache.txt"));
46325
+ const hasSource = fs17.existsSync(path34.join(cwd, "CMakeLists.txt"));
46326
+ const hasBuildCache = fs17.existsSync(path34.join(cwd, "CMakeCache.txt")) || fs17.existsSync(path34.join(cwd, "build", "CMakeCache.txt"));
45322
46327
  return (hasSource || hasBuildCache) && isCommandAvailable("ctest");
45323
46328
  }
45324
46329
  function detectSwiftTest(cwd) {
45325
- return fs17.existsSync(path31.join(cwd, "Package.swift")) && isCommandAvailable("swift");
46330
+ return fs17.existsSync(path34.join(cwd, "Package.swift")) && isCommandAvailable("swift");
45326
46331
  }
45327
46332
  function detectDartTest(cwd) {
45328
- return fs17.existsSync(path31.join(cwd, "pubspec.yaml")) && (isCommandAvailable("dart") || isCommandAvailable("flutter"));
46333
+ return fs17.existsSync(path34.join(cwd, "pubspec.yaml")) && (isCommandAvailable("dart") || isCommandAvailable("flutter"));
45329
46334
  }
45330
46335
  function detectRSpec(cwd) {
45331
- const hasRSpecFile = fs17.existsSync(path31.join(cwd, ".rspec"));
45332
- const hasGemfile = fs17.existsSync(path31.join(cwd, "Gemfile"));
45333
- const hasSpecDir = fs17.existsSync(path31.join(cwd, "spec"));
46336
+ const hasRSpecFile = fs17.existsSync(path34.join(cwd, ".rspec"));
46337
+ const hasGemfile = fs17.existsSync(path34.join(cwd, "Gemfile"));
46338
+ const hasSpecDir = fs17.existsSync(path34.join(cwd, "spec"));
45334
46339
  const hasRSpec = hasRSpecFile || hasGemfile && hasSpecDir;
45335
46340
  return hasRSpec && (isCommandAvailable("bundle") || isCommandAvailable("rspec"));
45336
46341
  }
45337
46342
  function detectMinitest(cwd) {
45338
- return fs17.existsSync(path31.join(cwd, "test")) && (fs17.existsSync(path31.join(cwd, "Gemfile")) || fs17.existsSync(path31.join(cwd, "Rakefile"))) && isCommandAvailable("ruby");
46343
+ return fs17.existsSync(path34.join(cwd, "test")) && (fs17.existsSync(path34.join(cwd, "Gemfile")) || fs17.existsSync(path34.join(cwd, "Rakefile"))) && isCommandAvailable("ruby");
45339
46344
  }
45340
46345
  async function detectTestFramework(cwd) {
45341
46346
  const baseDir = cwd;
45342
46347
  try {
45343
- const packageJsonPath = path31.join(baseDir, "package.json");
46348
+ const packageJsonPath = path34.join(baseDir, "package.json");
45344
46349
  if (fs17.existsSync(packageJsonPath)) {
45345
46350
  const content = fs17.readFileSync(packageJsonPath, "utf-8");
45346
46351
  const pkg = JSON.parse(content);
@@ -45361,16 +46366,16 @@ async function detectTestFramework(cwd) {
45361
46366
  return "jest";
45362
46367
  if (hasDevDependency(devDeps, "mocha", "@types/mocha"))
45363
46368
  return "mocha";
45364
- if (fs17.existsSync(path31.join(baseDir, "bun.lockb")) || fs17.existsSync(path31.join(baseDir, "bun.lock"))) {
46369
+ if (fs17.existsSync(path34.join(baseDir, "bun.lockb")) || fs17.existsSync(path34.join(baseDir, "bun.lock"))) {
45365
46370
  if (scripts.test?.includes("bun"))
45366
46371
  return "bun";
45367
46372
  }
45368
46373
  }
45369
46374
  } catch {}
45370
46375
  try {
45371
- const pyprojectTomlPath = path31.join(baseDir, "pyproject.toml");
45372
- const setupCfgPath = path31.join(baseDir, "setup.cfg");
45373
- const requirementsTxtPath = path31.join(baseDir, "requirements.txt");
46376
+ const pyprojectTomlPath = path34.join(baseDir, "pyproject.toml");
46377
+ const setupCfgPath = path34.join(baseDir, "setup.cfg");
46378
+ const requirementsTxtPath = path34.join(baseDir, "requirements.txt");
45374
46379
  if (fs17.existsSync(pyprojectTomlPath)) {
45375
46380
  const content = fs17.readFileSync(pyprojectTomlPath, "utf-8");
45376
46381
  if (content.includes("[tool.pytest"))
@@ -45390,7 +46395,7 @@ async function detectTestFramework(cwd) {
45390
46395
  }
45391
46396
  } catch {}
45392
46397
  try {
45393
- const cargoTomlPath = path31.join(baseDir, "Cargo.toml");
46398
+ const cargoTomlPath = path34.join(baseDir, "Cargo.toml");
45394
46399
  if (fs17.existsSync(cargoTomlPath)) {
45395
46400
  const content = fs17.readFileSync(cargoTomlPath, "utf-8");
45396
46401
  if (content.includes("[dev-dependencies]")) {
@@ -45401,9 +46406,9 @@ async function detectTestFramework(cwd) {
45401
46406
  }
45402
46407
  } catch {}
45403
46408
  try {
45404
- const pesterConfigPath = path31.join(baseDir, "pester.config.ps1");
45405
- const pesterConfigJsonPath = path31.join(baseDir, "pester.config.ps1.json");
45406
- const pesterPs1Path = path31.join(baseDir, "tests.ps1");
46409
+ const pesterConfigPath = path34.join(baseDir, "pester.config.ps1");
46410
+ const pesterConfigJsonPath = path34.join(baseDir, "pester.config.ps1.json");
46411
+ const pesterPs1Path = path34.join(baseDir, "tests.ps1");
45407
46412
  if (fs17.existsSync(pesterConfigPath) || fs17.existsSync(pesterConfigJsonPath) || fs17.existsSync(pesterPs1Path)) {
45408
46413
  return "pester";
45409
46414
  }
@@ -45432,12 +46437,12 @@ function isTestDirectoryPath(normalizedPath) {
45432
46437
  return normalizedPath.split("/").some((segment) => TEST_DIRECTORY_NAMES.includes(segment));
45433
46438
  }
45434
46439
  function resolveWorkspacePath(file3, workingDir) {
45435
- return path31.isAbsolute(file3) ? path31.resolve(file3) : path31.resolve(workingDir, file3);
46440
+ return path34.isAbsolute(file3) ? path34.resolve(file3) : path34.resolve(workingDir, file3);
45436
46441
  }
45437
46442
  function toWorkspaceOutputPath(absolutePath, workingDir, preferRelative) {
45438
46443
  if (!preferRelative)
45439
46444
  return absolutePath;
45440
- return path31.relative(workingDir, absolutePath);
46445
+ return path34.relative(workingDir, absolutePath);
45441
46446
  }
45442
46447
  function dedupePush(target, value) {
45443
46448
  if (!target.includes(value)) {
@@ -45474,18 +46479,18 @@ function buildLanguageSpecificTestNames(nameWithoutExt, ext) {
45474
46479
  }
45475
46480
  }
45476
46481
  function getRepoLevelCandidateDirectories(workingDir, relativePath, ext) {
45477
- const relativeDir = path31.dirname(relativePath);
46482
+ const relativeDir = path34.dirname(relativePath);
45478
46483
  const nestedRelativeDir = relativeDir === "." ? "" : relativeDir;
45479
46484
  const directories = TEST_DIRECTORY_NAMES.flatMap((dirName) => {
45480
- const rootDir = path31.join(workingDir, dirName);
45481
- return nestedRelativeDir ? [rootDir, path31.join(rootDir, nestedRelativeDir)] : [rootDir];
46485
+ const rootDir = path34.join(workingDir, dirName);
46486
+ return nestedRelativeDir ? [rootDir, path34.join(rootDir, nestedRelativeDir)] : [rootDir];
45482
46487
  });
45483
46488
  const normalizedRelativePath = relativePath.replace(/\\/g, "/");
45484
46489
  if (ext === ".java" && normalizedRelativePath.startsWith("src/main/java/")) {
45485
- directories.push(path31.join(workingDir, "src/test/java", path31.dirname(normalizedRelativePath.slice("src/main/java/".length))));
46490
+ directories.push(path34.join(workingDir, "src/test/java", path34.dirname(normalizedRelativePath.slice("src/main/java/".length))));
45486
46491
  }
45487
46492
  if ((ext === ".kt" || ext === ".java") && normalizedRelativePath.startsWith("src/main/kotlin/")) {
45488
- directories.push(path31.join(workingDir, "src/test/kotlin", path31.dirname(normalizedRelativePath.slice("src/main/kotlin/".length))));
46493
+ directories.push(path34.join(workingDir, "src/test/kotlin", path34.dirname(normalizedRelativePath.slice("src/main/kotlin/".length))));
45489
46494
  }
45490
46495
  return [...new Set(directories)];
45491
46496
  }
@@ -45513,23 +46518,23 @@ function isLanguageSpecificTestFile(basename5) {
45513
46518
  }
45514
46519
  function isConventionTestFilePath(filePath) {
45515
46520
  const normalizedPath = filePath.replace(/\\/g, "/");
45516
- const basename5 = path31.basename(filePath);
46521
+ const basename5 = path34.basename(filePath);
45517
46522
  return hasCompoundTestExtension(basename5) || basename5.includes(".spec.") || basename5.includes(".test.") || isLanguageSpecificTestFile(basename5) || isTestDirectoryPath(normalizedPath);
45518
46523
  }
45519
46524
  function getTestFilesFromConvention(sourceFiles, workingDir = process.cwd()) {
45520
46525
  const testFiles = [];
45521
46526
  for (const file3 of sourceFiles) {
45522
46527
  const absoluteFile = resolveWorkspacePath(file3, workingDir);
45523
- const relativeFile = path31.relative(workingDir, absoluteFile);
45524
- const basename5 = path31.basename(absoluteFile);
45525
- const dirname13 = path31.dirname(absoluteFile);
45526
- const preferRelativeOutput = !path31.isAbsolute(file3);
46528
+ const relativeFile = path34.relative(workingDir, absoluteFile);
46529
+ const basename5 = path34.basename(absoluteFile);
46530
+ const dirname16 = path34.dirname(absoluteFile);
46531
+ const preferRelativeOutput = !path34.isAbsolute(file3);
45527
46532
  if (isConventionTestFilePath(relativeFile) || isConventionTestFilePath(file3)) {
45528
46533
  dedupePush(testFiles, toWorkspaceOutputPath(absoluteFile, workingDir, preferRelativeOutput));
45529
46534
  continue;
45530
46535
  }
45531
46536
  const nameWithoutExt = basename5.replace(/\.[^.]+$/, "");
45532
- const ext = path31.extname(basename5);
46537
+ const ext = path34.extname(basename5);
45533
46538
  const genericTestNames = [
45534
46539
  `${nameWithoutExt}.spec${ext}`,
45535
46540
  `${nameWithoutExt}.test${ext}`
@@ -45538,7 +46543,7 @@ function getTestFilesFromConvention(sourceFiles, workingDir = process.cwd()) {
45538
46543
  const colocatedCandidates = [
45539
46544
  ...genericTestNames,
45540
46545
  ...languageSpecificTestNames
45541
- ].map((candidateName) => path31.join(dirname13, candidateName));
46546
+ ].map((candidateName) => path34.join(dirname16, candidateName));
45542
46547
  const testDirectoryNames = [
45543
46548
  basename5,
45544
46549
  ...genericTestNames,
@@ -45547,8 +46552,8 @@ function getTestFilesFromConvention(sourceFiles, workingDir = process.cwd()) {
45547
46552
  const repoLevelDirectories = getRepoLevelCandidateDirectories(workingDir, relativeFile, ext);
45548
46553
  const possibleTestFiles = [
45549
46554
  ...colocatedCandidates,
45550
- ...TEST_DIRECTORY_NAMES.flatMap((dirName) => testDirectoryNames.map((candidateName) => path31.join(dirname13, dirName, candidateName))),
45551
- ...repoLevelDirectories.flatMap((candidateDir) => testDirectoryNames.map((candidateName) => path31.join(candidateDir, candidateName)))
46555
+ ...TEST_DIRECTORY_NAMES.flatMap((dirName) => testDirectoryNames.map((candidateName) => path34.join(dirname16, dirName, candidateName))),
46556
+ ...repoLevelDirectories.flatMap((candidateDir) => testDirectoryNames.map((candidateName) => path34.join(candidateDir, candidateName)))
45552
46557
  ];
45553
46558
  for (const testFile of possibleTestFiles) {
45554
46559
  if (fs17.existsSync(testFile)) {
@@ -45569,7 +46574,7 @@ async function getTestFilesFromGraph(sourceFiles, workingDir) {
45569
46574
  try {
45570
46575
  const absoluteTestFile = resolveWorkspacePath(testFile, workingDir);
45571
46576
  const content = fs17.readFileSync(absoluteTestFile, "utf-8");
45572
- const testDir = path31.dirname(absoluteTestFile);
46577
+ const testDir = path34.dirname(absoluteTestFile);
45573
46578
  const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
45574
46579
  let match;
45575
46580
  match = importRegex.exec(content);
@@ -45577,8 +46582,8 @@ async function getTestFilesFromGraph(sourceFiles, workingDir) {
45577
46582
  const importPath = match[1];
45578
46583
  let resolvedImport;
45579
46584
  if (importPath.startsWith(".")) {
45580
- resolvedImport = path31.resolve(testDir, importPath);
45581
- const existingExt = path31.extname(resolvedImport);
46585
+ resolvedImport = path34.resolve(testDir, importPath);
46586
+ const existingExt = path34.extname(resolvedImport);
45582
46587
  if (!existingExt) {
45583
46588
  for (const extToTry of [
45584
46589
  ".ts",
@@ -45598,12 +46603,12 @@ async function getTestFilesFromGraph(sourceFiles, workingDir) {
45598
46603
  } else {
45599
46604
  continue;
45600
46605
  }
45601
- const importBasename = path31.basename(resolvedImport, path31.extname(resolvedImport));
45602
- const importDir = path31.dirname(resolvedImport);
46606
+ const importBasename = path34.basename(resolvedImport, path34.extname(resolvedImport));
46607
+ const importDir = path34.dirname(resolvedImport);
45603
46608
  for (const sourceFile of absoluteSourceFiles) {
45604
- const sourceDir = path31.dirname(sourceFile);
45605
- const sourceBasename = path31.basename(sourceFile, path31.extname(sourceFile));
45606
- const isRelatedDir = importDir === sourceDir || importDir === path31.join(sourceDir, "__tests__") || importDir === path31.join(sourceDir, "tests") || importDir === path31.join(sourceDir, "test") || importDir === path31.join(sourceDir, "spec");
46609
+ const sourceDir = path34.dirname(sourceFile);
46610
+ const sourceBasename = path34.basename(sourceFile, path34.extname(sourceFile));
46611
+ const isRelatedDir = importDir === sourceDir || importDir === path34.join(sourceDir, "__tests__") || importDir === path34.join(sourceDir, "tests") || importDir === path34.join(sourceDir, "test") || importDir === path34.join(sourceDir, "spec");
45607
46612
  if (resolvedImport === sourceFile || importBasename === sourceBasename && isRelatedDir) {
45608
46613
  dedupePush(testFiles, testFile);
45609
46614
  break;
@@ -45616,8 +46621,8 @@ async function getTestFilesFromGraph(sourceFiles, workingDir) {
45616
46621
  while (match !== null) {
45617
46622
  const importPath = match[1];
45618
46623
  if (importPath.startsWith(".")) {
45619
- let resolvedImport = path31.resolve(testDir, importPath);
45620
- const existingExt = path31.extname(resolvedImport);
46624
+ let resolvedImport = path34.resolve(testDir, importPath);
46625
+ const existingExt = path34.extname(resolvedImport);
45621
46626
  if (!existingExt) {
45622
46627
  for (const extToTry of [
45623
46628
  ".ts",
@@ -45634,12 +46639,12 @@ async function getTestFilesFromGraph(sourceFiles, workingDir) {
45634
46639
  }
45635
46640
  }
45636
46641
  }
45637
- const importDir = path31.dirname(resolvedImport);
45638
- const importBasename = path31.basename(resolvedImport, path31.extname(resolvedImport));
46642
+ const importDir = path34.dirname(resolvedImport);
46643
+ const importBasename = path34.basename(resolvedImport, path34.extname(resolvedImport));
45639
46644
  for (const sourceFile of absoluteSourceFiles) {
45640
- const sourceDir = path31.dirname(sourceFile);
45641
- const sourceBasename = path31.basename(sourceFile, path31.extname(sourceFile));
45642
- const isRelatedDir = importDir === sourceDir || importDir === path31.join(sourceDir, "__tests__") || importDir === path31.join(sourceDir, "tests") || importDir === path31.join(sourceDir, "test") || importDir === path31.join(sourceDir, "spec");
46645
+ const sourceDir = path34.dirname(sourceFile);
46646
+ const sourceBasename = path34.basename(sourceFile, path34.extname(sourceFile));
46647
+ const isRelatedDir = importDir === sourceDir || importDir === path34.join(sourceDir, "__tests__") || importDir === path34.join(sourceDir, "tests") || importDir === path34.join(sourceDir, "test") || importDir === path34.join(sourceDir, "spec");
45643
46648
  if (resolvedImport === sourceFile || importBasename === sourceBasename && isRelatedDir) {
45644
46649
  dedupePush(testFiles, testFile);
45645
46650
  break;
@@ -45742,8 +46747,8 @@ function buildTestCommand(framework, scope, files, coverage, baseDir) {
45742
46747
  return ["mvn", "test"];
45743
46748
  case "gradle": {
45744
46749
  const isWindows = process.platform === "win32";
45745
- const hasGradlewBat = fs17.existsSync(path31.join(baseDir, "gradlew.bat"));
45746
- const hasGradlew = fs17.existsSync(path31.join(baseDir, "gradlew"));
46750
+ const hasGradlewBat = fs17.existsSync(path34.join(baseDir, "gradlew.bat"));
46751
+ const hasGradlew = fs17.existsSync(path34.join(baseDir, "gradlew"));
45747
46752
  if (hasGradlewBat && isWindows)
45748
46753
  return ["gradlew.bat", "test"];
45749
46754
  if (hasGradlew)
@@ -45760,7 +46765,7 @@ function buildTestCommand(framework, scope, files, coverage, baseDir) {
45760
46765
  "cmake-build-release",
45761
46766
  "out"
45762
46767
  ];
45763
- const actualBuildDir = buildDirCandidates.find((d) => fs17.existsSync(path31.join(baseDir, d, "CMakeCache.txt"))) ?? "build";
46768
+ const actualBuildDir = buildDirCandidates.find((d) => fs17.existsSync(path34.join(baseDir, d, "CMakeCache.txt"))) ?? "build";
45764
46769
  return ["ctest", "--test-dir", actualBuildDir];
45765
46770
  }
45766
46771
  case "swift-test":
@@ -46413,7 +47418,7 @@ var init_test_runner = __esm(() => {
46413
47418
  const sourceFiles = args.files.filter((file3) => {
46414
47419
  if (directTestFiles.includes(file3))
46415
47420
  return false;
46416
- const ext = path31.extname(file3).toLowerCase();
47421
+ const ext = path34.extname(file3).toLowerCase();
46417
47422
  return SOURCE_EXTENSIONS.has(ext);
46418
47423
  });
46419
47424
  const invalidFiles = args.files.filter((file3) => !directTestFiles.includes(file3) && !sourceFiles.includes(file3));
@@ -46448,7 +47453,7 @@ var init_test_runner = __esm(() => {
46448
47453
  if (isConventionTestFilePath(f)) {
46449
47454
  return false;
46450
47455
  }
46451
- const ext = path31.extname(f).toLowerCase();
47456
+ const ext = path34.extname(f).toLowerCase();
46452
47457
  return SOURCE_EXTENSIONS.has(ext);
46453
47458
  });
46454
47459
  if (sourceFiles.length === 0) {
@@ -46475,7 +47480,7 @@ var init_test_runner = __esm(() => {
46475
47480
  if (isConventionTestFilePath(f)) {
46476
47481
  return false;
46477
47482
  }
46478
- const ext = path31.extname(f).toLowerCase();
47483
+ const ext = path34.extname(f).toLowerCase();
46479
47484
  return SOURCE_EXTENSIONS.has(ext);
46480
47485
  });
46481
47486
  if (sourceFiles.length === 0) {
@@ -46493,8 +47498,8 @@ var init_test_runner = __esm(() => {
46493
47498
  const impactResult = await analyzeImpact(sourceFiles, workingDir);
46494
47499
  if (impactResult.impactedTests.length > 0) {
46495
47500
  testFiles = impactResult.impactedTests.map((absPath) => {
46496
- const relativePath = path31.relative(workingDir, absPath);
46497
- return path31.isAbsolute(relativePath) ? absPath : relativePath;
47501
+ const relativePath = path34.relative(workingDir, absPath);
47502
+ return path34.isAbsolute(relativePath) ? absPath : relativePath;
46498
47503
  });
46499
47504
  } else {
46500
47505
  graphFallbackReason = "no impacted tests found via impact analysis, falling back to graph";
@@ -46570,7 +47575,7 @@ var init_test_runner = __esm(() => {
46570
47575
 
46571
47576
  // src/services/preflight-service.ts
46572
47577
  import * as fs18 from "fs";
46573
- import * as path32 from "path";
47578
+ import * as path35 from "path";
46574
47579
  function validateDirectoryPath(dir) {
46575
47580
  if (!dir || typeof dir !== "string") {
46576
47581
  throw new Error("Directory path is required");
@@ -46578,8 +47583,8 @@ function validateDirectoryPath(dir) {
46578
47583
  if (dir.includes("..")) {
46579
47584
  throw new Error("Directory path must not contain path traversal sequences");
46580
47585
  }
46581
- const normalized = path32.normalize(dir);
46582
- const absolutePath = path32.isAbsolute(normalized) ? normalized : path32.resolve(normalized);
47586
+ const normalized = path35.normalize(dir);
47587
+ const absolutePath = path35.isAbsolute(normalized) ? normalized : path35.resolve(normalized);
46583
47588
  return absolutePath;
46584
47589
  }
46585
47590
  function validateTimeout(timeoutMs, defaultValue) {
@@ -46602,7 +47607,7 @@ function validateTimeout(timeoutMs, defaultValue) {
46602
47607
  }
46603
47608
  function getPackageVersion(dir) {
46604
47609
  try {
46605
- const packagePath = path32.join(dir, "package.json");
47610
+ const packagePath = path35.join(dir, "package.json");
46606
47611
  if (fs18.existsSync(packagePath)) {
46607
47612
  const content = fs18.readFileSync(packagePath, "utf-8");
46608
47613
  const pkg = JSON.parse(content);
@@ -46613,7 +47618,7 @@ function getPackageVersion(dir) {
46613
47618
  }
46614
47619
  function getChangelogVersion(dir) {
46615
47620
  try {
46616
- const changelogPath = path32.join(dir, "CHANGELOG.md");
47621
+ const changelogPath = path35.join(dir, "CHANGELOG.md");
46617
47622
  if (fs18.existsSync(changelogPath)) {
46618
47623
  const content = fs18.readFileSync(changelogPath, "utf-8");
46619
47624
  const match = content.match(/^##\s*\[?(\d+\.\d+\.\d+)\]?/m);
@@ -46627,7 +47632,7 @@ function getChangelogVersion(dir) {
46627
47632
  function getVersionFileVersion(dir) {
46628
47633
  const possibleFiles = ["VERSION.txt", "version.txt", "VERSION", "version"];
46629
47634
  for (const file3 of possibleFiles) {
46630
- const filePath = path32.join(dir, file3);
47635
+ const filePath = path35.join(dir, file3);
46631
47636
  if (fs18.existsSync(filePath)) {
46632
47637
  try {
46633
47638
  const content = fs18.readFileSync(filePath, "utf-8").trim();
@@ -46954,7 +47959,7 @@ async function runEvidenceCheck(dir) {
46954
47959
  async function runRequirementCoverageCheck(dir, currentPhase) {
46955
47960
  const startTime = Date.now();
46956
47961
  try {
46957
- const specPath = path32.join(dir, ".swarm", "spec.md");
47962
+ const specPath = path35.join(dir, ".swarm", "spec.md");
46958
47963
  if (!fs18.existsSync(specPath)) {
46959
47964
  return {
46960
47965
  type: "req_coverage",
@@ -48071,7 +49076,7 @@ var init_manager3 = __esm(() => {
48071
49076
 
48072
49077
  // src/commands/reset.ts
48073
49078
  import * as fs19 from "fs";
48074
- import * as path33 from "path";
49079
+ import * as path36 from "path";
48075
49080
  async function handleResetCommand(directory, args) {
48076
49081
  const hasConfirm = args.includes("--confirm");
48077
49082
  if (!hasConfirm) {
@@ -48111,7 +49116,7 @@ async function handleResetCommand(directory, args) {
48111
49116
  }
48112
49117
  for (const filename of ["SWARM_PLAN.md", "SWARM_PLAN.json"]) {
48113
49118
  try {
48114
- const rootPath = path33.join(directory, filename);
49119
+ const rootPath = path36.join(directory, filename);
48115
49120
  if (fs19.existsSync(rootPath)) {
48116
49121
  fs19.unlinkSync(rootPath);
48117
49122
  results.push(`- \u2705 Deleted ${filename} (root)`);
@@ -48151,7 +49156,7 @@ var init_reset = __esm(() => {
48151
49156
 
48152
49157
  // src/commands/reset-session.ts
48153
49158
  import * as fs20 from "fs";
48154
- import * as path34 from "path";
49159
+ import * as path37 from "path";
48155
49160
  async function handleResetSessionCommand(directory, _args) {
48156
49161
  const results = [];
48157
49162
  try {
@@ -48166,13 +49171,13 @@ async function handleResetSessionCommand(directory, _args) {
48166
49171
  results.push("\u274C Failed to delete state.json");
48167
49172
  }
48168
49173
  try {
48169
- const sessionDir = path34.dirname(validateSwarmPath(directory, "session/state.json"));
49174
+ const sessionDir = path37.dirname(validateSwarmPath(directory, "session/state.json"));
48170
49175
  if (fs20.existsSync(sessionDir)) {
48171
49176
  const files = fs20.readdirSync(sessionDir);
48172
49177
  const otherFiles = files.filter((f) => f !== "state.json");
48173
49178
  let deletedCount = 0;
48174
49179
  for (const file3 of otherFiles) {
48175
- const filePath = path34.join(sessionDir, file3);
49180
+ const filePath = path37.join(sessionDir, file3);
48176
49181
  if (fs20.lstatSync(filePath).isFile()) {
48177
49182
  fs20.unlinkSync(filePath);
48178
49183
  deletedCount++;
@@ -48204,7 +49209,7 @@ var init_reset_session = __esm(() => {
48204
49209
  });
48205
49210
 
48206
49211
  // src/summaries/manager.ts
48207
- import * as path35 from "path";
49212
+ import * as path38 from "path";
48208
49213
  function sanitizeSummaryId(id) {
48209
49214
  if (!id || id.length === 0) {
48210
49215
  throw new Error("Invalid summary ID: empty string");
@@ -48227,7 +49232,7 @@ function sanitizeSummaryId(id) {
48227
49232
  }
48228
49233
  async function loadFullOutput(directory, id) {
48229
49234
  const sanitizedId = sanitizeSummaryId(id);
48230
- const relativePath = path35.join("summaries", `${sanitizedId}.json`);
49235
+ const relativePath = path38.join("summaries", `${sanitizedId}.json`);
48231
49236
  validateSwarmPath(directory, relativePath);
48232
49237
  const content = await readSwarmFileAsync(directory, relativePath);
48233
49238
  if (content === null) {
@@ -48290,7 +49295,7 @@ var init_retrieve = __esm(() => {
48290
49295
 
48291
49296
  // src/commands/rollback.ts
48292
49297
  import * as fs21 from "fs";
48293
- import * as path36 from "path";
49298
+ import * as path39 from "path";
48294
49299
  async function handleRollbackCommand(directory, args) {
48295
49300
  const phaseArg = args[0];
48296
49301
  if (!phaseArg) {
@@ -48355,8 +49360,8 @@ async function handleRollbackCommand(directory, args) {
48355
49360
  if (EXCLUDE_FILES.has(file3) || file3.startsWith("plan-ledger.archived-")) {
48356
49361
  continue;
48357
49362
  }
48358
- const src = path36.join(checkpointDir, file3);
48359
- const dest = path36.join(swarmDir, file3);
49363
+ const src = path39.join(checkpointDir, file3);
49364
+ const dest = path39.join(swarmDir, file3);
48360
49365
  try {
48361
49366
  fs21.cpSync(src, dest, { recursive: true, force: true });
48362
49367
  successes.push(file3);
@@ -48375,12 +49380,12 @@ async function handleRollbackCommand(directory, args) {
48375
49380
  ].join(`
48376
49381
  `);
48377
49382
  }
48378
- const existingLedgerPath = path36.join(swarmDir, "plan-ledger.jsonl");
49383
+ const existingLedgerPath = path39.join(swarmDir, "plan-ledger.jsonl");
48379
49384
  if (fs21.existsSync(existingLedgerPath)) {
48380
49385
  fs21.unlinkSync(existingLedgerPath);
48381
49386
  }
48382
49387
  try {
48383
- const planJsonPath = path36.join(swarmDir, "plan.json");
49388
+ const planJsonPath = path39.join(swarmDir, "plan.json");
48384
49389
  if (fs21.existsSync(planJsonPath)) {
48385
49390
  const planRaw = fs21.readFileSync(planJsonPath, "utf-8");
48386
49391
  const plan = PlanSchema.parse(JSON.parse(planRaw));
@@ -48471,9 +49476,9 @@ Ensure this is a git repository with commit history.`;
48471
49476
  `);
48472
49477
  try {
48473
49478
  const fs22 = await import("fs/promises");
48474
- const path37 = await import("path");
48475
- const reportPath = path37.join(directory, ".swarm", "simulate-report.md");
48476
- await fs22.mkdir(path37.dirname(reportPath), { recursive: true });
49479
+ const path40 = await import("path");
49480
+ const reportPath = path40.join(directory, ".swarm", "simulate-report.md");
49481
+ await fs22.mkdir(path40.dirname(reportPath), { recursive: true });
48477
49482
  await fs22.writeFile(reportPath, report, "utf-8");
48478
49483
  } catch (err) {
48479
49484
  const writeErr = err instanceof Error ? err.message : String(err);
@@ -48497,12 +49502,12 @@ async function handleSpecifyCommand(_directory, args) {
48497
49502
 
48498
49503
  // src/turbo/lean/state.ts
48499
49504
  import * as fs22 from "fs";
48500
- import * as path37 from "path";
49505
+ import * as path40 from "path";
48501
49506
  function nowISO2() {
48502
49507
  return new Date().toISOString();
48503
49508
  }
48504
49509
  function ensureSwarmDir2(directory) {
48505
- const swarmDir = path37.resolve(directory, ".swarm");
49510
+ const swarmDir = path40.resolve(directory, ".swarm");
48506
49511
  if (!fs22.existsSync(swarmDir)) {
48507
49512
  fs22.mkdirSync(swarmDir, { recursive: true });
48508
49513
  }
@@ -48546,7 +49551,7 @@ function markStateUnreadable2(directory, reason) {
48546
49551
  }
48547
49552
  function readPersisted2(directory) {
48548
49553
  try {
48549
- const filePath = path37.join(directory, ".swarm", STATE_FILE2);
49554
+ const filePath = path40.join(directory, ".swarm", STATE_FILE2);
48550
49555
  if (!fs22.existsSync(filePath)) {
48551
49556
  const seed = emptyPersisted2();
48552
49557
  try {
@@ -48582,7 +49587,7 @@ function writePersisted2(directory, persisted) {
48582
49587
  let payload;
48583
49588
  try {
48584
49589
  ensureSwarmDir2(directory);
48585
- filePath = path37.join(directory, ".swarm", STATE_FILE2);
49590
+ filePath = path40.join(directory, ".swarm", STATE_FILE2);
48586
49591
  tmpPath = `${filePath}.tmp.${Date.now()}`;
48587
49592
  persisted.updatedAt = nowISO2();
48588
49593
  payload = `${JSON.stringify(persisted, null, 2)}
@@ -49246,7 +50251,7 @@ __export(exports_commands, {
49246
50251
  COMMAND_NAMES: () => COMMAND_NAMES
49247
50252
  });
49248
50253
  import fs23 from "fs";
49249
- import path38 from "path";
50254
+ import path41 from "path";
49250
50255
  function buildHelpText() {
49251
50256
  const lines = ["## Swarm Commands", ""];
49252
50257
  const CATEGORIES = [
@@ -49349,9 +50354,9 @@ function createSwarmCommandHandler(directory, agents) {
49349
50354
  return;
49350
50355
  }
49351
50356
  let isFirstRun = false;
49352
- const sentinelPath = path38.join(directory, ".swarm", ".first-run-complete");
50357
+ const sentinelPath = path41.join(directory, ".swarm", ".first-run-complete");
49353
50358
  try {
49354
- const swarmDir = path38.join(directory, ".swarm");
50359
+ const swarmDir = path41.join(directory, ".swarm");
49355
50360
  fs23.mkdirSync(swarmDir, { recursive: true });
49356
50361
  fs23.writeFileSync(sentinelPath, `first-run-complete: ${new Date().toISOString()}
49357
50362
  `, { flag: "wx" });
@@ -49576,24 +50581,24 @@ function validateAliases() {
49576
50581
  }
49577
50582
  aliasTargets.get(target).push(name);
49578
50583
  const visited = new Set;
49579
- const path39 = [];
50584
+ const path42 = [];
49580
50585
  let current = target;
49581
50586
  while (current) {
49582
50587
  const currentEntry = COMMAND_REGISTRY[current];
49583
50588
  if (!currentEntry)
49584
50589
  break;
49585
50590
  if (visited.has(current)) {
49586
- const cycleStart = path39.indexOf(current);
50591
+ const cycleStart = path42.indexOf(current);
49587
50592
  const fullChain = [
49588
50593
  name,
49589
- ...path39.slice(0, cycleStart > 0 ? cycleStart : path39.length),
50594
+ ...path42.slice(0, cycleStart > 0 ? cycleStart : path42.length),
49590
50595
  current
49591
50596
  ].join(" \u2192 ");
49592
50597
  errors5.push(`Circular alias detected: ${fullChain}`);
49593
50598
  break;
49594
50599
  }
49595
50600
  visited.add(current);
49596
- path39.push(current);
50601
+ path42.push(current);
49597
50602
  current = currentEntry.aliasOf || "";
49598
50603
  }
49599
50604
  }
@@ -49866,17 +50871,21 @@ var init_registry = __esm(() => {
49866
50871
  category: "diagnostics"
49867
50872
  },
49868
50873
  finalize: {
49869
- handler: (ctx) => handleCloseCommand(ctx.directory, ctx.args),
50874
+ handler: (ctx) => handleCloseCommand(ctx.directory, ctx.args, {
50875
+ sessionID: ctx.sessionID
50876
+ }),
49870
50877
  description: "Use /swarm finalize to finalize the swarm project and archive evidence",
49871
- details: "Idempotent 4-stage terminal finalization: (1) finalize writes retrospectives for in-progress phases, (2) archive creates timestamped bundle of swarm artifacts and evidence, (3) clean removes active-state files for a clean slate, (4) align performs safe git ff-only to main. Resets agent sessions and delegation chains. Reads .swarm/close-lessons.md for explicit lessons and runs curation.",
49872
- args: "--prune-branches",
50878
+ details: "Idempotent 4-stage terminal finalization: (1) finalize writes retrospectives for in-progress phases, (2) archive creates timestamped bundle of swarm artifacts and evidence, (3) clean removes active-state files for a clean slate, (4) align performs safe git ff-only to main. Resets agent sessions and delegation chains. Reads .swarm/close-lessons.md for explicit lessons and runs curation. Use --skill-review to run the quota-bounded skill_improver in proposal mode.",
50879
+ args: "--prune-branches, --skill-review",
49873
50880
  category: "core"
49874
50881
  },
49875
50882
  close: {
49876
- handler: (ctx) => handleCloseCommand(ctx.directory, ctx.args),
50883
+ handler: (ctx) => handleCloseCommand(ctx.directory, ctx.args, {
50884
+ sessionID: ctx.sessionID
50885
+ }),
49877
50886
  description: "Use /swarm close (deprecated alias) to finalize and archive swarm state",
49878
50887
  details: "Deprecated alias for /swarm finalize. Preserved for backward compatibility.",
49879
- args: "--prune-branches",
50888
+ args: "--prune-branches, --skill-review",
49880
50889
  category: "core",
49881
50890
  aliasOf: "finalize",
49882
50891
  deprecated: true
@@ -50085,53 +51094,53 @@ init_cache_paths();
50085
51094
  init_constants();
50086
51095
  import * as fs24 from "fs";
50087
51096
  import * as os7 from "os";
50088
- import * as path39 from "path";
51097
+ import * as path42 from "path";
50089
51098
  var { version: version4 } = package_default;
50090
51099
  var CONFIG_DIR = getPluginConfigDir();
50091
- var OPENCODE_CONFIG_PATH = path39.join(CONFIG_DIR, "opencode.json");
50092
- var PLUGIN_CONFIG_PATH = path39.join(CONFIG_DIR, "opencode-swarm.json");
50093
- var PROMPTS_DIR = path39.join(CONFIG_DIR, "opencode-swarm");
51100
+ var OPENCODE_CONFIG_PATH = path42.join(CONFIG_DIR, "opencode.json");
51101
+ var PLUGIN_CONFIG_PATH = path42.join(CONFIG_DIR, "opencode-swarm.json");
51102
+ var PROMPTS_DIR = path42.join(CONFIG_DIR, "opencode-swarm");
50094
51103
  var OPENCODE_PLUGIN_CACHE_PATHS = getPluginCachePaths();
50095
51104
  var OPENCODE_PLUGIN_LOCK_FILE_PATHS = getPluginLockFilePaths();
50096
51105
  function isSafeCachePath(p) {
50097
- const resolved = path39.resolve(p);
50098
- const home = path39.resolve(os7.homedir());
51106
+ const resolved = path42.resolve(p);
51107
+ const home = path42.resolve(os7.homedir());
50099
51108
  if (resolved === "/" || resolved === home || resolved.length <= home.length) {
50100
51109
  return false;
50101
51110
  }
50102
- const segments = resolved.split(path39.sep).filter((s) => s.length > 0);
51111
+ const segments = resolved.split(path42.sep).filter((s) => s.length > 0);
50103
51112
  if (segments.length < 4) {
50104
51113
  return false;
50105
51114
  }
50106
- const leaf = path39.basename(resolved);
51115
+ const leaf = path42.basename(resolved);
50107
51116
  if (leaf !== "opencode-swarm@latest" && leaf !== "opencode-swarm") {
50108
51117
  return false;
50109
51118
  }
50110
- const parent = path39.basename(path39.dirname(resolved));
51119
+ const parent = path42.basename(path42.dirname(resolved));
50111
51120
  if (parent !== "packages" && parent !== "node_modules") {
50112
51121
  return false;
50113
51122
  }
50114
- const grandparent = path39.basename(path39.dirname(path39.dirname(resolved)));
51123
+ const grandparent = path42.basename(path42.dirname(path42.dirname(resolved)));
50115
51124
  if (grandparent !== "opencode") {
50116
51125
  return false;
50117
51126
  }
50118
51127
  return true;
50119
51128
  }
50120
51129
  function isSafeLockFilePath(p) {
50121
- const resolved = path39.resolve(p);
50122
- const home = path39.resolve(os7.homedir());
51130
+ const resolved = path42.resolve(p);
51131
+ const home = path42.resolve(os7.homedir());
50123
51132
  if (resolved === "/" || resolved === home || resolved.length <= home.length) {
50124
51133
  return false;
50125
51134
  }
50126
- const segments = resolved.split(path39.sep).filter((s) => s.length > 0);
51135
+ const segments = resolved.split(path42.sep).filter((s) => s.length > 0);
50127
51136
  if (segments.length < 4) {
50128
51137
  return false;
50129
51138
  }
50130
- const leaf = path39.basename(resolved);
51139
+ const leaf = path42.basename(resolved);
50131
51140
  if (leaf !== "bun.lock" && leaf !== "bun.lockb" && leaf !== "package-lock.json") {
50132
51141
  return false;
50133
51142
  }
50134
- const parent = path39.basename(path39.dirname(resolved));
51143
+ const parent = path42.basename(path42.dirname(resolved));
50135
51144
  if (parent !== "opencode") {
50136
51145
  return false;
50137
51146
  }
@@ -50157,8 +51166,8 @@ function saveJson(filepath, data) {
50157
51166
  }
50158
51167
  function writeProjectConfigIfMissing(cwd) {
50159
51168
  try {
50160
- const opencodeDir = path39.join(cwd, ".opencode");
50161
- const projectConfigPath = path39.join(opencodeDir, "opencode-swarm.json");
51169
+ const opencodeDir = path42.join(cwd, ".opencode");
51170
+ const projectConfigPath = path42.join(opencodeDir, "opencode-swarm.json");
50162
51171
  if (fs24.existsSync(projectConfigPath)) {
50163
51172
  return;
50164
51173
  }
@@ -50175,7 +51184,7 @@ async function install() {
50175
51184
  `);
50176
51185
  ensureDir(CONFIG_DIR);
50177
51186
  ensureDir(PROMPTS_DIR);
50178
- const LEGACY_CONFIG_PATH = path39.join(CONFIG_DIR, "config.json");
51187
+ const LEGACY_CONFIG_PATH = path42.join(CONFIG_DIR, "config.json");
50179
51188
  let opencodeConfig = loadJson(OPENCODE_CONFIG_PATH);
50180
51189
  if (!opencodeConfig) {
50181
51190
  const legacyConfig = loadJson(LEGACY_CONFIG_PATH);