@opencode_weave/weave 0.7.0 → 0.7.3

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
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- import { join as join13 } from "path";
2
+ import { join as join14 } from "path";
3
3
 
4
4
  // src/config/loader.ts
5
5
  import { existsSync as existsSync2, readFileSync } from "node:fs";
@@ -22,7 +22,8 @@ var AgentOverrideConfigSchema = z.object({
22
22
  tools: z.record(z.string(), z.boolean()).optional(),
23
23
  disable: z.boolean().optional(),
24
24
  mode: z.enum(["subagent", "primary", "all"]).optional(),
25
- maxTokens: z.number().optional()
25
+ maxTokens: z.number().optional(),
26
+ display_name: z.string().optional()
26
27
  });
27
28
  var AgentOverridesSchema = z.record(z.string(), AgentOverrideConfigSchema);
28
29
  var CategoryConfigSchema = z.object({
@@ -52,7 +53,8 @@ var TmuxConfigSchema = z.object({
52
53
  var ExperimentalConfigSchema = z.object({
53
54
  plugin_load_timeout_ms: z.number().min(1000).optional(),
54
55
  context_window_warning_threshold: z.number().min(0).max(1).optional(),
55
- context_window_critical_threshold: z.number().min(0).max(1).optional()
56
+ context_window_critical_threshold: z.number().min(0).max(1).optional(),
57
+ task_system: z.boolean().default(true)
56
58
  });
57
59
  var DelegationTriggerSchema = z.object({
58
60
  domain: z.string(),
@@ -223,6 +225,7 @@ var AGENT_DISPLAY_NAMES = {
223
225
  weft: "weft"
224
226
  };
225
227
  var BUILTIN_CONFIG_KEYS = new Set(Object.keys(AGENT_DISPLAY_NAMES));
228
+ var INITIAL_BUILTIN_DISPLAY_NAMES = new Map(Object.entries(AGENT_DISPLAY_NAMES));
226
229
  var reverseDisplayNames = null;
227
230
  function getReverseDisplayNames() {
228
231
  if (reverseDisplayNames === null) {
@@ -234,6 +237,11 @@ function registerAgentDisplayName(configKey, displayName) {
234
237
  if (BUILTIN_CONFIG_KEYS.has(configKey)) {
235
238
  throw new Error(`Cannot register display name for "${configKey}": it is a built-in agent name`);
236
239
  }
240
+ for (const [builtinKey, initialDisplayName] of INITIAL_BUILTIN_DISPLAY_NAMES) {
241
+ if (initialDisplayName.toLowerCase() === displayName.toLowerCase()) {
242
+ throw new Error(`Display name "${displayName}" is reserved for built-in agent "${builtinKey}"`);
243
+ }
244
+ }
237
245
  const reverse = getReverseDisplayNames();
238
246
  const existingKey = reverse[displayName.toLowerCase()];
239
247
  if (existingKey !== undefined && BUILTIN_CONFIG_KEYS.has(existingKey)) {
@@ -242,6 +250,13 @@ function registerAgentDisplayName(configKey, displayName) {
242
250
  AGENT_DISPLAY_NAMES[configKey] = displayName;
243
251
  reverseDisplayNames = null;
244
252
  }
253
+ function updateBuiltinDisplayName(configKey, displayName) {
254
+ if (!BUILTIN_CONFIG_KEYS.has(configKey)) {
255
+ throw new Error(`Cannot update builtin display name for "${configKey}": not a built-in agent`);
256
+ }
257
+ AGENT_DISPLAY_NAMES[configKey] = displayName;
258
+ reverseDisplayNames = null;
259
+ }
245
260
  function getAgentDisplayName(configKey) {
246
261
  const exactMatch = AGENT_DISPLAY_NAMES[configKey];
247
262
  if (exactMatch !== undefined)
@@ -1233,6 +1248,8 @@ Use this structure:
1233
1248
  \`\`\`
1234
1249
 
1235
1250
  CRITICAL: Use \`- [ ]\` checkboxes for ALL actionable items. The /start-work system tracks progress by counting these checkboxes.
1251
+
1252
+ Use the exact section headings shown in the template above (\`## TL;DR\`, \`## Context\`, \`## Objectives\`, \`## TODOs\`, \`## Verification\`). Consistent headings help downstream tooling parse the plan.
1236
1253
  </PlanOutput>
1237
1254
 
1238
1255
  <Constraints>
@@ -1725,12 +1742,19 @@ var AGENT_NAME_VARIANTS = {
1725
1742
  loom: ["loom", "Loom"],
1726
1743
  tapestry: ["tapestry", "Tapestry"]
1727
1744
  };
1745
+ var INITIAL_NAME_VARIANTS = new Map(Object.entries(AGENT_NAME_VARIANTS).map(([k, v]) => [k, [...v]]));
1728
1746
  function registerAgentNameVariants(name, variants) {
1729
1747
  if (AGENT_NAME_VARIANTS[name])
1730
1748
  return;
1731
1749
  const titleCase = name.charAt(0).toUpperCase() + name.slice(1);
1732
1750
  AGENT_NAME_VARIANTS[name] = variants ?? [name, titleCase];
1733
1751
  }
1752
+ function addBuiltinNameVariant(configKey, variant) {
1753
+ const existing = AGENT_NAME_VARIANTS[configKey];
1754
+ if (existing && !existing.includes(variant)) {
1755
+ existing.push(variant);
1756
+ }
1757
+ }
1734
1758
  function stripDisabledAgentReferences(prompt, disabled) {
1735
1759
  if (disabled.size === 0)
1736
1760
  return prompt;
@@ -1743,7 +1767,7 @@ function stripDisabledAgentReferences(prompt, disabled) {
1743
1767
  }
1744
1768
  if (disabledVariants.length === 0)
1745
1769
  return prompt;
1746
- const pattern = new RegExp(`\\b(${disabledVariants.map((v) => v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b`);
1770
+ const pattern = new RegExp(`(?<!\\w)(${disabledVariants.map((v) => v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})(?!\\w)`);
1747
1771
  const lines = prompt.split(`
1748
1772
  `);
1749
1773
  const filtered = lines.filter((line) => !pattern.test(line));
@@ -1888,7 +1912,10 @@ var KNOWN_TOOL_NAMES = new Set([
1888
1912
  "call_weave_agent",
1889
1913
  "webfetch",
1890
1914
  "todowrite",
1891
- "skill"
1915
+ "skill",
1916
+ "task_create",
1917
+ "task_update",
1918
+ "task_list"
1892
1919
  ]);
1893
1920
  var AGENT_NAME_PATTERN = /^[a-z][a-z0-9_-]*$/;
1894
1921
  function parseFallbackModels(models) {
@@ -1988,6 +2015,26 @@ function createManagers(options) {
1988
2015
  fingerprint,
1989
2016
  customAgentMetadata
1990
2017
  });
2018
+ if (pluginConfig.agents) {
2019
+ for (const [name, override] of Object.entries(pluginConfig.agents)) {
2020
+ const displayName = override.display_name?.trim();
2021
+ if (displayName) {
2022
+ try {
2023
+ updateBuiltinDisplayName(name, displayName);
2024
+ addBuiltinNameVariant(name, displayName);
2025
+ if (agents[name]) {
2026
+ agents[name] = { ...agents[name], description: displayName };
2027
+ }
2028
+ } catch (err) {
2029
+ if (err instanceof Error && err.message.includes("not a built-in agent")) {
2030
+ log(`Skipping display_name override for non-builtin agent "${name}"`);
2031
+ } else {
2032
+ throw err;
2033
+ }
2034
+ }
2035
+ }
2036
+ }
2037
+ }
1991
2038
  if (pluginConfig.custom_agents) {
1992
2039
  const disabledSet = new Set(pluginConfig.disabled_agents ?? []);
1993
2040
  for (const [name, customConfig] of Object.entries(pluginConfig.custom_agents)) {
@@ -2248,6 +2295,275 @@ function createSkillResolver(discovered) {
2248
2295
  return resolveMultipleSkills(skillNames, disabledSkills, discovered);
2249
2296
  };
2250
2297
  }
2298
+ // src/features/task-system/tools/task-create.ts
2299
+ import { tool } from "@opencode-ai/plugin";
2300
+
2301
+ // src/features/task-system/storage.ts
2302
+ import { mkdirSync as mkdirSync2, writeFileSync, readFileSync as readFileSync4, renameSync, unlinkSync, readdirSync as readdirSync2, statSync, openSync, closeSync } from "fs";
2303
+ import { join as join5, basename } from "path";
2304
+ import { randomUUID } from "crypto";
2305
+
2306
+ // src/features/task-system/types.ts
2307
+ import { z as z2 } from "zod";
2308
+ var TaskStatus = {
2309
+ PENDING: "pending",
2310
+ IN_PROGRESS: "in_progress",
2311
+ COMPLETED: "completed",
2312
+ DELETED: "deleted"
2313
+ };
2314
+ var TaskStatusSchema = z2.enum(["pending", "in_progress", "completed", "deleted"]);
2315
+ var TaskObjectSchema = z2.object({
2316
+ id: z2.string(),
2317
+ subject: z2.string(),
2318
+ description: z2.string(),
2319
+ status: TaskStatusSchema,
2320
+ threadID: z2.string(),
2321
+ blocks: z2.array(z2.string()).default([]),
2322
+ blockedBy: z2.array(z2.string()).default([]),
2323
+ metadata: z2.record(z2.string(), z2.unknown()).optional()
2324
+ });
2325
+ var TaskCreateInputSchema = z2.object({
2326
+ subject: z2.string().describe("Short title for the task (required)"),
2327
+ description: z2.string().optional().describe("Detailed description of the task"),
2328
+ blocks: z2.array(z2.string()).optional().describe("Task IDs that this task blocks"),
2329
+ blockedBy: z2.array(z2.string()).optional().describe("Task IDs that block this task"),
2330
+ metadata: z2.record(z2.string(), z2.unknown()).optional().describe("Arbitrary key-value metadata")
2331
+ });
2332
+ var TaskUpdateInputSchema = z2.object({
2333
+ id: z2.string().describe("Task ID to update (required, format: T-{uuid})"),
2334
+ subject: z2.string().optional().describe("New subject/title"),
2335
+ description: z2.string().optional().describe("New description"),
2336
+ status: TaskStatusSchema.optional().describe("New status"),
2337
+ addBlocks: z2.array(z2.string()).optional().describe("Task IDs to add to blocks (additive, no replacement)"),
2338
+ addBlockedBy: z2.array(z2.string()).optional().describe("Task IDs to add to blockedBy (additive, no replacement)"),
2339
+ metadata: z2.record(z2.string(), z2.unknown()).optional().describe("Metadata to merge (null values delete keys)")
2340
+ });
2341
+ var TaskListInputSchema = z2.object({});
2342
+
2343
+ // src/features/task-system/storage.ts
2344
+ function getTaskDir(directory, configDir) {
2345
+ const base = configDir ?? join5(getHomeDir(), ".config", "opencode");
2346
+ const slug = sanitizeSlug(basename(directory));
2347
+ return join5(base, "tasks", slug);
2348
+ }
2349
+ function getHomeDir() {
2350
+ return process.env.HOME ?? process.env.USERPROFILE ?? "/tmp";
2351
+ }
2352
+ function sanitizeSlug(name) {
2353
+ return name.toLowerCase().replace(/[^a-z0-9-_]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "default";
2354
+ }
2355
+ function generateTaskId() {
2356
+ return `T-${randomUUID()}`;
2357
+ }
2358
+ function readJsonSafe(filePath, schema) {
2359
+ try {
2360
+ const raw = readFileSync4(filePath, "utf-8");
2361
+ const parsed = JSON.parse(raw);
2362
+ return schema.parse(parsed);
2363
+ } catch {
2364
+ return null;
2365
+ }
2366
+ }
2367
+ function writeJsonAtomic(filePath, data) {
2368
+ const dir = join5(filePath, "..");
2369
+ mkdirSync2(dir, { recursive: true });
2370
+ const tmpPath = `${filePath}.tmp`;
2371
+ writeFileSync(tmpPath, JSON.stringify(data, null, 2), "utf-8");
2372
+ renameSync(tmpPath, filePath);
2373
+ }
2374
+ function ensureDir(dirPath) {
2375
+ mkdirSync2(dirPath, { recursive: true });
2376
+ }
2377
+ function listTaskFiles(taskDir) {
2378
+ try {
2379
+ return readdirSync2(taskDir).filter((f) => f.startsWith("T-") && f.endsWith(".json")).map((f) => join5(taskDir, f));
2380
+ } catch {
2381
+ return [];
2382
+ }
2383
+ }
2384
+ function getTaskFilePath(taskDir, taskId) {
2385
+ return join5(taskDir, `${taskId}.json`);
2386
+ }
2387
+ function readTask(taskDir, taskId) {
2388
+ return readJsonSafe(getTaskFilePath(taskDir, taskId), TaskObjectSchema);
2389
+ }
2390
+ function writeTask(taskDir, task) {
2391
+ writeJsonAtomic(getTaskFilePath(taskDir, task.id), task);
2392
+ }
2393
+ function readAllTasks(taskDir) {
2394
+ const files = listTaskFiles(taskDir);
2395
+ const tasks = [];
2396
+ for (const file of files) {
2397
+ const task = readJsonSafe(file, TaskObjectSchema);
2398
+ if (task)
2399
+ tasks.push(task);
2400
+ }
2401
+ return tasks;
2402
+ }
2403
+
2404
+ // src/features/task-system/todo-sync.ts
2405
+ function syncTaskToTodo(task) {
2406
+ if (task.status === TaskStatus.DELETED) {
2407
+ return null;
2408
+ }
2409
+ const statusMap = {
2410
+ [TaskStatus.PENDING]: "pending",
2411
+ [TaskStatus.IN_PROGRESS]: "in_progress",
2412
+ [TaskStatus.COMPLETED]: "completed"
2413
+ };
2414
+ const priority = task.metadata?.priority ?? undefined;
2415
+ return {
2416
+ id: task.id,
2417
+ content: task.subject,
2418
+ status: statusMap[task.status] ?? "pending",
2419
+ ...priority ? { priority } : {}
2420
+ };
2421
+ }
2422
+ function todosMatch(a, b) {
2423
+ if (a.id && b.id)
2424
+ return a.id === b.id;
2425
+ return a.content === b.content;
2426
+ }
2427
+ async function syncTaskTodoUpdate(writer, sessionId, task) {
2428
+ if (!writer) {
2429
+ log("[task-sync] No todo writer available — skipping sidebar sync");
2430
+ return;
2431
+ }
2432
+ try {
2433
+ const currentTodos = await writer.read(sessionId);
2434
+ const todoItem = syncTaskToTodo(task);
2435
+ const filtered = currentTodos.filter((t) => !todosMatch(t, { id: task.id, content: task.subject, status: "pending" }));
2436
+ if (todoItem) {
2437
+ filtered.push(todoItem);
2438
+ }
2439
+ await writer.update(sessionId, filtered);
2440
+ } catch (err) {
2441
+ log("[task-sync] Failed to sync task to sidebar (non-fatal)", { taskId: task.id, error: String(err) });
2442
+ }
2443
+ }
2444
+
2445
+ // src/features/task-system/tools/task-create.ts
2446
+ function createTaskCreateTool(options) {
2447
+ const { directory, configDir, todoWriter = null } = options;
2448
+ return tool({
2449
+ description: "Create a new task. Use this instead of todowrite for task tracking. " + "Each task gets a unique ID and is stored atomically — creating a task never destroys existing tasks or todos.",
2450
+ args: {
2451
+ subject: tool.schema.string().describe("Short title for the task (required)"),
2452
+ description: tool.schema.string().optional().describe("Detailed description of the task"),
2453
+ blocks: tool.schema.array(tool.schema.string()).optional().describe("Task IDs that this task blocks"),
2454
+ blockedBy: tool.schema.array(tool.schema.string()).optional().describe("Task IDs that block this task"),
2455
+ metadata: tool.schema.record(tool.schema.string(), tool.schema.unknown()).optional().describe("Arbitrary key-value metadata")
2456
+ },
2457
+ async execute(args, context) {
2458
+ const taskDir = getTaskDir(directory, configDir);
2459
+ ensureDir(taskDir);
2460
+ const task = {
2461
+ id: generateTaskId(),
2462
+ subject: args.subject,
2463
+ description: args.description ?? "",
2464
+ status: TaskStatus.PENDING,
2465
+ threadID: context.sessionID,
2466
+ blocks: args.blocks ?? [],
2467
+ blockedBy: args.blockedBy ?? [],
2468
+ metadata: args.metadata
2469
+ };
2470
+ writeTask(taskDir, task);
2471
+ log("[task-create] Created task", { id: task.id, subject: task.subject });
2472
+ await syncTaskTodoUpdate(todoWriter, context.sessionID, task);
2473
+ return JSON.stringify({ task: { id: task.id, subject: task.subject } });
2474
+ }
2475
+ });
2476
+ }
2477
+ // src/features/task-system/tools/task-update.ts
2478
+ import { tool as tool2 } from "@opencode-ai/plugin";
2479
+ var TASK_ID_PATTERN = /^T-[A-Za-z0-9-]+$/;
2480
+ function createTaskUpdateTool(options) {
2481
+ const { directory, configDir, todoWriter = null } = options;
2482
+ return tool2({
2483
+ description: "Update an existing task by ID. Modifies only the specified fields — " + "other tasks and non-task todos are completely untouched. " + "blocks/blockedBy are additive (appended, never replaced).",
2484
+ args: {
2485
+ id: tool2.schema.string().describe("Task ID to update (required, format: T-{uuid})"),
2486
+ subject: tool2.schema.string().optional().describe("New subject/title"),
2487
+ description: tool2.schema.string().optional().describe("New description"),
2488
+ status: tool2.schema.enum(["pending", "in_progress", "completed", "deleted"]).optional().describe("New status"),
2489
+ addBlocks: tool2.schema.array(tool2.schema.string()).optional().describe("Task IDs to add to blocks (additive)"),
2490
+ addBlockedBy: tool2.schema.array(tool2.schema.string()).optional().describe("Task IDs to add to blockedBy (additive)"),
2491
+ metadata: tool2.schema.record(tool2.schema.string(), tool2.schema.unknown()).optional().describe("Metadata to merge (null values delete keys)")
2492
+ },
2493
+ async execute(args, context) {
2494
+ if (!TASK_ID_PATTERN.test(args.id)) {
2495
+ return JSON.stringify({ error: "invalid_task_id", message: `Invalid task ID format: ${args.id}. Expected T-{uuid}` });
2496
+ }
2497
+ const taskDir = getTaskDir(directory, configDir);
2498
+ const task = readTask(taskDir, args.id);
2499
+ if (!task) {
2500
+ return JSON.stringify({ error: "task_not_found", message: `Task ${args.id} not found` });
2501
+ }
2502
+ if (args.subject !== undefined)
2503
+ task.subject = args.subject;
2504
+ if (args.description !== undefined)
2505
+ task.description = args.description;
2506
+ if (args.status !== undefined)
2507
+ task.status = args.status;
2508
+ if (args.addBlocks?.length) {
2509
+ const existing = new Set(task.blocks);
2510
+ for (const b of args.addBlocks) {
2511
+ if (!existing.has(b)) {
2512
+ task.blocks.push(b);
2513
+ existing.add(b);
2514
+ }
2515
+ }
2516
+ }
2517
+ if (args.addBlockedBy?.length) {
2518
+ const existing = new Set(task.blockedBy);
2519
+ for (const b of args.addBlockedBy) {
2520
+ if (!existing.has(b)) {
2521
+ task.blockedBy.push(b);
2522
+ existing.add(b);
2523
+ }
2524
+ }
2525
+ }
2526
+ if (args.metadata) {
2527
+ const meta = task.metadata ?? {};
2528
+ for (const [key, value] of Object.entries(args.metadata)) {
2529
+ if (value === null) {
2530
+ delete meta[key];
2531
+ } else {
2532
+ meta[key] = value;
2533
+ }
2534
+ }
2535
+ task.metadata = Object.keys(meta).length > 0 ? meta : undefined;
2536
+ }
2537
+ writeTask(taskDir, task);
2538
+ log("[task-update] Updated task", { id: task.id });
2539
+ await syncTaskTodoUpdate(todoWriter, context.sessionID, task);
2540
+ return JSON.stringify({ task });
2541
+ }
2542
+ });
2543
+ }
2544
+ // src/features/task-system/tools/task-list.ts
2545
+ import { tool as tool3 } from "@opencode-ai/plugin";
2546
+ function createTaskListTool(options) {
2547
+ const { directory, configDir } = options;
2548
+ return tool3({
2549
+ description: "List all active tasks (pending and in_progress). " + "Excludes completed and deleted tasks. " + "Shows unresolved blockers for each task.",
2550
+ args: {},
2551
+ async execute(_args, _context) {
2552
+ const taskDir = getTaskDir(directory, configDir);
2553
+ const allTasks = readAllTasks(taskDir);
2554
+ const activeTasks = allTasks.filter((t) => t.status !== TaskStatus.COMPLETED && t.status !== TaskStatus.DELETED);
2555
+ const completedIds = new Set(allTasks.filter((t) => t.status === TaskStatus.COMPLETED).map((t) => t.id));
2556
+ const tasks = activeTasks.map((t) => ({
2557
+ id: t.id,
2558
+ subject: t.subject,
2559
+ status: t.status,
2560
+ blockedBy: t.blockedBy.filter((b) => !completedIds.has(b))
2561
+ }));
2562
+ log("[task-list] Listed tasks", { count: tasks.length });
2563
+ return JSON.stringify({ tasks });
2564
+ }
2565
+ });
2566
+ }
2251
2567
  // src/create-tools.ts
2252
2568
  async function createTools(options) {
2253
2569
  const { ctx, pluginConfig } = options;
@@ -2258,6 +2574,13 @@ async function createTools(options) {
2258
2574
  });
2259
2575
  const resolveSkillsFn = createSkillResolver(skillResult);
2260
2576
  const tools = {};
2577
+ if (pluginConfig.experimental?.task_system !== false) {
2578
+ const toolOptions = { directory: ctx.directory };
2579
+ tools.task_create = createTaskCreateTool(toolOptions);
2580
+ tools.task_update = createTaskUpdateTool(toolOptions);
2581
+ tools.task_list = createTaskListTool(toolOptions);
2582
+ log("[task-system] Registered task tools (task_create, task_update, task_list)");
2583
+ }
2261
2584
  return {
2262
2585
  tools,
2263
2586
  availableSkills: skillResult.skills,
@@ -2450,17 +2773,17 @@ var WORK_STATE_FILE = "state.json";
2450
2773
  var WORK_STATE_PATH = `${WEAVE_DIR}/${WORK_STATE_FILE}`;
2451
2774
  var PLANS_DIR = `${WEAVE_DIR}/plans`;
2452
2775
  // src/features/work-state/storage.ts
2453
- import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync, unlinkSync, mkdirSync as mkdirSync2, readdirSync as readdirSync2, statSync } from "fs";
2454
- import { join as join6, basename } from "path";
2776
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync3, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
2777
+ import { join as join7, basename as basename2 } from "path";
2455
2778
  import { execSync } from "child_process";
2456
2779
  var UNCHECKED_RE = /^[-*]\s*\[\s*\]/gm;
2457
2780
  var CHECKED_RE = /^[-*]\s*\[[xX]\]/gm;
2458
2781
  function readWorkState(directory) {
2459
- const filePath = join6(directory, WEAVE_DIR, WORK_STATE_FILE);
2782
+ const filePath = join7(directory, WEAVE_DIR, WORK_STATE_FILE);
2460
2783
  try {
2461
2784
  if (!existsSync7(filePath))
2462
2785
  return null;
2463
- const raw = readFileSync5(filePath, "utf-8");
2786
+ const raw = readFileSync6(filePath, "utf-8");
2464
2787
  const parsed = JSON.parse(raw);
2465
2788
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
2466
2789
  return null;
@@ -2476,21 +2799,21 @@ function readWorkState(directory) {
2476
2799
  }
2477
2800
  function writeWorkState(directory, state) {
2478
2801
  try {
2479
- const dir = join6(directory, WEAVE_DIR);
2802
+ const dir = join7(directory, WEAVE_DIR);
2480
2803
  if (!existsSync7(dir)) {
2481
- mkdirSync2(dir, { recursive: true });
2804
+ mkdirSync3(dir, { recursive: true });
2482
2805
  }
2483
- writeFileSync(join6(dir, WORK_STATE_FILE), JSON.stringify(state, null, 2), "utf-8");
2806
+ writeFileSync2(join7(dir, WORK_STATE_FILE), JSON.stringify(state, null, 2), "utf-8");
2484
2807
  return true;
2485
2808
  } catch {
2486
2809
  return false;
2487
2810
  }
2488
2811
  }
2489
2812
  function clearWorkState(directory) {
2490
- const filePath = join6(directory, WEAVE_DIR, WORK_STATE_FILE);
2813
+ const filePath = join7(directory, WEAVE_DIR, WORK_STATE_FILE);
2491
2814
  try {
2492
2815
  if (existsSync7(filePath)) {
2493
- unlinkSync(filePath);
2816
+ unlinkSync2(filePath);
2494
2817
  }
2495
2818
  return true;
2496
2819
  } catch {
@@ -2531,13 +2854,13 @@ function getHeadSha(directory) {
2531
2854
  }
2532
2855
  }
2533
2856
  function findPlans(directory) {
2534
- const plansDir = join6(directory, PLANS_DIR);
2857
+ const plansDir = join7(directory, PLANS_DIR);
2535
2858
  try {
2536
2859
  if (!existsSync7(plansDir))
2537
2860
  return [];
2538
- const files = readdirSync2(plansDir).filter((f) => f.endsWith(".md")).map((f) => {
2539
- const fullPath = join6(plansDir, f);
2540
- const stat = statSync(fullPath);
2861
+ const files = readdirSync3(plansDir).filter((f) => f.endsWith(".md")).map((f) => {
2862
+ const fullPath = join7(plansDir, f);
2863
+ const stat = statSync2(fullPath);
2541
2864
  return { path: fullPath, mtime: stat.mtimeMs };
2542
2865
  }).sort((a, b) => b.mtime - a.mtime).map((f) => f.path);
2543
2866
  return files;
@@ -2550,7 +2873,7 @@ function getPlanProgress(planPath) {
2550
2873
  return { total: 0, completed: 0, isComplete: true };
2551
2874
  }
2552
2875
  try {
2553
- const content = readFileSync5(planPath, "utf-8");
2876
+ const content = readFileSync6(planPath, "utf-8");
2554
2877
  const unchecked = content.match(UNCHECKED_RE) || [];
2555
2878
  const checked = content.match(CHECKED_RE) || [];
2556
2879
  const total = unchecked.length + checked.length;
@@ -2565,7 +2888,7 @@ function getPlanProgress(planPath) {
2565
2888
  }
2566
2889
  }
2567
2890
  function getPlanName(planPath) {
2568
- return basename(planPath, ".md");
2891
+ return basename2(planPath, ".md");
2569
2892
  }
2570
2893
  function pauseWork(directory) {
2571
2894
  const state = readWorkState(directory);
@@ -2582,7 +2905,7 @@ function resumeWork(directory) {
2582
2905
  return writeWorkState(directory, state);
2583
2906
  }
2584
2907
  // src/features/work-state/validation.ts
2585
- import { readFileSync as readFileSync6, existsSync as existsSync8 } from "fs";
2908
+ import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
2586
2909
  import { resolve as resolve3, sep as sep2 } from "path";
2587
2910
  function validatePlan(planPath, projectDir) {
2588
2911
  const errors = [];
@@ -2605,13 +2928,13 @@ function validatePlan(planPath, projectDir) {
2605
2928
  });
2606
2929
  return { valid: false, errors, warnings };
2607
2930
  }
2608
- const content = readFileSync6(resolvedPlanPath, "utf-8");
2931
+ const content = readFileSync7(resolvedPlanPath, "utf-8");
2609
2932
  validateStructure(content, errors, warnings);
2610
2933
  validateCheckboxes(content, errors, warnings);
2611
2934
  validateFileReferences(content, projectDir, warnings);
2612
2935
  validateNumbering(content, errors, warnings);
2613
2936
  validateEffortEstimate(content, warnings);
2614
- validateVerificationSection(content, errors);
2937
+ validateVerificationSection(content, warnings);
2615
2938
  return {
2616
2939
  valid: errors.length === 0,
2617
2940
  errors,
@@ -2643,15 +2966,15 @@ function hasSection(content, heading) {
2643
2966
  return content.split(`
2644
2967
  `).some((line) => line.trim() === heading);
2645
2968
  }
2646
- function validateStructure(content, errors, warnings) {
2647
- const requiredSections = [
2648
- ["## TL;DR", "Missing required section: ## TL;DR"],
2649
- ["## TODOs", "Missing required section: ## TODOs"],
2650
- ["## Verification", "Missing required section: ## Verification"]
2969
+ function validateStructure(content, _errors, warnings) {
2970
+ const expectedSections = [
2971
+ ["## TL;DR", "Missing expected section: ## TL;DR"],
2972
+ ["## TODOs", "Missing expected section: ## TODOs"],
2973
+ ["## Verification", "Missing expected section: ## Verification"]
2651
2974
  ];
2652
- for (const [heading, message] of requiredSections) {
2975
+ for (const [heading, message] of expectedSections) {
2653
2976
  if (!hasSection(content, heading)) {
2654
- errors.push({ severity: "error", category: "structure", message });
2977
+ warnings.push({ severity: "warning", category: "structure", message });
2655
2978
  }
2656
2979
  }
2657
2980
  const optionalSections = [
@@ -2667,6 +2990,14 @@ function validateStructure(content, errors, warnings) {
2667
2990
  function validateCheckboxes(content, errors, warnings) {
2668
2991
  const todosSection = extractSection(content, "## TODOs");
2669
2992
  if (todosSection === null) {
2993
+ const hasAnyCheckbox = /^- \[[ x]\] /m.test(content);
2994
+ if (!hasAnyCheckbox) {
2995
+ errors.push({
2996
+ severity: "error",
2997
+ category: "checkboxes",
2998
+ message: "Plan contains no checkboxes (- [ ] or - [x]) — nothing to execute"
2999
+ });
3000
+ }
2670
3001
  return;
2671
3002
  }
2672
3003
  const checkboxPattern = /^- \[[ x]\] /m;
@@ -2847,17 +3178,17 @@ function validateEffortEstimate(content, warnings) {
2847
3178
  });
2848
3179
  }
2849
3180
  }
2850
- function validateVerificationSection(content, errors) {
3181
+ function validateVerificationSection(content, warnings) {
2851
3182
  const verificationSection = extractSection(content, "## Verification");
2852
3183
  if (verificationSection === null) {
2853
3184
  return;
2854
3185
  }
2855
3186
  const hasCheckbox = /^- \[[ x]\] /m.test(verificationSection);
2856
3187
  if (!hasCheckbox) {
2857
- errors.push({
2858
- severity: "error",
3188
+ warnings.push({
3189
+ severity: "warning",
2859
3190
  category: "verification",
2860
- message: "## Verification section contains no checkboxes — at least one verifiable condition is required"
3191
+ message: "## Verification section contains no checkboxes — consider adding verifiable conditions"
2861
3192
  });
2862
3193
  }
2863
3194
  }
@@ -2868,8 +3199,8 @@ var ACTIVE_INSTANCE_FILE = "active-instance.json";
2868
3199
  var WORKFLOWS_DIR_PROJECT = ".opencode/workflows";
2869
3200
  var WORKFLOWS_DIR_USER = "workflows";
2870
3201
  // src/features/workflow/storage.ts
2871
- import { existsSync as existsSync9, readFileSync as readFileSync7, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync3, readdirSync as readdirSync3 } from "fs";
2872
- import { join as join7 } from "path";
3202
+ import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync4 } from "fs";
3203
+ import { join as join8 } from "path";
2873
3204
  import { randomBytes } from "node:crypto";
2874
3205
  function generateInstanceId() {
2875
3206
  return `wf_${randomBytes(4).toString("hex")}`;
@@ -2905,11 +3236,11 @@ function createWorkflowInstance(definition, definitionPath, goal, sessionId) {
2905
3236
  };
2906
3237
  }
2907
3238
  function readWorkflowInstance(directory, instanceId) {
2908
- const filePath = join7(directory, WORKFLOWS_STATE_DIR, instanceId, INSTANCE_STATE_FILE);
3239
+ const filePath = join8(directory, WORKFLOWS_STATE_DIR, instanceId, INSTANCE_STATE_FILE);
2909
3240
  try {
2910
3241
  if (!existsSync9(filePath))
2911
3242
  return null;
2912
- const raw = readFileSync7(filePath, "utf-8");
3243
+ const raw = readFileSync8(filePath, "utf-8");
2913
3244
  const parsed = JSON.parse(raw);
2914
3245
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
2915
3246
  return null;
@@ -2922,22 +3253,22 @@ function readWorkflowInstance(directory, instanceId) {
2922
3253
  }
2923
3254
  function writeWorkflowInstance(directory, instance) {
2924
3255
  try {
2925
- const dir = join7(directory, WORKFLOWS_STATE_DIR, instance.instance_id);
3256
+ const dir = join8(directory, WORKFLOWS_STATE_DIR, instance.instance_id);
2926
3257
  if (!existsSync9(dir)) {
2927
- mkdirSync3(dir, { recursive: true });
3258
+ mkdirSync4(dir, { recursive: true });
2928
3259
  }
2929
- writeFileSync2(join7(dir, INSTANCE_STATE_FILE), JSON.stringify(instance, null, 2), "utf-8");
3260
+ writeFileSync3(join8(dir, INSTANCE_STATE_FILE), JSON.stringify(instance, null, 2), "utf-8");
2930
3261
  return true;
2931
3262
  } catch {
2932
3263
  return false;
2933
3264
  }
2934
3265
  }
2935
3266
  function readActiveInstance(directory) {
2936
- const filePath = join7(directory, WORKFLOWS_STATE_DIR, ACTIVE_INSTANCE_FILE);
3267
+ const filePath = join8(directory, WORKFLOWS_STATE_DIR, ACTIVE_INSTANCE_FILE);
2937
3268
  try {
2938
3269
  if (!existsSync9(filePath))
2939
3270
  return null;
2940
- const raw = readFileSync7(filePath, "utf-8");
3271
+ const raw = readFileSync8(filePath, "utf-8");
2941
3272
  const parsed = JSON.parse(raw);
2942
3273
  if (!parsed || typeof parsed !== "object" || typeof parsed.instance_id !== "string")
2943
3274
  return null;
@@ -2948,22 +3279,22 @@ function readActiveInstance(directory) {
2948
3279
  }
2949
3280
  function setActiveInstance(directory, instanceId) {
2950
3281
  try {
2951
- const dir = join7(directory, WORKFLOWS_STATE_DIR);
3282
+ const dir = join8(directory, WORKFLOWS_STATE_DIR);
2952
3283
  if (!existsSync9(dir)) {
2953
- mkdirSync3(dir, { recursive: true });
3284
+ mkdirSync4(dir, { recursive: true });
2954
3285
  }
2955
3286
  const pointer = { instance_id: instanceId };
2956
- writeFileSync2(join7(dir, ACTIVE_INSTANCE_FILE), JSON.stringify(pointer, null, 2), "utf-8");
3287
+ writeFileSync3(join8(dir, ACTIVE_INSTANCE_FILE), JSON.stringify(pointer, null, 2), "utf-8");
2957
3288
  return true;
2958
3289
  } catch {
2959
3290
  return false;
2960
3291
  }
2961
3292
  }
2962
3293
  function clearActiveInstance(directory) {
2963
- const filePath = join7(directory, WORKFLOWS_STATE_DIR, ACTIVE_INSTANCE_FILE);
3294
+ const filePath = join8(directory, WORKFLOWS_STATE_DIR, ACTIVE_INSTANCE_FILE);
2964
3295
  try {
2965
3296
  if (existsSync9(filePath)) {
2966
- unlinkSync2(filePath);
3297
+ unlinkSync3(filePath);
2967
3298
  }
2968
3299
  return true;
2969
3300
  } catch {
@@ -2983,35 +3314,35 @@ import * as os3 from "os";
2983
3314
  import { parse as parseJsonc } from "jsonc-parser";
2984
3315
 
2985
3316
  // src/features/workflow/schema.ts
2986
- import { z as z2 } from "zod";
2987
- var CompletionConfigSchema = z2.object({
2988
- method: z2.enum(["user_confirm", "plan_created", "plan_complete", "review_verdict", "agent_signal"]),
2989
- plan_name: z2.string().optional(),
2990
- keywords: z2.array(z2.string()).optional()
3317
+ import { z as z3 } from "zod";
3318
+ var CompletionConfigSchema = z3.object({
3319
+ method: z3.enum(["user_confirm", "plan_created", "plan_complete", "review_verdict", "agent_signal"]),
3320
+ plan_name: z3.string().optional(),
3321
+ keywords: z3.array(z3.string()).optional()
2991
3322
  });
2992
- var ArtifactRefSchema = z2.object({
2993
- name: z2.string(),
2994
- description: z2.string().optional()
3323
+ var ArtifactRefSchema = z3.object({
3324
+ name: z3.string(),
3325
+ description: z3.string().optional()
2995
3326
  });
2996
- var StepArtifactsSchema = z2.object({
2997
- inputs: z2.array(ArtifactRefSchema).optional(),
2998
- outputs: z2.array(ArtifactRefSchema).optional()
3327
+ var StepArtifactsSchema = z3.object({
3328
+ inputs: z3.array(ArtifactRefSchema).optional(),
3329
+ outputs: z3.array(ArtifactRefSchema).optional()
2999
3330
  });
3000
- var WorkflowStepSchema = z2.object({
3001
- id: z2.string().regex(/^[a-z][a-z0-9-]*$/, "Step ID must be lowercase alphanumeric with hyphens"),
3002
- name: z2.string(),
3003
- type: z2.enum(["interactive", "autonomous", "gate"]),
3004
- agent: z2.string(),
3005
- prompt: z2.string(),
3331
+ var WorkflowStepSchema = z3.object({
3332
+ id: z3.string().regex(/^[a-z][a-z0-9-]*$/, "Step ID must be lowercase alphanumeric with hyphens"),
3333
+ name: z3.string(),
3334
+ type: z3.enum(["interactive", "autonomous", "gate"]),
3335
+ agent: z3.string(),
3336
+ prompt: z3.string(),
3006
3337
  completion: CompletionConfigSchema,
3007
3338
  artifacts: StepArtifactsSchema.optional(),
3008
- on_reject: z2.enum(["pause", "fail"]).optional()
3339
+ on_reject: z3.enum(["pause", "fail"]).optional()
3009
3340
  });
3010
- var WorkflowDefinitionSchema = z2.object({
3011
- name: z2.string().regex(/^[a-z][a-z0-9-]*$/, "Workflow name must be lowercase alphanumeric with hyphens"),
3012
- description: z2.string().optional(),
3013
- version: z2.number().int().positive(),
3014
- steps: z2.array(WorkflowStepSchema).min(1, "Workflow must have at least one step")
3341
+ var WorkflowDefinitionSchema = z3.object({
3342
+ name: z3.string().regex(/^[a-z][a-z0-9-]*$/, "Workflow name must be lowercase alphanumeric with hyphens"),
3343
+ description: z3.string().optional(),
3344
+ version: z3.number().int().positive(),
3345
+ steps: z3.array(WorkflowStepSchema).min(1, "Workflow must have at least one step")
3015
3346
  });
3016
3347
 
3017
3348
  // src/features/workflow/discovery.ts
@@ -3153,7 +3484,7 @@ function truncateSummary(text) {
3153
3484
  }
3154
3485
  // src/features/workflow/completion.ts
3155
3486
  import { existsSync as existsSync11 } from "fs";
3156
- import { join as join9 } from "path";
3487
+ import { join as join10 } from "path";
3157
3488
  var DEFAULT_CONFIRM_KEYWORDS = ["confirmed", "approved", "continue", "done", "let's proceed", "looks good", "lgtm"];
3158
3489
  var VERDICT_APPROVE_RE = /\[\s*APPROVE\s*\]/i;
3159
3490
  var VERDICT_REJECT_RE = /\[\s*REJECT\s*\]/i;
@@ -3205,7 +3536,7 @@ function checkPlanCreated(context) {
3205
3536
  summary: `Plan created at ${matchingPlan}`
3206
3537
  };
3207
3538
  }
3208
- const directPath = join9(directory, ".weave", "plans", `${planName}.md`);
3539
+ const directPath = join10(directory, ".weave", "plans", `${planName}.md`);
3209
3540
  if (existsSync11(directPath)) {
3210
3541
  return {
3211
3542
  complete: true,
@@ -3221,7 +3552,7 @@ function checkPlanComplete(context) {
3221
3552
  if (!planName) {
3222
3553
  return { complete: false, reason: "plan_complete requires plan_name in completion config" };
3223
3554
  }
3224
- const planPath = join9(directory, ".weave", "plans", `${planName}.md`);
3555
+ const planPath = join10(directory, ".weave", "plans", `${planName}.md`);
3225
3556
  if (!existsSync11(planPath)) {
3226
3557
  return { complete: false, reason: `Plan file not found: ${planPath}` };
3227
3558
  }
@@ -4183,8 +4514,8 @@ function clearSession2(sessionId) {
4183
4514
  sessionMap.delete(sessionId);
4184
4515
  }
4185
4516
  // src/features/analytics/storage.ts
4186
- import { existsSync as existsSync12, mkdirSync as mkdirSync4, appendFileSync as appendFileSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync3, statSync as statSync2 } from "fs";
4187
- import { join as join10 } from "path";
4517
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, appendFileSync as appendFileSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync4, statSync as statSync3 } from "fs";
4518
+ import { join as join11 } from "path";
4188
4519
 
4189
4520
  // src/features/analytics/types.ts
4190
4521
  var ANALYTICS_DIR = ".weave/analytics";
@@ -4199,30 +4530,30 @@ function zeroTokenUsage() {
4199
4530
  // src/features/analytics/storage.ts
4200
4531
  var MAX_SESSION_ENTRIES = 1000;
4201
4532
  function ensureAnalyticsDir(directory) {
4202
- const dir = join10(directory, ANALYTICS_DIR);
4203
- mkdirSync4(dir, { recursive: true, mode: 448 });
4533
+ const dir = join11(directory, ANALYTICS_DIR);
4534
+ mkdirSync5(dir, { recursive: true, mode: 448 });
4204
4535
  return dir;
4205
4536
  }
4206
4537
  function appendSessionSummary(directory, summary) {
4207
4538
  try {
4208
4539
  const dir = ensureAnalyticsDir(directory);
4209
- const filePath = join10(dir, SESSION_SUMMARIES_FILE);
4540
+ const filePath = join11(dir, SESSION_SUMMARIES_FILE);
4210
4541
  const line = JSON.stringify(summary) + `
4211
4542
  `;
4212
4543
  appendFileSync2(filePath, line, { encoding: "utf-8", mode: 384 });
4213
4544
  try {
4214
4545
  const TYPICAL_ENTRY_BYTES = 200;
4215
4546
  const rotationSizeThreshold = MAX_SESSION_ENTRIES * TYPICAL_ENTRY_BYTES * 0.9;
4216
- const { size } = statSync2(filePath);
4547
+ const { size } = statSync3(filePath);
4217
4548
  if (size > rotationSizeThreshold) {
4218
- const content = readFileSync9(filePath, "utf-8");
4549
+ const content = readFileSync10(filePath, "utf-8");
4219
4550
  const lines = content.split(`
4220
4551
  `).filter((l) => l.trim().length > 0);
4221
4552
  if (lines.length > MAX_SESSION_ENTRIES) {
4222
4553
  const trimmed = lines.slice(-MAX_SESSION_ENTRIES).join(`
4223
4554
  `) + `
4224
4555
  `;
4225
- writeFileSync3(filePath, trimmed, { encoding: "utf-8", mode: 384 });
4556
+ writeFileSync4(filePath, trimmed, { encoding: "utf-8", mode: 384 });
4226
4557
  }
4227
4558
  }
4228
4559
  } catch {}
@@ -4232,11 +4563,11 @@ function appendSessionSummary(directory, summary) {
4232
4563
  }
4233
4564
  }
4234
4565
  function readSessionSummaries(directory) {
4235
- const filePath = join10(directory, ANALYTICS_DIR, SESSION_SUMMARIES_FILE);
4566
+ const filePath = join11(directory, ANALYTICS_DIR, SESSION_SUMMARIES_FILE);
4236
4567
  try {
4237
4568
  if (!existsSync12(filePath))
4238
4569
  return [];
4239
- const content = readFileSync9(filePath, "utf-8");
4570
+ const content = readFileSync10(filePath, "utf-8");
4240
4571
  const lines = content.split(`
4241
4572
  `).filter((line) => line.trim().length > 0);
4242
4573
  const summaries = [];
@@ -4253,19 +4584,19 @@ function readSessionSummaries(directory) {
4253
4584
  function writeFingerprint(directory, fingerprint) {
4254
4585
  try {
4255
4586
  const dir = ensureAnalyticsDir(directory);
4256
- const filePath = join10(dir, FINGERPRINT_FILE);
4257
- writeFileSync3(filePath, JSON.stringify(fingerprint, null, 2), { encoding: "utf-8", mode: 384 });
4587
+ const filePath = join11(dir, FINGERPRINT_FILE);
4588
+ writeFileSync4(filePath, JSON.stringify(fingerprint, null, 2), { encoding: "utf-8", mode: 384 });
4258
4589
  return true;
4259
4590
  } catch {
4260
4591
  return false;
4261
4592
  }
4262
4593
  }
4263
4594
  function readFingerprint(directory) {
4264
- const filePath = join10(directory, ANALYTICS_DIR, FINGERPRINT_FILE);
4595
+ const filePath = join11(directory, ANALYTICS_DIR, FINGERPRINT_FILE);
4265
4596
  try {
4266
4597
  if (!existsSync12(filePath))
4267
4598
  return null;
4268
- const content = readFileSync9(filePath, "utf-8");
4599
+ const content = readFileSync10(filePath, "utf-8");
4269
4600
  const parsed = JSON.parse(content);
4270
4601
  if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.stack))
4271
4602
  return null;
@@ -4277,23 +4608,23 @@ function readFingerprint(directory) {
4277
4608
  function writeMetricsReport(directory, report) {
4278
4609
  try {
4279
4610
  const dir = ensureAnalyticsDir(directory);
4280
- const filePath = join10(dir, METRICS_REPORTS_FILE);
4611
+ const filePath = join11(dir, METRICS_REPORTS_FILE);
4281
4612
  const line = JSON.stringify(report) + `
4282
4613
  `;
4283
4614
  appendFileSync2(filePath, line, { encoding: "utf-8", mode: 384 });
4284
4615
  try {
4285
4616
  const TYPICAL_ENTRY_BYTES = 200;
4286
4617
  const rotationSizeThreshold = MAX_METRICS_ENTRIES * TYPICAL_ENTRY_BYTES * 0.9;
4287
- const { size } = statSync2(filePath);
4618
+ const { size } = statSync3(filePath);
4288
4619
  if (size > rotationSizeThreshold) {
4289
- const content = readFileSync9(filePath, "utf-8");
4620
+ const content = readFileSync10(filePath, "utf-8");
4290
4621
  const lines = content.split(`
4291
4622
  `).filter((l) => l.trim().length > 0);
4292
4623
  if (lines.length > MAX_METRICS_ENTRIES) {
4293
4624
  const trimmed = lines.slice(-MAX_METRICS_ENTRIES).join(`
4294
4625
  `) + `
4295
4626
  `;
4296
- writeFileSync3(filePath, trimmed, { encoding: "utf-8", mode: 384 });
4627
+ writeFileSync4(filePath, trimmed, { encoding: "utf-8", mode: 384 });
4297
4628
  }
4298
4629
  }
4299
4630
  } catch {}
@@ -4303,11 +4634,11 @@ function writeMetricsReport(directory, report) {
4303
4634
  }
4304
4635
  }
4305
4636
  function readMetricsReports(directory) {
4306
- const filePath = join10(directory, ANALYTICS_DIR, METRICS_REPORTS_FILE);
4637
+ const filePath = join11(directory, ANALYTICS_DIR, METRICS_REPORTS_FILE);
4307
4638
  try {
4308
4639
  if (!existsSync12(filePath))
4309
4640
  return [];
4310
- const content = readFileSync9(filePath, "utf-8");
4641
+ const content = readFileSync10(filePath, "utf-8");
4311
4642
  const lines = content.split(`
4312
4643
  `).filter((line) => line.trim().length > 0);
4313
4644
  const reports = [];
@@ -4468,7 +4799,7 @@ function topTools(summaries, limit = 5) {
4468
4799
  counts[t.tool] = (counts[t.tool] ?? 0) + t.count;
4469
4800
  }
4470
4801
  }
4471
- return Object.entries(counts).map(([tool, count]) => ({ tool, count })).sort((a, b) => b.count - a.count).slice(0, limit);
4802
+ return Object.entries(counts).map(([tool4, count]) => ({ tool: tool4, count })).sort((a, b) => b.count - a.count).slice(0, limit);
4472
4803
  }
4473
4804
  function formatMetricsMarkdown(reports, summaries, args) {
4474
4805
  if (reports.length === 0 && summaries.length === 0) {
@@ -4533,7 +4864,7 @@ function formatMetricsMarkdown(reports, summaries, args) {
4533
4864
  }
4534
4865
 
4535
4866
  // src/features/analytics/plan-parser.ts
4536
- import { readFileSync as readFileSync10 } from "fs";
4867
+ import { readFileSync as readFileSync11 } from "fs";
4537
4868
  function extractSection2(content, heading) {
4538
4869
  const lines = content.split(`
4539
4870
  `);
@@ -4568,7 +4899,7 @@ function extractFilePath2(raw) {
4568
4899
  function extractPlannedFiles(planPath) {
4569
4900
  let content;
4570
4901
  try {
4571
- content = readFileSync10(planPath, "utf-8");
4902
+ content = readFileSync11(planPath, "utf-8");
4572
4903
  } catch {
4573
4904
  return [];
4574
4905
  }
@@ -4718,7 +5049,7 @@ function generateMetricsReport(directory, state) {
4718
5049
  // src/plugin/plugin-interface.ts
4719
5050
  var FINALIZE_TODOS_MARKER = "<!-- weave:finalize-todos -->";
4720
5051
  function createPluginInterface(args) {
4721
- const { pluginConfig, hooks, tools, configHandler, agents, client, directory = "", tracker } = args;
5052
+ const { pluginConfig, hooks, tools, configHandler, agents, client, directory = "", tracker, taskSystemEnabled = false } = args;
4722
5053
  const lastAssistantMessageText = new Map;
4723
5054
  const lastUserMessageText = new Map;
4724
5055
  const todoFinalizedSessions = new Set;
@@ -4730,9 +5061,24 @@ function createPluginInterface(args) {
4730
5061
  agents,
4731
5062
  availableTools: []
4732
5063
  });
4733
- config.agent = result.agents;
4734
- config.command = result.commands;
4735
- if (result.defaultAgent) {
5064
+ const existingAgents = config.agent ?? {};
5065
+ if (Object.keys(existingAgents).length > 0) {
5066
+ log("[config] Merging Weave agents over existing agents", {
5067
+ existingCount: Object.keys(existingAgents).length,
5068
+ weaveCount: Object.keys(result.agents).length,
5069
+ existingKeys: Object.keys(existingAgents)
5070
+ });
5071
+ const collisions = Object.keys(result.agents).filter((key) => (key in existingAgents));
5072
+ if (collisions.length > 0) {
5073
+ log("[config] Weave agents overriding user-defined agents with same name", {
5074
+ overriddenKeys: collisions
5075
+ });
5076
+ }
5077
+ }
5078
+ config.agent = { ...existingAgents, ...result.agents };
5079
+ const existingCommands = config.command ?? {};
5080
+ config.command = { ...existingCommands, ...result.commands };
5081
+ if (result.defaultAgent && !config.default_agent) {
4736
5082
  config.default_agent = result.defaultAgent;
4737
5083
  }
4738
5084
  },
@@ -4804,7 +5150,7 @@ ${result.contextInjection}`;
4804
5150
  `).trim() ?? "";
4805
5151
  if (userText && sessionID) {
4806
5152
  lastUserMessageText.set(sessionID, userText);
4807
- if (!userText.includes(FINALIZE_TODOS_MARKER)) {
5153
+ if (!taskSystemEnabled && !userText.includes(FINALIZE_TODOS_MARKER)) {
4808
5154
  todoFinalizedSessions.delete(sessionID);
4809
5155
  }
4810
5156
  }
@@ -4841,7 +5187,7 @@ ${cmdResult.contextInjection}`;
4841
5187
  const isStartWork = promptText.includes("<session-context>");
4842
5188
  const isContinuation = promptText.includes(CONTINUATION_MARKER);
4843
5189
  const isWorkflowContinuation = promptText.includes(WORKFLOW_CONTINUATION_MARKER);
4844
- const isTodoFinalize = promptText.includes(FINALIZE_TODOS_MARKER);
5190
+ const isTodoFinalize = !taskSystemEnabled && promptText.includes(FINALIZE_TODOS_MARKER);
4845
5191
  const isActiveWorkflow = (() => {
4846
5192
  const wf = getActiveWorkflowInstance(directory);
4847
5193
  return wf != null && wf.status === "running";
@@ -5025,7 +5371,7 @@ ${cmdResult.contextInjection}`;
5025
5371
  }
5026
5372
  }
5027
5373
  }
5028
- if (event.type === "session.idle" && client && !continuationFired) {
5374
+ if (event.type === "session.idle" && client && !continuationFired && !taskSystemEnabled) {
5029
5375
  const evt = event;
5030
5376
  const sessionId = evt.properties?.sessionID ?? "";
5031
5377
  if (sessionId && !todoFinalizedSessions.has(sessionId)) {
@@ -5141,14 +5487,14 @@ Use todowrite NOW to mark all of them as "completed" (or "cancelled" if abandone
5141
5487
  };
5142
5488
  }
5143
5489
  // src/features/analytics/fingerprint.ts
5144
- import { existsSync as existsSync13, readFileSync as readFileSync12, readdirSync as readdirSync5 } from "fs";
5145
- import { join as join12 } from "path";
5490
+ import { existsSync as existsSync13, readFileSync as readFileSync13, readdirSync as readdirSync6 } from "fs";
5491
+ import { join as join13 } from "path";
5146
5492
  import { arch } from "os";
5147
5493
 
5148
5494
  // src/shared/version.ts
5149
- import { readFileSync as readFileSync11 } from "fs";
5495
+ import { readFileSync as readFileSync12 } from "fs";
5150
5496
  import { fileURLToPath } from "url";
5151
- import { dirname as dirname2, join as join11 } from "path";
5497
+ import { dirname as dirname2, join as join12 } from "path";
5152
5498
  var cachedVersion;
5153
5499
  function getWeaveVersion() {
5154
5500
  if (cachedVersion !== undefined)
@@ -5157,7 +5503,7 @@ function getWeaveVersion() {
5157
5503
  const thisDir = dirname2(fileURLToPath(import.meta.url));
5158
5504
  for (const rel of ["../../package.json", "../package.json"]) {
5159
5505
  try {
5160
- const pkg = JSON.parse(readFileSync11(join11(thisDir, rel), "utf-8"));
5506
+ const pkg = JSON.parse(readFileSync12(join12(thisDir, rel), "utf-8"));
5161
5507
  if (pkg.name === "@opencode_weave/weave" && typeof pkg.version === "string") {
5162
5508
  const version = pkg.version;
5163
5509
  cachedVersion = version;
@@ -5262,7 +5608,7 @@ function detectStack(directory) {
5262
5608
  const detected = [];
5263
5609
  for (const marker of STACK_MARKERS) {
5264
5610
  for (const file of marker.files) {
5265
- if (existsSync13(join12(directory, file))) {
5611
+ if (existsSync13(join13(directory, file))) {
5266
5612
  detected.push({
5267
5613
  name: marker.name,
5268
5614
  confidence: marker.confidence,
@@ -5273,9 +5619,9 @@ function detectStack(directory) {
5273
5619
  }
5274
5620
  }
5275
5621
  try {
5276
- const pkgPath = join12(directory, "package.json");
5622
+ const pkgPath = join13(directory, "package.json");
5277
5623
  if (existsSync13(pkgPath)) {
5278
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
5624
+ const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
5279
5625
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
5280
5626
  if (deps.react) {
5281
5627
  detected.push({
@@ -5288,7 +5634,7 @@ function detectStack(directory) {
5288
5634
  } catch {}
5289
5635
  if (!detected.some((d) => d.name === "dotnet")) {
5290
5636
  try {
5291
- const entries = readdirSync5(directory);
5637
+ const entries = readdirSync6(directory);
5292
5638
  const dotnetFile = entries.find((e) => e.endsWith(".csproj") || e.endsWith(".fsproj") || e.endsWith(".sln"));
5293
5639
  if (dotnetFile) {
5294
5640
  detected.push({
@@ -5308,27 +5654,27 @@ function detectStack(directory) {
5308
5654
  });
5309
5655
  }
5310
5656
  function detectPackageManager(directory) {
5311
- if (existsSync13(join12(directory, "bun.lockb")))
5657
+ if (existsSync13(join13(directory, "bun.lockb")))
5312
5658
  return "bun";
5313
- if (existsSync13(join12(directory, "pnpm-lock.yaml")))
5659
+ if (existsSync13(join13(directory, "pnpm-lock.yaml")))
5314
5660
  return "pnpm";
5315
- if (existsSync13(join12(directory, "yarn.lock")))
5661
+ if (existsSync13(join13(directory, "yarn.lock")))
5316
5662
  return "yarn";
5317
- if (existsSync13(join12(directory, "package-lock.json")))
5663
+ if (existsSync13(join13(directory, "package-lock.json")))
5318
5664
  return "npm";
5319
- if (existsSync13(join12(directory, "package.json")))
5665
+ if (existsSync13(join13(directory, "package.json")))
5320
5666
  return "npm";
5321
5667
  return;
5322
5668
  }
5323
5669
  function detectMonorepo(directory) {
5324
5670
  for (const marker of MONOREPO_MARKERS) {
5325
- if (existsSync13(join12(directory, marker)))
5671
+ if (existsSync13(join13(directory, marker)))
5326
5672
  return true;
5327
5673
  }
5328
5674
  try {
5329
- const pkgPath = join12(directory, "package.json");
5675
+ const pkgPath = join13(directory, "package.json");
5330
5676
  if (existsSync13(pkgPath)) {
5331
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
5677
+ const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
5332
5678
  if (pkg.workspaces)
5333
5679
  return true;
5334
5680
  }
@@ -5486,7 +5832,7 @@ class SessionTracker {
5486
5832
  const now = new Date;
5487
5833
  const startedAt = new Date(session.startedAt);
5488
5834
  const durationMs = now.getTime() - startedAt.getTime();
5489
- const toolUsage = Object.entries(session.toolCounts).map(([tool, count]) => ({ tool, count }));
5835
+ const toolUsage = Object.entries(session.toolCounts).map(([tool4, count]) => ({ tool: tool4, count }));
5490
5836
  const totalToolCalls = toolUsage.reduce((sum, entry) => sum + entry.count, 0);
5491
5837
  const summary = {
5492
5838
  sessionId,
@@ -5548,7 +5894,7 @@ var WeavePlugin = async (ctx) => {
5548
5894
  const analyticsEnabled = pluginConfig.analytics?.enabled === true;
5549
5895
  const fingerprintEnabled = analyticsEnabled && pluginConfig.analytics?.use_fingerprint === true;
5550
5896
  const fingerprint = fingerprintEnabled ? getOrCreateFingerprint(ctx.directory) : null;
5551
- const configDir = join13(ctx.directory, ".opencode");
5897
+ const configDir = join14(ctx.directory, ".opencode");
5552
5898
  const toolsResult = await createTools({ ctx, pluginConfig });
5553
5899
  const managers = createManagers({ ctx, pluginConfig, resolveSkills: toolsResult.resolveSkillsFn, fingerprint, configDir });
5554
5900
  const hooks = createHooks({ pluginConfig, isHookEnabled, directory: ctx.directory, analyticsEnabled });
@@ -5561,7 +5907,8 @@ var WeavePlugin = async (ctx) => {
5561
5907
  agents: managers.agents,
5562
5908
  client: ctx.client,
5563
5909
  directory: ctx.directory,
5564
- tracker: analytics?.tracker
5910
+ tracker: analytics?.tracker,
5911
+ taskSystemEnabled: pluginConfig.experimental?.task_system !== false
5565
5912
  });
5566
5913
  };
5567
5914
  var src_default = WeavePlugin;