opencode-swarm 6.38.0 → 6.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -80,7 +80,13 @@ var init_tool_names = __esm(() => {
80
80
  "update_task_status",
81
81
  "write_retro",
82
82
  "declare_scope",
83
- "knowledge_query"
83
+ "knowledge_query",
84
+ "doc_scan",
85
+ "doc_extract",
86
+ "curator_analyze",
87
+ "knowledgeAdd",
88
+ "knowledgeRecall",
89
+ "knowledgeRemove"
84
90
  ];
85
91
  TOOL_NAME_SET = new Set(TOOL_NAMES);
86
92
  });
@@ -146,6 +152,7 @@ var init_constants = __esm(() => {
146
152
  architect: [
147
153
  "checkpoint",
148
154
  "check_gate_status",
155
+ "completion_verify",
149
156
  "complexity_hotspots",
150
157
  "detect_domains",
151
158
  "evidence_check",
@@ -157,6 +164,7 @@ var init_constants = __esm(() => {
157
164
  "diff",
158
165
  "pkg_audit",
159
166
  "pre_check_batch",
167
+ "quality_budget",
160
168
  "retrieve_summary",
161
169
  "save_plan",
162
170
  "schema_drift",
@@ -166,7 +174,19 @@ var init_constants = __esm(() => {
166
174
  "todo_extract",
167
175
  "update_task_status",
168
176
  "write_retro",
169
- "declare_scope"
177
+ "declare_scope",
178
+ "sast_scan",
179
+ "sbom_generate",
180
+ "build_check",
181
+ "syntax_check",
182
+ "placeholder_scan",
183
+ "phase_complete",
184
+ "doc_scan",
185
+ "doc_extract",
186
+ "curator_analyze",
187
+ "knowledgeAdd",
188
+ "knowledgeRecall",
189
+ "knowledgeRemove"
170
190
  ],
171
191
  explorer: [
172
192
  "complexity_hotspots",
@@ -177,7 +197,9 @@ var init_constants = __esm(() => {
177
197
  "retrieve_summary",
178
198
  "schema_drift",
179
199
  "symbols",
180
- "todo_extract"
200
+ "todo_extract",
201
+ "doc_scan",
202
+ "knowledgeRecall"
181
203
  ],
182
204
  coder: [
183
205
  "diff",
@@ -185,7 +207,11 @@ var init_constants = __esm(() => {
185
207
  "lint",
186
208
  "symbols",
187
209
  "extract_code_blocks",
188
- "retrieve_summary"
210
+ "retrieve_summary",
211
+ "build_check",
212
+ "syntax_check",
213
+ "knowledgeAdd",
214
+ "knowledgeRecall"
189
215
  ],
190
216
  test_engineer: [
191
217
  "test_runner",
@@ -195,7 +221,9 @@ var init_constants = __esm(() => {
195
221
  "retrieve_summary",
196
222
  "imports",
197
223
  "complexity_hotspots",
198
- "pkg_audit"
224
+ "pkg_audit",
225
+ "build_check",
226
+ "syntax_check"
199
227
  ],
200
228
  sme: [
201
229
  "complexity_hotspots",
@@ -204,7 +232,8 @@ var init_constants = __esm(() => {
204
232
  "imports",
205
233
  "retrieve_summary",
206
234
  "schema_drift",
207
- "symbols"
235
+ "symbols",
236
+ "knowledgeRecall"
208
237
  ],
209
238
  reviewer: [
210
239
  "diff",
@@ -217,28 +246,34 @@ var init_constants = __esm(() => {
217
246
  "complexity_hotspots",
218
247
  "retrieve_summary",
219
248
  "extract_code_blocks",
220
- "test_runner"
249
+ "test_runner",
250
+ "sast_scan",
251
+ "placeholder_scan",
252
+ "knowledgeRecall"
221
253
  ],
222
254
  critic: [
223
255
  "complexity_hotspots",
224
256
  "detect_domains",
225
257
  "imports",
226
258
  "retrieve_summary",
227
- "symbols"
259
+ "symbols",
260
+ "knowledgeRecall"
228
261
  ],
229
262
  critic_sounding_board: [
230
263
  "complexity_hotspots",
231
264
  "detect_domains",
232
265
  "imports",
233
266
  "retrieve_summary",
234
- "symbols"
267
+ "symbols",
268
+ "knowledgeRecall"
235
269
  ],
236
270
  critic_drift_verifier: [
237
271
  "complexity_hotspots",
238
272
  "detect_domains",
239
273
  "imports",
240
274
  "retrieve_summary",
241
- "symbols"
275
+ "symbols",
276
+ "knowledgeRecall"
242
277
  ],
243
278
  docs: [
244
279
  "detect_domains",
@@ -248,9 +283,15 @@ var init_constants = __esm(() => {
248
283
  "retrieve_summary",
249
284
  "schema_drift",
250
285
  "symbols",
251
- "todo_extract"
286
+ "todo_extract",
287
+ "knowledgeRecall"
252
288
  ],
253
- designer: ["extract_code_blocks", "retrieve_summary", "symbols"]
289
+ designer: [
290
+ "extract_code_blocks",
291
+ "retrieve_summary",
292
+ "symbols",
293
+ "knowledgeRecall"
294
+ ]
254
295
  };
255
296
  for (const [agentName, tools] of Object.entries(AGENT_TOOL_MAP)) {
256
297
  const invalidTools = tools.filter((tool) => !TOOL_NAME_SET.has(tool));
@@ -14869,8 +14910,8 @@ var init_schema = __esm(() => {
14869
14910
  });
14870
14911
  CheckpointConfigSchema = exports_external.object({
14871
14912
  enabled: exports_external.boolean().default(true),
14872
- auto_checkpoint_threshold: exports_external.number().min(1).max(20).default(3)
14873
- });
14913
+ auto_checkpoint_threshold: exports_external.number().int().min(1).max(20).default(3)
14914
+ }).strict();
14874
14915
  AutomationModeSchema = exports_external.enum(["manual", "hybrid", "auto"]);
14875
14916
  AutomationCapabilitiesSchema = exports_external.object({
14876
14917
  plan_sync: exports_external.boolean().default(true),
@@ -14990,7 +15031,8 @@ var init_schema = __esm(() => {
14990
15031
  block_on_threshold: exports_external.boolean().default(false).describe("If true, block phase completion when threshold exceeded. Default: advisory only.")
14991
15032
  }).optional(),
14992
15033
  incremental_verify: IncrementalVerifyConfigSchema.optional(),
14993
- compaction_service: CompactionConfigSchema.optional()
15034
+ compaction_service: CompactionConfigSchema.optional(),
15035
+ turbo_mode: exports_external.boolean().default(false).optional()
14994
15036
  });
14995
15037
  });
14996
15038
 
@@ -15169,7 +15211,8 @@ var init_plan_schema = __esm(() => {
15169
15211
  id: exports_external.number().int().min(1),
15170
15212
  name: exports_external.string().min(1),
15171
15213
  status: PhaseStatusSchema.default("pending"),
15172
- tasks: exports_external.array(TaskSchema).default([])
15214
+ tasks: exports_external.array(TaskSchema).default([]),
15215
+ required_agents: exports_external.array(exports_external.string()).optional()
15173
15216
  });
15174
15217
  PlanSchema = exports_external.object({
15175
15218
  schema_version: exports_external.literal("1.0.0"),
@@ -16077,7 +16120,7 @@ async function updateTaskStatus(directory, taskId, status) {
16077
16120
  throw new Error(`Task not found: ${taskId}`);
16078
16121
  }
16079
16122
  const updatedPlan = { ...plan, phases: updatedPhases };
16080
- await savePlan(directory, updatedPlan, { preserveCompletedStatuses: false });
16123
+ await savePlan(directory, updatedPlan, { preserveCompletedStatuses: true });
16081
16124
  return updatedPlan;
16082
16125
  }
16083
16126
  function derivePlanMarkdown(plan) {
@@ -16433,8 +16476,8 @@ function getTaskBlockers(task, summary, status) {
16433
16476
  }
16434
16477
  return blockers;
16435
16478
  }
16436
- async function buildTaskSummary(task, taskId) {
16437
- const result = await loadEvidence(".", taskId);
16479
+ async function buildTaskSummary(directory, task, taskId) {
16480
+ const result = await loadEvidence(directory, taskId);
16438
16481
  const bundle = result.status === "found" ? result.bundle : null;
16439
16482
  const phase = task?.phase ?? 0;
16440
16483
  const status = getTaskStatus(task, bundle);
@@ -16463,18 +16506,18 @@ async function buildTaskSummary(task, taskId) {
16463
16506
  lastEvidenceTimestamp: lastTimestamp
16464
16507
  };
16465
16508
  }
16466
- async function buildPhaseSummary(phase) {
16467
- const taskIds = await listEvidenceTaskIds(".");
16509
+ async function buildPhaseSummary(directory, phase) {
16510
+ const taskIds = await listEvidenceTaskIds(directory);
16468
16511
  const phaseTaskIds = new Set(phase.tasks.map((t) => t.id));
16469
16512
  const taskSummaries = [];
16470
16513
  const _taskMap = new Map(phase.tasks.map((t) => [t.id, t]));
16471
16514
  for (const task of phase.tasks) {
16472
- const summary = await buildTaskSummary(task, task.id);
16515
+ const summary = await buildTaskSummary(directory, task, task.id);
16473
16516
  taskSummaries.push(summary);
16474
16517
  }
16475
16518
  const extraTaskIds = taskIds.filter((id) => !phaseTaskIds.has(id));
16476
16519
  for (const taskId of extraTaskIds) {
16477
- const summary = await buildTaskSummary(undefined, taskId);
16520
+ const summary = await buildTaskSummary(directory, undefined, taskId);
16478
16521
  if (summary.phase === phase.id) {
16479
16522
  taskSummaries.push(summary);
16480
16523
  }
@@ -16575,7 +16618,7 @@ async function buildEvidenceSummary(directory, currentPhase) {
16575
16618
  let totalTasks = 0;
16576
16619
  let completedTasks = 0;
16577
16620
  for (const phase of phasesToProcess) {
16578
- const summary = await buildPhaseSummary(phase);
16621
+ const summary = await buildPhaseSummary(directory, phase);
16579
16622
  phaseSummaries.push(summary);
16580
16623
  totalTasks += summary.totalTasks;
16581
16624
  completedTasks += summary.completedTasks;
@@ -30153,6 +30196,11 @@ function isGitRepo() {
30153
30196
  }
30154
30197
  function handleSave(label, directory) {
30155
30198
  try {
30199
+ let maxCheckpoints = 20;
30200
+ try {
30201
+ const { config: config3 } = loadPluginConfigWithMeta(directory);
30202
+ maxCheckpoints = config3.checkpoint?.auto_checkpoint_threshold ?? maxCheckpoints;
30203
+ } catch {}
30156
30204
  const log2 = readCheckpointLog(directory);
30157
30205
  const existingCheckpoint = log2.checkpoints.find((c) => c.label === label);
30158
30206
  if (existingCheckpoint) {
@@ -30257,6 +30305,7 @@ function handleDelete(label, directory) {
30257
30305
  var CHECKPOINT_LOG_PATH = ".swarm/checkpoints.json", MAX_LABEL_LENGTH = 100, GIT_TIMEOUT_MS = 30000, SHELL_METACHARACTERS, SAFE_LABEL_PATTERN, CONTROL_CHAR_PATTERN, NON_ASCII_PATTERN, checkpoint;
30258
30306
  var init_checkpoint = __esm(() => {
30259
30307
  init_tool();
30308
+ init_config();
30260
30309
  init_create_tool();
30261
30310
  SHELL_METACHARACTERS = /[;|&$`(){}<>!'"]/;
30262
30311
  SAFE_LABEL_PATTERN = /^[a-zA-Z0-9_ -]+$/;
@@ -30280,7 +30329,7 @@ var init_checkpoint = __esm(() => {
30280
30329
  let label;
30281
30330
  try {
30282
30331
  action = String(args2.action);
30283
- label = args2.label !== undefined ? String(args2.label) : undefined;
30332
+ label = args2.label !== undefined && args2.label !== null ? String(args2.label) : undefined;
30284
30333
  } catch {
30285
30334
  return JSON.stringify({
30286
30335
  action: "unknown",
@@ -31853,6 +31902,163 @@ var require_proper_lockfile = __commonJS((exports, module2) => {
31853
31902
  module2.exports.checkSync = checkSync;
31854
31903
  });
31855
31904
 
31905
+ // src/hooks/knowledge-store.ts
31906
+ import { existsSync as existsSync7 } from "fs";
31907
+ import { appendFile, mkdir, readFile as readFile2, writeFile } from "fs/promises";
31908
+ import * as os4 from "os";
31909
+ import * as path12 from "path";
31910
+ function resolveSwarmKnowledgePath(directory) {
31911
+ return path12.join(directory, ".swarm", "knowledge.jsonl");
31912
+ }
31913
+ function resolveSwarmRejectedPath(directory) {
31914
+ return path12.join(directory, ".swarm", "knowledge-rejected.jsonl");
31915
+ }
31916
+ function resolveHiveKnowledgePath() {
31917
+ const platform = process.platform;
31918
+ const home = os4.homedir();
31919
+ let dataDir;
31920
+ if (platform === "win32") {
31921
+ dataDir = path12.join(process.env.LOCALAPPDATA || path12.join(home, "AppData", "Local"), "opencode-swarm", "Data");
31922
+ } else if (platform === "darwin") {
31923
+ dataDir = path12.join(home, "Library", "Application Support", "opencode-swarm");
31924
+ } else {
31925
+ dataDir = path12.join(process.env.XDG_DATA_HOME || path12.join(home, ".local", "share"), "opencode-swarm");
31926
+ }
31927
+ return path12.join(dataDir, "shared-learnings.jsonl");
31928
+ }
31929
+ function resolveHiveRejectedPath() {
31930
+ const hivePath = resolveHiveKnowledgePath();
31931
+ return path12.join(path12.dirname(hivePath), "shared-learnings-rejected.jsonl");
31932
+ }
31933
+ async function readKnowledge(filePath) {
31934
+ if (!existsSync7(filePath))
31935
+ return [];
31936
+ const content = await readFile2(filePath, "utf-8");
31937
+ const results = [];
31938
+ for (const line of content.split(`
31939
+ `)) {
31940
+ const trimmed = line.trim();
31941
+ if (!trimmed)
31942
+ continue;
31943
+ try {
31944
+ results.push(JSON.parse(trimmed));
31945
+ } catch {
31946
+ console.warn(`[knowledge-store] Skipping corrupted JSONL line in ${filePath}: ${trimmed.slice(0, 80)}`);
31947
+ }
31948
+ }
31949
+ return results;
31950
+ }
31951
+ async function readRejectedLessons(directory) {
31952
+ return readKnowledge(resolveSwarmRejectedPath(directory));
31953
+ }
31954
+ async function appendKnowledge(filePath, entry) {
31955
+ await mkdir(path12.dirname(filePath), { recursive: true });
31956
+ await appendFile(filePath, `${JSON.stringify(entry)}
31957
+ `, "utf-8");
31958
+ }
31959
+ async function rewriteKnowledge(filePath, entries) {
31960
+ const dir = path12.dirname(filePath);
31961
+ await mkdir(dir, { recursive: true });
31962
+ let release = null;
31963
+ try {
31964
+ release = await import_proper_lockfile.default.lock(dir, {
31965
+ retries: { retries: 3, minTimeout: 100 }
31966
+ });
31967
+ const content = entries.map((e) => JSON.stringify(e)).join(`
31968
+ `) + (entries.length > 0 ? `
31969
+ ` : "");
31970
+ await writeFile(filePath, content, "utf-8");
31971
+ } finally {
31972
+ if (release) {
31973
+ try {
31974
+ await release();
31975
+ } catch {}
31976
+ }
31977
+ }
31978
+ }
31979
+ async function appendRejectedLesson(directory, lesson) {
31980
+ const filePath = resolveSwarmRejectedPath(directory);
31981
+ const existing = await readRejectedLessons(directory);
31982
+ const MAX = 20;
31983
+ const updated = [...existing, lesson];
31984
+ if (updated.length > MAX) {
31985
+ const trimmed = updated.slice(updated.length - MAX);
31986
+ await rewriteKnowledge(filePath, trimmed);
31987
+ } else {
31988
+ await appendKnowledge(filePath, lesson);
31989
+ }
31990
+ }
31991
+ function normalize2(text) {
31992
+ return text.toLowerCase().replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
31993
+ }
31994
+ function wordBigrams(text) {
31995
+ const words = normalize2(text).split(" ").filter(Boolean);
31996
+ const bigrams = new Set;
31997
+ for (let i2 = 0;i2 < words.length - 1; i2++) {
31998
+ bigrams.add(`${words[i2]} ${words[i2 + 1]}`);
31999
+ }
32000
+ return bigrams;
32001
+ }
32002
+ function jaccardBigram(a, b) {
32003
+ if (a.size === 0 && b.size === 0)
32004
+ return 1;
32005
+ const aArr = Array.from(a);
32006
+ const intersection3 = new Set(aArr.filter((x) => b.has(x)));
32007
+ const union3 = new Set([...aArr, ...Array.from(b)]);
32008
+ return intersection3.size / union3.size;
32009
+ }
32010
+ function findNearDuplicate(candidate, entries, threshold = 0.6) {
32011
+ const candidateBigrams = wordBigrams(candidate);
32012
+ return entries.find((entry) => {
32013
+ const entryBigrams = wordBigrams(entry.lesson);
32014
+ return jaccardBigram(candidateBigrams, entryBigrams) >= threshold;
32015
+ });
32016
+ }
32017
+ function computeConfidence(confirmedByCount, autoGenerated) {
32018
+ let score = 0.5;
32019
+ score += Math.min(confirmedByCount, 3) * 0.1;
32020
+ if (!autoGenerated)
32021
+ score += 0.1;
32022
+ return Math.min(score, 1);
32023
+ }
32024
+ function inferTags(lesson) {
32025
+ const lower = lesson.toLowerCase();
32026
+ const tags = [];
32027
+ if (/\b(?:typescript|ts)\b/.test(lower))
32028
+ tags.push("typescript");
32029
+ if (/\b(?:javascript|js)\b/.test(lower))
32030
+ tags.push("javascript");
32031
+ if (/\b(?:python)\b/.test(lower))
32032
+ tags.push("python");
32033
+ if (/\b(?:bun|node|deno)\b/.test(lower))
32034
+ tags.push("runtime");
32035
+ if (/\b(?:react|vue|svelte|angular)\b/.test(lower))
32036
+ tags.push("frontend");
32037
+ if (/\b(?:git|github|gitlab)\b/.test(lower))
32038
+ tags.push("git");
32039
+ if (/\b(?:docker|kubernetes|k8s)\b/.test(lower))
32040
+ tags.push("container");
32041
+ if (/\b(?:sql|postgres|mysql|sqlite)\b/.test(lower))
32042
+ tags.push("database");
32043
+ if (/\b(?:test|spec|vitest|jest|mocha)\b/.test(lower))
32044
+ tags.push("testing");
32045
+ if (/\b(?:ci|cd|pipeline|workflow|action)\b/.test(lower))
32046
+ tags.push("ci-cd");
32047
+ if (/\b(?:security|auth|token|password|encrypt)\b/.test(lower))
32048
+ tags.push("security");
32049
+ if (/\b(?:performance|latency|throughput|cache)\b/.test(lower))
32050
+ tags.push("performance");
32051
+ if (/\b(?:api|rest|graphql|grpc|endpoint)\b/.test(lower))
32052
+ tags.push("api");
32053
+ if (/\b(?:swarm|architect|agent|hook|plan)\b/.test(lower))
32054
+ tags.push("opencode-swarm");
32055
+ return Array.from(new Set(tags));
32056
+ }
32057
+ var import_proper_lockfile;
32058
+ var init_knowledge_store = __esm(() => {
32059
+ import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
32060
+ });
32061
+
31856
32062
  // src/services/config-doctor.ts
31857
32063
  var exports_config_doctor = {};
31858
32064
  __export(exports_config_doctor, {
@@ -33338,6 +33544,59 @@ var init_profiles = __esm(() => {
33338
33544
  ]
33339
33545
  }
33340
33546
  });
33547
+ LANGUAGE_REGISTRY.register({
33548
+ id: "php",
33549
+ displayName: "PHP",
33550
+ tier: 3,
33551
+ extensions: [".php", ".phtml"],
33552
+ treeSitter: { grammarId: "php", wasmFile: "tree-sitter-php.wasm" },
33553
+ build: {
33554
+ detectFiles: ["composer.json"],
33555
+ commands: []
33556
+ },
33557
+ test: {
33558
+ detectFiles: ["phpunit.xml", "phpunit.xml.dist"],
33559
+ frameworks: [
33560
+ {
33561
+ name: "PHPUnit",
33562
+ detect: "phpunit.xml",
33563
+ cmd: "vendor/bin/phpunit",
33564
+ priority: 1
33565
+ }
33566
+ ]
33567
+ },
33568
+ lint: {
33569
+ detectFiles: [".php-cs-fixer.php", "phpcs.xml"],
33570
+ linters: [
33571
+ {
33572
+ name: "PHP-CS-Fixer",
33573
+ detect: ".php-cs-fixer.php",
33574
+ cmd: "vendor/bin/php-cs-fixer fix --dry-run --diff",
33575
+ priority: 1
33576
+ }
33577
+ ]
33578
+ },
33579
+ audit: {
33580
+ detectFiles: ["composer.lock"],
33581
+ command: "composer audit --format=json",
33582
+ outputFormat: "json"
33583
+ },
33584
+ sast: { nativeRuleSet: "php", semgrepSupport: "ga" },
33585
+ prompts: {
33586
+ coderConstraints: [
33587
+ "Follow PSR-12 coding standards",
33588
+ "Use strict types declaration: declare(strict_types=1)",
33589
+ "Prefer type hints and return type declarations on all functions",
33590
+ "Use dependency injection over static methods and singletons"
33591
+ ],
33592
+ reviewerChecklist: [
33593
+ "Verify no user input reaches SQL queries without parameterised binding",
33594
+ "Check for XSS \u2014 all output must be escaped with htmlspecialchars()",
33595
+ "Confirm no eval(), exec(), or shell_exec() with user-controlled input",
33596
+ "Validate proper error handling \u2014 no bare catch blocks that swallow errors"
33597
+ ]
33598
+ }
33599
+ });
33341
33600
  });
33342
33601
 
33343
33602
  // src/lang/detector.ts
@@ -33735,6 +33994,53 @@ var init_discovery = __esm(() => {
33735
33994
  });
33736
33995
  });
33737
33996
 
33997
+ // src/utils/path-security.ts
33998
+ function containsPathTraversal(str) {
33999
+ if (/\.\.[/\\]/.test(str))
34000
+ return true;
34001
+ if (/(?:^|[/\\])\.\.(?:[/\\]|$)/.test(str))
34002
+ return true;
34003
+ if (/%2e%2e/i.test(str))
34004
+ return true;
34005
+ if (/%2e\./i.test(str))
34006
+ return true;
34007
+ if (/%2e/i.test(str) && /\.\./.test(str))
34008
+ return true;
34009
+ if (/%252e%252e/i.test(str))
34010
+ return true;
34011
+ if (/\uff0e/.test(str))
34012
+ return true;
34013
+ if (/\u3002/.test(str))
34014
+ return true;
34015
+ if (/\uff65/.test(str))
34016
+ return true;
34017
+ if (/%2f/i.test(str))
34018
+ return true;
34019
+ if (/%5c/i.test(str))
34020
+ return true;
34021
+ return false;
34022
+ }
34023
+ function containsControlChars(str) {
34024
+ return /[\0\t\r\n]/.test(str);
34025
+ }
34026
+ function validateDirectory(directory) {
34027
+ if (!directory || directory.trim() === "") {
34028
+ throw new Error("Invalid directory: empty");
34029
+ }
34030
+ if (containsPathTraversal(directory)) {
34031
+ throw new Error("Invalid directory: path traversal detected");
34032
+ }
34033
+ if (containsControlChars(directory)) {
34034
+ throw new Error("Invalid directory: control characters detected");
34035
+ }
34036
+ if (directory.startsWith("/") || directory.startsWith("\\")) {
34037
+ throw new Error("Invalid directory: absolute path");
34038
+ }
34039
+ if (/^[A-Za-z]:[/\\]/.test(directory)) {
34040
+ throw new Error("Invalid directory: Windows absolute path");
34041
+ }
34042
+ }
34043
+
33738
34044
  // src/tools/lint.ts
33739
34045
  import * as fs12 from "fs";
33740
34046
  import * as path23 from "path";
@@ -33908,7 +34214,7 @@ async function detectAvailableLinter(directory) {
33908
34214
  async function _detectAvailableLinter(_projectDir, biomeBin, eslintBin) {
33909
34215
  const DETECT_TIMEOUT = 2000;
33910
34216
  try {
33911
- const biomeProc = Bun.spawn(["npx", "biome", "--version"], {
34217
+ const biomeProc = Bun.spawn([biomeBin, "--version"], {
33912
34218
  stdout: "pipe",
33913
34219
  stderr: "pipe"
33914
34220
  });
@@ -33922,7 +34228,7 @@ async function _detectAvailableLinter(_projectDir, biomeBin, eslintBin) {
33922
34228
  }
33923
34229
  } catch {}
33924
34230
  try {
33925
- const eslintProc = Bun.spawn(["npx", "eslint", "--version"], {
34231
+ const eslintProc = Bun.spawn([eslintBin, "--version"], {
33926
34232
  stdout: "pipe",
33927
34233
  stderr: "pipe"
33928
34234
  });
@@ -34133,19 +34439,6 @@ function isHighEntropyString(str) {
34133
34439
  const entropy = calculateShannonEntropy(str);
34134
34440
  return entropy > 4;
34135
34441
  }
34136
- function containsPathTraversal(str) {
34137
- if (/\.\.[/\\]/.test(str))
34138
- return true;
34139
- if (/[/\\]\.\.$/.test(str) || str === "..")
34140
- return true;
34141
- if (/\.\.[/\\]/.test(path24.normalize(str.replace(/\*/g, "x"))))
34142
- return true;
34143
- if (str.includes("%2e%2e") || str.includes("%2E%2E"))
34144
- return true;
34145
- if (str.includes("..") && /%2e/i.test(str))
34146
- return true;
34147
- return false;
34148
- }
34149
34442
  function validateExcludePattern(exc) {
34150
34443
  if (exc.length === 0)
34151
34444
  return null;
@@ -34198,9 +34491,6 @@ function isExcluded(entry, relPath, exactNames, globPatterns) {
34198
34491
  }
34199
34492
  return false;
34200
34493
  }
34201
- function containsControlChars(str) {
34202
- return /[\0\t\r\n]/.test(str);
34203
- }
34204
34494
  function validateDirectoryInput(dir) {
34205
34495
  if (!dir || dir.length === 0) {
34206
34496
  return "directory is required";
@@ -34812,31 +35102,6 @@ var init_secretscan = __esm(() => {
34812
35102
  // src/tools/test-runner.ts
34813
35103
  import * as fs14 from "fs";
34814
35104
  import * as path25 from "path";
34815
- function containsPathTraversal2(str) {
34816
- if (/\.\.[/\\]/.test(str))
34817
- return true;
34818
- if (/(?:^|[/\\])\.\.(?:[/\\]|$)/.test(str))
34819
- return true;
34820
- if (/%2e%2e/i.test(str))
34821
- return true;
34822
- if (/%2e\./i.test(str))
34823
- return true;
34824
- if (/%2e/i.test(str) && /\.\./.test(str))
34825
- return true;
34826
- if (/%252e%252e/i.test(str))
34827
- return true;
34828
- if (/\uff0e/.test(str))
34829
- return true;
34830
- if (/\u3002/.test(str))
34831
- return true;
34832
- if (/\uff65/.test(str))
34833
- return true;
34834
- if (/%2f/i.test(str))
34835
- return true;
34836
- if (/%5c/i.test(str))
34837
- return true;
34838
- return false;
34839
- }
34840
35105
  function isAbsolutePath(str) {
34841
35106
  if (str.startsWith("/"))
34842
35107
  return true;
@@ -34848,9 +35113,6 @@ function isAbsolutePath(str) {
34848
35113
  return true;
34849
35114
  return false;
34850
35115
  }
34851
- function containsControlChars2(str) {
34852
- return /[\x00-\x08\x0a\x0b\x0c\x0d\x0e-\x1f\x7f\x80-\x9f]/.test(str);
34853
- }
34854
35116
  function containsPowerShellMetacharacters(str) {
34855
35117
  return POWERSHELL_METACHARACTERS.test(str);
34856
35118
  }
@@ -34871,9 +35133,9 @@ function validateArgs2(args2) {
34871
35133
  return false;
34872
35134
  if (isAbsolutePath(f))
34873
35135
  return false;
34874
- if (containsPathTraversal2(f))
35136
+ if (containsPathTraversal(f))
34875
35137
  return false;
34876
- if (containsControlChars2(f))
35138
+ if (containsControlChars(f))
34877
35139
  return false;
34878
35140
  if (containsPowerShellMetacharacters(f))
34879
35141
  return false;
@@ -35702,7 +35964,7 @@ var init_test_runner = __esm(() => {
35702
35964
  };
35703
35965
  return JSON.stringify(errorResult, null, 2);
35704
35966
  }
35705
- if (containsControlChars2(workingDir)) {
35967
+ if (containsControlChars(workingDir)) {
35706
35968
  const errorResult = {
35707
35969
  success: false,
35708
35970
  framework: "none",
@@ -35711,7 +35973,7 @@ var init_test_runner = __esm(() => {
35711
35973
  };
35712
35974
  return JSON.stringify(errorResult, null, 2);
35713
35975
  }
35714
- if (containsPathTraversal2(workingDir)) {
35976
+ if (containsPathTraversal(workingDir)) {
35715
35977
  const errorResult = {
35716
35978
  success: false,
35717
35979
  framework: "none",
@@ -35978,12 +36240,13 @@ async function runLintCheck(dir, linter, timeoutMs) {
35978
36240
  const startTime = Date.now();
35979
36241
  try {
35980
36242
  const lintPromise = runLint(linter, "check", dir);
36243
+ let timeoutId;
35981
36244
  const timeoutPromise = new Promise((_, reject) => {
35982
- setTimeout(() => {
36245
+ timeoutId = setTimeout(() => {
35983
36246
  reject(new Error(`Lint check timed out after ${timeoutMs}ms`));
35984
36247
  }, timeoutMs);
35985
36248
  });
35986
- const result = await Promise.race([lintPromise, timeoutPromise]);
36249
+ const result = await Promise.race([lintPromise, timeoutPromise]).finally(() => clearTimeout(timeoutId));
35987
36250
  if (!result.success) {
35988
36251
  return {
35989
36252
  type: "lint",
@@ -36475,7 +36738,7 @@ function deriveRequiredGates(agentType) {
36475
36738
  }
36476
36739
  function expandRequiredGates(existingGates, newAgentType) {
36477
36740
  const newGates = deriveRequiredGates(newAgentType);
36478
- const combined = [...new Set([...existingGates, ...newGates])];
36741
+ const combined = [...new Set([...existingGates ?? [], ...newGates])];
36479
36742
  return combined.sort();
36480
36743
  }
36481
36744
  function getEvidenceDir(directory) {
@@ -36610,10 +36873,10 @@ function createPreflightIntegration(config3) {
36610
36873
  });
36611
36874
  const report = await runPreflight(directory, request.currentPhase, preflightConfig);
36612
36875
  if (statusArtifact) {
36613
- const state2 = report.overall === "pass" ? "success" : "failure";
36614
- statusArtifact.recordOutcome(state2, request.currentPhase, report.message);
36876
+ const state = report.overall === "pass" ? "success" : "failure";
36877
+ statusArtifact.recordOutcome(state, request.currentPhase, report.message);
36615
36878
  console.log("[PreflightIntegration] Status artifact updated", {
36616
- state: state2,
36879
+ state,
36617
36880
  phase: request.currentPhase,
36618
36881
  message: report.message
36619
36882
  });
@@ -36643,6 +36906,395 @@ var init_preflight_integration = __esm(() => {
36643
36906
  init_preflight_service();
36644
36907
  });
36645
36908
 
36909
+ // src/tools/doc-scan.ts
36910
+ var exports_doc_scan = {};
36911
+ __export(exports_doc_scan, {
36912
+ scanDocIndex: () => scanDocIndex,
36913
+ extractDocConstraints: () => extractDocConstraints,
36914
+ doc_scan: () => doc_scan,
36915
+ doc_extract: () => doc_extract
36916
+ });
36917
+ import * as crypto4 from "crypto";
36918
+ import * as fs25 from "fs";
36919
+ import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
36920
+ import * as path36 from "path";
36921
+ function normalizeSeparators(filePath) {
36922
+ return filePath.replace(/\\/g, "/");
36923
+ }
36924
+ function matchesDocPattern(filePath, patterns) {
36925
+ const normalizedPath = normalizeSeparators(filePath);
36926
+ const basename5 = path36.basename(filePath);
36927
+ for (const pattern of patterns) {
36928
+ if (!pattern.includes("/") && !pattern.includes("\\")) {
36929
+ if (basename5 === pattern) {
36930
+ return true;
36931
+ }
36932
+ continue;
36933
+ }
36934
+ if (pattern.startsWith("**/")) {
36935
+ const filenamePattern = pattern.slice(3);
36936
+ if (basename5 === filenamePattern) {
36937
+ return true;
36938
+ }
36939
+ continue;
36940
+ }
36941
+ const patternNormalized = normalizeSeparators(pattern);
36942
+ const dirPrefix = patternNormalized.replace(/\/\*\*.*$/, "").replace(/\/\*.*$/, "");
36943
+ if (normalizedPath.startsWith(`${dirPrefix}/`) || normalizedPath === dirPrefix) {
36944
+ return true;
36945
+ }
36946
+ }
36947
+ return false;
36948
+ }
36949
+ function extractTitleAndSummary(content, filename) {
36950
+ const lines = content.split(`
36951
+ `);
36952
+ let title = filename;
36953
+ let summary = "";
36954
+ let foundTitle = false;
36955
+ const potentialSummaryLines = [];
36956
+ for (let i2 = 0;i2 < lines.length && i2 < READ_LINES_LIMIT; i2++) {
36957
+ const line = lines[i2].trim();
36958
+ if (!foundTitle && line.startsWith("# ")) {
36959
+ title = line.slice(2).trim();
36960
+ foundTitle = true;
36961
+ continue;
36962
+ }
36963
+ if (line && !line.startsWith("#")) {
36964
+ potentialSummaryLines.push(line);
36965
+ }
36966
+ }
36967
+ for (const line of potentialSummaryLines) {
36968
+ summary += (summary ? " " : "") + line;
36969
+ if (summary.length >= MAX_SUMMARY_LENGTH) {
36970
+ break;
36971
+ }
36972
+ }
36973
+ if (summary.length > MAX_SUMMARY_LENGTH) {
36974
+ summary = `${summary.slice(0, MAX_SUMMARY_LENGTH - 3)}...`;
36975
+ }
36976
+ return { title, summary };
36977
+ }
36978
+ function stripMarkdown(text) {
36979
+ return text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/^\s*[-*\u2022]\s+/gm, "").replace(/^\s*\d+\.\s+/gm, "").trim();
36980
+ }
36981
+ async function scanDocIndex(directory) {
36982
+ const manifestPath = path36.join(directory, ".swarm", "doc-manifest.json");
36983
+ const defaultPatterns = DocsConfigSchema.parse({}).doc_patterns;
36984
+ const extraPatterns = [
36985
+ "ARCHITECTURE.md",
36986
+ "CLAUDE.md",
36987
+ "AGENTS.md",
36988
+ ".github/*.md",
36989
+ "doc/**/*.md"
36990
+ ];
36991
+ const allPatterns = [...defaultPatterns, ...extraPatterns];
36992
+ try {
36993
+ const manifestContent = await readFile5(manifestPath, "utf-8");
36994
+ const existingManifest = JSON.parse(manifestContent);
36995
+ if (existingManifest.schema_version === 1 && existingManifest.files) {
36996
+ let cacheValid = true;
36997
+ for (const file3 of existingManifest.files) {
36998
+ try {
36999
+ const fullPath = path36.join(directory, file3.path);
37000
+ const stat2 = fs25.statSync(fullPath);
37001
+ if (stat2.mtimeMs > new Date(existingManifest.scanned_at).getTime()) {
37002
+ cacheValid = false;
37003
+ break;
37004
+ }
37005
+ } catch {
37006
+ cacheValid = false;
37007
+ break;
37008
+ }
37009
+ }
37010
+ if (cacheValid) {
37011
+ return { manifest: existingManifest, cached: true };
37012
+ }
37013
+ }
37014
+ } catch {}
37015
+ const discoveredFiles = [];
37016
+ let rawEntries;
37017
+ try {
37018
+ rawEntries = fs25.readdirSync(directory, { recursive: true });
37019
+ } catch {
37020
+ const manifest2 = {
37021
+ schema_version: 1,
37022
+ scanned_at: new Date().toISOString(),
37023
+ files: []
37024
+ };
37025
+ return { manifest: manifest2, cached: false };
37026
+ }
37027
+ const entries = rawEntries.filter((e) => typeof e === "string");
37028
+ for (const entry of entries) {
37029
+ const fullPath = path36.join(directory, entry);
37030
+ let stat2;
37031
+ try {
37032
+ stat2 = fs25.statSync(fullPath);
37033
+ } catch {
37034
+ continue;
37035
+ }
37036
+ if (!stat2.isFile())
37037
+ continue;
37038
+ const pathParts = normalizeSeparators(entry).split("/");
37039
+ let skipThisFile = false;
37040
+ for (const part of pathParts) {
37041
+ if (SKIP_DIRECTORIES2.has(part)) {
37042
+ skipThisFile = true;
37043
+ break;
37044
+ }
37045
+ }
37046
+ if (skipThisFile)
37047
+ continue;
37048
+ for (const pattern of SKIP_PATTERNS) {
37049
+ if (pattern.test(entry)) {
37050
+ skipThisFile = true;
37051
+ break;
37052
+ }
37053
+ }
37054
+ if (skipThisFile)
37055
+ continue;
37056
+ if (!matchesDocPattern(entry, allPatterns)) {
37057
+ continue;
37058
+ }
37059
+ let content;
37060
+ try {
37061
+ content = fs25.readFileSync(fullPath, "utf-8");
37062
+ } catch {
37063
+ continue;
37064
+ }
37065
+ const { title, summary } = extractTitleAndSummary(content, path36.basename(entry));
37066
+ const lineCount = content.split(`
37067
+ `).length;
37068
+ discoveredFiles.push({
37069
+ path: entry,
37070
+ title,
37071
+ summary,
37072
+ lines: lineCount,
37073
+ mtime: stat2.mtimeMs
37074
+ });
37075
+ }
37076
+ discoveredFiles.sort((a, b) => a.path.toLowerCase().localeCompare(b.path.toLowerCase()));
37077
+ let truncated = false;
37078
+ if (discoveredFiles.length > MAX_INDEXED_FILES) {
37079
+ discoveredFiles.splice(MAX_INDEXED_FILES);
37080
+ truncated = true;
37081
+ }
37082
+ if (truncated && discoveredFiles.length > 0) {
37083
+ discoveredFiles[0].summary = `[Warning: ${MAX_INDEXED_FILES}+ docs found, listing first ${MAX_INDEXED_FILES}] ` + discoveredFiles[0].summary;
37084
+ }
37085
+ const manifest = {
37086
+ schema_version: 1,
37087
+ scanned_at: new Date().toISOString(),
37088
+ files: discoveredFiles
37089
+ };
37090
+ try {
37091
+ await mkdir5(path36.dirname(manifestPath), { recursive: true });
37092
+ await writeFile4(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
37093
+ } catch {}
37094
+ return { manifest, cached: false };
37095
+ }
37096
+ function isConstraintLine(line) {
37097
+ const upperLine = line.toUpperCase();
37098
+ for (const pattern of CONSTRAINT_PATTERNS) {
37099
+ if (pattern.test(upperLine)) {
37100
+ return true;
37101
+ }
37102
+ }
37103
+ if (/^\s*[-*\u2022]/.test(line) && ACTION_WORDS.test(line)) {
37104
+ return true;
37105
+ }
37106
+ return false;
37107
+ }
37108
+ function extractConstraintsFromContent(content) {
37109
+ const lines = content.split(`
37110
+ `);
37111
+ const constraints = [];
37112
+ for (const line of lines) {
37113
+ if (constraints.length >= MAX_CONSTRAINTS_PER_DOC) {
37114
+ break;
37115
+ }
37116
+ const trimmed = line.trim();
37117
+ if (!trimmed)
37118
+ continue;
37119
+ if (isConstraintLine(trimmed)) {
37120
+ const cleaned = stripMarkdown(trimmed);
37121
+ const len = cleaned.length;
37122
+ if (len >= MIN_LESSON_LENGTH && len <= MAX_CONSTRAINT_LENGTH) {
37123
+ constraints.push(cleaned);
37124
+ }
37125
+ }
37126
+ }
37127
+ return constraints;
37128
+ }
37129
+ async function extractDocConstraints(directory, taskFiles, taskDescription) {
37130
+ const manifestPath = path36.join(directory, ".swarm", "doc-manifest.json");
37131
+ let manifest;
37132
+ try {
37133
+ const content = await readFile5(manifestPath, "utf-8");
37134
+ manifest = JSON.parse(content);
37135
+ } catch {
37136
+ const result = await scanDocIndex(directory);
37137
+ manifest = result.manifest;
37138
+ }
37139
+ const knowledgePath = resolveSwarmKnowledgePath(directory);
37140
+ const existingEntries = await readKnowledge(knowledgePath);
37141
+ const taskContext = [...taskFiles, taskDescription].join(" ");
37142
+ const taskBigrams = wordBigrams(normalize2(taskContext));
37143
+ let extractedCount = 0;
37144
+ let skippedCount = 0;
37145
+ const details = [];
37146
+ for (const docFile of manifest.files) {
37147
+ const docContext = `${docFile.path} ${docFile.title} ${docFile.summary}`;
37148
+ const docBigrams = wordBigrams(normalize2(docContext));
37149
+ const score = jaccardBigram(taskBigrams, docBigrams);
37150
+ if (score <= RELEVANCE_THRESHOLD) {
37151
+ skippedCount++;
37152
+ continue;
37153
+ }
37154
+ let fullContent;
37155
+ try {
37156
+ fullContent = await readFile5(path36.join(directory, docFile.path), "utf-8");
37157
+ } catch {
37158
+ skippedCount++;
37159
+ continue;
37160
+ }
37161
+ const constraints = extractConstraintsFromContent(fullContent);
37162
+ if (constraints.length === 0) {
37163
+ skippedCount++;
37164
+ continue;
37165
+ }
37166
+ const docDetails = {
37167
+ path: docFile.path,
37168
+ score,
37169
+ constraints: []
37170
+ };
37171
+ for (const constraint of constraints) {
37172
+ const duplicate = findNearDuplicate(constraint, existingEntries, DEDUP_THRESHOLD);
37173
+ if (!duplicate) {
37174
+ const entry = {
37175
+ id: crypto4.randomUUID(),
37176
+ tier: "swarm",
37177
+ lesson: constraint,
37178
+ category: "architecture",
37179
+ tags: ["doc-scan", path36.basename(docFile.path)],
37180
+ scope: "global",
37181
+ confidence: 0.5,
37182
+ status: "candidate",
37183
+ confirmed_by: [],
37184
+ project_name: "",
37185
+ retrieval_outcomes: {
37186
+ applied_count: 0,
37187
+ succeeded_after_count: 0,
37188
+ failed_after_count: 0
37189
+ },
37190
+ schema_version: 1,
37191
+ created_at: new Date().toISOString(),
37192
+ updated_at: new Date().toISOString(),
37193
+ auto_generated: true,
37194
+ hive_eligible: false
37195
+ };
37196
+ await appendKnowledge(knowledgePath, entry);
37197
+ existingEntries.push(entry);
37198
+ extractedCount++;
37199
+ docDetails.constraints.push(constraint);
37200
+ }
37201
+ }
37202
+ if (docDetails.constraints.length > 0) {
37203
+ details.push(docDetails);
37204
+ } else {
37205
+ skippedCount++;
37206
+ }
37207
+ }
37208
+ return { extracted: extractedCount, skipped: skippedCount, details };
37209
+ }
37210
+ var SKIP_DIRECTORIES2, SKIP_PATTERNS, MAX_SUMMARY_LENGTH = 200, MAX_INDEXED_FILES = 100, READ_LINES_LIMIT = 30, MIN_LESSON_LENGTH = 15, MAX_CONSTRAINTS_PER_DOC = 5, MAX_CONSTRAINT_LENGTH = 200, RELEVANCE_THRESHOLD = 0.1, DEDUP_THRESHOLD = 0.6, CONSTRAINT_PATTERNS, ACTION_WORDS, doc_scan, doc_extract;
37211
+ var init_doc_scan = __esm(() => {
37212
+ init_dist();
37213
+ init_schema();
37214
+ init_knowledge_store();
37215
+ init_create_tool();
37216
+ SKIP_DIRECTORIES2 = new Set([
37217
+ "node_modules",
37218
+ ".git",
37219
+ ".swarm",
37220
+ "dist",
37221
+ "build",
37222
+ ".next",
37223
+ "vendor"
37224
+ ]);
37225
+ SKIP_PATTERNS = [/\.test\./, /\.spec\./, /\.d\.ts$/];
37226
+ CONSTRAINT_PATTERNS = [
37227
+ /\bMUST\b/,
37228
+ /\bMUST NOT\b/,
37229
+ /\bSHOULD\b/,
37230
+ /\bSHOULD NOT\b/,
37231
+ /\bDO NOT\b/,
37232
+ /\bALWAYS\b/,
37233
+ /\bNEVER\b/,
37234
+ /\bREQUIRED\b/
37235
+ ];
37236
+ ACTION_WORDS = /\b(must|should|don't|avoid|ensure|use|follow)\b/i;
37237
+ doc_scan = createSwarmTool({
37238
+ description: "Scan project documentation files and build an index manifest. Caches results in .swarm/doc-manifest.json for fast subsequent scans.",
37239
+ args: {
37240
+ force: tool.schema.boolean().optional().describe("Force re-scan even if cache is valid")
37241
+ },
37242
+ execute: async (args2, directory) => {
37243
+ let force = false;
37244
+ try {
37245
+ if (args2 && typeof args2 === "object") {
37246
+ const obj = args2;
37247
+ if (obj.force === true)
37248
+ force = true;
37249
+ }
37250
+ } catch {}
37251
+ if (force) {
37252
+ const manifestPath = path36.join(directory, ".swarm", "doc-manifest.json");
37253
+ try {
37254
+ fs25.unlinkSync(manifestPath);
37255
+ } catch {}
37256
+ }
37257
+ const { manifest, cached: cached3 } = await scanDocIndex(directory);
37258
+ return JSON.stringify({
37259
+ success: true,
37260
+ files_count: manifest.files.length,
37261
+ cached: cached3,
37262
+ manifest
37263
+ }, null, 2);
37264
+ }
37265
+ });
37266
+ doc_extract = createSwarmTool({
37267
+ description: "Extract actionable constraints from project documentation relevant to the current task. Scans docs via doc-manifest, scores relevance via Jaccard bigram similarity, and stores non-duplicate constraints in .swarm/knowledge.jsonl.",
37268
+ args: {
37269
+ task_files: tool.schema.array(tool.schema.string()).describe("List of file paths involved in the current task"),
37270
+ task_description: tool.schema.string().describe("Description of the current task")
37271
+ },
37272
+ execute: async (args2, directory) => {
37273
+ let taskFiles = [];
37274
+ let taskDescription = "";
37275
+ try {
37276
+ if (args2 && typeof args2 === "object") {
37277
+ const obj = args2;
37278
+ if (Array.isArray(obj.task_files)) {
37279
+ taskFiles = obj.task_files.filter((f) => typeof f === "string");
37280
+ }
37281
+ if (typeof obj.task_description === "string") {
37282
+ taskDescription = obj.task_description;
37283
+ }
37284
+ }
37285
+ } catch {}
37286
+ if (taskFiles.length === 0 && !taskDescription) {
37287
+ return JSON.stringify({
37288
+ success: false,
37289
+ error: "task_files or task_description is required"
37290
+ });
37291
+ }
37292
+ const result = await extractDocConstraints(directory, taskFiles, taskDescription);
37293
+ return JSON.stringify({ success: true, ...result }, null, 2);
37294
+ }
37295
+ });
37296
+ });
37297
+
36646
37298
  // src/hooks/curator-drift.ts
36647
37299
  var exports_curator_drift = {};
36648
37300
  __export(exports_curator_drift, {
@@ -36651,11 +37303,11 @@ __export(exports_curator_drift, {
36651
37303
  readPriorDriftReports: () => readPriorDriftReports,
36652
37304
  buildDriftInjectionText: () => buildDriftInjectionText
36653
37305
  });
36654
- import * as fs27 from "fs";
36655
- import * as path38 from "path";
37306
+ import * as fs28 from "fs";
37307
+ import * as path39 from "path";
36656
37308
  async function readPriorDriftReports(directory) {
36657
- const swarmDir = path38.join(directory, ".swarm");
36658
- const entries = await fs27.promises.readdir(swarmDir).catch(() => null);
37309
+ const swarmDir = path39.join(directory, ".swarm");
37310
+ const entries = await fs28.promises.readdir(swarmDir).catch(() => null);
36659
37311
  if (entries === null)
36660
37312
  return [];
36661
37313
  const reportFiles = entries.filter((name2) => name2.startsWith(DRIFT_REPORT_PREFIX) && name2.endsWith(".json")).sort();
@@ -36666,7 +37318,7 @@ async function readPriorDriftReports(directory) {
36666
37318
  continue;
36667
37319
  try {
36668
37320
  const report = JSON.parse(content);
36669
- if (typeof report.phase !== "number" || typeof report.alignment !== "string") {
37321
+ if (typeof report.phase !== "number" || typeof report.alignment !== "string" || typeof report.timestamp !== "string" || typeof report.drift_score !== "number" || typeof report.schema_version !== "number" || !Array.isArray(report.compounding_effects)) {
36670
37322
  console.warn(`[curator-drift] Skipping corrupt drift report: ${filename}`);
36671
37323
  continue;
36672
37324
  }
@@ -36681,10 +37333,10 @@ async function readPriorDriftReports(directory) {
36681
37333
  async function writeDriftReport(directory, report) {
36682
37334
  const filename = `${DRIFT_REPORT_PREFIX}${report.phase}.json`;
36683
37335
  const filePath = validateSwarmPath(directory, filename);
36684
- const swarmDir = path38.dirname(filePath);
36685
- await fs27.promises.mkdir(swarmDir, { recursive: true });
37336
+ const swarmDir = path39.dirname(filePath);
37337
+ await fs28.promises.mkdir(swarmDir, { recursive: true });
36686
37338
  try {
36687
- await fs27.promises.writeFile(filePath, JSON.stringify(report, null, 2), "utf-8");
37339
+ await fs28.promises.writeFile(filePath, JSON.stringify(report, null, 2), "utf-8");
36688
37340
  } catch (err2) {
36689
37341
  throw new Error(`[curator-drift] Failed to write drift report to ${filePath}: ${String(err2)}`);
36690
37342
  }
@@ -38274,11 +38926,11 @@ ${JSON.stringify(symbolNames, null, 2)}`);
38274
38926
  throw toThrow;
38275
38927
  }, "quit_");
38276
38928
  var scriptDirectory = "";
38277
- function locateFile(path45) {
38929
+ function locateFile(path46) {
38278
38930
  if (Module["locateFile"]) {
38279
- return Module["locateFile"](path45, scriptDirectory);
38931
+ return Module["locateFile"](path46, scriptDirectory);
38280
38932
  }
38281
- return scriptDirectory + path45;
38933
+ return scriptDirectory + path46;
38282
38934
  }
38283
38935
  __name(locateFile, "locateFile");
38284
38936
  var readAsync, readBinary;
@@ -40063,8 +40715,8 @@ async function loadGrammar(languageId) {
40063
40715
  const wasmFileName = getWasmFileName(normalizedId);
40064
40716
  const grammarsPath = getGrammarsPath();
40065
40717
  const wasmPath = fileURLToPath(new URL(`${grammarsPath}${wasmFileName}`, import.meta.url));
40066
- const { existsSync: existsSync27 } = await import("fs");
40067
- if (!existsSync27(wasmPath)) {
40718
+ const { existsSync: existsSync28 } = await import("fs");
40719
+ if (!existsSync28(wasmPath)) {
40068
40720
  throw new Error(`Grammar file not found for ${languageId}: ${wasmPath}
40069
40721
  Make sure to run 'bun run build' to copy grammar files to dist/lang/grammars/`);
40070
40722
  }
@@ -40094,8 +40746,6 @@ var init_runtime = __esm(() => {
40094
40746
  c: "tree-sitter-cpp.wasm",
40095
40747
  csharp: "tree-sitter-c-sharp.wasm",
40096
40748
  css: "tree-sitter-css.wasm",
40097
- html: "tree-sitter-html.wasm",
40098
- json: "tree-sitter-json.wasm",
40099
40749
  bash: "tree-sitter-bash.wasm",
40100
40750
  ruby: "tree-sitter-ruby.wasm",
40101
40751
  php: "tree-sitter-php.wasm",
@@ -40188,7 +40838,9 @@ function startAgentSession(sessionId, agentName, staleDurationMs = 7200000, dire
40188
40838
  applyRehydrationCache(sessionState);
40189
40839
  if (directory) {
40190
40840
  let rehydrationPromise;
40191
- rehydrationPromise = rehydrateSessionFromDisk(directory, sessionState).catch(() => {}).finally(() => {
40841
+ rehydrationPromise = rehydrateSessionFromDisk(directory, sessionState).catch((err2) => {
40842
+ console.warn("[state] Rehydration failed:", err2 instanceof Error ? err2.message : String(err2));
40843
+ }).finally(() => {
40192
40844
  swarmState.pendingRehydrations.delete(rehydrationPromise);
40193
40845
  });
40194
40846
  swarmState.pendingRehydrations.add(rehydrationPromise);
@@ -40701,10 +41353,11 @@ Two small delegations with two QA gates > one large delegation with one QA gate.
40701
41353
  Never silently consume LOW-confidence result as verified.
40702
41354
  6f-1. **DOCUMENTATION AWARENESS**
40703
41355
  Before implementation begins:
40704
- 1. Check if .swarm/doc-manifest.json exists. If not, delegate to explorer to run DOCUMENTATION DISCOVERY MODE.
41356
+ 1. Check if .swarm/doc-manifest.json exists. If not, delegate to explorer to run DOCUMENTATION DISCOVERY MODE (or call doc_scan directly).
40705
41357
  2. The explorer indexes project documentation (CONTRIBUTING.md, architecture.md, README.md, etc.) and writes constraints to the knowledge system.
40706
- 3. Before starting each phase, call knowledge_recall with query "doc-constraints" to check if any project documentation constrains the current task.
40707
- 4. Key constraints from project docs (commit conventions, release process, test framework, platform requirements) take priority over your own assumptions.
41358
+ 3. When beginning a new task, if .swarm/doc-manifest.json exists, call doc_extract with the task's file list and description to load relevant documentation constraints.
41359
+ 4. Before starting each phase, call knowledge_recall with query "doc-constraints" to check if any project documentation constrains the current task.
41360
+ 5. Key constraints from project docs (commit conventions, release process, test framework, platform requirements) take priority over your own assumptions.
40708
41361
  7. **TIERED QA GATE** \u2014 Execute AFTER every coder task. Pipeline determined by change tier:
40709
41362
  NOTE: These gates are enforced by runtime hooks. If you skip the {{AGENT_PREFIX}}reviewer delegation,
40710
41363
  the next coder delegation will be BLOCKED by the plugin. This is not a suggestion \u2014
@@ -42423,9 +43076,10 @@ MIGRATION_NEEDED: [yes \u2014 description of required caller updates | no]
42423
43076
 
42424
43077
  ## DOCUMENTATION DISCOVERY MODE
42425
43078
  Activates automatically during codebase reality check at plan ingestion.
43079
+ Use the doc_scan tool to scan and index documentation files. If doc_scan is unavailable, fall back to manual globbing.
42426
43080
 
42427
43081
  STEPS:
42428
- 1. Glob for documentation files:
43082
+ 1. Call doc_scan to build the manifest, OR glob for documentation files:
42429
43083
  - Root: README.md, CONTRIBUTING.md, CHANGELOG.md, ARCHITECTURE.md, CLAUDE.md, AGENTS.md, .github/*.md
42430
43084
  - docs/**/*.md, doc/**/*.md (one level deep only)
42431
43085
 
@@ -44580,7 +45234,24 @@ async function handleBenchmarkCommand(directory, args2) {
44580
45234
  }
44581
45235
 
44582
45236
  // src/commands/checkpoint.ts
45237
+ init_zod();
44583
45238
  init_checkpoint();
45239
+ var CheckpointResultSchema = exports_external.object({
45240
+ action: exports_external.string().optional(),
45241
+ success: exports_external.boolean(),
45242
+ error: exports_external.string().optional(),
45243
+ checkpoints: exports_external.array(exports_external.unknown()).optional()
45244
+ }).passthrough();
45245
+ function safeParseResult(result) {
45246
+ const parsed = CheckpointResultSchema.safeParse(JSON.parse(result));
45247
+ if (!parsed.success) {
45248
+ return {
45249
+ success: false,
45250
+ error: `Invalid response: ${parsed.error.message}`
45251
+ };
45252
+ }
45253
+ return parsed.data;
45254
+ }
44584
45255
  async function handleCheckpointCommand(directory, args2) {
44585
45256
  const subcommand = args2[0] || "list";
44586
45257
  const label = args2[1];
@@ -44603,7 +45274,7 @@ async function handleSave2(directory, label) {
44603
45274
  const result = await checkpoint.execute({ action: "save", label }, {
44604
45275
  directory
44605
45276
  });
44606
- const parsed = JSON.parse(result);
45277
+ const parsed = safeParseResult(result);
44607
45278
  if (parsed.success) {
44608
45279
  return `\u2713 Checkpoint saved: "${label}"`;
44609
45280
  } else {
@@ -44622,7 +45293,7 @@ async function handleRestore2(directory, label) {
44622
45293
  const result = await checkpoint.execute({ action: "restore", label }, {
44623
45294
  directory
44624
45295
  });
44625
- const parsed = JSON.parse(result);
45296
+ const parsed = safeParseResult(result);
44626
45297
  if (parsed.success) {
44627
45298
  return `\u2713 Restored to checkpoint: "${label}"`;
44628
45299
  } else {
@@ -44641,7 +45312,7 @@ async function handleDelete2(directory, label) {
44641
45312
  const result = await checkpoint.execute({ action: "delete", label }, {
44642
45313
  directory
44643
45314
  });
44644
- const parsed = JSON.parse(result);
45315
+ const parsed = safeParseResult(result);
44645
45316
  if (parsed.success) {
44646
45317
  return `\u2713 Checkpoint deleted: "${label}"`;
44647
45318
  } else {
@@ -44657,7 +45328,7 @@ async function handleList2(directory) {
44657
45328
  const result = await checkpoint.execute({ action: "list" }, {
44658
45329
  directory
44659
45330
  });
44660
- const parsed = JSON.parse(result);
45331
+ const parsed = safeParseResult(result);
44661
45332
  if (!parsed.success) {
44662
45333
  return `Error: ${parsed.error || "Failed to list checkpoints"}`;
44663
45334
  }
@@ -44729,162 +45400,7 @@ import path15 from "path";
44729
45400
  import * as fs9 from "fs";
44730
45401
  import * as path13 from "path";
44731
45402
  init_event_bus();
44732
-
44733
- // src/hooks/knowledge-store.ts
44734
- var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
44735
- import { existsSync as existsSync7 } from "fs";
44736
- import { appendFile, mkdir, readFile as readFile2, writeFile } from "fs/promises";
44737
- import * as os4 from "os";
44738
- import * as path12 from "path";
44739
- function resolveSwarmKnowledgePath(directory) {
44740
- return path12.join(directory, ".swarm", "knowledge.jsonl");
44741
- }
44742
- function resolveSwarmRejectedPath(directory) {
44743
- return path12.join(directory, ".swarm", "knowledge-rejected.jsonl");
44744
- }
44745
- function resolveHiveKnowledgePath() {
44746
- const platform = process.platform;
44747
- const home = os4.homedir();
44748
- let dataDir;
44749
- if (platform === "win32") {
44750
- dataDir = path12.join(process.env.LOCALAPPDATA || path12.join(home, "AppData", "Local"), "opencode-swarm", "Data");
44751
- } else if (platform === "darwin") {
44752
- dataDir = path12.join(home, "Library", "Application Support", "opencode-swarm");
44753
- } else {
44754
- dataDir = path12.join(process.env.XDG_DATA_HOME || path12.join(home, ".local", "share"), "opencode-swarm");
44755
- }
44756
- return path12.join(dataDir, "shared-learnings.jsonl");
44757
- }
44758
- function resolveHiveRejectedPath() {
44759
- const hivePath = resolveHiveKnowledgePath();
44760
- return path12.join(path12.dirname(hivePath), "shared-learnings-rejected.jsonl");
44761
- }
44762
- async function readKnowledge(filePath) {
44763
- if (!existsSync7(filePath))
44764
- return [];
44765
- const content = await readFile2(filePath, "utf-8");
44766
- const results = [];
44767
- for (const line of content.split(`
44768
- `)) {
44769
- const trimmed = line.trim();
44770
- if (!trimmed)
44771
- continue;
44772
- try {
44773
- results.push(JSON.parse(trimmed));
44774
- } catch {
44775
- console.warn(`[knowledge-store] Skipping corrupted JSONL line in ${filePath}: ${trimmed.slice(0, 80)}`);
44776
- }
44777
- }
44778
- return results;
44779
- }
44780
- async function readRejectedLessons(directory) {
44781
- return readKnowledge(resolveSwarmRejectedPath(directory));
44782
- }
44783
- async function appendKnowledge(filePath, entry) {
44784
- await mkdir(path12.dirname(filePath), { recursive: true });
44785
- await appendFile(filePath, `${JSON.stringify(entry)}
44786
- `, "utf-8");
44787
- }
44788
- async function rewriteKnowledge(filePath, entries) {
44789
- const dir = path12.dirname(filePath);
44790
- await mkdir(dir, { recursive: true });
44791
- let release = null;
44792
- try {
44793
- release = await import_proper_lockfile.default.lock(dir, {
44794
- retries: { retries: 3, minTimeout: 100 }
44795
- });
44796
- const content = entries.map((e) => JSON.stringify(e)).join(`
44797
- `) + (entries.length > 0 ? `
44798
- ` : "");
44799
- await writeFile(filePath, content, "utf-8");
44800
- } finally {
44801
- if (release) {
44802
- try {
44803
- await release();
44804
- } catch {}
44805
- }
44806
- }
44807
- }
44808
- async function appendRejectedLesson(directory, lesson) {
44809
- const filePath = resolveSwarmRejectedPath(directory);
44810
- const existing = await readRejectedLessons(directory);
44811
- const MAX = 20;
44812
- const updated = [...existing, lesson];
44813
- if (updated.length > MAX) {
44814
- const trimmed = updated.slice(updated.length - MAX);
44815
- await rewriteKnowledge(filePath, trimmed);
44816
- } else {
44817
- await appendKnowledge(filePath, lesson);
44818
- }
44819
- }
44820
- function normalize2(text) {
44821
- return text.toLowerCase().replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
44822
- }
44823
- function wordBigrams(text) {
44824
- const words = normalize2(text).split(" ").filter(Boolean);
44825
- const bigrams = new Set;
44826
- for (let i2 = 0;i2 < words.length - 1; i2++) {
44827
- bigrams.add(`${words[i2]} ${words[i2 + 1]}`);
44828
- }
44829
- return bigrams;
44830
- }
44831
- function jaccardBigram(a, b) {
44832
- if (a.size === 0 && b.size === 0)
44833
- return 1;
44834
- const aArr = Array.from(a);
44835
- const intersection3 = new Set(aArr.filter((x) => b.has(x)));
44836
- const union3 = new Set([...aArr, ...Array.from(b)]);
44837
- return intersection3.size / union3.size;
44838
- }
44839
- function findNearDuplicate(candidate, entries, threshold = 0.6) {
44840
- const candidateBigrams = wordBigrams(candidate);
44841
- return entries.find((entry) => {
44842
- const entryBigrams = wordBigrams(entry.lesson);
44843
- return jaccardBigram(candidateBigrams, entryBigrams) >= threshold;
44844
- });
44845
- }
44846
- function computeConfidence(confirmedByCount, autoGenerated) {
44847
- let score = 0.5;
44848
- score += Math.min(confirmedByCount, 3) * 0.1;
44849
- if (!autoGenerated)
44850
- score += 0.1;
44851
- return Math.min(score, 1);
44852
- }
44853
- function inferTags(lesson) {
44854
- const lower = lesson.toLowerCase();
44855
- const tags = [];
44856
- if (/\b(?:typescript|ts)\b/.test(lower))
44857
- tags.push("typescript");
44858
- if (/\b(?:javascript|js)\b/.test(lower))
44859
- tags.push("javascript");
44860
- if (/\b(?:python)\b/.test(lower))
44861
- tags.push("python");
44862
- if (/\b(?:bun|node|deno)\b/.test(lower))
44863
- tags.push("runtime");
44864
- if (/\b(?:react|vue|svelte|angular)\b/.test(lower))
44865
- tags.push("frontend");
44866
- if (/\b(?:git|github|gitlab)\b/.test(lower))
44867
- tags.push("git");
44868
- if (/\b(?:docker|kubernetes|k8s)\b/.test(lower))
44869
- tags.push("container");
44870
- if (/\b(?:sql|postgres|mysql|sqlite)\b/.test(lower))
44871
- tags.push("database");
44872
- if (/\b(?:test|spec|vitest|jest|mocha)\b/.test(lower))
44873
- tags.push("testing");
44874
- if (/\b(?:ci|cd|pipeline|workflow|action)\b/.test(lower))
44875
- tags.push("ci-cd");
44876
- if (/\b(?:security|auth|token|password|encrypt)\b/.test(lower))
44877
- tags.push("security");
44878
- if (/\b(?:performance|latency|throughput|cache)\b/.test(lower))
44879
- tags.push("performance");
44880
- if (/\b(?:api|rest|graphql|grpc|endpoint)\b/.test(lower))
44881
- tags.push("api");
44882
- if (/\b(?:swarm|architect|agent|hook|plan)\b/.test(lower))
44883
- tags.push("opencode-swarm");
44884
- return Array.from(new Set(tags));
44885
- }
44886
-
44887
- // src/hooks/curator.ts
45403
+ init_knowledge_store();
44888
45404
  init_utils2();
44889
45405
  var CURATOR_LLM_TIMEOUT_MS = 30000;
44890
45406
  function parseKnowledgeRecommendations(llmOutput) {
@@ -45387,7 +45903,11 @@ async function applyCuratorKnowledgeUpdates(directory, recommendations, _knowled
45387
45903
  return { applied, skipped };
45388
45904
  }
45389
45905
 
45906
+ // src/hooks/hive-promoter.ts
45907
+ init_knowledge_store();
45908
+
45390
45909
  // src/hooks/knowledge-validator.ts
45910
+ init_knowledge_store();
45391
45911
  var import_proper_lockfile2 = __toESM(require_proper_lockfile(), 1);
45392
45912
  import { appendFile as appendFile2, mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
45393
45913
  import * as path14 from "path";
@@ -45993,6 +46513,7 @@ async function promoteFromSwarm(directory, lessonId) {
45993
46513
  }
45994
46514
 
45995
46515
  // src/commands/curate.ts
46516
+ init_knowledge_store();
45996
46517
  async function handleCurateCommand(directory, _args) {
45997
46518
  try {
45998
46519
  const config3 = KnowledgeConfigSchema.parse({});
@@ -46021,6 +46542,7 @@ function formatCurationSummary(summary) {
46021
46542
  }
46022
46543
 
46023
46544
  // src/commands/dark-matter.ts
46545
+ init_knowledge_store();
46024
46546
  import path17 from "path";
46025
46547
 
46026
46548
  // src/tools/co-change-analyzer.ts
@@ -47862,7 +48384,8 @@ async function extractFromLegacy(directory) {
47862
48384
  mappedStatus = "complete";
47863
48385
  else if (status === "IN PROGRESS")
47864
48386
  mappedStatus = "in_progress";
47865
- const headerLineIndex = lines.indexOf(match[0]);
48387
+ const headerLineIndex = planContent.substring(0, match.index).split(`
48388
+ `).length - 1;
47866
48389
  let completed = 0;
47867
48390
  let total = 0;
47868
48391
  if (headerLineIndex !== -1) {
@@ -47927,6 +48450,7 @@ async function handleHistoryCommand(directory, _args) {
47927
48450
  init_schema();
47928
48451
 
47929
48452
  // src/hooks/knowledge-migrator.ts
48453
+ init_knowledge_store();
47930
48454
  import { randomUUID as randomUUID2 } from "crypto";
47931
48455
  import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
47932
48456
  import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
@@ -48155,6 +48679,7 @@ async function writeSentinel(sentinelPath, migrated, dropped) {
48155
48679
  }
48156
48680
 
48157
48681
  // src/commands/knowledge.ts
48682
+ init_knowledge_store();
48158
48683
  async function handleKnowledgeQuarantineCommand(directory, args2) {
48159
48684
  const entryId = args2[0];
48160
48685
  if (!entryId) {
@@ -48260,9 +48785,9 @@ async function getPlanData(directory, phaseArg) {
48260
48785
  return {
48261
48786
  hasPlan: true,
48262
48787
  fullMarkdown,
48263
- requestedPhase: NaN,
48788
+ requestedPhase: null,
48264
48789
  phaseMarkdown: null,
48265
- errorMessage: null,
48790
+ errorMessage: `Invalid phase number: "${phaseArg}"`,
48266
48791
  isLegacy: false
48267
48792
  };
48268
48793
  }
@@ -48313,9 +48838,9 @@ async function getPlanData(directory, phaseArg) {
48313
48838
  return {
48314
48839
  hasPlan: true,
48315
48840
  fullMarkdown: planContent,
48316
- requestedPhase: NaN,
48841
+ requestedPhase: null,
48317
48842
  phaseMarkdown: null,
48318
- errorMessage: null,
48843
+ errorMessage: `Invalid phase number: "${phaseArg}"`,
48319
48844
  isLegacy: true
48320
48845
  };
48321
48846
  }
@@ -48555,9 +49080,16 @@ function sanitizeSummaryId(id) {
48555
49080
  }
48556
49081
  async function storeSummary(directory, id, fullOutput, summaryText, maxStoredBytes) {
48557
49082
  const sanitizedId = sanitizeSummaryId(id);
48558
- const outputBytes = Buffer.byteLength(fullOutput, "utf8");
48559
- if (outputBytes > maxStoredBytes) {
48560
- throw new Error(`Summary fullOutput size (${outputBytes} bytes) exceeds maximum (${maxStoredBytes} bytes)`);
49083
+ const preCheckEntry = {
49084
+ id: sanitizedId,
49085
+ summaryText,
49086
+ fullOutput,
49087
+ timestamp: Date.now(),
49088
+ originalBytes: Buffer.byteLength(fullOutput, "utf8")
49089
+ };
49090
+ const serializedSize = Buffer.byteLength(JSON.stringify(preCheckEntry), "utf8");
49091
+ if (serializedSize > maxStoredBytes) {
49092
+ throw new Error(`Summary entry size (${serializedSize} bytes) exceeds maximum (${maxStoredBytes} bytes)`);
48561
49093
  }
48562
49094
  const relativePath = path27.join("summaries", `${sanitizedId}.json`);
48563
49095
  const summaryPath = validateSwarmPath(directory, relativePath);
@@ -48567,7 +49099,7 @@ async function storeSummary(directory, id, fullOutput, summaryText, maxStoredByt
48567
49099
  summaryText,
48568
49100
  fullOutput,
48569
49101
  timestamp: Date.now(),
48570
- originalBytes: outputBytes
49102
+ originalBytes: Buffer.byteLength(fullOutput, "utf8")
48571
49103
  };
48572
49104
  const entryJson = JSON.stringify(entry);
48573
49105
  mkdirSync9(summaryDir, { recursive: true });
@@ -49133,7 +49665,15 @@ function makeInitialState() {
49133
49665
  lastSnapshotAt: null
49134
49666
  };
49135
49667
  }
49136
- var state = makeInitialState();
49668
+ var sessionStates = new Map;
49669
+ function getSessionState(sessionId) {
49670
+ let state = sessionStates.get(sessionId);
49671
+ if (!state) {
49672
+ state = makeInitialState();
49673
+ sessionStates.set(sessionId, state);
49674
+ }
49675
+ return state;
49676
+ }
49137
49677
  function appendSnapshot(directory, tier, budgetPct, message) {
49138
49678
  try {
49139
49679
  const snapshotPath = path29.join(directory, ".swarm", "context-snapshot.md");
@@ -49172,6 +49712,7 @@ function createCompactionService(config3, directory, injectMessage) {
49172
49712
  if (budgetPct <= 0)
49173
49713
  return;
49174
49714
  const sessionId = _input.sessionID;
49715
+ const state = getSessionState(sessionId);
49175
49716
  try {
49176
49717
  if (budgetPct >= config3.emergencyThreshold && budgetPct > state.lastEmergencyAt + 5) {
49177
49718
  state.lastEmergencyAt = budgetPct;
@@ -49203,29 +49744,27 @@ function createCompactionService(config3, directory, injectMessage) {
49203
49744
  }
49204
49745
  };
49205
49746
  }
49206
- function getCompactionMetrics() {
49207
- return {
49208
- compactionCount: state.observationCount + state.reflectionCount + state.emergencyCount,
49209
- lastSnapshotAt: state.lastSnapshotAt
49210
- };
49747
+ function getCompactionMetrics(sessionId) {
49748
+ if (sessionId) {
49749
+ const state = getSessionState(sessionId);
49750
+ return {
49751
+ compactionCount: state.observationCount + state.reflectionCount + state.emergencyCount,
49752
+ lastSnapshotAt: state.lastSnapshotAt
49753
+ };
49754
+ }
49755
+ let total = 0;
49756
+ let lastSnapshot = null;
49757
+ for (const state of sessionStates.values()) {
49758
+ total += state.observationCount + state.reflectionCount + state.emergencyCount;
49759
+ if (state.lastSnapshotAt && (!lastSnapshot || state.lastSnapshotAt > lastSnapshot)) {
49760
+ lastSnapshot = state.lastSnapshotAt;
49761
+ }
49762
+ }
49763
+ return { compactionCount: total, lastSnapshotAt: lastSnapshot };
49211
49764
  }
49212
49765
 
49213
49766
  // src/services/context-budget-service.ts
49214
49767
  init_utils2();
49215
- function validateDirectory(directory) {
49216
- if (!directory || directory.trim() === "") {
49217
- throw new Error("Invalid directory: empty");
49218
- }
49219
- if (/\.\.[/\\]/.test(directory)) {
49220
- throw new Error("Invalid directory: path traversal detected");
49221
- }
49222
- if (directory.startsWith("/") || directory.startsWith("\\")) {
49223
- throw new Error("Invalid directory: absolute path");
49224
- }
49225
- if (/^[A-Za-z]:[\\/]/.test(directory)) {
49226
- throw new Error("Invalid directory: Windows absolute path");
49227
- }
49228
- }
49229
49768
  var DEFAULT_CONTEXT_BUDGET_CONFIG = {
49230
49769
  enabled: true,
49231
49770
  budgetTokens: 40000,
@@ -49252,10 +49791,14 @@ async function readBudgetState(directory) {
49252
49791
  return null;
49253
49792
  }
49254
49793
  }
49255
- async function writeBudgetState(directory, state2) {
49256
- const resolvedPath = validateSwarmPath(directory, "session/budget-state.json");
49257
- const content = JSON.stringify(state2, null, 2);
49258
- await Bun.write(resolvedPath, content);
49794
+ async function writeBudgetState(directory, state) {
49795
+ try {
49796
+ const resolvedPath = validateSwarmPath(directory, "session/budget-state.json");
49797
+ const content = JSON.stringify(state, null, 2);
49798
+ await Bun.write(resolvedPath, content);
49799
+ } catch (error93) {
49800
+ console.warn("[context-budget] Failed to write budget state:", error93 instanceof Error ? error93.message : String(error93));
49801
+ }
49259
49802
  }
49260
49803
  async function countEvents(directory) {
49261
49804
  const content = await readSwarmFileAsync(directory, "events.jsonl");
@@ -49343,25 +49886,25 @@ async function formatBudgetWarning(report, directory, config3) {
49343
49886
  return formatWarningMessage(report);
49344
49887
  }
49345
49888
  const budgetState = await readBudgetState(directory);
49346
- const state2 = budgetState || {
49889
+ const state = budgetState || {
49347
49890
  warningFiredAtTurn: null,
49348
49891
  criticalFiredAtTurn: null,
49349
49892
  lastInjectedAtTurn: null
49350
49893
  };
49351
49894
  const currentTurn = report.estimatedTurnCount;
49352
49895
  if (report.status === "warning") {
49353
- if (config3.warningMode === "once" && state2.warningFiredAtTurn !== null) {
49896
+ if (config3.warningMode === "once" && state.warningFiredAtTurn !== null) {
49354
49897
  return null;
49355
49898
  }
49356
- if (config3.warningMode === "interval" && state2.warningFiredAtTurn !== null && currentTurn - state2.warningFiredAtTurn < config3.warningIntervalTurns) {
49899
+ if (config3.warningMode === "interval" && state.warningFiredAtTurn !== null && currentTurn - state.warningFiredAtTurn < config3.warningIntervalTurns) {
49357
49900
  return null;
49358
49901
  }
49359
- state2.warningFiredAtTurn = currentTurn;
49360
- state2.lastInjectedAtTurn = currentTurn;
49361
- await writeBudgetState(directory, state2);
49902
+ state.warningFiredAtTurn = currentTurn;
49903
+ state.lastInjectedAtTurn = currentTurn;
49904
+ await writeBudgetState(directory, state);
49362
49905
  } else if (report.status === "critical") {
49363
- state2.criticalFiredAtTurn = currentTurn;
49364
- state2.lastInjectedAtTurn = currentTurn;
49906
+ state.criticalFiredAtTurn = currentTurn;
49907
+ state.lastInjectedAtTurn = currentTurn;
49365
49908
  }
49366
49909
  return formatWarningMessage(report);
49367
49910
  }
@@ -49525,6 +50068,7 @@ async function handleTurboCommand(_directory, args2, sessionID) {
49525
50068
 
49526
50069
  // src/tools/write-retro.ts
49527
50070
  init_tool();
50071
+ init_evidence_schema();
49528
50072
  init_manager();
49529
50073
  init_create_tool();
49530
50074
  async function executeWriteRetro(args2, directory) {
@@ -49759,8 +50303,9 @@ async function executeWriteRetro(args2, directory) {
49759
50303
  };
49760
50304
  const taxonomy = [];
49761
50305
  try {
49762
- for (const taskSuffix of ["1", "2", "3", "4", "5"]) {
49763
- const phaseTaskId = `${phase}.${taskSuffix}`;
50306
+ const allTaskIds = await listEvidenceTaskIds(directory);
50307
+ const phaseTaskIds = allTaskIds.filter((id) => id.startsWith(`${phase}.`));
50308
+ for (const phaseTaskId of phaseTaskIds) {
49764
50309
  const result = await loadEvidence(directory, phaseTaskId);
49765
50310
  if (result.status !== "found")
49766
50311
  continue;
@@ -49794,6 +50339,13 @@ async function executeWriteRetro(args2, directory) {
49794
50339
  }
49795
50340
  } catch {}
49796
50341
  retroEntry.error_taxonomy = [...new Set(taxonomy)];
50342
+ const validationResult = RetrospectiveEvidenceSchema.safeParse(retroEntry);
50343
+ if (!validationResult.success) {
50344
+ return JSON.stringify({
50345
+ success: false,
50346
+ error: `Retrospective entry failed validation: ${validationResult.error.message}`
50347
+ }, null, 2);
50348
+ }
49797
50349
  try {
49798
50350
  await saveEvidence(directory, taskId, retroEntry);
49799
50351
  return JSON.stringify({
@@ -49858,7 +50410,7 @@ var write_retro = createSwarmTool({
49858
50410
  }
49859
50411
  });
49860
50412
 
49861
- // src/commands/write_retro.ts
50413
+ // src/commands/write-retro.ts
49862
50414
  async function handleWriteRetroCommand(directory, args2) {
49863
50415
  if (args2.length === 0 || !args2[0] || args2[0].trim() === "") {
49864
50416
  return `## Usage: /swarm write-retro <json>
@@ -50124,6 +50676,7 @@ init_schema();
50124
50676
 
50125
50677
  // src/hooks/agent-activity.ts
50126
50678
  import { renameSync as renameSync8, unlinkSync as unlinkSync4 } from "fs";
50679
+ import * as nodePath2 from "path";
50127
50680
  init_utils();
50128
50681
  init_utils2();
50129
50682
  function createAgentActivityHooks(config3, directory) {
@@ -50148,7 +50701,7 @@ function createAgentActivityHooks(config3, directory) {
50148
50701
  return;
50149
50702
  swarmState.activeToolCalls.delete(input.callID);
50150
50703
  const duration5 = Date.now() - entry.startTime;
50151
- const success3 = output.output != null;
50704
+ const success3 = output.success === true;
50152
50705
  const key = entry.tool;
50153
50706
  const existing = swarmState.toolAggregates.get(key) ?? {
50154
50707
  tool: key,
@@ -50193,7 +50746,7 @@ async function doFlush(directory) {
50193
50746
  const activitySection = renderActivitySection();
50194
50747
  const updated = replaceOrAppendSection(existing, "## Agent Activity", activitySection);
50195
50748
  const flushedCount = swarmState.pendingEvents;
50196
- const path30 = `${directory}/.swarm/context.md`;
50749
+ const path30 = nodePath2.join(directory, ".swarm", "context.md");
50197
50750
  const tempPath = `${path30}.tmp`;
50198
50751
  try {
50199
50752
  await Bun.write(tempPath, updated);
@@ -50247,7 +50800,7 @@ ${content.substring(endIndex + 1)}`;
50247
50800
  // src/hooks/compaction-customizer.ts
50248
50801
  init_manager2();
50249
50802
  import * as fs20 from "fs";
50250
- import { join as join26 } from "path";
50803
+ import { join as join27 } from "path";
50251
50804
  init_utils2();
50252
50805
  function createCompactionCustomizerHook(config3, directory) {
50253
50806
  const enabled = config3.hooks?.compaction !== false;
@@ -50293,7 +50846,7 @@ function createCompactionCustomizerHook(config3, directory) {
50293
50846
  }
50294
50847
  }
50295
50848
  try {
50296
- const summariesDir = join26(directory, ".swarm", "summaries");
50849
+ const summariesDir = join27(directory, ".swarm", "summaries");
50297
50850
  const files = await fs20.promises.readdir(summariesDir);
50298
50851
  if (files.length > 0) {
50299
50852
  const count = files.length;
@@ -50849,6 +51402,67 @@ init_telemetry();
50849
51402
  import * as path30 from "path";
50850
51403
  init_constants();
50851
51404
  init_schema();
51405
+
51406
+ // src/context/zone-classifier.ts
51407
+ function classifyFile(filePath) {
51408
+ const normalized = filePath.toLowerCase().replace(/\\/g, "/");
51409
+ if (normalized.endsWith(".wasm") || normalized.includes("/dist/") || normalized.includes("/build/") || normalized.includes(".swarm/checkpoints/")) {
51410
+ return {
51411
+ filePath,
51412
+ zone: "generated",
51413
+ confidence: "high",
51414
+ reason: "path matches generated file patterns"
51415
+ };
51416
+ }
51417
+ if (normalized.includes("/test/") || normalized.includes("/tests/") || normalized.includes("/__tests__/") || normalized.includes(".test.") || normalized.includes(".spec.") || normalized.includes("/mocks/")) {
51418
+ return {
51419
+ filePath,
51420
+ zone: "test",
51421
+ confidence: "high",
51422
+ reason: "path matches test/**, tests/**, __tests__/**, *.test.*, *.spec.*, or **/mocks/**"
51423
+ };
51424
+ }
51425
+ if (normalized.includes("/scripts/") || normalized.includes("makefile") || normalized.includes("dockerfile") || normalized.includes(".github/")) {
51426
+ return {
51427
+ filePath,
51428
+ zone: "build",
51429
+ confidence: "high",
51430
+ reason: "path matches build scripts or CI configuration"
51431
+ };
51432
+ }
51433
+ if (normalized.endsWith(".json") && !normalized.includes(".github/") || normalized.endsWith(".yaml") && !normalized.includes(".github/") || normalized.endsWith(".yml") && !normalized.includes(".github/") || normalized.endsWith(".toml") || normalized.includes(".env") || normalized.endsWith("biome.json") || normalized.endsWith("tsconfig.json")) {
51434
+ return {
51435
+ filePath,
51436
+ zone: "config",
51437
+ confidence: "high",
51438
+ reason: "extension is config file type"
51439
+ };
51440
+ }
51441
+ if (normalized.includes("/docs/") || normalized.endsWith(".md") && !normalized.includes("/") || normalized.includes("readme") || normalized.includes("changelog") || normalized.includes("license")) {
51442
+ return {
51443
+ filePath,
51444
+ zone: "docs",
51445
+ confidence: "high",
51446
+ reason: "path matches docs/** or documentation files"
51447
+ };
51448
+ }
51449
+ if (normalized.includes("/src/") || normalized.includes("/lib/")) {
51450
+ return {
51451
+ filePath,
51452
+ zone: "production",
51453
+ confidence: "high",
51454
+ reason: "path matches src/** or lib/**"
51455
+ };
51456
+ }
51457
+ return {
51458
+ filePath,
51459
+ zone: "production",
51460
+ confidence: "medium",
51461
+ reason: "default classification"
51462
+ };
51463
+ }
51464
+
51465
+ // src/hooks/guardrails.ts
50852
51466
  init_manager2();
50853
51467
  init_telemetry();
50854
51468
  init_utils();
@@ -51138,6 +51752,12 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
51138
51752
  const delegArgs = output.args;
51139
51753
  const delegTargetPath = delegArgs?.filePath ?? delegArgs?.path ?? delegArgs?.file ?? delegArgs?.target;
51140
51754
  if (typeof delegTargetPath === "string" && delegTargetPath.length > 0) {
51755
+ const agentName2 = swarmState.activeAgent.get(input.sessionID) ?? "unknown";
51756
+ const cwd = directory ?? process.cwd();
51757
+ const authorityCheck = checkFileAuthority(agentName2, delegTargetPath, cwd);
51758
+ if (!authorityCheck.allowed) {
51759
+ throw new Error(`WRITE BLOCKED: Agent "${agentName2}" is not authorised to write "${delegTargetPath}". Reason: ${authorityCheck.reason}`);
51760
+ }
51141
51761
  if (!currentSession.modifiedFilesThisCoderTask.includes(delegTargetPath)) {
51142
51762
  currentSession.modifiedFilesThisCoderTask.push(delegTargetPath);
51143
51763
  }
@@ -51446,7 +52066,7 @@ function createGuardrailsHooks(directory, directoryOrConfig, config3) {
51446
52066
  session.partialGateWarningsIssuedForTask?.delete(session.currentTaskId);
51447
52067
  if (session.declaredCoderScope !== null) {
51448
52068
  const undeclaredFiles = session.modifiedFilesThisCoderTask.map((f) => f.replace(/[\r\n\t]/g, "_")).filter((f) => !isInDeclaredScope(f, session.declaredCoderScope));
51449
- if (undeclaredFiles.length > 2) {
52069
+ if (undeclaredFiles.length >= 1) {
51450
52070
  const safeTaskId = String(session.currentTaskId ?? "").replace(/[\r\n\t]/g, "_");
51451
52071
  session.lastScopeViolation = `Scope violation for task ${safeTaskId}: ` + `${undeclaredFiles.length} undeclared files modified: ` + undeclaredFiles.join(", ");
51452
52072
  session.scopeViolationDetected = true;
@@ -51832,6 +52452,98 @@ function hashArgs(args2) {
51832
52452
  return 0;
51833
52453
  }
51834
52454
  }
52455
+ var AGENT_AUTHORITY_RULES = {
52456
+ architect: {
52457
+ blockedExact: [".swarm/plan.md", ".swarm/plan.json"],
52458
+ blockedZones: ["generated"]
52459
+ },
52460
+ coder: {
52461
+ blockedPrefix: [".swarm/"],
52462
+ allowedPrefix: ["src/", "tests/", "docs/", "scripts/"],
52463
+ blockedZones: ["generated", "config"]
52464
+ },
52465
+ reviewer: {
52466
+ blockedExact: [".swarm/plan.md", ".swarm/plan.json"],
52467
+ blockedPrefix: ["src/"],
52468
+ allowedPrefix: [".swarm/evidence/", ".swarm/outputs/"],
52469
+ blockedZones: ["generated"]
52470
+ },
52471
+ explorer: {
52472
+ readOnly: true
52473
+ },
52474
+ sme: {
52475
+ readOnly: true
52476
+ },
52477
+ test_engineer: {
52478
+ blockedExact: [".swarm/plan.md", ".swarm/plan.json"],
52479
+ blockedPrefix: ["src/"],
52480
+ allowedPrefix: ["tests/", ".swarm/evidence/"],
52481
+ blockedZones: ["generated"]
52482
+ },
52483
+ docs: {
52484
+ allowedPrefix: ["docs/", ".swarm/outputs/"],
52485
+ blockedZones: ["generated"]
52486
+ },
52487
+ designer: {
52488
+ allowedPrefix: ["docs/", ".swarm/outputs/"],
52489
+ blockedZones: ["generated"]
52490
+ },
52491
+ critic: {
52492
+ allowedPrefix: [".swarm/evidence/"],
52493
+ blockedZones: ["generated"]
52494
+ }
52495
+ };
52496
+ function checkFileAuthority(agentName, filePath, _cwd) {
52497
+ const normalizedAgent = agentName.toLowerCase();
52498
+ const normalizedPath = filePath.replace(/\\/g, "/");
52499
+ const rules = AGENT_AUTHORITY_RULES[normalizedAgent];
52500
+ if (!rules) {
52501
+ return { allowed: false, reason: `Unknown agent: ${agentName}` };
52502
+ }
52503
+ if (rules.readOnly) {
52504
+ return {
52505
+ allowed: false,
52506
+ reason: `Path blocked: ${normalizedPath} (agent ${normalizedAgent} is read-only)`
52507
+ };
52508
+ }
52509
+ if (rules.blockedExact) {
52510
+ for (const blocked of rules.blockedExact) {
52511
+ if (normalizedPath === blocked) {
52512
+ return { allowed: false, reason: `Path blocked: ${normalizedPath}` };
52513
+ }
52514
+ }
52515
+ }
52516
+ if (rules.blockedPrefix) {
52517
+ for (const prefix of rules.blockedPrefix) {
52518
+ if (normalizedPath.startsWith(prefix)) {
52519
+ return {
52520
+ allowed: false,
52521
+ reason: `Path blocked: ${normalizedPath} is under ${prefix}`
52522
+ };
52523
+ }
52524
+ }
52525
+ }
52526
+ if (rules.allowedPrefix && rules.allowedPrefix.length > 0) {
52527
+ const isAllowed = rules.allowedPrefix.some((prefix) => normalizedPath.startsWith(prefix));
52528
+ if (!isAllowed) {
52529
+ return {
52530
+ allowed: false,
52531
+ reason: `Path ${normalizedPath} not in allowed list for ${normalizedAgent}`
52532
+ };
52533
+ }
52534
+ }
52535
+ if (rules.blockedZones && rules.blockedZones.length > 0) {
52536
+ const { zone } = classifyFile(normalizedPath);
52537
+ if (rules.blockedZones.includes(zone)) {
52538
+ return {
52539
+ allowed: false,
52540
+ reason: `Path blocked: ${normalizedPath} is in ${zone} zone`,
52541
+ zone
52542
+ };
52543
+ }
52544
+ }
52545
+ return { allowed: true };
52546
+ }
51835
52547
 
51836
52548
  // src/hooks/delegation-gate.ts
51837
52549
  init_utils2();
@@ -51936,8 +52648,8 @@ function createDelegationGateHook(config3, directory) {
51936
52648
  const session = swarmState.agentSessions.get(input.sessionID);
51937
52649
  if (!session || !session.taskWorkflowStates)
51938
52650
  return;
51939
- for (const [taskId, state2] of session.taskWorkflowStates) {
51940
- if (state2 !== "coder_delegated")
52651
+ for (const [taskId, state] of session.taskWorkflowStates) {
52652
+ if (state !== "coder_delegated")
51941
52653
  continue;
51942
52654
  const turbo = hasActiveTurboMode(input.sessionID);
51943
52655
  if (turbo) {
@@ -51968,23 +52680,23 @@ function createDelegationGateHook(config3, directory) {
51968
52680
  if (targetAgent === "test_engineer")
51969
52681
  hasTestEngineer = true;
51970
52682
  if (targetAgent === "reviewer" && session.taskWorkflowStates) {
51971
- for (const [taskId, state2] of session.taskWorkflowStates) {
51972
- if (state2 === "coder_delegated" || state2 === "pre_check_passed") {
52683
+ for (const [taskId, state] of session.taskWorkflowStates) {
52684
+ if (state === "coder_delegated" || state === "pre_check_passed") {
51973
52685
  try {
51974
52686
  advanceTaskState(session, taskId, "reviewer_run");
51975
52687
  } catch (err2) {
51976
- console.warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state2}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52688
+ console.warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
51977
52689
  }
51978
52690
  }
51979
52691
  }
51980
52692
  }
51981
52693
  if (targetAgent === "test_engineer" && session.taskWorkflowStates) {
51982
- for (const [taskId, state2] of session.taskWorkflowStates) {
51983
- if (state2 === "reviewer_run") {
52694
+ for (const [taskId, state] of session.taskWorkflowStates) {
52695
+ if (state === "reviewer_run") {
51984
52696
  try {
51985
52697
  advanceTaskState(session, taskId, "tests_run");
51986
52698
  } catch (err2) {
51987
- console.warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state2}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52699
+ console.warn(`[delegation-gate] toolAfter: could not advance ${taskId} (${state}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
51988
52700
  }
51989
52701
  }
51990
52702
  }
@@ -52000,12 +52712,12 @@ function createDelegationGateHook(config3, directory) {
52000
52712
  if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
52001
52713
  otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
52002
52714
  }
52003
- for (const [taskId, state2] of otherSession.taskWorkflowStates) {
52004
- if (state2 === "coder_delegated" || state2 === "pre_check_passed") {
52715
+ for (const [taskId, state] of otherSession.taskWorkflowStates) {
52716
+ if (state === "coder_delegated" || state === "pre_check_passed") {
52005
52717
  try {
52006
52718
  advanceTaskState(otherSession, taskId, "reviewer_run");
52007
52719
  } catch (err2) {
52008
- console.warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state2}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52720
+ console.warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52009
52721
  }
52010
52722
  }
52011
52723
  }
@@ -52015,12 +52727,12 @@ function createDelegationGateHook(config3, directory) {
52015
52727
  if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
52016
52728
  otherSession.taskWorkflowStates.set(seedTaskId, "reviewer_run");
52017
52729
  }
52018
- for (const [taskId, state2] of otherSession.taskWorkflowStates) {
52019
- if (state2 === "reviewer_run") {
52730
+ for (const [taskId, state] of otherSession.taskWorkflowStates) {
52731
+ if (state === "reviewer_run") {
52020
52732
  try {
52021
52733
  advanceTaskState(otherSession, taskId, "tests_run");
52022
52734
  } catch (err2) {
52023
- console.warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state2}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52735
+ console.warn(`[delegation-gate] toolAfter cross-session: could not advance ${taskId} (${state}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52024
52736
  }
52025
52737
  }
52026
52738
  }
@@ -52084,23 +52796,23 @@ function createDelegationGateHook(config3, directory) {
52084
52796
  session.qaSkipTaskIds = [];
52085
52797
  }
52086
52798
  if (lastCoderIndex !== -1 && hasReviewer && session.taskWorkflowStates) {
52087
- for (const [taskId, state2] of session.taskWorkflowStates) {
52088
- if (state2 === "coder_delegated" || state2 === "pre_check_passed") {
52799
+ for (const [taskId, state] of session.taskWorkflowStates) {
52800
+ if (state === "coder_delegated" || state === "pre_check_passed") {
52089
52801
  try {
52090
52802
  advanceTaskState(session, taskId, "reviewer_run");
52091
52803
  } catch (err2) {
52092
- console.warn(`[delegation-gate] fallback: could not advance ${taskId} (${state2}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52804
+ console.warn(`[delegation-gate] fallback: could not advance ${taskId} (${state}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52093
52805
  }
52094
52806
  }
52095
52807
  }
52096
52808
  }
52097
52809
  if (lastCoderIndex !== -1 && hasReviewer && hasTestEngineer && session.taskWorkflowStates) {
52098
- for (const [taskId, state2] of session.taskWorkflowStates) {
52099
- if (state2 === "reviewer_run") {
52810
+ for (const [taskId, state] of session.taskWorkflowStates) {
52811
+ if (state === "reviewer_run") {
52100
52812
  try {
52101
52813
  advanceTaskState(session, taskId, "tests_run");
52102
52814
  } catch (err2) {
52103
- console.warn(`[delegation-gate] fallback: could not advance ${taskId} (${state2}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52815
+ console.warn(`[delegation-gate] fallback: could not advance ${taskId} (${state}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52104
52816
  }
52105
52817
  }
52106
52818
  }
@@ -52115,12 +52827,12 @@ function createDelegationGateHook(config3, directory) {
52115
52827
  if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
52116
52828
  otherSession.taskWorkflowStates.set(seedTaskId, "coder_delegated");
52117
52829
  }
52118
- for (const [taskId, state2] of otherSession.taskWorkflowStates) {
52119
- if (state2 === "coder_delegated" || state2 === "pre_check_passed") {
52830
+ for (const [taskId, state] of otherSession.taskWorkflowStates) {
52831
+ if (state === "coder_delegated" || state === "pre_check_passed") {
52120
52832
  try {
52121
52833
  advanceTaskState(otherSession, taskId, "reviewer_run");
52122
52834
  } catch (err2) {
52123
- console.warn(`[delegation-gate] fallback cross-session: could not advance ${taskId} (${state2}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52835
+ console.warn(`[delegation-gate] fallback cross-session: could not advance ${taskId} (${state}) \u2192 reviewer_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52124
52836
  }
52125
52837
  }
52126
52838
  }
@@ -52136,12 +52848,12 @@ function createDelegationGateHook(config3, directory) {
52136
52848
  if (seedTaskId && !otherSession.taskWorkflowStates.has(seedTaskId)) {
52137
52849
  otherSession.taskWorkflowStates.set(seedTaskId, "reviewer_run");
52138
52850
  }
52139
- for (const [taskId, state2] of otherSession.taskWorkflowStates) {
52140
- if (state2 === "reviewer_run") {
52851
+ for (const [taskId, state] of otherSession.taskWorkflowStates) {
52852
+ if (state === "reviewer_run") {
52141
52853
  try {
52142
52854
  advanceTaskState(otherSession, taskId, "tests_run");
52143
52855
  } catch (err2) {
52144
- console.warn(`[delegation-gate] fallback cross-session: could not advance ${taskId} (${state2}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52856
+ console.warn(`[delegation-gate] fallback cross-session: could not advance ${taskId} (${state}) \u2192 tests_run: ${err2 instanceof Error ? err2.message : String(err2)}`);
52145
52857
  }
52146
52858
  }
52147
52859
  }
@@ -52720,7 +53432,7 @@ init_schema();
52720
53432
  init_manager();
52721
53433
  init_detector();
52722
53434
  init_manager2();
52723
- import * as fs25 from "fs";
53435
+ import * as fs26 from "fs";
52724
53436
 
52725
53437
  // src/services/decision-drift-analyzer.ts
52726
53438
  init_utils2();
@@ -53592,6 +54304,13 @@ function createSystemEnhancerHook(config3, directory) {
53592
54304
  const maxInjectionTokens = config3.context_budget?.max_injection_tokens ?? Number.POSITIVE_INFINITY;
53593
54305
  let injectedTokens = 0;
53594
54306
  const contextContent = await readSwarmFileAsync(directory, "context.md");
54307
+ try {
54308
+ const { scanDocIndex: scanDocIndex2 } = await Promise.resolve().then(() => (init_doc_scan(), exports_doc_scan));
54309
+ const { manifest, cached: cached3 } = await scanDocIndex2(directory);
54310
+ if (!cached3) {
54311
+ warn(`[system-enhancer] Doc manifest generated: ${manifest.files.length} files indexed`);
54312
+ }
54313
+ } catch {}
53595
54314
  const scoringEnabled = config3.context_budget?.scoring?.enabled === true;
53596
54315
  if (!scoringEnabled) {
53597
54316
  let plan2 = null;
@@ -53623,11 +54342,11 @@ function createSystemEnhancerHook(config3, directory) {
53623
54342
  if (handoffContent) {
53624
54343
  const handoffPath = validateSwarmPath(directory, "handoff.md");
53625
54344
  const consumedPath = validateSwarmPath(directory, "handoff-consumed.md");
53626
- if (fs25.existsSync(consumedPath)) {
54345
+ if (fs26.existsSync(consumedPath)) {
53627
54346
  warn("Duplicate handoff detected: handoff-consumed.md already exists");
53628
- fs25.unlinkSync(consumedPath);
54347
+ fs26.unlinkSync(consumedPath);
53629
54348
  }
53630
- fs25.renameSync(handoffPath, consumedPath);
54349
+ fs26.renameSync(handoffPath, consumedPath);
53631
54350
  const handoffBlock = `## HANDOFF \u2014 Resuming from model switch
53632
54351
  The previous model's session ended. Here is your starting context:
53633
54352
 
@@ -53773,7 +54492,7 @@ ${handoffBlock}`);
53773
54492
  const lastHint = session.lastCompactionHint || 0;
53774
54493
  for (const threshold of thresholds) {
53775
54494
  if (totalToolCalls >= threshold && lastHint < threshold) {
53776
- const totalToolCallsPlaceholder = "$" + "{totalToolCalls}";
54495
+ const totalToolCallsPlaceholder = "${totalToolCalls}";
53777
54496
  const messageTemplate = compactionConfig?.message ?? `[SWARM HINT] Session has ${totalToolCallsPlaceholder} tool calls. Consider compacting at next phase boundary to maintain context quality.`;
53778
54497
  const message = messageTemplate.replace(totalToolCallsPlaceholder, String(totalToolCalls));
53779
54498
  tryInject(message);
@@ -53908,11 +54627,11 @@ ${budgetWarning}`);
53908
54627
  if (handoffContent) {
53909
54628
  const handoffPath = validateSwarmPath(directory, "handoff.md");
53910
54629
  const consumedPath = validateSwarmPath(directory, "handoff-consumed.md");
53911
- if (fs25.existsSync(consumedPath)) {
54630
+ if (fs26.existsSync(consumedPath)) {
53912
54631
  warn("Duplicate handoff detected: handoff-consumed.md already exists");
53913
- fs25.unlinkSync(consumedPath);
54632
+ fs26.unlinkSync(consumedPath);
53914
54633
  }
53915
- fs25.renameSync(handoffPath, consumedPath);
54634
+ fs26.renameSync(handoffPath, consumedPath);
53916
54635
  const handoffBlock = `## HANDOFF \u2014 Resuming from model switch
53917
54636
  The previous model's session ended. Here is your starting context:
53918
54637
 
@@ -54109,7 +54828,7 @@ ${handoffBlock}`;
54109
54828
  const lastHint_b = session_b.lastCompactionHint || 0;
54110
54829
  for (const threshold of thresholds_b) {
54111
54830
  if (totalToolCalls_b >= threshold && lastHint_b < threshold) {
54112
- const totalToolCallsPlaceholder_b = "$" + "{totalToolCalls}";
54831
+ const totalToolCallsPlaceholder_b = "${totalToolCalls}";
54113
54832
  const messageTemplate_b = compactionConfig_b?.message ?? `[SWARM HINT] Session has ${totalToolCallsPlaceholder_b} tool calls. Consider compacting at next phase boundary to maintain context quality.`;
54114
54833
  const compactionText = messageTemplate_b.replace(totalToolCallsPlaceholder_b, String(totalToolCalls_b));
54115
54834
  candidates.push({
@@ -54682,8 +55401,8 @@ function isReadTool(toolName) {
54682
55401
  }
54683
55402
 
54684
55403
  // src/hooks/incremental-verify.ts
54685
- import * as fs26 from "fs";
54686
- import * as path36 from "path";
55404
+ import * as fs27 from "fs";
55405
+ import * as path37 from "path";
54687
55406
 
54688
55407
  // src/hooks/spawn-helper.ts
54689
55408
  import { spawn } from "child_process";
@@ -54758,21 +55477,21 @@ function spawnAsync(command, cwd, timeoutMs) {
54758
55477
  // src/hooks/incremental-verify.ts
54759
55478
  var emittedSkipAdvisories = new Set;
54760
55479
  function detectPackageManager(projectDir) {
54761
- if (fs26.existsSync(path36.join(projectDir, "bun.lockb")))
55480
+ if (fs27.existsSync(path37.join(projectDir, "bun.lockb")))
54762
55481
  return "bun";
54763
- if (fs26.existsSync(path36.join(projectDir, "pnpm-lock.yaml")))
55482
+ if (fs27.existsSync(path37.join(projectDir, "pnpm-lock.yaml")))
54764
55483
  return "pnpm";
54765
- if (fs26.existsSync(path36.join(projectDir, "yarn.lock")))
55484
+ if (fs27.existsSync(path37.join(projectDir, "yarn.lock")))
54766
55485
  return "yarn";
54767
- if (fs26.existsSync(path36.join(projectDir, "package-lock.json")))
55486
+ if (fs27.existsSync(path37.join(projectDir, "package-lock.json")))
54768
55487
  return "npm";
54769
55488
  return "bun";
54770
55489
  }
54771
55490
  function detectTypecheckCommand(projectDir) {
54772
- const pkgPath = path36.join(projectDir, "package.json");
54773
- if (fs26.existsSync(pkgPath)) {
55491
+ const pkgPath = path37.join(projectDir, "package.json");
55492
+ if (fs27.existsSync(pkgPath)) {
54774
55493
  try {
54775
- const pkg = JSON.parse(fs26.readFileSync(pkgPath, "utf8"));
55494
+ const pkg = JSON.parse(fs27.readFileSync(pkgPath, "utf8"));
54776
55495
  const scripts = pkg.scripts;
54777
55496
  if (scripts?.typecheck) {
54778
55497
  const pm = detectPackageManager(projectDir);
@@ -54786,8 +55505,8 @@ function detectTypecheckCommand(projectDir) {
54786
55505
  ...pkg.dependencies,
54787
55506
  ...pkg.devDependencies
54788
55507
  };
54789
- if (!deps?.typescript && !fs26.existsSync(path36.join(projectDir, "tsconfig.json"))) {}
54790
- const hasTSMarkers = deps?.typescript || fs26.existsSync(path36.join(projectDir, "tsconfig.json"));
55508
+ if (!deps?.typescript && !fs27.existsSync(path37.join(projectDir, "tsconfig.json"))) {}
55509
+ const hasTSMarkers = deps?.typescript || fs27.existsSync(path37.join(projectDir, "tsconfig.json"));
54791
55510
  if (hasTSMarkers) {
54792
55511
  return { command: ["npx", "tsc", "--noEmit"], language: "typescript" };
54793
55512
  }
@@ -54795,17 +55514,17 @@ function detectTypecheckCommand(projectDir) {
54795
55514
  return null;
54796
55515
  }
54797
55516
  }
54798
- if (fs26.existsSync(path36.join(projectDir, "go.mod"))) {
55517
+ if (fs27.existsSync(path37.join(projectDir, "go.mod"))) {
54799
55518
  return { command: ["go", "vet", "./..."], language: "go" };
54800
55519
  }
54801
- if (fs26.existsSync(path36.join(projectDir, "Cargo.toml"))) {
55520
+ if (fs27.existsSync(path37.join(projectDir, "Cargo.toml"))) {
54802
55521
  return { command: ["cargo", "check"], language: "rust" };
54803
55522
  }
54804
- if (fs26.existsSync(path36.join(projectDir, "pyproject.toml")) || fs26.existsSync(path36.join(projectDir, "requirements.txt")) || fs26.existsSync(path36.join(projectDir, "setup.py"))) {
55523
+ if (fs27.existsSync(path37.join(projectDir, "pyproject.toml")) || fs27.existsSync(path37.join(projectDir, "requirements.txt")) || fs27.existsSync(path37.join(projectDir, "setup.py"))) {
54805
55524
  return { command: null, language: "python" };
54806
55525
  }
54807
55526
  try {
54808
- const entries = fs26.readdirSync(projectDir);
55527
+ const entries = fs27.readdirSync(projectDir);
54809
55528
  if (entries.some((f) => f.endsWith(".csproj") || f.endsWith(".sln"))) {
54810
55529
  return {
54811
55530
  command: ["dotnet", "build", "--no-restore"],
@@ -54874,9 +55593,10 @@ ${errorSummary}`);
54874
55593
  }
54875
55594
 
54876
55595
  // src/hooks/knowledge-reader.ts
54877
- import { existsSync as existsSync22 } from "fs";
54878
- import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
54879
- import * as path37 from "path";
55596
+ init_knowledge_store();
55597
+ import { existsSync as existsSync23 } from "fs";
55598
+ import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
55599
+ import * as path38 from "path";
54880
55600
  var JACCARD_THRESHOLD = 0.6;
54881
55601
  var HIVE_TIER_BOOST = 0.05;
54882
55602
  var SAME_PROJECT_PENALTY = -0.05;
@@ -54924,16 +55644,16 @@ function inferCategoriesFromPhase(phaseDescription) {
54924
55644
  return ["process", "tooling"];
54925
55645
  }
54926
55646
  async function recordLessonsShown(directory, lessonIds, currentPhase) {
54927
- const shownFile = path37.join(directory, ".swarm", ".knowledge-shown.json");
55647
+ const shownFile = path38.join(directory, ".swarm", ".knowledge-shown.json");
54928
55648
  try {
54929
55649
  let shownData = {};
54930
- if (existsSync22(shownFile)) {
54931
- const content = await readFile5(shownFile, "utf-8");
55650
+ if (existsSync23(shownFile)) {
55651
+ const content = await readFile6(shownFile, "utf-8");
54932
55652
  shownData = JSON.parse(content);
54933
55653
  }
54934
55654
  shownData[currentPhase] = lessonIds;
54935
- await mkdir5(path37.dirname(shownFile), { recursive: true });
54936
- await writeFile4(shownFile, JSON.stringify(shownData, null, 2), "utf-8");
55655
+ await mkdir6(path38.dirname(shownFile), { recursive: true });
55656
+ await writeFile5(shownFile, JSON.stringify(shownData, null, 2), "utf-8");
54937
55657
  } catch {
54938
55658
  console.warn("[swarm] Knowledge: failed to record shown lessons");
54939
55659
  }
@@ -55029,12 +55749,12 @@ async function readMergedKnowledge(directory, config3, context) {
55029
55749
  return topN;
55030
55750
  }
55031
55751
  async function updateRetrievalOutcome(directory, phaseInfo, phaseSucceeded) {
55032
- const shownFile = path37.join(directory, ".swarm", ".knowledge-shown.json");
55752
+ const shownFile = path38.join(directory, ".swarm", ".knowledge-shown.json");
55033
55753
  try {
55034
- if (!existsSync22(shownFile)) {
55754
+ if (!existsSync23(shownFile)) {
55035
55755
  return;
55036
55756
  }
55037
- const content = await readFile5(shownFile, "utf-8");
55757
+ const content = await readFile6(shownFile, "utf-8");
55038
55758
  const shownData = JSON.parse(content);
55039
55759
  const shownIds = shownData[phaseInfo];
55040
55760
  if (!shownIds || shownIds.length === 0) {
@@ -55062,7 +55782,7 @@ async function updateRetrievalOutcome(directory, phaseInfo, phaseSucceeded) {
55062
55782
  const remainingIds = shownIds.filter((id) => !foundInSwarm.has(id));
55063
55783
  if (remainingIds.length === 0) {
55064
55784
  delete shownData[phaseInfo];
55065
- await writeFile4(shownFile, JSON.stringify(shownData, null, 2), "utf-8");
55785
+ await writeFile5(shownFile, JSON.stringify(shownData, null, 2), "utf-8");
55066
55786
  return;
55067
55787
  }
55068
55788
  const hivePath = resolveHiveKnowledgePath();
@@ -55083,13 +55803,14 @@ async function updateRetrievalOutcome(directory, phaseInfo, phaseSucceeded) {
55083
55803
  await rewriteKnowledge(hivePath, hiveEntries);
55084
55804
  }
55085
55805
  delete shownData[phaseInfo];
55086
- await writeFile4(shownFile, JSON.stringify(shownData, null, 2), "utf-8");
55806
+ await writeFile5(shownFile, JSON.stringify(shownData, null, 2), "utf-8");
55087
55807
  } catch {
55088
55808
  console.warn("[swarm] Knowledge: failed to update retrieval outcomes");
55089
55809
  }
55090
55810
  }
55091
55811
 
55092
55812
  // src/hooks/knowledge-curator.ts
55813
+ init_knowledge_store();
55093
55814
  init_utils2();
55094
55815
  var seenRetroSections = new Map;
55095
55816
  function pruneSeenRetroSections() {
@@ -55416,20 +56137,6 @@ init_manager2();
55416
56137
 
55417
56138
  // src/services/run-memory.ts
55418
56139
  init_utils2();
55419
- function validateDirectory2(directory) {
55420
- if (!directory || directory.trim() === "") {
55421
- throw new Error("Invalid directory: empty");
55422
- }
55423
- if (/\.\.[/\\]/.test(directory)) {
55424
- throw new Error("Invalid directory: path traversal detected");
55425
- }
55426
- if (directory.startsWith("/") || directory.startsWith("\\")) {
55427
- throw new Error("Invalid directory: absolute path");
55428
- }
55429
- if (/^[A-Za-z]:[\\/]/.test(directory)) {
55430
- throw new Error("Invalid directory: Windows absolute path");
55431
- }
55432
- }
55433
56140
  var RUN_MEMORY_FILENAME = "run-memory.jsonl";
55434
56141
  var MAX_SUMMARY_TOKENS = 500;
55435
56142
  function groupByTaskId(entries) {
@@ -55461,7 +56168,7 @@ function summarizeTask(taskId, entries) {
55461
56168
  }
55462
56169
  }
55463
56170
  async function getRunMemorySummary(directory) {
55464
- validateDirectory2(directory);
56171
+ validateDirectory(directory);
55465
56172
  const content = await readSwarmFileAsync(directory, RUN_MEMORY_FILENAME);
55466
56173
  if (!content) {
55467
56174
  return null;
@@ -55509,7 +56216,7 @@ Use this data to avoid repeating known failure patterns.`;
55509
56216
  const availableContentTokens = MAX_SUMMARY_TOKENS - prefixTokens - suffixTokens;
55510
56217
  if (availableContentTokens > 0) {
55511
56218
  const maxContentChars = Math.floor(availableContentTokens / 0.33);
55512
- summaryText = summaryText.slice(-maxContentChars);
56219
+ summaryText = summaryText.slice(0, maxContentChars);
55513
56220
  } else {
55514
56221
  summaryText = "";
55515
56222
  }
@@ -55519,6 +56226,7 @@ Use this data to avoid repeating known failure patterns.`;
55519
56226
 
55520
56227
  // src/hooks/knowledge-injector.ts
55521
56228
  init_curator_drift();
56229
+ init_knowledge_store();
55522
56230
  init_utils2();
55523
56231
  function formatStars(confidence) {
55524
56232
  if (confidence >= 0.9)
@@ -55664,7 +56372,7 @@ ${injectionText}`;
55664
56372
  // src/hooks/scope-guard.ts
55665
56373
  init_constants();
55666
56374
  init_schema();
55667
- import * as path39 from "path";
56375
+ import * as path40 from "path";
55668
56376
  var WRITE_TOOLS = new Set([
55669
56377
  "write",
55670
56378
  "edit",
@@ -55726,13 +56434,13 @@ function createScopeGuardHook(config3, directory, injectAdvisory) {
55726
56434
  }
55727
56435
  function isFileInScope(filePath, scopeEntries, directory) {
55728
56436
  const dir = directory ?? process.cwd();
55729
- const resolvedFile = path39.resolve(dir, filePath);
56437
+ const resolvedFile = path40.resolve(dir, filePath);
55730
56438
  return scopeEntries.some((scope) => {
55731
- const resolvedScope = path39.resolve(dir, scope);
56439
+ const resolvedScope = path40.resolve(dir, scope);
55732
56440
  if (resolvedFile === resolvedScope)
55733
56441
  return true;
55734
- const rel = path39.relative(resolvedScope, resolvedFile);
55735
- return rel.length > 0 && !rel.startsWith("..") && !path39.isAbsolute(rel);
56442
+ const rel = path40.relative(resolvedScope, resolvedFile);
56443
+ return rel.length > 0 && !rel.startsWith("..") && !path40.isAbsolute(rel);
55736
56444
  });
55737
56445
  }
55738
56446
 
@@ -55781,8 +56489,8 @@ function createSelfReviewHook(config3, injectAdvisory) {
55781
56489
  }
55782
56490
 
55783
56491
  // src/hooks/slop-detector.ts
55784
- import * as fs28 from "fs";
55785
- import * as path40 from "path";
56492
+ import * as fs29 from "fs";
56493
+ import * as path41 from "path";
55786
56494
  var WRITE_EDIT_TOOLS = new Set([
55787
56495
  "write",
55788
56496
  "edit",
@@ -55827,12 +56535,12 @@ function checkBoilerplateExplosion(content, taskDescription, threshold) {
55827
56535
  function walkFiles(dir, exts, deadline) {
55828
56536
  const results = [];
55829
56537
  try {
55830
- for (const entry of fs28.readdirSync(dir, { withFileTypes: true })) {
56538
+ for (const entry of fs29.readdirSync(dir, { withFileTypes: true })) {
55831
56539
  if (deadline !== undefined && Date.now() > deadline)
55832
56540
  break;
55833
56541
  if (entry.isSymbolicLink())
55834
56542
  continue;
55835
- const full = path40.join(dir, entry.name);
56543
+ const full = path41.join(dir, entry.name);
55836
56544
  if (entry.isDirectory()) {
55837
56545
  if (entry.name === "node_modules" || entry.name === ".git")
55838
56546
  continue;
@@ -55847,7 +56555,7 @@ function walkFiles(dir, exts, deadline) {
55847
56555
  return results;
55848
56556
  }
55849
56557
  function checkDeadExports(content, projectDir, startTime) {
55850
- const hasPackageJson = fs28.existsSync(path40.join(projectDir, "package.json"));
56558
+ const hasPackageJson = fs29.existsSync(path41.join(projectDir, "package.json"));
55851
56559
  if (!hasPackageJson)
55852
56560
  return null;
55853
56561
  const exportMatches = content.matchAll(/^\+(?:export)\s+(?:function|class|const|type|interface)\s+(\w{3,})/gm);
@@ -55870,7 +56578,7 @@ function checkDeadExports(content, projectDir, startTime) {
55870
56578
  if (found || Date.now() - startTime > 480)
55871
56579
  break;
55872
56580
  try {
55873
- const text = fs28.readFileSync(file3, "utf-8");
56581
+ const text = fs29.readFileSync(file3, "utf-8");
55874
56582
  if (importPattern.test(text))
55875
56583
  found = true;
55876
56584
  importPattern.lastIndex = 0;
@@ -56003,7 +56711,7 @@ Review before proceeding.`;
56003
56711
 
56004
56712
  // src/hooks/steering-consumed.ts
56005
56713
  init_utils2();
56006
- import * as fs29 from "fs";
56714
+ import * as fs30 from "fs";
56007
56715
  function recordSteeringConsumed(directory, directiveId) {
56008
56716
  try {
56009
56717
  const eventsPath = validateSwarmPath(directory, "events.jsonl");
@@ -56012,7 +56720,7 @@ function recordSteeringConsumed(directory, directiveId) {
56012
56720
  directiveId,
56013
56721
  timestamp: new Date().toISOString()
56014
56722
  };
56015
- fs29.appendFileSync(eventsPath, `${JSON.stringify(event)}
56723
+ fs30.appendFileSync(eventsPath, `${JSON.stringify(event)}
56016
56724
  `, "utf-8");
56017
56725
  } catch {}
56018
56726
  }
@@ -56057,6 +56765,19 @@ init_config_doctor();
56057
56765
 
56058
56766
  // src/session/snapshot-reader.ts
56059
56767
  init_utils2();
56768
+ var TRANSIENT_SESSION_FIELDS = [
56769
+ { name: "revisionLimitHit", resetValue: false },
56770
+ { name: "coderRevisions", resetValue: 0 },
56771
+ { name: "selfFixAttempted", resetValue: false },
56772
+ { name: "lastGateFailure", resetValue: null },
56773
+ { name: "architectWriteCount", resetValue: 0 },
56774
+ { name: "selfCodingWarnedAtCount", resetValue: 0 },
56775
+ { name: "pendingAdvisoryMessages", resetValue: [] },
56776
+ { name: "model_fallback_index", resetValue: 0 },
56777
+ { name: "modelFallbackExhausted", resetValue: false },
56778
+ { name: "scopeViolationDetected", resetValue: false },
56779
+ { name: "delegationActive", resetValue: false }
56780
+ ];
56060
56781
  var VALID_TASK_WORKFLOW_STATES = [
56061
56782
  "idle",
56062
56783
  "coder_delegated",
@@ -56203,17 +56924,9 @@ async function rehydrateState(snapshot) {
56203
56924
  window2.warningReason = "";
56204
56925
  }
56205
56926
  }
56206
- session.revisionLimitHit = false;
56207
- session.coderRevisions = 0;
56208
- session.selfFixAttempted = false;
56209
- session.lastGateFailure = null;
56210
- session.architectWriteCount = 0;
56211
- session.selfCodingWarnedAtCount = 0;
56212
- session.pendingAdvisoryMessages = [];
56213
- session.model_fallback_index = 0;
56214
- session.modelFallbackExhausted = false;
56215
- session.scopeViolationDetected = false;
56216
- session.delegationActive = false;
56927
+ for (const field of TRANSIENT_SESSION_FIELDS) {
56928
+ session[field.name] = field.resetValue;
56929
+ }
56217
56930
  swarmState.agentSessions.set(sessionId, session);
56218
56931
  }
56219
56932
  }
@@ -56409,8 +57122,8 @@ var build_check = createSwarmTool({
56409
57122
  init_dist();
56410
57123
  init_manager();
56411
57124
  init_create_tool();
56412
- import * as fs30 from "fs";
56413
- import * as path41 from "path";
57125
+ import * as fs31 from "fs";
57126
+ import * as path42 from "path";
56414
57127
  var EVIDENCE_DIR = ".swarm/evidence";
56415
57128
  var TASK_ID_PATTERN2 = /^\d+\.\d+(\.\d+)*$/;
56416
57129
  function isValidTaskId3(taskId) {
@@ -56427,18 +57140,18 @@ function isValidTaskId3(taskId) {
56427
57140
  return TASK_ID_PATTERN2.test(taskId);
56428
57141
  }
56429
57142
  function isPathWithinSwarm(filePath, workspaceRoot) {
56430
- const normalizedWorkspace = path41.resolve(workspaceRoot);
56431
- const swarmPath = path41.join(normalizedWorkspace, ".swarm", "evidence");
56432
- const normalizedPath = path41.resolve(filePath);
57143
+ const normalizedWorkspace = path42.resolve(workspaceRoot);
57144
+ const swarmPath = path42.join(normalizedWorkspace, ".swarm", "evidence");
57145
+ const normalizedPath = path42.resolve(filePath);
56433
57146
  return normalizedPath.startsWith(swarmPath);
56434
57147
  }
56435
57148
  function readEvidenceFile(evidencePath) {
56436
- if (!fs30.existsSync(evidencePath)) {
57149
+ if (!fs31.existsSync(evidencePath)) {
56437
57150
  return null;
56438
57151
  }
56439
57152
  let content;
56440
57153
  try {
56441
- content = fs30.readFileSync(evidencePath, "utf-8");
57154
+ content = fs31.readFileSync(evidencePath, "utf-8");
56442
57155
  } catch {
56443
57156
  return null;
56444
57157
  }
@@ -56492,7 +57205,7 @@ var check_gate_status = createSwarmTool({
56492
57205
  };
56493
57206
  return JSON.stringify(errorResult, null, 2);
56494
57207
  }
56495
- const evidencePath = path41.join(directory, EVIDENCE_DIR, `${taskIdInput}.json`);
57208
+ const evidencePath = path42.join(directory, EVIDENCE_DIR, `${taskIdInput}.json`);
56496
57209
  if (!isPathWithinSwarm(evidencePath, directory)) {
56497
57210
  const errorResult = {
56498
57211
  taskId: taskIdInput,
@@ -56584,8 +57297,8 @@ init_checkpoint();
56584
57297
  // src/tools/completion-verify.ts
56585
57298
  init_dist();
56586
57299
  init_utils2();
56587
- import * as fs31 from "fs";
56588
- import * as path42 from "path";
57300
+ import * as fs32 from "fs";
57301
+ import * as path43 from "path";
56589
57302
  init_create_tool();
56590
57303
  function extractMatches(regex, text) {
56591
57304
  return Array.from(text.matchAll(regex));
@@ -56667,7 +57380,7 @@ async function executeCompletionVerify(args2, directory) {
56667
57380
  let plan;
56668
57381
  try {
56669
57382
  const planPath = validateSwarmPath(directory, "plan.json");
56670
- const planRaw = fs31.readFileSync(planPath, "utf-8");
57383
+ const planRaw = fs32.readFileSync(planPath, "utf-8");
56671
57384
  plan = JSON.parse(planRaw);
56672
57385
  } catch {
56673
57386
  const result2 = {
@@ -56697,7 +57410,7 @@ async function executeCompletionVerify(args2, directory) {
56697
57410
  return JSON.stringify(result2, null, 2);
56698
57411
  }
56699
57412
  let tasksChecked = 0;
56700
- let tasksSkipped = 0;
57413
+ const tasksSkipped = 0;
56701
57414
  let tasksBlocked = 0;
56702
57415
  const blockedTasks = [];
56703
57416
  for (const task of targetPhase.tasks) {
@@ -56708,20 +57421,33 @@ async function executeCompletionVerify(args2, directory) {
56708
57421
  const fileTargets = parseFilePaths(task.description, task.files_touched);
56709
57422
  const identifiers = parseIdentifiers(task.description);
56710
57423
  if (fileTargets.length === 0) {
56711
- tasksSkipped++;
57424
+ blockedTasks.push({
57425
+ task_id: task.id,
57426
+ identifier: "",
57427
+ file_path: "",
57428
+ reason: "No file targets \u2014 cannot verify completion without files_touched"
57429
+ });
57430
+ tasksBlocked++;
56712
57431
  continue;
56713
57432
  }
56714
57433
  if (identifiers.length === 0) {
56715
- tasksSkipped++;
57434
+ blockedTasks.push({
57435
+ task_id: task.id,
57436
+ identifier: "",
57437
+ file_path: "",
57438
+ reason: "No identifiers \u2014 cannot verify completion without identifiers"
57439
+ });
57440
+ tasksBlocked++;
56716
57441
  continue;
56717
57442
  }
56718
- let foundCount = 0;
57443
+ const foundIdentifiers = new Set;
56719
57444
  let hasFileReadFailure = false;
56720
57445
  for (const filePath of fileTargets) {
56721
- const resolvedPath = path42.resolve(directory, filePath);
57446
+ const normalizedPath = filePath.replace(/\\/g, "/");
57447
+ const resolvedPath = path43.resolve(directory, normalizedPath);
56722
57448
  let fileContent;
56723
57449
  try {
56724
- fileContent = fs31.readFileSync(resolvedPath, "utf-8");
57450
+ fileContent = fs32.readFileSync(resolvedPath, "utf-8");
56725
57451
  } catch {
56726
57452
  blockedTasks.push({
56727
57453
  task_id: task.id,
@@ -56734,11 +57460,11 @@ async function executeCompletionVerify(args2, directory) {
56734
57460
  }
56735
57461
  for (const identifier of identifiers) {
56736
57462
  if (fileContent.includes(identifier)) {
56737
- foundCount++;
56738
- break;
57463
+ foundIdentifiers.add(identifier);
56739
57464
  }
56740
57465
  }
56741
57466
  }
57467
+ const foundCount = foundIdentifiers.size;
56742
57468
  if (hasFileReadFailure || foundCount === 0) {
56743
57469
  if (!hasFileReadFailure) {
56744
57470
  blockedTasks.push({
@@ -56763,9 +57489,9 @@ async function executeCompletionVerify(args2, directory) {
56763
57489
  blockedTasks
56764
57490
  };
56765
57491
  try {
56766
- const evidenceDir = path42.join(directory, ".swarm", "evidence", `${phase}`);
56767
- const evidencePath = path42.join(evidenceDir, "completion-verify.json");
56768
- fs31.mkdirSync(evidenceDir, { recursive: true });
57492
+ const evidenceDir = path43.join(directory, ".swarm", "evidence", `${phase}`);
57493
+ const evidencePath = path43.join(evidenceDir, "completion-verify.json");
57494
+ fs32.mkdirSync(evidenceDir, { recursive: true });
56769
57495
  const evidenceBundle = {
56770
57496
  schema_version: "1.0.0",
56771
57497
  task_id: "completion-verify",
@@ -56786,7 +57512,7 @@ async function executeCompletionVerify(args2, directory) {
56786
57512
  }
56787
57513
  ]
56788
57514
  };
56789
- fs31.writeFileSync(evidencePath, JSON.stringify(evidenceBundle, null, 2), "utf-8");
57515
+ fs32.writeFileSync(evidencePath, JSON.stringify(evidenceBundle, null, 2), "utf-8");
56790
57516
  } catch {}
56791
57517
  return JSON.stringify(result, null, 2);
56792
57518
  }
@@ -56826,16 +57552,13 @@ var completion_verify = createSwarmTool({
56826
57552
  // src/tools/complexity-hotspots.ts
56827
57553
  init_dist();
56828
57554
  init_create_tool();
56829
- import * as fs32 from "fs";
56830
- import * as path43 from "path";
57555
+ import * as fs33 from "fs";
57556
+ import * as path44 from "path";
56831
57557
  var MAX_FILE_SIZE_BYTES2 = 256 * 1024;
56832
57558
  var DEFAULT_DAYS = 90;
56833
57559
  var DEFAULT_TOP_N = 20;
56834
57560
  var DEFAULT_EXTENSIONS = "ts,tsx,js,jsx,py,rs,ps1";
56835
57561
  var SHELL_METACHAR_REGEX = /[;&|%$`\\]/;
56836
- function containsControlChars3(str) {
56837
- return /[\0\t\r\n]/.test(str);
56838
- }
56839
57562
  function validateDays(days) {
56840
57563
  if (typeof days === "undefined") {
56841
57564
  return { valid: true, value: DEFAULT_DAYS, error: null };
@@ -56867,7 +57590,7 @@ function validateExtensions(extensions) {
56867
57590
  if (typeof extensions !== "string") {
56868
57591
  return { valid: false, value: "", error: "extensions must be a string" };
56869
57592
  }
56870
- if (containsControlChars3(extensions)) {
57593
+ if (containsControlChars(extensions)) {
56871
57594
  return {
56872
57595
  valid: false,
56873
57596
  value: "",
@@ -56956,11 +57679,11 @@ function estimateComplexity(content) {
56956
57679
  }
56957
57680
  function getComplexityForFile(filePath) {
56958
57681
  try {
56959
- const stat2 = fs32.statSync(filePath);
57682
+ const stat2 = fs33.statSync(filePath);
56960
57683
  if (stat2.size > MAX_FILE_SIZE_BYTES2) {
56961
57684
  return null;
56962
57685
  }
56963
- const content = fs32.readFileSync(filePath, "utf-8");
57686
+ const content = fs33.readFileSync(filePath, "utf-8");
56964
57687
  return estimateComplexity(content);
56965
57688
  } catch {
56966
57689
  return null;
@@ -56971,7 +57694,7 @@ async function analyzeHotspots(days, topN, extensions, directory) {
56971
57694
  const extSet = new Set(extensions.map((e) => e.startsWith(".") ? e : `.${e}`));
56972
57695
  const filteredChurn = new Map;
56973
57696
  for (const [file3, count] of churnMap) {
56974
- const ext = path43.extname(file3).toLowerCase();
57697
+ const ext = path44.extname(file3).toLowerCase();
56975
57698
  if (extSet.has(ext)) {
56976
57699
  filteredChurn.set(file3, count);
56977
57700
  }
@@ -56981,8 +57704,8 @@ async function analyzeHotspots(days, topN, extensions, directory) {
56981
57704
  let analyzedFiles = 0;
56982
57705
  for (const [file3, churnCount] of filteredChurn) {
56983
57706
  let fullPath = file3;
56984
- if (!fs32.existsSync(fullPath)) {
56985
- fullPath = path43.join(cwd, file3);
57707
+ if (!fs33.existsSync(fullPath)) {
57708
+ fullPath = path44.join(cwd, file3);
56986
57709
  }
56987
57710
  const complexity = getComplexityForFile(fullPath);
56988
57711
  if (complexity !== null) {
@@ -57190,8 +57913,8 @@ var curator_analyze = createSwarmTool({
57190
57913
  });
57191
57914
  // src/tools/declare-scope.ts
57192
57915
  init_tool();
57193
- import * as fs33 from "fs";
57194
- import * as path44 from "path";
57916
+ import * as fs34 from "fs";
57917
+ import * as path45 from "path";
57195
57918
  init_create_tool();
57196
57919
  function validateTaskIdFormat(taskId) {
57197
57920
  const taskIdPattern = /^\d+\.\d+(\.\d+)*$/;
@@ -57270,8 +57993,8 @@ async function executeDeclareScope(args2, fallbackDir) {
57270
57993
  };
57271
57994
  }
57272
57995
  }
57273
- normalizedDir = path44.normalize(args2.working_directory);
57274
- const pathParts = normalizedDir.split(path44.sep);
57996
+ normalizedDir = path45.normalize(args2.working_directory);
57997
+ const pathParts = normalizedDir.split(path45.sep);
57275
57998
  if (pathParts.includes("..")) {
57276
57999
  return {
57277
58000
  success: false,
@@ -57281,11 +58004,11 @@ async function executeDeclareScope(args2, fallbackDir) {
57281
58004
  ]
57282
58005
  };
57283
58006
  }
57284
- const resolvedDir = path44.resolve(normalizedDir);
58007
+ const resolvedDir = path45.resolve(normalizedDir);
57285
58008
  try {
57286
- const realPath = fs33.realpathSync(resolvedDir);
57287
- const planPath2 = path44.join(realPath, ".swarm", "plan.json");
57288
- if (!fs33.existsSync(planPath2)) {
58009
+ const realPath = fs34.realpathSync(resolvedDir);
58010
+ const planPath2 = path45.join(realPath, ".swarm", "plan.json");
58011
+ if (!fs34.existsSync(planPath2)) {
57289
58012
  return {
57290
58013
  success: false,
57291
58014
  message: `Invalid working_directory: plan not found in "${realPath}"`,
@@ -57308,8 +58031,8 @@ async function executeDeclareScope(args2, fallbackDir) {
57308
58031
  console.warn("[declare-scope] fallbackDir is undefined, falling back to process.cwd()");
57309
58032
  }
57310
58033
  const directory = normalizedDir || fallbackDir;
57311
- const planPath = path44.resolve(directory, ".swarm", "plan.json");
57312
- if (!fs33.existsSync(planPath)) {
58034
+ const planPath = path45.resolve(directory, ".swarm", "plan.json");
58035
+ if (!fs34.existsSync(planPath)) {
57313
58036
  return {
57314
58037
  success: false,
57315
58038
  message: "No plan found",
@@ -57318,7 +58041,7 @@ async function executeDeclareScope(args2, fallbackDir) {
57318
58041
  }
57319
58042
  let planContent;
57320
58043
  try {
57321
- planContent = JSON.parse(fs33.readFileSync(planPath, "utf-8"));
58044
+ planContent = JSON.parse(fs34.readFileSync(planPath, "utf-8"));
57322
58045
  } catch {
57323
58046
  return {
57324
58047
  success: false,
@@ -57404,6 +58127,11 @@ var languageDefinitions = [
57404
58127
  id: "rust",
57405
58128
  extensions: [".rs"],
57406
58129
  commentNodes: ["line_comment", "block_comment"]
58130
+ },
58131
+ {
58132
+ id: "php",
58133
+ extensions: [".php", ".phtml"],
58134
+ commentNodes: ["comment"]
57407
58135
  }
57408
58136
  ];
57409
58137
  var extensionMap = new Map;
@@ -57467,10 +58195,16 @@ async function computeASTDiff(filePath, oldContent, newContent) {
57467
58195
  };
57468
58196
  }
57469
58197
  try {
58198
+ let timeoutId;
57470
58199
  const parser = await Promise.race([
57471
58200
  loadGrammar(language.id),
57472
- new Promise((_, reject) => setTimeout(() => reject(new Error("AST_TIMEOUT")), AST_TIMEOUT_MS))
57473
- ]);
58201
+ new Promise((_, reject) => {
58202
+ timeoutId = setTimeout(() => reject(new Error("AST_TIMEOUT")), AST_TIMEOUT_MS);
58203
+ })
58204
+ ]).finally(() => {
58205
+ if (timeoutId)
58206
+ clearTimeout(timeoutId);
58207
+ });
57474
58208
  const oldTree = parser.parse(oldContent);
57475
58209
  const newTree = parser.parse(newContent);
57476
58210
  if (!oldTree || !newTree) {
@@ -57643,20 +58377,20 @@ function validateBase(base) {
57643
58377
  function validatePaths(paths) {
57644
58378
  if (!paths)
57645
58379
  return null;
57646
- for (const path45 of paths) {
57647
- if (!path45 || path45.length === 0) {
58380
+ for (const path46 of paths) {
58381
+ if (!path46 || path46.length === 0) {
57648
58382
  return "empty path not allowed";
57649
58383
  }
57650
- if (path45.length > MAX_PATH_LENGTH) {
58384
+ if (path46.length > MAX_PATH_LENGTH) {
57651
58385
  return `path exceeds maximum length of ${MAX_PATH_LENGTH}`;
57652
58386
  }
57653
- if (SHELL_METACHARACTERS2.test(path45)) {
58387
+ if (SHELL_METACHARACTERS2.test(path46)) {
57654
58388
  return "path contains shell metacharacters";
57655
58389
  }
57656
- if (path45.startsWith("-")) {
58390
+ if (path46.startsWith("-")) {
57657
58391
  return 'path cannot start with "-" (option-like arguments not allowed)';
57658
58392
  }
57659
- if (CONTROL_CHAR_PATTERN2.test(path45)) {
58393
+ if (CONTROL_CHAR_PATTERN2.test(path46)) {
57660
58394
  return "path contains control characters";
57661
58395
  }
57662
58396
  }
@@ -57737,8 +58471,8 @@ var diff = createSwarmTool({
57737
58471
  if (parts2.length >= 3) {
57738
58472
  const additions = parseInt(parts2[0], 10) || 0;
57739
58473
  const deletions = parseInt(parts2[1], 10) || 0;
57740
- const path45 = parts2[2];
57741
- files.push({ path: path45, additions, deletions });
58474
+ const path46 = parts2[2];
58475
+ files.push({ path: path46, additions, deletions });
57742
58476
  }
57743
58477
  }
57744
58478
  const contractChanges = [];
@@ -57833,391 +58567,10 @@ var diff = createSwarmTool({
57833
58567
  }
57834
58568
  }
57835
58569
  });
57836
- // src/tools/doc-scan.ts
57837
- init_dist();
57838
- init_schema();
57839
- import * as crypto4 from "crypto";
57840
- import * as fs34 from "fs";
57841
- import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
57842
- import * as path45 from "path";
57843
- init_create_tool();
57844
- var SKIP_DIRECTORIES2 = new Set([
57845
- "node_modules",
57846
- ".git",
57847
- ".swarm",
57848
- "dist",
57849
- "build",
57850
- ".next",
57851
- "vendor"
57852
- ]);
57853
- var SKIP_PATTERNS = [/\.test\./, /\.spec\./, /\.d\.ts$/];
57854
- var MAX_SUMMARY_LENGTH = 200;
57855
- var MAX_INDEXED_FILES = 100;
57856
- var READ_LINES_LIMIT = 30;
57857
- var MIN_LESSON_LENGTH = 15;
57858
- var MAX_CONSTRAINTS_PER_DOC = 5;
57859
- var MAX_CONSTRAINT_LENGTH = 200;
57860
- var RELEVANCE_THRESHOLD = 0.1;
57861
- var DEDUP_THRESHOLD = 0.6;
57862
- function normalizeSeparators(filePath) {
57863
- return filePath.replace(/\\/g, "/");
57864
- }
57865
- function matchesDocPattern(filePath, patterns) {
57866
- const normalizedPath = normalizeSeparators(filePath);
57867
- const basename5 = path45.basename(filePath);
57868
- for (const pattern of patterns) {
57869
- if (!pattern.includes("/") && !pattern.includes("\\")) {
57870
- if (basename5 === pattern) {
57871
- return true;
57872
- }
57873
- continue;
57874
- }
57875
- if (pattern.startsWith("**/")) {
57876
- const filenamePattern = pattern.slice(3);
57877
- if (basename5 === filenamePattern) {
57878
- return true;
57879
- }
57880
- continue;
57881
- }
57882
- const patternNormalized = normalizeSeparators(pattern);
57883
- const dirPrefix = patternNormalized.replace(/\/\*\*.*$/, "").replace(/\/\*.*$/, "");
57884
- if (normalizedPath.startsWith(`${dirPrefix}/`) || normalizedPath === dirPrefix) {
57885
- return true;
57886
- }
57887
- }
57888
- return false;
57889
- }
57890
- function extractTitleAndSummary(content, filename) {
57891
- const lines = content.split(`
57892
- `);
57893
- let title = filename;
57894
- let summary = "";
57895
- let foundTitle = false;
57896
- const potentialSummaryLines = [];
57897
- for (let i2 = 0;i2 < lines.length && i2 < READ_LINES_LIMIT; i2++) {
57898
- const line = lines[i2].trim();
57899
- if (!foundTitle && line.startsWith("# ")) {
57900
- title = line.slice(2).trim();
57901
- foundTitle = true;
57902
- continue;
57903
- }
57904
- if (line && !line.startsWith("#")) {
57905
- potentialSummaryLines.push(line);
57906
- }
57907
- }
57908
- for (const line of potentialSummaryLines) {
57909
- summary += (summary ? " " : "") + line;
57910
- if (summary.length >= MAX_SUMMARY_LENGTH) {
57911
- break;
57912
- }
57913
- }
57914
- if (summary.length > MAX_SUMMARY_LENGTH) {
57915
- summary = `${summary.slice(0, MAX_SUMMARY_LENGTH - 3)}...`;
57916
- }
57917
- return { title, summary };
57918
- }
57919
- function stripMarkdown(text) {
57920
- return text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/^\s*[-*\u2022]\s+/gm, "").replace(/^\s*\d+\.\s+/gm, "").trim();
57921
- }
57922
- async function scanDocIndex(directory) {
57923
- const manifestPath = path45.join(directory, ".swarm", "doc-manifest.json");
57924
- const defaultPatterns = DocsConfigSchema.parse({}).doc_patterns;
57925
- const extraPatterns = [
57926
- "ARCHITECTURE.md",
57927
- "CLAUDE.md",
57928
- "AGENTS.md",
57929
- ".github/*.md",
57930
- "doc/**/*.md"
57931
- ];
57932
- const allPatterns = [...defaultPatterns, ...extraPatterns];
57933
- try {
57934
- const manifestContent = await readFile6(manifestPath, "utf-8");
57935
- const existingManifest = JSON.parse(manifestContent);
57936
- if (existingManifest.schema_version === 1 && existingManifest.files) {
57937
- let cacheValid = true;
57938
- for (const file3 of existingManifest.files) {
57939
- try {
57940
- const fullPath = path45.join(directory, file3.path);
57941
- const stat2 = fs34.statSync(fullPath);
57942
- if (stat2.mtimeMs > new Date(existingManifest.scanned_at).getTime()) {
57943
- cacheValid = false;
57944
- break;
57945
- }
57946
- } catch {
57947
- cacheValid = false;
57948
- break;
57949
- }
57950
- }
57951
- if (cacheValid) {
57952
- return { manifest: existingManifest, cached: true };
57953
- }
57954
- }
57955
- } catch {}
57956
- const discoveredFiles = [];
57957
- let rawEntries;
57958
- try {
57959
- rawEntries = fs34.readdirSync(directory, { recursive: true });
57960
- } catch {
57961
- const manifest2 = {
57962
- schema_version: 1,
57963
- scanned_at: new Date().toISOString(),
57964
- files: []
57965
- };
57966
- return { manifest: manifest2, cached: false };
57967
- }
57968
- const entries = rawEntries.filter((e) => typeof e === "string");
57969
- for (const entry of entries) {
57970
- const fullPath = path45.join(directory, entry);
57971
- let stat2;
57972
- try {
57973
- stat2 = fs34.statSync(fullPath);
57974
- } catch {
57975
- continue;
57976
- }
57977
- if (!stat2.isFile())
57978
- continue;
57979
- const pathParts = normalizeSeparators(entry).split("/");
57980
- let skipThisFile = false;
57981
- for (const part of pathParts) {
57982
- if (SKIP_DIRECTORIES2.has(part)) {
57983
- skipThisFile = true;
57984
- break;
57985
- }
57986
- }
57987
- if (skipThisFile)
57988
- continue;
57989
- for (const pattern of SKIP_PATTERNS) {
57990
- if (pattern.test(entry)) {
57991
- skipThisFile = true;
57992
- break;
57993
- }
57994
- }
57995
- if (skipThisFile)
57996
- continue;
57997
- if (!matchesDocPattern(entry, allPatterns)) {
57998
- continue;
57999
- }
58000
- let content;
58001
- try {
58002
- content = fs34.readFileSync(fullPath, "utf-8");
58003
- } catch {
58004
- continue;
58005
- }
58006
- const { title, summary } = extractTitleAndSummary(content, path45.basename(entry));
58007
- const lineCount = content.split(`
58008
- `).length;
58009
- discoveredFiles.push({
58010
- path: entry,
58011
- title,
58012
- summary,
58013
- lines: lineCount,
58014
- mtime: stat2.mtimeMs
58015
- });
58016
- }
58017
- discoveredFiles.sort((a, b) => a.path.toLowerCase().localeCompare(b.path.toLowerCase()));
58018
- let truncated = false;
58019
- if (discoveredFiles.length > MAX_INDEXED_FILES) {
58020
- discoveredFiles.splice(MAX_INDEXED_FILES);
58021
- truncated = true;
58022
- }
58023
- if (truncated && discoveredFiles.length > 0) {
58024
- discoveredFiles[0].summary = `[Warning: ${MAX_INDEXED_FILES}+ docs found, listing first ${MAX_INDEXED_FILES}] ` + discoveredFiles[0].summary;
58025
- }
58026
- const manifest = {
58027
- schema_version: 1,
58028
- scanned_at: new Date().toISOString(),
58029
- files: discoveredFiles
58030
- };
58031
- try {
58032
- await mkdir6(path45.dirname(manifestPath), { recursive: true });
58033
- await writeFile5(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
58034
- } catch {}
58035
- return { manifest, cached: false };
58036
- }
58037
- var CONSTRAINT_PATTERNS = [
58038
- /\bMUST\b/,
58039
- /\bMUST NOT\b/,
58040
- /\bSHOULD\b/,
58041
- /\bSHOULD NOT\b/,
58042
- /\bDO NOT\b/,
58043
- /\bALWAYS\b/,
58044
- /\bNEVER\b/,
58045
- /\bREQUIRED\b/
58046
- ];
58047
- var ACTION_WORDS = /\b(must|should|don't|avoid|ensure|use|follow)\b/i;
58048
- function isConstraintLine(line) {
58049
- const upperLine = line.toUpperCase();
58050
- for (const pattern of CONSTRAINT_PATTERNS) {
58051
- if (pattern.test(upperLine)) {
58052
- return true;
58053
- }
58054
- }
58055
- if (/^\s*[-*\u2022]/.test(line) && ACTION_WORDS.test(line)) {
58056
- return true;
58057
- }
58058
- return false;
58059
- }
58060
- function extractConstraintsFromContent(content) {
58061
- const lines = content.split(`
58062
- `);
58063
- const constraints = [];
58064
- for (const line of lines) {
58065
- if (constraints.length >= MAX_CONSTRAINTS_PER_DOC) {
58066
- break;
58067
- }
58068
- const trimmed = line.trim();
58069
- if (!trimmed)
58070
- continue;
58071
- if (isConstraintLine(trimmed)) {
58072
- const cleaned = stripMarkdown(trimmed);
58073
- const len = cleaned.length;
58074
- if (len >= MIN_LESSON_LENGTH && len <= MAX_CONSTRAINT_LENGTH) {
58075
- constraints.push(cleaned);
58076
- }
58077
- }
58078
- }
58079
- return constraints;
58080
- }
58081
- async function extractDocConstraints(directory, taskFiles, taskDescription) {
58082
- const manifestPath = path45.join(directory, ".swarm", "doc-manifest.json");
58083
- let manifest;
58084
- try {
58085
- const content = await readFile6(manifestPath, "utf-8");
58086
- manifest = JSON.parse(content);
58087
- } catch {
58088
- const result = await scanDocIndex(directory);
58089
- manifest = result.manifest;
58090
- }
58091
- const knowledgePath = resolveSwarmKnowledgePath(directory);
58092
- const existingEntries = await readKnowledge(knowledgePath);
58093
- const taskContext = [...taskFiles, taskDescription].join(" ");
58094
- const taskBigrams = wordBigrams(normalize2(taskContext));
58095
- let extractedCount = 0;
58096
- let skippedCount = 0;
58097
- const details = [];
58098
- for (const docFile of manifest.files) {
58099
- const docContext = `${docFile.path} ${docFile.title} ${docFile.summary}`;
58100
- const docBigrams = wordBigrams(normalize2(docContext));
58101
- const score = jaccardBigram(taskBigrams, docBigrams);
58102
- if (score <= RELEVANCE_THRESHOLD) {
58103
- skippedCount++;
58104
- continue;
58105
- }
58106
- let fullContent;
58107
- try {
58108
- fullContent = await readFile6(path45.join(directory, docFile.path), "utf-8");
58109
- } catch {
58110
- skippedCount++;
58111
- continue;
58112
- }
58113
- const constraints = extractConstraintsFromContent(fullContent);
58114
- if (constraints.length === 0) {
58115
- skippedCount++;
58116
- continue;
58117
- }
58118
- const docDetails = {
58119
- path: docFile.path,
58120
- score,
58121
- constraints: []
58122
- };
58123
- for (const constraint of constraints) {
58124
- const duplicate = findNearDuplicate(constraint, existingEntries, DEDUP_THRESHOLD);
58125
- if (!duplicate) {
58126
- const entry = {
58127
- id: crypto4.randomUUID(),
58128
- tier: "swarm",
58129
- lesson: constraint,
58130
- category: "architecture",
58131
- tags: ["doc-scan", path45.basename(docFile.path)],
58132
- scope: "global",
58133
- confidence: 0.5,
58134
- status: "candidate",
58135
- confirmed_by: [],
58136
- project_name: "",
58137
- retrieval_outcomes: {
58138
- applied_count: 0,
58139
- succeeded_after_count: 0,
58140
- failed_after_count: 0
58141
- },
58142
- schema_version: 1,
58143
- created_at: new Date().toISOString(),
58144
- updated_at: new Date().toISOString(),
58145
- auto_generated: true,
58146
- hive_eligible: false
58147
- };
58148
- await appendKnowledge(knowledgePath, entry);
58149
- existingEntries.push(entry);
58150
- extractedCount++;
58151
- docDetails.constraints.push(constraint);
58152
- }
58153
- }
58154
- if (docDetails.constraints.length > 0) {
58155
- details.push(docDetails);
58156
- } else {
58157
- skippedCount++;
58158
- }
58159
- }
58160
- return { extracted: extractedCount, skipped: skippedCount, details };
58161
- }
58162
- var doc_scan = createSwarmTool({
58163
- description: "Scan project documentation files and build an index manifest. Caches results in .swarm/doc-manifest.json for fast subsequent scans.",
58164
- args: {
58165
- force: tool.schema.boolean().optional().describe("Force re-scan even if cache is valid")
58166
- },
58167
- execute: async (args2, directory) => {
58168
- let force = false;
58169
- try {
58170
- if (args2 && typeof args2 === "object") {
58171
- const obj = args2;
58172
- if (obj.force === true)
58173
- force = true;
58174
- }
58175
- } catch {}
58176
- if (force) {
58177
- const manifestPath = path45.join(directory, ".swarm", "doc-manifest.json");
58178
- try {
58179
- fs34.unlinkSync(manifestPath);
58180
- } catch {}
58181
- }
58182
- const { manifest, cached: cached3 } = await scanDocIndex(directory);
58183
- return JSON.stringify({
58184
- success: true,
58185
- files_count: manifest.files.length,
58186
- cached: cached3,
58187
- manifest
58188
- }, null, 2);
58189
- }
58190
- });
58191
- var doc_extract = createSwarmTool({
58192
- description: "Extract actionable constraints from project documentation relevant to the current task. Scans docs via doc-manifest, scores relevance via Jaccard bigram similarity, and stores non-duplicate constraints in .swarm/knowledge.jsonl.",
58193
- args: {
58194
- task_files: tool.schema.array(tool.schema.string()).describe("List of file paths involved in the current task"),
58195
- task_description: tool.schema.string().describe("Description of the current task")
58196
- },
58197
- execute: async (args2, directory) => {
58198
- let taskFiles = [];
58199
- let taskDescription = "";
58200
- try {
58201
- if (args2 && typeof args2 === "object") {
58202
- const obj = args2;
58203
- if (Array.isArray(obj.task_files)) {
58204
- taskFiles = obj.task_files.filter((f) => typeof f === "string");
58205
- }
58206
- if (typeof obj.task_description === "string") {
58207
- taskDescription = obj.task_description;
58208
- }
58209
- }
58210
- } catch {}
58211
- if (taskFiles.length === 0 && !taskDescription) {
58212
- return JSON.stringify({
58213
- success: false,
58214
- error: "task_files or task_description is required"
58215
- });
58216
- }
58217
- const result = await extractDocConstraints(directory, taskFiles, taskDescription);
58218
- return JSON.stringify({ success: true, ...result }, null, 2);
58219
- }
58220
- });
58570
+
58571
+ // src/tools/index.ts
58572
+ init_doc_scan();
58573
+
58221
58574
  // src/tools/domain-detector.ts
58222
58575
  init_tool();
58223
58576
  var DOMAIN_PATTERNS = {
@@ -58416,11 +58769,8 @@ var LEGACY_EVIDENCE_ALIAS_MAP = {
58416
58769
  function normalizeEvidenceType(type) {
58417
58770
  return LEGACY_EVIDENCE_ALIAS_MAP[type.toLowerCase()] || type;
58418
58771
  }
58419
- function containsControlChars4(str) {
58420
- return /[\0\t\r\n]/.test(str);
58421
- }
58422
58772
  function validateRequiredTypes(input) {
58423
- if (containsControlChars4(input)) {
58773
+ if (containsControlChars(input)) {
58424
58774
  return "required_types contains control characters";
58425
58775
  }
58426
58776
  if (SHELL_METACHAR_REGEX2.test(input)) {
@@ -58859,12 +59209,6 @@ var BINARY_SIGNATURES2 = [
58859
59209
  var BINARY_PREFIX_BYTES2 = 4;
58860
59210
  var BINARY_NULL_CHECK_BYTES2 = 8192;
58861
59211
  var BINARY_NULL_THRESHOLD2 = 0.1;
58862
- function containsPathTraversal3(str) {
58863
- return /\.\.[/\\]/.test(str);
58864
- }
58865
- function containsControlChars5(str) {
58866
- return /[\0\t\r\n]/.test(str);
58867
- }
58868
59212
  function validateFileInput(file3) {
58869
59213
  if (!file3 || file3.length === 0) {
58870
59214
  return "file is required";
@@ -58872,10 +59216,10 @@ function validateFileInput(file3) {
58872
59216
  if (file3.length > MAX_FILE_PATH_LENGTH2) {
58873
59217
  return `file exceeds maximum length of ${MAX_FILE_PATH_LENGTH2}`;
58874
59218
  }
58875
- if (containsControlChars5(file3)) {
59219
+ if (containsControlChars(file3)) {
58876
59220
  return "file contains control characters";
58877
59221
  }
58878
- if (containsPathTraversal3(file3)) {
59222
+ if (containsPathTraversal(file3)) {
58879
59223
  return "file contains path traversal";
58880
59224
  }
58881
59225
  return null;
@@ -58887,10 +59231,10 @@ function validateSymbolInput(symbol3) {
58887
59231
  if (symbol3.length > MAX_SYMBOL_LENGTH) {
58888
59232
  return `symbol exceeds maximum length of ${MAX_SYMBOL_LENGTH}`;
58889
59233
  }
58890
- if (containsControlChars5(symbol3)) {
59234
+ if (containsControlChars(symbol3)) {
58891
59235
  return "symbol contains control characters";
58892
59236
  }
58893
- if (containsPathTraversal3(symbol3)) {
59237
+ if (containsPathTraversal(symbol3)) {
58894
59238
  return "symbol contains path traversal";
58895
59239
  }
58896
59240
  return null;
@@ -59047,7 +59391,7 @@ function findSourceFiles(dir, files = [], stats = { skippedDirs: [], skippedFile
59047
59391
  return files;
59048
59392
  }
59049
59393
  var imports = createSwarmTool({
59050
- description: "Find all consumers that import from a given file. Returns JSON with file path, line numbers, and import metadata for each consumer. Useful for understanding dependency relationships.",
59394
+ description: "Find all reverse dependencies (consumers) that import from a given file. Returns JSON with file path, line numbers, and import metadata for each consumer. Use this to understand who depends on a module before refactoring.",
59051
59395
  args: {
59052
59396
  file: tool.schema.string().describe('Source file path to find importers for (e.g., "./src/utils.ts")'),
59053
59397
  symbol: tool.schema.string().optional().describe('Optional specific symbol to filter imports (e.g., "MyClass")')
@@ -59202,6 +59546,9 @@ var imports = createSwarmTool({
59202
59546
  });
59203
59547
  // src/tools/knowledge-add.ts
59204
59548
  init_dist();
59549
+ init_config();
59550
+ init_knowledge_store();
59551
+ import { randomUUID as randomUUID4 } from "crypto";
59205
59552
  init_manager2();
59206
59553
  init_create_tool();
59207
59554
  var VALID_CATEGORIES2 = [
@@ -59276,7 +59623,7 @@ var knowledgeAdd = createSwarmTool({
59276
59623
  project_name = plan?.title ?? "";
59277
59624
  } catch {}
59278
59625
  const entry = {
59279
- id: crypto.randomUUID(),
59626
+ id: randomUUID4(),
59280
59627
  tier: "swarm",
59281
59628
  lesson,
59282
59629
  category,
@@ -59297,6 +59644,22 @@ var knowledgeAdd = createSwarmTool({
59297
59644
  auto_generated: true,
59298
59645
  hive_eligible: false
59299
59646
  };
59647
+ try {
59648
+ const { config: config3 } = loadPluginConfigWithMeta(directory);
59649
+ if (config3.knowledge?.validation_enabled !== false) {
59650
+ const validation = validateLesson(lesson, [], {
59651
+ category,
59652
+ scope,
59653
+ confidence: 0.5
59654
+ });
59655
+ if (!validation.valid) {
59656
+ return JSON.stringify({
59657
+ success: false,
59658
+ error: `Validation failed: ${validation.reason}`
59659
+ });
59660
+ }
59661
+ }
59662
+ } catch {}
59300
59663
  try {
59301
59664
  await appendKnowledge(resolveSwarmKnowledgePath(directory), entry);
59302
59665
  } catch (err2) {
@@ -59315,8 +59678,10 @@ var knowledgeAdd = createSwarmTool({
59315
59678
  });
59316
59679
  // src/tools/knowledge-query.ts
59317
59680
  init_dist();
59318
- import { existsSync as existsSync30 } from "fs";
59681
+ init_config();
59682
+ init_knowledge_store();
59319
59683
  init_create_tool();
59684
+ import { existsSync as existsSync31 } from "fs";
59320
59685
  var DEFAULT_LIMIT = 10;
59321
59686
  var MAX_LESSON_LENGTH = 200;
59322
59687
  var VALID_CATEGORIES3 = [
@@ -59385,19 +59750,19 @@ function validateLimit(limit) {
59385
59750
  }
59386
59751
  async function readSwarmKnowledge(directory) {
59387
59752
  const swarmPath = resolveSwarmKnowledgePath(directory);
59388
- if (!existsSync30(swarmPath)) {
59753
+ if (!existsSync31(swarmPath)) {
59389
59754
  return [];
59390
59755
  }
59391
59756
  return readKnowledge(swarmPath);
59392
59757
  }
59393
59758
  async function readHiveKnowledge() {
59394
59759
  const hivePath = resolveHiveKnowledgePath();
59395
- if (!existsSync30(hivePath)) {
59760
+ if (!existsSync31(hivePath)) {
59396
59761
  return [];
59397
59762
  }
59398
59763
  return readKnowledge(hivePath);
59399
59764
  }
59400
- function filterSwarmEntries(entries, filters) {
59765
+ function filterSwarmEntries(entries, filters, scopeFilter) {
59401
59766
  return entries.filter((entry) => {
59402
59767
  if (filters.status && entry.status !== filters.status) {
59403
59768
  return false;
@@ -59408,6 +59773,12 @@ function filterSwarmEntries(entries, filters) {
59408
59773
  if (filters.minScore !== undefined && entry.confidence < filters.minScore) {
59409
59774
  return false;
59410
59775
  }
59776
+ if (scopeFilter && scopeFilter.length > 0) {
59777
+ const entryScope = entry.scope ?? "global";
59778
+ if (!scopeFilter.some((pattern) => entryScope === pattern)) {
59779
+ return false;
59780
+ }
59781
+ }
59411
59782
  return true;
59412
59783
  });
59413
59784
  }
@@ -59492,9 +59863,14 @@ var knowledge_query = createSwarmTool({
59492
59863
  minScore: minScore ?? undefined
59493
59864
  };
59494
59865
  const results = [];
59866
+ let scopeFilter;
59867
+ try {
59868
+ const { config: config3 } = loadPluginConfigWithMeta(directory);
59869
+ scopeFilter = config3.knowledge?.scope_filter;
59870
+ } catch {}
59495
59871
  if (tier === "swarm" || tier === "all") {
59496
59872
  const swarmEntries = await readSwarmKnowledge(directory);
59497
- const filtered = filterSwarmEntries(swarmEntries, filters);
59873
+ const filtered = filterSwarmEntries(swarmEntries, filters, scopeFilter);
59498
59874
  for (const entry of filtered) {
59499
59875
  results.push({ entry, tier: "swarm" });
59500
59876
  }
@@ -59544,6 +59920,7 @@ var knowledge_query = createSwarmTool({
59544
59920
  });
59545
59921
  // src/tools/knowledge-recall.ts
59546
59922
  init_dist();
59923
+ init_knowledge_store();
59547
59924
  init_create_tool();
59548
59925
  var knowledgeRecall = createSwarmTool({
59549
59926
  description: "Search the knowledge base for relevant past decisions, patterns, and lessons learned. Returns ranked results by semantic similarity.",
@@ -59627,6 +60004,7 @@ var knowledgeRecall = createSwarmTool({
59627
60004
  });
59628
60005
  // src/tools/knowledge-remove.ts
59629
60006
  init_dist();
60007
+ init_knowledge_store();
59630
60008
  init_create_tool();
59631
60009
  var knowledgeRemove = createSwarmTool({
59632
60010
  description: "Delete an outdated knowledge entry by ID. Double-deletion is idempotent \u2014 removing a non-existent entry returns a clear message without error.",
@@ -60022,7 +60400,14 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
60022
60400
  if (retroFound && retroEntry?.lessons_learned && retroEntry.lessons_learned.length > 0) {
60023
60401
  try {
60024
60402
  const projectName = path49.basename(dir);
60025
- await curateAndStoreSwarm(retroEntry.lessons_learned, projectName, { phase_number: phase }, dir, knowledgeConfig);
60403
+ const curationResult = await curateAndStoreSwarm(retroEntry.lessons_learned, projectName, { phase_number: phase }, dir, knowledgeConfig);
60404
+ if (curationResult) {
60405
+ const sessionState = swarmState.agentSessions.get(sessionID);
60406
+ if (sessionState) {
60407
+ sessionState.pendingAdvisoryMessages ??= [];
60408
+ sessionState.pendingAdvisoryMessages.push(`[CURATOR] Knowledge curation: ${curationResult.stored} stored, ${curationResult.skipped} skipped, ${curationResult.rejected} rejected.`);
60409
+ }
60410
+ }
60026
60411
  } catch (error93) {
60027
60412
  safeWarn("[phase_complete] Failed to curate lessons from retrospective:", error93);
60028
60413
  }
@@ -60056,7 +60441,17 @@ async function executePhaseComplete(args2, workingDirectory, directory) {
60056
60441
  } catch (curatorError) {
60057
60442
  safeWarn("[phase_complete] Curator pipeline error (non-blocking):", curatorError);
60058
60443
  }
60059
- const effectiveRequired = [...phaseCompleteConfig.required_agents];
60444
+ let phaseRequiredAgents;
60445
+ try {
60446
+ const planPath = validateSwarmPath(dir, "plan.json");
60447
+ const planRaw = fs38.readFileSync(planPath, "utf-8");
60448
+ const plan = JSON.parse(planRaw);
60449
+ const phaseObj = plan.phases.find((p) => p.id === phase);
60450
+ phaseRequiredAgents = phaseObj?.required_agents;
60451
+ } catch {}
60452
+ const effectiveRequired = [
60453
+ ...phaseRequiredAgents ?? phaseCompleteConfig.required_agents
60454
+ ];
60060
60455
  if (phaseCompleteConfig.require_docs && !effectiveRequired.includes("docs")) {
60061
60456
  effectiveRequired.push("docs");
60062
60457
  }
@@ -62521,7 +62916,7 @@ var javascriptRules = [
62521
62916
  languages: ["javascript", "typescript"],
62522
62917
  description: "Potential command injection via child_process.exec() with unsanitized input",
62523
62918
  remediation: "Never pass user input directly to exec(). Use execFile() with arguments array or sanitize input thoroughly.",
62524
- pattern: /exec\s*\(\s*[`'"]/,
62919
+ pattern: /exec\s*\(\s*(?:[`'"]|\w)/,
62525
62920
  validate: (_match, _context) => {
62526
62921
  return true;
62527
62922
  }
@@ -62844,9 +63239,7 @@ function executeRulesSync(filePath, content, language) {
62844
63239
  }
62845
63240
 
62846
63241
  // src/sast/semgrep.ts
62847
- import { execFile as execFile2, execFileSync as execFileSync2, spawn as spawn2 } from "child_process";
62848
- import { promisify as promisify2 } from "util";
62849
- var _execFileAsync = promisify2(execFile2);
63242
+ import { execFileSync as execFileSync2, spawn as spawn2 } from "child_process";
62850
63243
  var semgrepAvailableCache = null;
62851
63244
  var DEFAULT_RULES_DIR = ".swarm/semgrep-rules";
62852
63245
  var DEFAULT_TIMEOUT_MS3 = 30000;
@@ -62871,7 +63264,12 @@ function parseSemgrepResults(semgrepOutput) {
62871
63264
  try {
62872
63265
  const parsed = JSON.parse(semgrepOutput);
62873
63266
  const results = parsed.results || parsed;
63267
+ if (!Array.isArray(results)) {
63268
+ return [];
63269
+ }
62874
63270
  for (const result of results) {
63271
+ if (!result || typeof result !== "object")
63272
+ continue;
62875
63273
  const severity = mapSemgrepSeverity(result.extra?.severity || result.severity);
62876
63274
  findings.push({
62877
63275
  rule_id: result.check_id || result.rule_id || "unknown",
@@ -63340,7 +63738,7 @@ function validatePath(inputPath, baseDir, workspaceDir) {
63340
63738
  }
63341
63739
  return null;
63342
63740
  }
63343
- function validateDirectory3(dir, workspaceDir) {
63741
+ function validateDirectory2(dir, workspaceDir) {
63344
63742
  if (!dir || dir.length === 0) {
63345
63743
  return "directory is required";
63346
63744
  }
@@ -63697,7 +64095,7 @@ async function runQualityBudgetWrapped(changedFiles, directory, _config) {
63697
64095
  async function runPreCheckBatch(input, workspaceDir, contextDir) {
63698
64096
  const effectiveWorkspaceDir = workspaceDir || input.directory || contextDir;
63699
64097
  const { files, directory, sast_threshold = "medium", config: config3 } = input;
63700
- const dirError = validateDirectory3(directory, effectiveWorkspaceDir);
64098
+ const dirError = validateDirectory2(directory, effectiveWorkspaceDir);
63701
64099
  if (dirError) {
63702
64100
  warn(`pre_check_batch: Invalid directory: ${dirError}`);
63703
64101
  return {
@@ -63908,7 +64306,7 @@ var pre_check_batch = createSwarmTool({
63908
64306
  }
63909
64307
  const resolvedDirectory = path53.resolve(typedArgs.directory);
63910
64308
  const workspaceAnchor = resolvedDirectory;
63911
- const dirError = validateDirectory3(resolvedDirectory, workspaceAnchor);
64309
+ const dirError = validateDirectory2(resolvedDirectory, workspaceAnchor);
63912
64310
  if (dirError) {
63913
64311
  const errorResult = {
63914
64312
  gates_passed: false,
@@ -64021,10 +64419,11 @@ import * as path54 from "path";
64021
64419
  var LOCKS_DIR = ".swarm/locks";
64022
64420
  var LOCK_TIMEOUT_MS = 5 * 60 * 1000;
64023
64421
  function getLockFilePath(directory, filePath) {
64024
- if (filePath.includes("..")) {
64422
+ const normalized = path54.resolve(directory, filePath);
64423
+ if (!normalized.startsWith(path54.resolve(directory))) {
64025
64424
  throw new Error("Invalid file path: path traversal not allowed");
64026
64425
  }
64027
- const hash3 = Buffer.from(filePath).toString("base64").replace(/[/+=]/g, "_");
64426
+ const hash3 = Buffer.from(normalized).toString("base64").replace(/[/+=]/g, "_");
64028
64427
  return path54.join(directory, LOCKS_DIR, `${hash3}.lock`);
64029
64428
  }
64030
64429
  function tryAcquireLock(directory, filePath, agent, taskId) {
@@ -64204,12 +64603,24 @@ async function executeSavePlan(args2, fallbackDir) {
64204
64603
  });
64205
64604
  await fs44.promises.writeFile(markerPath, marker, "utf8");
64206
64605
  } catch {}
64606
+ const warnings = [];
64607
+ let criticReviewFound = false;
64608
+ for (const [, session] of swarmState.agentSessions) {
64609
+ if (session.phaseAgentsDispatched?.has("critic") || session.lastCompletedPhaseAgentsDispatched?.has("critic")) {
64610
+ criticReviewFound = true;
64611
+ break;
64612
+ }
64613
+ }
64614
+ if (!criticReviewFound) {
64615
+ warnings.push("No critic review detected before plan save. Consider delegating to critic for plan validation.");
64616
+ }
64207
64617
  return {
64208
64618
  success: true,
64209
64619
  message: "Plan saved successfully",
64210
64620
  plan_path: path55.join(dir, ".swarm", "plan.json"),
64211
64621
  phases_count: plan.phases.length,
64212
- tasks_count: tasksCount
64622
+ tasks_count: tasksCount,
64623
+ ...warnings.length > 0 ? { warnings } : {}
64213
64624
  };
64214
64625
  } finally {
64215
64626
  releaseLock(dir, planFilePath, lockTaskId);
@@ -64679,7 +65090,7 @@ function parseYarnLock(content) {
64679
65090
  const components = [];
64680
65091
  const blocks = content.split(/^(?=@?[\w-]+@)/m);
64681
65092
  for (const block of blocks) {
64682
- const pkgMatch = block.match(/^(@?[\w-]+)@([\d.]+)/m);
65093
+ const pkgMatch = block.match(/^(@?[\w-]+)@(\d+\.\d+\.\d+(?:-[\w.]+)?)/m);
64683
65094
  if (!pkgMatch)
64684
65095
  continue;
64685
65096
  const name2 = pkgMatch[1];
@@ -64943,8 +65354,8 @@ function parsePackageResolved(content) {
64943
65354
  const pins = resolved.pins || [];
64944
65355
  for (const pin of pins) {
64945
65356
  const identity = pin.identity || pin.package || "";
64946
- const state2 = pin.state || {};
64947
- const version3 = state2.version || state2.revision || "";
65357
+ const state = pin.state || {};
65358
+ const version3 = state.version || state.revision || "";
64948
65359
  let org = "";
64949
65360
  const location = pin.location || "";
64950
65361
  const orgMatch = location.match(/github\.com\/([^/]+)\//);
@@ -65030,7 +65441,9 @@ function detectComponents(filePath, content) {
65030
65441
  if (components.length > 0) {
65031
65442
  return components;
65032
65443
  }
65033
- } catch {}
65444
+ } catch (error93) {
65445
+ console.warn(`[sbom] Detector failed for ${filePath}:`, error93 instanceof Error ? error93.message : String(error93));
65446
+ }
65034
65447
  }
65035
65448
  return [];
65036
65449
  }
@@ -65638,15 +66051,6 @@ var WINDOWS_RESERVED_NAMES = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|:|$)/i;
65638
66051
  function containsControlCharacters(str) {
65639
66052
  return /[\0\t\n\r]/.test(str);
65640
66053
  }
65641
- function containsPathTraversal4(str) {
65642
- if (/^\/|^[A-Za-z]:[/\\]|\.\.[/\\]|\.\.$|~\/|^\\/.test(str)) {
65643
- return true;
65644
- }
65645
- if (str.includes("%2e%2e") || str.includes("%2E%2E")) {
65646
- return true;
65647
- }
65648
- return false;
65649
- }
65650
66054
  function containsWindowsAttacks(str) {
65651
66055
  if (/:[^\\/]/.test(str)) {
65652
66056
  return true;
@@ -65920,7 +66324,7 @@ var symbols = createSwarmTool({
65920
66324
  symbols: []
65921
66325
  }, null, 2);
65922
66326
  }
65923
- if (containsPathTraversal4(file3)) {
66327
+ if (containsPathTraversal(file3)) {
65924
66328
  return JSON.stringify({
65925
66329
  file: file3,
65926
66330
  error: "Path contains path traversal sequence",
@@ -66023,17 +66427,11 @@ var PRIORITY_MAP = {
66023
66427
  NOTE: "low"
66024
66428
  };
66025
66429
  var SHELL_METACHAR_REGEX3 = /[;&|%$`\\]/;
66026
- function containsPathTraversal5(str) {
66027
- return /\.\.[/\\]/.test(str);
66028
- }
66029
- function containsControlChars6(str) {
66030
- return /[\0\t\r\n]/.test(str);
66031
- }
66032
66430
  function validateTagsInput(tags) {
66033
66431
  if (!tags || tags.length === 0) {
66034
66432
  return "tags cannot be empty";
66035
66433
  }
66036
- if (containsControlChars6(tags)) {
66434
+ if (containsControlChars(tags)) {
66037
66435
  return "tags contains control characters";
66038
66436
  }
66039
66437
  if (SHELL_METACHAR_REGEX3.test(tags)) {
@@ -66048,10 +66446,10 @@ function validatePathsInput(paths, cwd) {
66048
66446
  if (!paths || paths.length === 0) {
66049
66447
  return { error: null, resolvedPath: cwd };
66050
66448
  }
66051
- if (containsControlChars6(paths)) {
66449
+ if (containsControlChars(paths)) {
66052
66450
  return { error: "paths contains control characters", resolvedPath: null };
66053
66451
  }
66054
- if (containsPathTraversal5(paths)) {
66452
+ if (containsPathTraversal(paths)) {
66055
66453
  return { error: "paths contains path traversal", resolvedPath: null };
66056
66454
  }
66057
66455
  try {
@@ -66434,8 +66832,8 @@ function checkReviewerGate(taskId, workingDirectory) {
66434
66832
  continue;
66435
66833
  }
66436
66834
  validSessionCount++;
66437
- const state2 = getTaskState(session, taskId);
66438
- if (state2 === "tests_run" || state2 === "complete") {
66835
+ const state = getTaskState(session, taskId);
66836
+ if (state === "tests_run" || state === "complete") {
66439
66837
  return { blocked: false, reason: "" };
66440
66838
  }
66441
66839
  }
@@ -66446,8 +66844,8 @@ function checkReviewerGate(taskId, workingDirectory) {
66446
66844
  for (const [sessionId, session] of swarmState.agentSessions) {
66447
66845
  if (!(session.taskWorkflowStates instanceof Map))
66448
66846
  continue;
66449
- const state2 = getTaskState(session, taskId);
66450
- stateEntries.push(`${sessionId}: ${state2}`);
66847
+ const state = getTaskState(session, taskId);
66848
+ stateEntries.push(`${sessionId}: ${state}`);
66451
66849
  }
66452
66850
  try {
66453
66851
  const resolvedDir2 = workingDirectory;
@@ -66670,19 +67068,35 @@ async function executeUpdateTaskStatus(args2, fallbackDir) {
66670
67068
  }
66671
67069
  } else {
66672
67070
  if (!fallbackDir) {
66673
- console.warn("[update-task-status] fallbackDir is undefined");
67071
+ return {
67072
+ success: false,
67073
+ message: "No working_directory provided and fallbackDir is undefined",
67074
+ errors: ["Cannot resolve directory for task status update"]
67075
+ };
66674
67076
  }
66675
67077
  directory = fallbackDir;
66676
67078
  }
66677
67079
  if (args2.status === "completed") {
66678
67080
  recoverTaskStateFromDelegations(args2.task_id);
66679
- const reviewerCheck = await checkReviewerGateWithScope(args2.task_id, directory);
66680
- if (reviewerCheck.blocked) {
66681
- return {
66682
- success: false,
66683
- message: "Gate check failed: reviewer delegation required before marking task as completed",
66684
- errors: [reviewerCheck.reason]
66685
- };
67081
+ let phaseRequiresReviewer = true;
67082
+ try {
67083
+ const planPath = path61.join(directory, ".swarm", "plan.json");
67084
+ const planRaw = fs50.readFileSync(planPath, "utf-8");
67085
+ const plan = JSON.parse(planRaw);
67086
+ const taskPhase = plan.phases.find((p) => p.tasks.some((t) => t.id === args2.task_id));
67087
+ if (taskPhase?.required_agents && !taskPhase.required_agents.includes("reviewer")) {
67088
+ phaseRequiresReviewer = false;
67089
+ }
67090
+ } catch {}
67091
+ if (phaseRequiresReviewer) {
67092
+ const reviewerCheck = await checkReviewerGateWithScope(args2.task_id, directory);
67093
+ if (reviewerCheck.blocked) {
67094
+ return {
67095
+ success: false,
67096
+ message: "Gate check failed: reviewer delegation required before marking task as completed",
67097
+ errors: [reviewerCheck.reason]
67098
+ };
67099
+ }
66686
67100
  }
66687
67101
  }
66688
67102
  try {