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/README.md +2 -2
- package/dist/cli/index.js +1341 -332
- package/dist/commands/close.d.ts +14 -1
- package/dist/commands/registry.d.ts +3 -3
- package/dist/index.js +1443 -1265
- package/dist/services/skill-improver.d.ts +1 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -34,7 +34,7 @@ var package_default;
|
|
|
34
34
|
var init_package = __esm(() => {
|
|
35
35
|
package_default = {
|
|
36
36
|
name: "opencode-swarm",
|
|
37
|
-
version: "7.
|
|
37
|
+
version: "7.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
|
|
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 =
|
|
37578
|
+
const swarmDir = path17.join(directory, ".swarm");
|
|
36630
37579
|
let planExists = false;
|
|
36631
37580
|
let planData = {
|
|
36632
|
-
title:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
36834
|
-
const destPath =
|
|
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 =
|
|
36845
|
-
const destDir =
|
|
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 =
|
|
36852
|
-
const destEntry =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
-
|
|
36929
|
-
|
|
36930
|
-
|
|
36931
|
-
|
|
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 ${
|
|
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 =
|
|
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
|
|
38153
|
+
import * as path18 from "path";
|
|
37149
38154
|
function getUserConfigDir2() {
|
|
37150
|
-
return process.env.XDG_CONFIG_HOME ||
|
|
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 =
|
|
37155
|
-
const projectConfigPath =
|
|
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
|
|
37282
|
-
import * as
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
37415
|
-
const resolvedPath =
|
|
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 =
|
|
37441
|
-
const relTarget =
|
|
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 =
|
|
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 !==
|
|
38468
|
+
return baseA === baseB && baseA !== path19.basename(fileA) && baseA !== path19.basename(fileB);
|
|
37464
38469
|
}
|
|
37465
38470
|
function hasSharedPrefix(fileA, fileB) {
|
|
37466
|
-
const dirA =
|
|
37467
|
-
const dirB =
|
|
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 =
|
|
37472
|
-
const baseB =
|
|
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 =
|
|
37527
|
-
const baseB =
|
|
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
|
|
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 =
|
|
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
|
|
38803
|
+
import * as path21 from "path";
|
|
37799
38804
|
function getPluginConfigDir() {
|
|
37800
|
-
return
|
|
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 ||
|
|
38808
|
+
const cacheBase = process.env.XDG_CACHE_HOME || path21.join(os5.homedir(), ".cache");
|
|
37804
38809
|
const configDir = getPluginConfigDir();
|
|
37805
38810
|
const paths = [
|
|
37806
|
-
|
|
37807
|
-
|
|
37808
|
-
|
|
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 =
|
|
37812
|
-
paths.push(
|
|
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 ||
|
|
37816
|
-
const appData = process.env.APPDATA ||
|
|
37817
|
-
paths.push(
|
|
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 ||
|
|
38827
|
+
const cacheBase = process.env.XDG_CACHE_HOME || path21.join(os5.homedir(), ".cache");
|
|
37823
38828
|
const configDir = getPluginConfigDir();
|
|
37824
38829
|
const paths = [
|
|
37825
|
-
|
|
37826
|
-
|
|
37827
|
-
|
|
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 =
|
|
37831
|
-
paths.push(
|
|
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 ||
|
|
37835
|
-
paths.push(
|
|
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
|
|
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
|
|
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 :
|
|
37848
|
-
return
|
|
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
|
|
38856
|
+
return join20(cacheDir(), "version-check.json");
|
|
37852
38857
|
}
|
|
37853
38858
|
function readVersionCache() {
|
|
37854
38859
|
try {
|
|
37855
|
-
const
|
|
37856
|
-
if (!
|
|
38860
|
+
const path22 = cacheFile();
|
|
38861
|
+
if (!existsSync12(path22))
|
|
37857
38862
|
return null;
|
|
37858
|
-
const raw = readFileSync6(
|
|
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
|
|
37898
|
-
import
|
|
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 (!
|
|
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 =
|
|
38196
|
-
if (!
|
|
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 ?
|
|
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 =
|
|
39253
|
+
const thisDir = path22.dirname(fileURLToPath(import.meta.url));
|
|
38249
39254
|
const grammarDir = resolveGrammarDir(thisDir);
|
|
38250
39255
|
const missing = [];
|
|
38251
|
-
if (!
|
|
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 (!
|
|
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 =
|
|
38274
|
-
if (!
|
|
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 =
|
|
38326
|
-
if (!
|
|
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 =
|
|
38367
|
-
if (!
|
|
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 =
|
|
38423
|
-
if (!
|
|
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 =
|
|
38589
|
-
const snapshotFiles =
|
|
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 (!
|
|
39627
|
+
if (!existsSync13(cachePath)) {
|
|
38623
39628
|
cacheRows.push(`\u2B1C ${cachePath} \u2014 absent`);
|
|
38624
39629
|
continue;
|
|
38625
39630
|
}
|
|
38626
|
-
const pkgJsonPath =
|
|
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
|
|
39719
|
+
import * as path23 from "path";
|
|
38715
39720
|
function getUserConfigDir3() {
|
|
38716
|
-
return process.env.XDG_CONFIG_HOME ||
|
|
39721
|
+
return process.env.XDG_CONFIG_HOME || path23.join(os6.homedir(), ".config");
|
|
38717
39722
|
}
|
|
38718
39723
|
function getConfigPaths(directory) {
|
|
38719
|
-
const userConfigPath =
|
|
38720
|
-
const projectConfigPath =
|
|
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 =
|
|
38746
|
-
const resolvedUser =
|
|
38747
|
-
const resolvedProject =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
39858
|
+
function validateConfigKey(path24, value, _config) {
|
|
38854
39859
|
const findings = [];
|
|
38855
|
-
switch (
|
|
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,
|
|
40107
|
+
function walkConfigAndValidate(obj, path24, config3, findings) {
|
|
39103
40108
|
if (obj === null || obj === undefined) {
|
|
39104
40109
|
return;
|
|
39105
40110
|
}
|
|
39106
|
-
if (
|
|
39107
|
-
const keyFindings = validateConfigKey(
|
|
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(
|
|
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, `${
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
41397
|
+
return path24.join(dir, matches[0]);
|
|
40393
41398
|
}
|
|
40394
41399
|
} catch {}
|
|
40395
41400
|
} else {
|
|
40396
|
-
const filePath =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 ??
|
|
40778
|
-
const indexPath =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
41710
|
-
|
|
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
|
|
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 =
|
|
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
|
|
42715
|
-
import { mkdir as
|
|
42716
|
-
import * as
|
|
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 =
|
|
42728
|
-
const contextPath =
|
|
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 (
|
|
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 (!
|
|
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
|
|
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 =
|
|
42925
|
-
if (
|
|
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
|
|
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
|
|
42946
|
-
await
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
43479
|
-
const biomeBin = isWindows ?
|
|
43480
|
-
const eslintBin = isWindows ?
|
|
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(
|
|
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(
|
|
44535
|
+
if (fs12.existsSync(path29.join(cwd, "ruff.toml")))
|
|
43531
44536
|
return isCommandAvailable("ruff");
|
|
43532
44537
|
try {
|
|
43533
|
-
const pyproject =
|
|
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(
|
|
44548
|
+
return fs12.existsSync(path29.join(cwd, "Cargo.toml")) && isCommandAvailable("cargo");
|
|
43544
44549
|
}
|
|
43545
44550
|
function detectGolangciLint(cwd) {
|
|
43546
|
-
return fs12.existsSync(
|
|
44551
|
+
return fs12.existsSync(path29.join(cwd, "go.mod")) && isCommandAvailable("golangci-lint");
|
|
43547
44552
|
}
|
|
43548
44553
|
function detectCheckstyle(cwd) {
|
|
43549
|
-
const hasMaven = fs12.existsSync(
|
|
43550
|
-
const hasGradle = fs12.existsSync(
|
|
43551
|
-
const hasBinary = hasMaven && isCommandAvailable("mvn") || hasGradle && (fs12.existsSync(
|
|
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(
|
|
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(
|
|
44579
|
+
if (fs12.existsSync(path29.join(cwd, "CMakeLists.txt"))) {
|
|
43575
44580
|
return isCommandAvailable("cppcheck");
|
|
43576
44581
|
}
|
|
43577
44582
|
try {
|
|
43578
|
-
const dirsToCheck = [cwd,
|
|
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(
|
|
44597
|
+
return fs12.existsSync(path29.join(cwd, "Package.swift")) && isCommandAvailable("swiftlint");
|
|
43593
44598
|
}
|
|
43594
44599
|
function detectDartAnalyze(cwd) {
|
|
43595
|
-
return fs12.existsSync(
|
|
44600
|
+
return fs12.existsSync(path29.join(cwd, "pubspec.yaml")) && (isCommandAvailable("dart") || isCommandAvailable("flutter"));
|
|
43596
44601
|
}
|
|
43597
44602
|
function detectRubocop(cwd) {
|
|
43598
|
-
return (fs12.existsSync(
|
|
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 =
|
|
44631
|
+
const candidate = path29.join(dir, "node_modules", ".bin", binName);
|
|
43627
44632
|
if (fs12.existsSync(candidate))
|
|
43628
44633
|
return candidate;
|
|
43629
|
-
const parent =
|
|
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(
|
|
44643
|
+
for (const dir of searchPath.split(path29.delimiter)) {
|
|
43639
44644
|
if (!dir)
|
|
43640
44645
|
continue;
|
|
43641
|
-
const candidate =
|
|
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 ?
|
|
43655
|
-
const eslintBin = isWindows ?
|
|
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(
|
|
43660
|
-
const eslintAncestor = findBinInAncestors(
|
|
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
|
|
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 =
|
|
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 (
|
|
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 =
|
|
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 =
|
|
44112
|
-
const resolvedRealPath =
|
|
44113
|
-
return resolvedRealPath === 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 =
|
|
44140
|
-
const relPath =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
44604
|
-
if (
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
44730
|
-
const cachePath =
|
|
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(
|
|
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
|
|
46025
|
+
import path32 from "path";
|
|
45021
46026
|
function getHistoryPath(workingDir) {
|
|
45022
|
-
return
|
|
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 =
|
|
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
|
|
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 =
|
|
45180
|
-
const pathParts = normalizedDir.split(
|
|
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 =
|
|
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 =
|
|
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 +
|
|
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
|
|
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(
|
|
46305
|
+
return fs17.existsSync(path34.join(cwd, "go.mod")) && isCommandAvailable("go");
|
|
45301
46306
|
}
|
|
45302
46307
|
function detectJavaMaven(cwd) {
|
|
45303
|
-
return fs17.existsSync(
|
|
46308
|
+
return fs17.existsSync(path34.join(cwd, "pom.xml")) && isCommandAvailable("mvn");
|
|
45304
46309
|
}
|
|
45305
46310
|
function detectGradle(cwd) {
|
|
45306
|
-
const hasBuildFile = fs17.existsSync(
|
|
45307
|
-
const hasGradlew = fs17.existsSync(
|
|
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(
|
|
45321
|
-
const hasBuildCache = fs17.existsSync(
|
|
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(
|
|
46330
|
+
return fs17.existsSync(path34.join(cwd, "Package.swift")) && isCommandAvailable("swift");
|
|
45326
46331
|
}
|
|
45327
46332
|
function detectDartTest(cwd) {
|
|
45328
|
-
return fs17.existsSync(
|
|
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(
|
|
45332
|
-
const hasGemfile = fs17.existsSync(
|
|
45333
|
-
const hasSpecDir = fs17.existsSync(
|
|
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(
|
|
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 =
|
|
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(
|
|
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 =
|
|
45372
|
-
const setupCfgPath =
|
|
45373
|
-
const requirementsTxtPath =
|
|
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 =
|
|
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 =
|
|
45405
|
-
const pesterConfigJsonPath =
|
|
45406
|
-
const pesterPs1Path =
|
|
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
|
|
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
|
|
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 =
|
|
46482
|
+
const relativeDir = path34.dirname(relativePath);
|
|
45478
46483
|
const nestedRelativeDir = relativeDir === "." ? "" : relativeDir;
|
|
45479
46484
|
const directories = TEST_DIRECTORY_NAMES.flatMap((dirName) => {
|
|
45480
|
-
const rootDir =
|
|
45481
|
-
return 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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
45524
|
-
const basename5 =
|
|
45525
|
-
const
|
|
45526
|
-
const preferRelativeOutput = !
|
|
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 =
|
|
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) =>
|
|
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) =>
|
|
45551
|
-
...repoLevelDirectories.flatMap((candidateDir) => testDirectoryNames.map((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 =
|
|
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 =
|
|
45581
|
-
const existingExt =
|
|
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 =
|
|
45602
|
-
const importDir =
|
|
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 =
|
|
45605
|
-
const sourceBasename =
|
|
45606
|
-
const isRelatedDir = importDir === sourceDir || importDir ===
|
|
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 =
|
|
45620
|
-
const existingExt =
|
|
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 =
|
|
45638
|
-
const importBasename =
|
|
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 =
|
|
45641
|
-
const sourceBasename =
|
|
45642
|
-
const isRelatedDir = importDir === sourceDir || importDir ===
|
|
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(
|
|
45746
|
-
const hasGradlew = fs17.existsSync(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
46497
|
-
return
|
|
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
|
|
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 =
|
|
46582
|
-
const absolutePath =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
48359
|
-
const dest =
|
|
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 =
|
|
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 =
|
|
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
|
|
48475
|
-
const reportPath =
|
|
48476
|
-
await fs22.mkdir(
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
50357
|
+
const sentinelPath = path41.join(directory, ".swarm", ".first-run-complete");
|
|
49353
50358
|
try {
|
|
49354
|
-
const swarmDir =
|
|
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
|
|
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 =
|
|
50591
|
+
const cycleStart = path42.indexOf(current);
|
|
49587
50592
|
const fullChain = [
|
|
49588
50593
|
name,
|
|
49589
|
-
...
|
|
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
|
-
|
|
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
|
|
51097
|
+
import * as path42 from "path";
|
|
50089
51098
|
var { version: version4 } = package_default;
|
|
50090
51099
|
var CONFIG_DIR = getPluginConfigDir();
|
|
50091
|
-
var OPENCODE_CONFIG_PATH =
|
|
50092
|
-
var PLUGIN_CONFIG_PATH =
|
|
50093
|
-
var PROMPTS_DIR =
|
|
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 =
|
|
50098
|
-
const home =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
50122
|
-
const home =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
50161
|
-
const projectConfigPath =
|
|
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 =
|
|
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);
|