@opencode_weave/weave 0.3.2

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.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +317 -0
  3. package/dist/agents/agent-builder.d.ts +10 -0
  4. package/dist/agents/builtin-agents.d.ts +16 -0
  5. package/dist/agents/dynamic-prompt-builder.d.ts +29 -0
  6. package/dist/agents/index.d.ts +8 -0
  7. package/dist/agents/loom/default.d.ts +2 -0
  8. package/dist/agents/loom/index.d.ts +2 -0
  9. package/dist/agents/model-resolution.d.ts +19 -0
  10. package/dist/agents/pattern/default.d.ts +2 -0
  11. package/dist/agents/pattern/index.d.ts +2 -0
  12. package/dist/agents/shuttle/default.d.ts +2 -0
  13. package/dist/agents/shuttle/index.d.ts +2 -0
  14. package/dist/agents/spindle/default.d.ts +2 -0
  15. package/dist/agents/spindle/index.d.ts +2 -0
  16. package/dist/agents/tapestry/default.d.ts +2 -0
  17. package/dist/agents/tapestry/index.d.ts +2 -0
  18. package/dist/agents/thread/default.d.ts +2 -0
  19. package/dist/agents/thread/index.d.ts +2 -0
  20. package/dist/agents/types.d.ts +82 -0
  21. package/dist/agents/warp/default.d.ts +2 -0
  22. package/dist/agents/warp/index.d.ts +2 -0
  23. package/dist/agents/weft/default.d.ts +2 -0
  24. package/dist/agents/weft/index.d.ts +2 -0
  25. package/dist/config/index.d.ts +3 -0
  26. package/dist/config/loader.d.ts +2 -0
  27. package/dist/config/merge.d.ts +3 -0
  28. package/dist/config/schema.d.ts +162 -0
  29. package/dist/create-managers.d.ts +18 -0
  30. package/dist/create-tools.d.ts +16 -0
  31. package/dist/features/builtin-commands/commands.d.ts +2 -0
  32. package/dist/features/builtin-commands/index.d.ts +2 -0
  33. package/dist/features/builtin-commands/templates/start-work.d.ts +1 -0
  34. package/dist/features/builtin-commands/types.d.ts +16 -0
  35. package/dist/features/skill-loader/builtin-skills.d.ts +2 -0
  36. package/dist/features/skill-loader/discovery.d.ts +12 -0
  37. package/dist/features/skill-loader/index.d.ts +7 -0
  38. package/dist/features/skill-loader/loader.d.ts +6 -0
  39. package/dist/features/skill-loader/merger.d.ts +2 -0
  40. package/dist/features/skill-loader/resolver.d.ts +6 -0
  41. package/dist/features/skill-loader/types.d.ts +25 -0
  42. package/dist/features/work-state/constants.d.ts +8 -0
  43. package/dist/features/work-state/index.d.ts +3 -0
  44. package/dist/features/work-state/storage.d.ts +38 -0
  45. package/dist/features/work-state/types.d.ts +27 -0
  46. package/dist/hooks/context-window-monitor.d.ts +19 -0
  47. package/dist/hooks/create-hooks.d.ts +32 -0
  48. package/dist/hooks/first-message-variant.d.ts +5 -0
  49. package/dist/hooks/index.d.ts +12 -0
  50. package/dist/hooks/keyword-detector.d.ts +8 -0
  51. package/dist/hooks/pattern-md-only.d.ts +13 -0
  52. package/dist/hooks/rules-injector.d.ts +6 -0
  53. package/dist/hooks/start-work-hook.d.ts +20 -0
  54. package/dist/hooks/verification-reminder.d.ts +22 -0
  55. package/dist/hooks/work-continuation.d.ts +17 -0
  56. package/dist/hooks/write-existing-file-guard.d.ts +14 -0
  57. package/dist/index.d.ts +5 -0
  58. package/dist/index.js +2191 -0
  59. package/dist/managers/background-manager.d.ts +68 -0
  60. package/dist/managers/config-handler.d.ts +54 -0
  61. package/dist/managers/index.d.ts +6 -0
  62. package/dist/managers/skill-mcp-manager.d.ts +30 -0
  63. package/dist/plugin/index.d.ts +1 -0
  64. package/dist/plugin/plugin-interface.d.ts +12 -0
  65. package/dist/plugin/types.d.ts +5 -0
  66. package/dist/shared/agent-display-names.d.ts +20 -0
  67. package/dist/shared/index.d.ts +3 -0
  68. package/dist/shared/log.d.ts +2 -0
  69. package/dist/shared/types.d.ts +6 -0
  70. package/dist/tools/index.d.ts +4 -0
  71. package/dist/tools/permissions.d.ts +18 -0
  72. package/dist/tools/registry.d.ts +29 -0
  73. package/package.json +52 -0
package/dist/index.js ADDED
@@ -0,0 +1,2191 @@
1
+ // src/config/loader.ts
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { join as join2 } from "node:path";
4
+ import { homedir } from "node:os";
5
+ import { parse } from "jsonc-parser";
6
+
7
+ // src/config/schema.ts
8
+ import { z } from "zod";
9
+ var AgentOverrideConfigSchema = z.object({
10
+ model: z.string().optional(),
11
+ fallback_models: z.array(z.string()).optional(),
12
+ variant: z.string().optional(),
13
+ category: z.string().optional(),
14
+ skills: z.array(z.string()).optional(),
15
+ temperature: z.number().min(0).max(2).optional(),
16
+ top_p: z.number().min(0).max(1).optional(),
17
+ prompt: z.string().optional(),
18
+ prompt_append: z.string().optional(),
19
+ tools: z.record(z.string(), z.boolean()).optional(),
20
+ disable: z.boolean().optional(),
21
+ mode: z.enum(["subagent", "primary", "all"]).optional(),
22
+ maxTokens: z.number().optional()
23
+ });
24
+ var AgentOverridesSchema = z.record(z.string(), AgentOverrideConfigSchema);
25
+ var CategoryConfigSchema = z.object({
26
+ description: z.string().optional(),
27
+ model: z.string().optional(),
28
+ fallback_models: z.array(z.string()).optional(),
29
+ variant: z.string().optional(),
30
+ temperature: z.number().min(0).max(2).optional(),
31
+ top_p: z.number().min(0).max(1).optional(),
32
+ maxTokens: z.number().optional(),
33
+ tools: z.record(z.string(), z.boolean()).optional(),
34
+ prompt_append: z.string().optional(),
35
+ disable: z.boolean().optional()
36
+ });
37
+ var CategoriesConfigSchema = z.record(z.string(), CategoryConfigSchema);
38
+ var BackgroundConfigSchema = z.object({
39
+ defaultConcurrency: z.number().min(1).optional(),
40
+ providerConcurrency: z.record(z.string(), z.number().min(0)).optional(),
41
+ modelConcurrency: z.record(z.string(), z.number().min(0)).optional(),
42
+ staleTimeoutMs: z.number().min(60000).optional()
43
+ });
44
+ var TmuxConfigSchema = z.object({
45
+ enabled: z.boolean().optional(),
46
+ layout: z.enum(["main-horizontal", "main-vertical", "tiled", "even-horizontal", "even-vertical"]).optional(),
47
+ main_pane_size: z.number().optional()
48
+ });
49
+ var SkillsConfigSchema = z.object({
50
+ paths: z.array(z.string()).optional(),
51
+ recursive: z.boolean().optional()
52
+ });
53
+ var ExperimentalConfigSchema = z.object({
54
+ plugin_load_timeout_ms: z.number().min(1000).optional(),
55
+ context_window_warning_threshold: z.number().min(0).max(1).optional(),
56
+ context_window_critical_threshold: z.number().min(0).max(1).optional()
57
+ });
58
+ var WeaveConfigSchema = z.object({
59
+ $schema: z.string().optional(),
60
+ agents: AgentOverridesSchema.optional(),
61
+ categories: CategoriesConfigSchema.optional(),
62
+ disabled_hooks: z.array(z.string()).optional(),
63
+ disabled_tools: z.array(z.string()).optional(),
64
+ disabled_agents: z.array(z.string()).optional(),
65
+ disabled_skills: z.array(z.string()).optional(),
66
+ background: BackgroundConfigSchema.optional(),
67
+ tmux: TmuxConfigSchema.optional(),
68
+ skills: SkillsConfigSchema.optional(),
69
+ experimental: ExperimentalConfigSchema.optional()
70
+ });
71
+
72
+ // src/config/merge.ts
73
+ function deepMergeObjects(base, override) {
74
+ const result = { ...base };
75
+ for (const key of Object.keys(override)) {
76
+ const overrideVal = override[key];
77
+ const baseVal = base[key];
78
+ if (overrideVal !== null && typeof overrideVal === "object" && !Array.isArray(overrideVal) && baseVal !== null && typeof baseVal === "object" && !Array.isArray(baseVal)) {
79
+ result[key] = deepMergeObjects(baseVal, overrideVal);
80
+ } else if (overrideVal !== undefined) {
81
+ result[key] = overrideVal;
82
+ }
83
+ }
84
+ return result;
85
+ }
86
+ function mergeStringArrays(base, override) {
87
+ if (!base && !override)
88
+ return;
89
+ return [...new Set([...base ?? [], ...override ?? []])];
90
+ }
91
+ function mergeConfigs(user, project) {
92
+ return {
93
+ ...user,
94
+ ...project,
95
+ agents: user.agents || project.agents ? deepMergeObjects(user.agents ?? {}, project.agents ?? {}) : undefined,
96
+ categories: user.categories || project.categories ? deepMergeObjects(user.categories ?? {}, project.categories ?? {}) : undefined,
97
+ disabled_hooks: mergeStringArrays(user.disabled_hooks, project.disabled_hooks),
98
+ disabled_tools: mergeStringArrays(user.disabled_tools, project.disabled_tools),
99
+ disabled_agents: mergeStringArrays(user.disabled_agents, project.disabled_agents),
100
+ disabled_skills: mergeStringArrays(user.disabled_skills, project.disabled_skills),
101
+ background: project.background ?? user.background,
102
+ tmux: project.tmux ?? user.tmux,
103
+ skills: project.skills ?? user.skills,
104
+ experimental: user.experimental || project.experimental ? { ...user.experimental, ...project.experimental } : undefined
105
+ };
106
+ }
107
+
108
+ // src/shared/log.ts
109
+ import * as fs from "fs";
110
+ import * as path from "path";
111
+ import * as os from "os";
112
+ var LOG_FILE = path.join(os.tmpdir(), "weave-opencode.log");
113
+ function log(message, data) {
114
+ try {
115
+ const timestamp = new Date().toISOString();
116
+ const entry = `[${timestamp}] ${message}${data !== undefined ? " " + JSON.stringify(data) : ""}
117
+ `;
118
+ fs.appendFileSync(LOG_FILE, entry);
119
+ } catch {}
120
+ }
121
+
122
+ // src/config/loader.ts
123
+ function readJsoncFile(filePath) {
124
+ try {
125
+ const text = readFileSync(filePath, "utf-8");
126
+ const errors = [];
127
+ const parsed = parse(text, errors);
128
+ if (errors.length > 0) {
129
+ log(`JSONC parse warnings in ${filePath}: ${errors.length} issue(s)`);
130
+ }
131
+ return parsed ?? {};
132
+ } catch (e) {
133
+ log(`Failed to read config file ${filePath}`, e);
134
+ return {};
135
+ }
136
+ }
137
+ function detectConfigFile(basePath) {
138
+ const jsoncPath = basePath + ".jsonc";
139
+ if (existsSync(jsoncPath))
140
+ return jsoncPath;
141
+ const jsonPath = basePath + ".json";
142
+ if (existsSync(jsonPath))
143
+ return jsonPath;
144
+ return null;
145
+ }
146
+ function loadWeaveConfig(directory, _ctx) {
147
+ const userBasePath = join2(homedir(), ".config", "opencode", "weave-opencode");
148
+ const projectBasePath = join2(directory, ".opencode", "weave-opencode");
149
+ const userConfigPath = detectConfigFile(userBasePath);
150
+ const projectConfigPath = detectConfigFile(projectBasePath);
151
+ const userRaw = userConfigPath ? readJsoncFile(userConfigPath) : {};
152
+ const projectRaw = projectConfigPath ? readJsoncFile(projectConfigPath) : {};
153
+ const merged = mergeConfigs(userRaw, projectRaw);
154
+ const result = WeaveConfigSchema.safeParse(merged);
155
+ if (!result.success) {
156
+ log("WeaveConfig validation errors — using defaults", result.error.issues);
157
+ return WeaveConfigSchema.parse({});
158
+ }
159
+ return result.data;
160
+ }
161
+
162
+ // src/shared/agent-display-names.ts
163
+ var AGENT_DISPLAY_NAMES = {
164
+ loom: "Loom (Main Orchestrator)",
165
+ tapestry: "Tapestry (Execution Orchestrator)",
166
+ shuttle: "shuttle",
167
+ pattern: "pattern",
168
+ thread: "thread",
169
+ spindle: "spindle",
170
+ warp: "warp"
171
+ };
172
+ function getAgentDisplayName(configKey) {
173
+ const exactMatch = AGENT_DISPLAY_NAMES[configKey];
174
+ if (exactMatch !== undefined)
175
+ return exactMatch;
176
+ const lowerKey = configKey.toLowerCase();
177
+ for (const [k, v] of Object.entries(AGENT_DISPLAY_NAMES)) {
178
+ if (k.toLowerCase() === lowerKey)
179
+ return v;
180
+ }
181
+ return configKey;
182
+ }
183
+ var REVERSE_DISPLAY_NAMES = Object.fromEntries(Object.entries(AGENT_DISPLAY_NAMES).map(([key, displayName]) => [displayName.toLowerCase(), key]));
184
+
185
+ // src/features/builtin-commands/templates/start-work.ts
186
+ var START_WORK_TEMPLATE = `You are being activated by the /start-work command to execute a Weave plan.
187
+
188
+ ## Your Mission
189
+ Read and execute the work plan, completing each task systematically.
190
+
191
+ ## Startup Procedure
192
+
193
+ 1. **Check for active work state**: Read \`.weave/state.json\` to see if there's a plan already in progress.
194
+ 2. **If resuming**: The system has injected context below with the active plan path and progress. Read the plan file, find the first unchecked \`- [ ]\` task, and continue from there.
195
+ 3. **If starting fresh**: The system has selected a plan and created work state. Read the plan file and begin from the first task.
196
+
197
+ ## Execution Loop
198
+
199
+ For each unchecked \`- [ ]\` task in the plan:
200
+
201
+ 1. **Read** the task description, acceptance criteria, and any references
202
+ 2. **Execute** the task — write code, run commands, create files as needed
203
+ 3. **Verify** the work — run tests, check for errors, validate acceptance criteria
204
+ 4. **Mark complete** — use the Edit tool to change \`- [ ]\` to \`- [x]\` in the plan file
205
+ 5. **Move on** — find the next unchecked task and repeat
206
+
207
+ ## Rules
208
+
209
+ - Work through tasks **top to bottom** unless dependencies require a different order
210
+ - **Verify every task** before marking it complete
211
+ - If blocked on a task, document the reason as a comment in the plan and move to the next unblocked task
212
+ - Report progress after each task: "Completed task N/M: [title]"
213
+ - Do NOT stop until all checkboxes are checked or you are explicitly told to stop
214
+ - After all tasks are complete, report a final summary`;
215
+
216
+ // src/features/builtin-commands/commands.ts
217
+ var BUILTIN_COMMANDS = {
218
+ "start-work": {
219
+ name: "start-work",
220
+ description: "Start executing a Weave plan created by Pattern",
221
+ agent: "tapestry",
222
+ template: `<command-instruction>
223
+ ${START_WORK_TEMPLATE}
224
+ </command-instruction>
225
+ <session-context>Session ID: $SESSION_ID Timestamp: $TIMESTAMP</session-context>
226
+ <user-request>$ARGUMENTS</user-request>`,
227
+ argumentHint: "[plan-name]"
228
+ }
229
+ };
230
+ // src/managers/config-handler.ts
231
+ class ConfigHandler {
232
+ pluginConfig;
233
+ constructor(options) {
234
+ this.pluginConfig = options.pluginConfig;
235
+ }
236
+ async handle(input) {
237
+ const { pluginConfig, agents = {}, availableTools = [] } = input;
238
+ this.applyProviderConfig();
239
+ const resolvedAgents = this.applyAgentConfig(agents, pluginConfig);
240
+ const resolvedTools = this.applyToolConfig(availableTools, pluginConfig);
241
+ const resolvedMcps = this.applyMcpConfig();
242
+ const resolvedCommands = this.applyCommandConfig();
243
+ this.applySkillConfig();
244
+ const defaultAgent = this.resolveDefaultAgent(resolvedAgents);
245
+ return {
246
+ agents: resolvedAgents,
247
+ defaultAgent,
248
+ tools: resolvedTools,
249
+ mcps: resolvedMcps,
250
+ commands: resolvedCommands
251
+ };
252
+ }
253
+ applyProviderConfig() {}
254
+ applyAgentConfig(agents, pluginConfig) {
255
+ const disabledSet = new Set(pluginConfig.disabled_agents ?? []);
256
+ const overrides = pluginConfig.agents ?? {};
257
+ const result = {};
258
+ for (const [name, agentConfig] of Object.entries(agents)) {
259
+ if (disabledSet.has(name)) {
260
+ continue;
261
+ }
262
+ const override = overrides[name];
263
+ const merged = override ? { ...agentConfig, ...override } : { ...agentConfig };
264
+ const displayName = getAgentDisplayName(name);
265
+ result[displayName] = merged;
266
+ }
267
+ return result;
268
+ }
269
+ resolveDefaultAgent(agents) {
270
+ const loomDisplayName = getAgentDisplayName("loom");
271
+ if (agents[loomDisplayName])
272
+ return loomDisplayName;
273
+ const firstKey = Object.keys(agents)[0];
274
+ return firstKey;
275
+ }
276
+ applyToolConfig(availableTools, pluginConfig) {
277
+ const disabledSet = new Set(pluginConfig.disabled_tools ?? []);
278
+ return availableTools.filter((tool) => !disabledSet.has(tool));
279
+ }
280
+ applyMcpConfig() {
281
+ return {};
282
+ }
283
+ applyCommandConfig() {
284
+ const commands = structuredClone(BUILTIN_COMMANDS);
285
+ for (const cmd of Object.values(commands)) {
286
+ if (cmd?.agent && typeof cmd.agent === "string") {
287
+ cmd.agent = getAgentDisplayName(cmd.agent);
288
+ }
289
+ }
290
+ return commands;
291
+ }
292
+ applySkillConfig() {}
293
+ }
294
+
295
+ // src/managers/background-manager.ts
296
+ var TERMINAL_STATUSES = new Set(["completed", "failed", "cancelled"]);
297
+
298
+ class BackgroundManager {
299
+ tasks = new Map;
300
+ maxConcurrent;
301
+ constructor(options) {
302
+ this.maxConcurrent = options?.maxConcurrent ?? 5;
303
+ }
304
+ spawn(options) {
305
+ const runningCount = this.getRunningCount();
306
+ if (runningCount >= this.maxConcurrent) {
307
+ throw new Error(`Concurrency limit reached: ${runningCount}/${this.maxConcurrent} tasks running`);
308
+ }
309
+ const id = crypto.randomUUID();
310
+ const record = {
311
+ id,
312
+ status: "pending",
313
+ options,
314
+ startedAt: new Date
315
+ };
316
+ this.tasks.set(id, record);
317
+ return id;
318
+ }
319
+ getTask(taskId) {
320
+ return this.tasks.get(taskId);
321
+ }
322
+ cancel(taskId) {
323
+ const record = this.tasks.get(taskId);
324
+ if (!record) {
325
+ return false;
326
+ }
327
+ if (TERMINAL_STATUSES.has(record.status)) {
328
+ return false;
329
+ }
330
+ record.status = "cancelled";
331
+ record.completedAt = new Date;
332
+ return true;
333
+ }
334
+ cancelAll() {
335
+ for (const record of this.tasks.values()) {
336
+ if (!TERMINAL_STATUSES.has(record.status)) {
337
+ record.status = "cancelled";
338
+ record.completedAt = new Date;
339
+ }
340
+ }
341
+ }
342
+ list(filter) {
343
+ const all = Array.from(this.tasks.values());
344
+ if (filter?.status !== undefined) {
345
+ return all.filter((t) => t.status === filter.status);
346
+ }
347
+ return all;
348
+ }
349
+ getRunningCount() {
350
+ let count = 0;
351
+ for (const record of this.tasks.values()) {
352
+ if (record.status === "running") {
353
+ count++;
354
+ }
355
+ }
356
+ return count;
357
+ }
358
+ }
359
+
360
+ // src/managers/skill-mcp-manager.ts
361
+ function createStdioClient(config, info) {
362
+ const { command, args = [] } = config;
363
+ const { serverName, skillName } = info;
364
+ if (!command) {
365
+ throw new Error(`missing 'command' field for stdio MCP server '${serverName}' in skill '${skillName}'`);
366
+ }
367
+ const env = { ...process.env, ...config.env ?? {} };
368
+ let proc = null;
369
+ try {
370
+ proc = Bun.spawn([command, ...args], {
371
+ env,
372
+ stdin: "pipe",
373
+ stdout: "pipe",
374
+ stderr: "pipe"
375
+ });
376
+ } catch (spawnError) {
377
+ const msg = spawnError instanceof Error ? spawnError.message : String(spawnError);
378
+ throw new Error(`stdio MCP server '${serverName}' in skill '${skillName}' failed to start: ${msg}`);
379
+ }
380
+ return {
381
+ async callTool(_params) {
382
+ throw new Error("not implemented in v1");
383
+ },
384
+ async close() {
385
+ if (proc) {
386
+ proc.kill();
387
+ proc = null;
388
+ }
389
+ }
390
+ };
391
+ }
392
+ function getClientKey(info) {
393
+ return `${info.sessionID}:${info.skillName}:${info.serverName}`;
394
+ }
395
+
396
+ class SkillMcpManager {
397
+ clients = new Map;
398
+ async getOrCreateClient(info, config) {
399
+ const key = getClientKey(info);
400
+ const existing = this.clients.get(key);
401
+ if (existing) {
402
+ return existing;
403
+ }
404
+ const { serverName, skillName } = info;
405
+ if (config.type === "http") {
406
+ throw new Error("HTTP MCP not supported in v1");
407
+ }
408
+ if (!config.command) {
409
+ throw new Error(`missing 'command' field for stdio MCP server '${serverName}' in skill '${skillName}'`);
410
+ }
411
+ const client = createStdioClient(config, info);
412
+ this.clients.set(key, client);
413
+ return client;
414
+ }
415
+ async disconnectSession(sessionID) {
416
+ const prefix = `${sessionID}:`;
417
+ const keysToRemove = [];
418
+ for (const key of this.clients.keys()) {
419
+ if (key.startsWith(prefix)) {
420
+ keysToRemove.push(key);
421
+ }
422
+ }
423
+ await Promise.all(keysToRemove.map(async (key) => {
424
+ const client = this.clients.get(key);
425
+ if (client) {
426
+ await client.close();
427
+ this.clients.delete(key);
428
+ }
429
+ }));
430
+ }
431
+ async disconnectAll() {
432
+ await Promise.all(Array.from(this.clients.entries()).map(async ([key, client]) => {
433
+ await client.close();
434
+ this.clients.delete(key);
435
+ }));
436
+ this.clients.clear();
437
+ }
438
+ getConnectedServers() {
439
+ return Array.from(this.clients.keys());
440
+ }
441
+ isConnected(info) {
442
+ return this.clients.has(getClientKey(info));
443
+ }
444
+ async callTool(info, config, name, args) {
445
+ const maxAttempts = 3;
446
+ let lastError = null;
447
+ for (let attempt = 1;attempt <= maxAttempts; attempt++) {
448
+ try {
449
+ const client = await this.getOrCreateClient(info, config);
450
+ const result = await client.callTool({ name, arguments: args });
451
+ return result.content;
452
+ } catch (error) {
453
+ lastError = error instanceof Error ? error : new Error(String(error));
454
+ if (!lastError.message.toLowerCase().includes("not connected")) {
455
+ throw lastError;
456
+ }
457
+ if (attempt === maxAttempts) {
458
+ throw new Error(`Failed after 3 reconnection attempts: ${lastError.message}`);
459
+ }
460
+ }
461
+ }
462
+ throw lastError ?? new Error("Operation failed with unknown error");
463
+ }
464
+ }
465
+
466
+ // src/agents/loom/default.ts
467
+ var LOOM_DEFAULTS = {
468
+ temperature: 0.1,
469
+ description: "Loom (Main Orchestrator)",
470
+ prompt: `<Role>
471
+ Loom — main orchestrator for Weave.
472
+ Plan tasks, coordinate work, and delegate to specialized agents.
473
+ You are the team lead. Understand the request, break it into tasks, delegate intelligently.
474
+ </Role>
475
+
476
+ <Discipline>
477
+ TODO OBSESSION (NON-NEGOTIABLE):
478
+ - 2+ steps → todowrite FIRST, atomic breakdown
479
+ - Mark in_progress before starting (ONE at a time)
480
+ - Mark completed IMMEDIATELY after each step
481
+ - NEVER batch completions
482
+
483
+ No todos on multi-step work = INCOMPLETE WORK.
484
+ </Discipline>
485
+
486
+ <SidebarTodos>
487
+ The user sees a Todo sidebar (~35 char width). Use todowrite strategically:
488
+
489
+ WHEN PLANNING (multi-step work):
490
+ - Create "in_progress": "Planning: [brief desc]"
491
+ - When plan ready: mark completed, add "Plan ready — /start-work"
492
+
493
+ WHEN DELEGATING TO AGENTS:
494
+ - Create "in_progress": "[agent]: [task]" (e.g. "thread: scan models")
495
+ - Mark "completed" when agent returns results
496
+ - If multiple delegations: one todo per active agent
497
+
498
+ WHEN DOING QUICK TASKS (no plan needed):
499
+ - One "in_progress" todo for current step
500
+ - Mark "completed" immediately when done
501
+
502
+ FORMAT RULES:
503
+ - Max 35 chars per todo content
504
+ - Max 5 visible todos at any time
505
+ - in_progress = yellow highlight — use for ACTIVE work only
506
+ - Prefix delegations with agent name
507
+ - After all work done: mark everything completed (sidebar hides)
508
+ </SidebarTodos>
509
+
510
+ <Delegation>
511
+ - Use thread for fast codebase exploration (read-only, cheap)
512
+ - Use spindle for external docs and research (read-only)
513
+ - Use pattern for detailed planning before complex implementations
514
+ - Use /start-work to hand off to Tapestry for todo-list driven execution of multi-step plans
515
+ - Use shuttle for category-specific specialized work
516
+ - Use Weft for reviewing completed work or validating plans before execution
517
+ - Use Warp for security audits when changes touch auth, crypto, tokens, or input validation
518
+ - Delegate aggressively to keep your context lean
519
+ </Delegation>
520
+
521
+ <PlanWorkflow>
522
+ For complex tasks that benefit from structured planning before execution:
523
+
524
+ 1. PLAN: Delegate to Pattern to produce a plan saved to \`.weave/plans/{name}.md\`
525
+ - Pattern researches the codebase, produces a structured plan with \`- [ ]\` checkboxes
526
+ - Pattern ONLY writes .md files in .weave/ — it never writes code
527
+ 2. REVIEW (optional): For complex plans, delegate to Weft to validate the plan before execution
528
+ - Weft reads the plan, verifies file references, checks executability
529
+ - If Weft rejects, send issues back to Pattern for revision
530
+ 3. EXECUTE: Tell the user to run \`/start-work\` to begin execution
531
+ - /start-work loads the plan, creates work state at \`.weave/state.json\`, and switches to Tapestry
532
+ - Tapestry reads the plan and works through tasks, marking checkboxes as it goes
533
+ 4. RESUME: If work was interrupted, \`/start-work\` resumes from the last unchecked task
534
+
535
+ When to use this workflow vs. direct execution:
536
+ - USE plan workflow: Large features, multi-file refactors, anything with 5+ steps or architectural decisions
537
+ - SKIP plan workflow: Quick fixes, single-file changes, simple questions
538
+ </PlanWorkflow>
539
+
540
+ <ReviewWorkflow>
541
+ After significant implementation work completes:
542
+ - Delegate to Weft to review the changes
543
+ - Weft is read-only and approval-biased — it rejects only for real problems
544
+ - If Weft approves: proceed confidently
545
+ - If Weft rejects: address the specific blocking issues, then re-review
546
+
547
+ When to invoke Weft:
548
+ - After completing a multi-step plan
549
+ - After any task that touches 3+ files
550
+ - Before shipping to the user when quality matters
551
+ - When you're unsure if work meets acceptance criteria
552
+
553
+ When to skip Weft:
554
+ - Single-file trivial changes
555
+ - User explicitly says "skip review"
556
+ - Simple question-answering (no code changes)
557
+
558
+ For security-relevant changes, also delegate to Warp:
559
+ - Warp is read-only and skeptical-biased — it rejects when security is at risk
560
+ - Warp self-triages: if no security-relevant changes, it fast-exits with APPROVE
561
+ - If Warp rejects: address the specific security issues before shipping
562
+ - Run Warp in parallel with Weft for comprehensive coverage
563
+ </ReviewWorkflow>
564
+
565
+ <Style>
566
+ - Start immediately. No acknowledgments.
567
+ - Dense > verbose.
568
+ - Match user's communication style.
569
+ </Style>`
570
+ };
571
+
572
+ // src/agents/loom/index.ts
573
+ var createLoomAgent = (model) => ({
574
+ ...LOOM_DEFAULTS,
575
+ model,
576
+ mode: "primary"
577
+ });
578
+ createLoomAgent.mode = "primary";
579
+
580
+ // src/agents/tapestry/default.ts
581
+ var TAPESTRY_DEFAULTS = {
582
+ temperature: 0.1,
583
+ description: "Tapestry (Execution Orchestrator)",
584
+ tools: {
585
+ task: false,
586
+ call_weave_agent: false
587
+ },
588
+ prompt: `<Role>
589
+ Tapestry — execution orchestrator for Weave.
590
+ You manage todo-list driven execution of multi-step plans.
591
+ Break plans into atomic tasks, track progress rigorously, execute sequentially.
592
+ You do NOT spawn subagents — you execute directly.
593
+ </Role>
594
+
595
+ <Discipline>
596
+ TODO OBSESSION (NON-NEGOTIABLE):
597
+ - Load existing todos first — never re-plan if a plan exists
598
+ - Mark in_progress before starting EACH task (ONE at a time)
599
+ - Mark completed IMMEDIATELY after finishing
600
+ - NEVER skip steps, NEVER batch completions
601
+
602
+ Execution without todos = lost work.
603
+ </Discipline>
604
+
605
+ <SidebarTodos>
606
+ The user sees a Todo sidebar (~35 char width). Use todowrite to keep it useful:
607
+
608
+ WHEN STARTING A PLAN:
609
+ - Create one "in_progress" todo for the current task (short title)
610
+ - Create "pending" todos for the next 2-3 upcoming tasks
611
+ - Create one summary todo: "[plan-name] 0/N done"
612
+
613
+ WHEN COMPLETING A TASK:
614
+ - Mark current task todo "completed"
615
+ - Mark next task todo "in_progress"
616
+ - Add next upcoming task as "pending" (keep 2-3 pending visible)
617
+ - Update summary todo: "[plan-name] K/N done"
618
+
619
+ WHEN BLOCKED:
620
+ - Mark current task "cancelled" with reason
621
+ - Set next unblocked task to "in_progress"
622
+
623
+ WHEN PLAN COMPLETES:
624
+ - Mark all remaining todos "completed"
625
+ - Update summary: "[plan-name] DONE N/N"
626
+
627
+ FORMAT RULES:
628
+ - Max 35 chars per todo content
629
+ - Use task number prefix: "3/7: Add user model"
630
+ - Summary todo always present during execution
631
+ - Max 5 visible todos (1 summary + 1 in_progress + 2-3 pending)
632
+ - in_progress = yellow highlight — use for CURRENT task only
633
+ </SidebarTodos>
634
+
635
+ <PlanExecution>
636
+ When activated by /start-work with a plan file:
637
+
638
+ 1. READ the plan file first — understand the full scope
639
+ 2. FIND the first unchecked \`- [ ]\` task
640
+ 3. For each task:
641
+ a. Read the task description, files, and acceptance criteria
642
+ b. Execute the work (write code, run commands, create files)
643
+ c. Verify: Read changed files, run tests, check acceptance criteria. If uncertain about quality, note that Loom should invoke Weft for formal review.
644
+ d. Mark complete: use Edit tool to change \`- [ ]\` to \`- [x]\` in the plan file
645
+ e. Report: "Completed task N/M: [title]"
646
+ 4. CONTINUE to the next unchecked task
647
+ 5. When ALL checkboxes are checked, report final summary
648
+
649
+ NEVER stop mid-plan unless explicitly told to or completely blocked.
650
+ </PlanExecution>
651
+
652
+ <Execution>
653
+ - Work through tasks top to bottom
654
+ - Verify each step before marking complete
655
+ - If blocked: document reason, move to next unblocked task
656
+ - Report completion with evidence (test output, file paths, commands run)
657
+ </Execution>
658
+
659
+ <Style>
660
+ - Terse status updates only
661
+ - No meta-commentary
662
+ - Dense > verbose
663
+ </Style>`
664
+ };
665
+
666
+ // src/agents/tapestry/index.ts
667
+ var createTapestryAgent = (model) => ({
668
+ ...TAPESTRY_DEFAULTS,
669
+ tools: { ...TAPESTRY_DEFAULTS.tools },
670
+ model,
671
+ mode: "primary"
672
+ });
673
+ createTapestryAgent.mode = "primary";
674
+
675
+ // src/agents/shuttle/default.ts
676
+ var SHUTTLE_DEFAULTS = {
677
+ temperature: 0.2,
678
+ description: "Shuttle (Domain Specialist)",
679
+ prompt: `<Role>
680
+ Shuttle — category-based specialist worker for Weave.
681
+ You execute domain-specific tasks assigned by the orchestrator.
682
+ You have full tool access and specialize based on your assigned category.
683
+ </Role>
684
+
685
+ <Execution>
686
+ - Execute the assigned task completely and precisely
687
+ - Use all available tools as needed for your domain
688
+ - Verify your work before reporting completion
689
+ - Be thorough: partial work is worse than asking for clarification
690
+ </Execution>
691
+
692
+ <Style>
693
+ - Start immediately. No acknowledgments.
694
+ - Report results with evidence.
695
+ - Dense > verbose.
696
+ </Style>`
697
+ };
698
+
699
+ // src/agents/shuttle/index.ts
700
+ var createShuttleAgent = (model) => ({
701
+ ...SHUTTLE_DEFAULTS,
702
+ model,
703
+ mode: "all"
704
+ });
705
+ createShuttleAgent.mode = "all";
706
+
707
+ // src/agents/pattern/default.ts
708
+ var PATTERN_DEFAULTS = {
709
+ temperature: 0.3,
710
+ description: "Pattern (Strategic Planner)",
711
+ prompt: `<Role>
712
+ Pattern — strategic planner for Weave.
713
+ You analyze requirements, research the codebase, and produce detailed implementation plans.
714
+ You think before acting. Plans should be concrete, not abstract.
715
+ You NEVER implement — you produce plans ONLY.
716
+ </Role>
717
+
718
+ <Planning>
719
+ A good plan includes:
720
+ - Clear objective and scope
721
+ - Files to create/modify with exact paths
722
+ - Implementation order (what depends on what)
723
+ - Test strategy (what to test, how)
724
+ - Potential pitfalls and how to handle them
725
+
726
+ Do NOT start implementing — produce the plan ONLY.
727
+ </Planning>
728
+
729
+ <PlanOutput>
730
+ Save plans to \`.weave/plans/{slug}.md\` where {slug} is a kebab-case name derived from the task.
731
+
732
+ Use this structure:
733
+
734
+ \`\`\`markdown
735
+ # {Plan Title}
736
+
737
+ ## TL;DR
738
+ > **Summary**: [1-2 sentence overview]
739
+ > **Estimated Effort**: [Quick | Short | Medium | Large | XL]
740
+
741
+ ## Context
742
+ ### Original Request
743
+ [What the user asked for]
744
+ ### Key Findings
745
+ [What you discovered researching the codebase]
746
+
747
+ ## Objectives
748
+ ### Core Objective
749
+ [The primary goal]
750
+ ### Deliverables
751
+ - [ ] [Concrete deliverable 1]
752
+ - [ ] [Concrete deliverable 2]
753
+ ### Definition of Done
754
+ - [ ] [Verifiable condition — ideally a command to run]
755
+ ### Guardrails (Must NOT)
756
+ - [Things explicitly out of scope or forbidden]
757
+
758
+ ## TODOs
759
+
760
+ - [ ] 1. [Task Title]
761
+ **What**: [Specific description]
762
+ **Files**: [Exact paths to create/modify]
763
+ **Acceptance**: [How to verify this task is done]
764
+
765
+ - [ ] 2. [Task Title]
766
+ ...
767
+
768
+ ## Verification
769
+ - [ ] All tests pass
770
+ - [ ] No regressions
771
+ - [ ] [Project-specific checks]
772
+ \`\`\`
773
+
774
+ CRITICAL: Use \`- [ ]\` checkboxes for ALL actionable items. The /start-work system tracks progress by counting these checkboxes.
775
+ </PlanOutput>
776
+
777
+ <Constraints>
778
+ - ONLY write .md files inside the .weave/ directory
779
+ - NEVER write code files (.ts, .js, .py, .go, etc.)
780
+ - NEVER edit source code
781
+ - After completing a plan, tell the user: "Plan saved to \`.weave/plans/{name}.md\`. Run /start-work to begin execution."
782
+ </Constraints>
783
+
784
+ <Research>
785
+ - Read relevant files before planning
786
+ - Check existing patterns in the codebase
787
+ - Understand dependencies before proposing changes
788
+ - Use thread (codebase explorer) for broad searches
789
+ - Use spindle (external researcher) for library/API docs
790
+ </Research>
791
+
792
+ <Style>
793
+ - Structured markdown output
794
+ - Numbered steps with clear acceptance criteria
795
+ - Concise — every word earns its place
796
+ </Style>`
797
+ };
798
+
799
+ // src/agents/pattern/index.ts
800
+ var createPatternAgent = (model) => ({
801
+ ...PATTERN_DEFAULTS,
802
+ model,
803
+ mode: "subagent"
804
+ });
805
+ createPatternAgent.mode = "subagent";
806
+
807
+ // src/agents/thread/default.ts
808
+ var THREAD_DEFAULTS = {
809
+ temperature: 0,
810
+ description: "Thread (Codebase Explorer)",
811
+ tools: {
812
+ write: false,
813
+ edit: false,
814
+ task: false,
815
+ call_weave_agent: false
816
+ },
817
+ prompt: `<Role>
818
+ Thread — codebase explorer for Weave.
819
+ You navigate and analyze code fast. Read-only access only.
820
+ Answer questions about the codebase with precision and speed.
821
+ </Role>
822
+
823
+ <Exploration>
824
+ - Use grep/glob/read tools to traverse the codebase
825
+ - Answer questions directly with file paths and line numbers
826
+ - Never guess — always verify with actual file reads
827
+ - Summarize findings concisely
828
+ </Exploration>
829
+
830
+ <Constraints>
831
+ - READ ONLY — never write, edit, or create files
832
+ - Never spawn subagents
833
+ - One clear answer, backed by evidence
834
+ </Constraints>`
835
+ };
836
+
837
+ // src/agents/thread/index.ts
838
+ var createThreadAgent = (model) => ({
839
+ ...THREAD_DEFAULTS,
840
+ tools: { ...THREAD_DEFAULTS.tools },
841
+ model,
842
+ mode: "subagent"
843
+ });
844
+ createThreadAgent.mode = "subagent";
845
+
846
+ // src/agents/spindle/default.ts
847
+ var SPINDLE_DEFAULTS = {
848
+ temperature: 0.1,
849
+ description: "Spindle (External Researcher)",
850
+ tools: {
851
+ write: false,
852
+ edit: false,
853
+ task: false,
854
+ call_weave_agent: false
855
+ },
856
+ prompt: `<Role>
857
+ Spindle — external researcher for Weave.
858
+ You search documentation, APIs, and external sources to answer questions.
859
+ Read-only access only. Never write or modify files.
860
+ </Role>
861
+
862
+ <Research>
863
+ - Search the web, read docs, fetch URLs as needed
864
+ - Synthesize findings from multiple sources
865
+ - Cite sources with URLs or file paths
866
+ - Report confidence level when information is uncertain
867
+ </Research>
868
+
869
+ <Constraints>
870
+ - READ ONLY — never write, edit, or create files
871
+ - Never spawn subagents
872
+ - Always cite your sources
873
+ </Constraints>`
874
+ };
875
+
876
+ // src/agents/spindle/index.ts
877
+ var createSpindleAgent = (model) => ({
878
+ ...SPINDLE_DEFAULTS,
879
+ tools: { ...SPINDLE_DEFAULTS.tools },
880
+ model,
881
+ mode: "subagent"
882
+ });
883
+ createSpindleAgent.mode = "subagent";
884
+
885
+ // src/agents/weft/default.ts
886
+ var WEFT_DEFAULTS = {
887
+ temperature: 0.1,
888
+ description: "Weft (Reviewer/Auditor)",
889
+ tools: {
890
+ write: false,
891
+ edit: false,
892
+ task: false,
893
+ call_weave_agent: false
894
+ },
895
+ prompt: `<Role>
896
+ Weft — reviewer and auditor for Weave.
897
+ You review completed work and plans with a critical but fair eye.
898
+ Read-only access only. You verify, you do not implement.
899
+ </Role>
900
+
901
+ <ReviewModes>
902
+ You operate in two modes depending on what you're asked to review:
903
+
904
+ **Plan Review** (reviewing Pattern's .weave/plans/*.md output):
905
+ - Verify referenced files actually exist (read them)
906
+ - Check each task has enough context to start working
907
+ - Look for contradictions or impossible requirements
908
+ - Do NOT question the author's approach or architecture choices
909
+
910
+ **Work Review** (reviewing completed implementation):
911
+ - Read every changed file (use git diff --stat, then Read each file)
912
+ - Check the code actually does what the task required
913
+ - Look for stubs, TODOs, placeholders, hardcoded values
914
+ - Verify tests exist and test real behavior
915
+ - Check for scope creep (changes outside the task spec)
916
+ </ReviewModes>
917
+
918
+ <Verdict>
919
+ Always end with a structured verdict:
920
+
921
+ **[APPROVE]** or **[REJECT]**
922
+
923
+ **Summary**: 1-2 sentences explaining the verdict.
924
+
925
+ If REJECT, list **Blocking Issues** (max 3):
926
+ 1. [Specific issue + what needs to change]
927
+ 2. [Specific issue + what needs to change]
928
+ 3. [Specific issue + what needs to change]
929
+
930
+ Each issue must be:
931
+ - Specific (exact file path, exact task, exact problem)
932
+ - Actionable (what exactly needs to change)
933
+ - Blocking (work genuinely cannot ship without this fix)
934
+ </Verdict>
935
+
936
+ <ApprovalBias>
937
+ APPROVE by default. REJECT only for true blockers.
938
+
939
+ NOT blocking issues (do not reject for these):
940
+ - Missing edge case handling
941
+ - Stylistic preferences
942
+ - "Could be clearer" suggestions
943
+ - Minor ambiguities a developer can resolve
944
+ - Suboptimal but working approaches
945
+
946
+ BLOCKING issues (reject for these):
947
+ - Referenced files don't exist
948
+ - Code doesn't do what the task required
949
+ - Tests are fake (expect(true).toBe(true))
950
+ - Critical logic errors in the happy path
951
+ - Task is impossible to start (zero context)
952
+ </ApprovalBias>
953
+
954
+ <Constraints>
955
+ - READ ONLY — never write, edit, or create files
956
+ - Never spawn subagents
957
+ - Max 3 blocking issues per rejection
958
+ - Be specific — file paths, line numbers, exact problems
959
+ - Dense > verbose. No filler.
960
+ </Constraints>`
961
+ };
962
+
963
+ // src/agents/weft/index.ts
964
+ var createWeftAgent = (model) => ({
965
+ ...WEFT_DEFAULTS,
966
+ tools: { ...WEFT_DEFAULTS.tools },
967
+ model,
968
+ mode: "subagent"
969
+ });
970
+ createWeftAgent.mode = "subagent";
971
+
972
+ // src/agents/warp/default.ts
973
+ var WARP_DEFAULTS = {
974
+ temperature: 0.1,
975
+ description: "Warp (Security Auditor)",
976
+ tools: {
977
+ write: false,
978
+ edit: false,
979
+ task: false,
980
+ call_weave_agent: false
981
+ },
982
+ prompt: `<Role>
983
+ Warp — security and specification compliance auditor for Weave.
984
+ You audit code changes for security vulnerabilities and specification violations.
985
+ Read-only access only. You audit, you do not implement.
986
+ </Role>
987
+
988
+ <Triage>
989
+ You are ALWAYS invoked on reviews. Self-triage to avoid wasting time on non-security changes.
990
+
991
+ **Step 1: Diff scan** (fast)
992
+ Run \`git diff --stat\` to see what files changed. If the changeset is purely:
993
+ - Documentation (.md files only)
994
+ - Test-only changes (no production code)
995
+ - CSS/styling-only changes
996
+ - Configuration comments or formatting
997
+
998
+ Then FAST EXIT with:
999
+ **[APPROVE]**
1000
+ **Summary**: No security-relevant changes detected. (Diff: [brief stat])
1001
+
1002
+ **Step 2: Pattern grep** (if Step 1 didn't fast-exit)
1003
+ Grep the changed files for security-sensitive patterns:
1004
+ - Auth/token handling: \`token\`, \`jwt\`, \`session\`, \`cookie\`, \`bearer\`, \`oauth\`, \`oidc\`, \`saml\`
1005
+ - Crypto: \`hash\`, \`encrypt\`, \`decrypt\`, \`hmac\`, \`sign\`, \`verify\`, \`bcrypt\`, \`argon\`, \`pbkdf\`
1006
+ - Input handling: \`sanitize\`, \`escape\`, \`validate\`, \`innerHTML\`, \`eval\`, \`exec\`, \`spawn\`, \`sql\`, \`query\`
1007
+ - Secrets: \`secret\`, \`password\`, \`api_key\`, \`apikey\`, \`private_key\`, \`credential\`
1008
+ - Network: \`cors\`, \`csp\`, \`helmet\`, \`https\`, \`redirect\`, \`origin\`, \`referer\`
1009
+ - Headers: \`set-cookie\`, \`x-frame\`, \`strict-transport\`, \`content-security-policy\`
1010
+
1011
+ If NO patterns match, FAST EXIT with [APPROVE].
1012
+ If patterns match, proceed to DEEP REVIEW.
1013
+
1014
+ **Step 3: Deep review** (only when triggered)
1015
+ Read each security-relevant changed file in full. Apply SecurityReview and SpecificationCompliance checks.
1016
+ </Triage>
1017
+
1018
+ <SecurityReview>
1019
+ Check for these vulnerability classes in changed code:
1020
+
1021
+ **Injection**
1022
+ - SQL injection: parameterized queries required, no string concatenation for SQL
1023
+ - XSS: output encoding, no raw innerHTML with user input, CSP headers
1024
+ - Command injection: no shell interpolation of user input, use execFile over exec
1025
+ - Path traversal: validate/normalize file paths, reject \`../\` sequences
1026
+
1027
+ **Authentication & Authorization**
1028
+ - Auth bypass: every protected endpoint checks auth before processing
1029
+ - Privilege escalation: role checks are server-side, not client-side
1030
+ - Session management: secure, httpOnly, sameSite cookies; session invalidation on logout
1031
+ - Password handling: bcrypt/argon2 only, never SHA/MD5 for passwords, salt per-user
1032
+
1033
+ **Token Handling**
1034
+ - JWT: verify signature before trusting claims, check exp/nbf/iss/aud
1035
+ - Refresh tokens: stored securely, rotated on use, bound to user
1036
+ - CSRF: state parameter in OAuth flows, anti-CSRF tokens on state-changing endpoints
1037
+ - Token leakage: tokens never in URLs, logs, or error messages
1038
+
1039
+ **Cryptography**
1040
+ - Algorithm selection: no MD5/SHA1 for security, minimum AES-256, RSA-2048
1041
+ - Key management: keys not hardcoded, rotatable, stored in env/vault
1042
+ - Random generation: crypto.randomBytes/crypto.getRandomValues, never Math.random for security
1043
+
1044
+ **Data Exposure**
1045
+ - Error leakage: stack traces and internal details hidden from end users
1046
+ - Logging: no sensitive data (tokens, passwords, PII) in log output
1047
+ - API responses: no over-fetching of sensitive fields
1048
+
1049
+ **Insecure Defaults**
1050
+ - CORS: not \`*\` in production, credentials mode requires explicit origins
1051
+ - HTTPS: redirects enforced, HSTS headers present
1052
+ - Debug mode: disabled in production configs
1053
+ </SecurityReview>
1054
+
1055
+ <SpecificationCompliance>
1056
+ When code implements a known protocol, verify compliance against the relevant specification.
1057
+
1058
+ **Built-in Spec Reference Table:**
1059
+
1060
+ | Spec | Key Requirements |
1061
+ |------|-----------------|
1062
+ | RFC 6749 (OAuth 2.0) | Authorization code flow requires PKCE for public clients, redirect_uri exact match, state parameter REQUIRED |
1063
+ | RFC 7636 (PKCE) | code_verifier 43-128 chars, code_challenge_method=S256 (plain only for constrained devices) |
1064
+ | RFC 7519 (JWT) | Validate exp, nbf, iss, aud before trusting claims; reject alg=none; typ header present |
1065
+ | RFC 7517 (JWK) | Key rotation via jwks_uri, kid matching, reject unknown key types |
1066
+ | RFC 7009 (Token Revocation) | Revoke both access + refresh tokens, return 200 even for invalid tokens |
1067
+ | OIDC Core 1.0 | nonce REQUIRED in implicit/hybrid flows, sub claim is user identifier, id_token signature verification |
1068
+ | WebAuthn Level 2 | Challenge must be random >=16 bytes, origin validation, RP ID matching, attestation verification |
1069
+ | RFC 6238 (TOTP) | Default period=30s, digits=6, algorithm=SHA1; clock drift tolerance (1-2 steps) |
1070
+ | RFC 4226 (HOTP) | Counter synchronization, resync window, throttling after failed attempts |
1071
+ | CORS (Fetch Standard) | Preflight for non-simple requests, Access-Control-Allow-Origin not \`*\` with credentials |
1072
+ | CSP (Level 3) | script-src avoids \`unsafe-inline\`/\`unsafe-eval\`, default-src as fallback |
1073
+
1074
+ **Verification Protocol:**
1075
+ 1. Use built-in knowledge (table above) as the primary reference
1076
+ 2. If confidence is below 90% on a spec requirement, use webfetch to verify against the actual RFC/spec document
1077
+ 3. If the project has a \`.weave/specs.json\` file, check it for project-specific spec requirements
1078
+
1079
+ **\`.weave/specs.json\` format** (optional, project-provided):
1080
+ \`\`\`json
1081
+ {
1082
+ "specs": [
1083
+ {
1084
+ "name": "OAuth 2.0",
1085
+ "url": "https://datatracker.ietf.org/doc/html/rfc6749",
1086
+ "requirements": ["PKCE required for all public clients", "state parameter mandatory"]
1087
+ }
1088
+ ]
1089
+ }
1090
+ \`\`\`
1091
+
1092
+ **Citing specs in findings**: Every spec-related finding MUST include:
1093
+ - The spec name and section (e.g., "RFC 6749 Section 4.1.1")
1094
+ - The specific requirement being violated
1095
+ - What the code does vs. what it should do
1096
+ </SpecificationCompliance>
1097
+
1098
+ <Verdict>
1099
+ Always end with a structured verdict:
1100
+
1101
+ **[APPROVE]** or **[REJECT]**
1102
+
1103
+ **Summary**: 1-2 sentences explaining the verdict.
1104
+
1105
+ If REJECT, list **Blocking Issues** (max 3):
1106
+ 1. [Specific issue + spec citation if applicable + what needs to change]
1107
+ 2. [Specific issue + spec citation if applicable + what needs to change]
1108
+ 3. [Specific issue + spec citation if applicable + what needs to change]
1109
+
1110
+ Each issue must be:
1111
+ - Specific (exact file path, exact line/function, exact problem)
1112
+ - Actionable (what exactly needs to change)
1113
+ - Blocking (genuine security risk or spec violation)
1114
+ - Cited (reference spec section when applicable)
1115
+ </Verdict>
1116
+
1117
+ <SkepticalBias>
1118
+ REJECT by default when security patterns are detected. APPROVE only when confident.
1119
+
1120
+ BLOCKING issues (reject for these):
1121
+ - Any authentication or authorization bypass
1122
+ - Unparameterized SQL/NoSQL queries with user input
1123
+ - Missing CSRF protection on state-changing endpoints
1124
+ - Hardcoded secrets, API keys, or private keys
1125
+ - Broken cryptography (MD5/SHA1 for security, ECB mode, weak keys)
1126
+ - JWT without signature verification or alg=none accepted
1127
+ - OAuth flows missing PKCE or state parameter
1128
+ - Token/password leakage in logs or URLs
1129
+ - Missing input validation on security boundaries
1130
+ - CORS wildcard with credentials
1131
+
1132
+ NOT blocking (note but do not reject):
1133
+ - Defense-in-depth improvements (nice-to-have additional layers)
1134
+ - Non-security code style preferences
1135
+ - Performance optimizations unrelated to DoS
1136
+ - Missing security headers on non-sensitive endpoints
1137
+ </SkepticalBias>
1138
+
1139
+ <Constraints>
1140
+ - READ ONLY — never write, edit, or create files
1141
+ - Never spawn subagents
1142
+ - Max 3 blocking issues per rejection
1143
+ - Every spec-related finding must cite the spec name and section
1144
+ - Be specific — file paths, line numbers, exact problems
1145
+ - Dense > verbose. No filler.
1146
+ </Constraints>`
1147
+ };
1148
+
1149
+ // src/agents/warp/index.ts
1150
+ var createWarpAgent = (model) => ({
1151
+ ...WARP_DEFAULTS,
1152
+ tools: { ...WARP_DEFAULTS.tools },
1153
+ model,
1154
+ mode: "subagent"
1155
+ });
1156
+ createWarpAgent.mode = "subagent";
1157
+
1158
+ // src/agents/model-resolution.ts
1159
+ var AGENT_MODEL_REQUIREMENTS = {
1160
+ loom: {
1161
+ fallbackChain: [
1162
+ { providers: ["github-copilot"], model: "claude-opus-4.6" },
1163
+ { providers: ["anthropic"], model: "claude-opus-4" },
1164
+ { providers: ["openai"], model: "gpt-5" }
1165
+ ]
1166
+ },
1167
+ tapestry: {
1168
+ fallbackChain: [
1169
+ { providers: ["github-copilot"], model: "claude-sonnet-4.6" },
1170
+ { providers: ["anthropic"], model: "claude-sonnet-4" },
1171
+ { providers: ["openai"], model: "gpt-5" }
1172
+ ]
1173
+ },
1174
+ shuttle: {
1175
+ fallbackChain: [
1176
+ { providers: ["github-copilot"], model: "claude-sonnet-4.6" },
1177
+ { providers: ["anthropic"], model: "claude-sonnet-4" },
1178
+ { providers: ["openai"], model: "gpt-5" }
1179
+ ]
1180
+ },
1181
+ pattern: {
1182
+ fallbackChain: [
1183
+ { providers: ["github-copilot"], model: "claude-opus-4.6" },
1184
+ { providers: ["anthropic"], model: "claude-opus-4" },
1185
+ { providers: ["openai"], model: "gpt-5" }
1186
+ ]
1187
+ },
1188
+ thread: {
1189
+ fallbackChain: [
1190
+ { providers: ["github-copilot"], model: "claude-haiku-4.5" },
1191
+ { providers: ["anthropic"], model: "claude-haiku-4" },
1192
+ { providers: ["google"], model: "gemini-3-flash" }
1193
+ ]
1194
+ },
1195
+ spindle: {
1196
+ fallbackChain: [
1197
+ { providers: ["github-copilot"], model: "claude-haiku-4.5" },
1198
+ { providers: ["anthropic"], model: "claude-haiku-4" },
1199
+ { providers: ["google"], model: "gemini-3-flash" }
1200
+ ]
1201
+ },
1202
+ weft: {
1203
+ fallbackChain: [
1204
+ { providers: ["github-copilot"], model: "claude-sonnet-4.6" },
1205
+ { providers: ["anthropic"], model: "claude-sonnet-4" },
1206
+ { providers: ["openai"], model: "gpt-5" }
1207
+ ]
1208
+ },
1209
+ warp: {
1210
+ fallbackChain: [
1211
+ { providers: ["github-copilot"], model: "claude-opus-4.6" },
1212
+ { providers: ["anthropic"], model: "claude-opus-4" },
1213
+ { providers: ["openai"], model: "gpt-5" }
1214
+ ]
1215
+ }
1216
+ };
1217
+ function resolveAgentModel(agentName, options) {
1218
+ const { availableModels, agentMode, uiSelectedModel, categoryModel, overrideModel, systemDefaultModel } = options;
1219
+ const requirement = AGENT_MODEL_REQUIREMENTS[agentName];
1220
+ if (overrideModel)
1221
+ return overrideModel;
1222
+ if (uiSelectedModel && (agentMode === "primary" || agentMode === "all")) {
1223
+ return uiSelectedModel;
1224
+ }
1225
+ if (categoryModel && availableModels.has(categoryModel))
1226
+ return categoryModel;
1227
+ for (const entry of requirement.fallbackChain) {
1228
+ for (const provider of entry.providers) {
1229
+ const qualified = `${provider}/${entry.model}`;
1230
+ if (availableModels.has(qualified))
1231
+ return qualified;
1232
+ if (availableModels.has(entry.model))
1233
+ return entry.model;
1234
+ }
1235
+ }
1236
+ if (systemDefaultModel)
1237
+ return systemDefaultModel;
1238
+ const first = requirement.fallbackChain[0];
1239
+ if (first && first.providers.length > 0) {
1240
+ return `${first.providers[0]}/${first.model}`;
1241
+ }
1242
+ return "github-copilot/claude-opus-4.6";
1243
+ }
1244
+
1245
+ // src/agents/types.ts
1246
+ function isFactory(source) {
1247
+ return typeof source === "function";
1248
+ }
1249
+
1250
+ // src/agents/agent-builder.ts
1251
+ function buildAgent(source, model, options) {
1252
+ const base = isFactory(source) ? source(model) : { ...source };
1253
+ if (base.category && options?.categories) {
1254
+ const categoryConfig = options.categories[base.category];
1255
+ if (categoryConfig) {
1256
+ if (!base.model) {
1257
+ base.model = categoryConfig.model;
1258
+ }
1259
+ if (base.temperature === undefined && categoryConfig.temperature !== undefined) {
1260
+ base.temperature = categoryConfig.temperature;
1261
+ }
1262
+ if (base.variant === undefined && categoryConfig.variant !== undefined) {
1263
+ base.variant = categoryConfig.variant;
1264
+ }
1265
+ }
1266
+ }
1267
+ if (base.skills?.length && options?.resolveSkills) {
1268
+ const skillContent = options.resolveSkills(base.skills, options.disabledSkills);
1269
+ if (skillContent) {
1270
+ base.prompt = skillContent + (base.prompt ? `
1271
+
1272
+ ` + base.prompt : "");
1273
+ }
1274
+ }
1275
+ return base;
1276
+ }
1277
+
1278
+ // src/agents/builtin-agents.ts
1279
+ var AGENT_FACTORIES = {
1280
+ loom: createLoomAgent,
1281
+ tapestry: createTapestryAgent,
1282
+ shuttle: createShuttleAgent,
1283
+ pattern: createPatternAgent,
1284
+ thread: createThreadAgent,
1285
+ spindle: createSpindleAgent,
1286
+ weft: createWeftAgent,
1287
+ warp: createWarpAgent
1288
+ };
1289
+ function createBuiltinAgents(options = {}) {
1290
+ const {
1291
+ disabledAgents = [],
1292
+ agentOverrides = {},
1293
+ categories,
1294
+ uiSelectedModel,
1295
+ systemDefaultModel,
1296
+ availableModels = new Set,
1297
+ disabledSkills,
1298
+ resolveSkills
1299
+ } = options;
1300
+ const disabledSet = new Set(disabledAgents);
1301
+ const result = {};
1302
+ for (const [name, factory] of Object.entries(AGENT_FACTORIES)) {
1303
+ if (disabledSet.has(name))
1304
+ continue;
1305
+ const override = agentOverrides[name];
1306
+ const overrideModel = override?.model;
1307
+ const resolvedModel = resolveAgentModel(name, {
1308
+ availableModels,
1309
+ agentMode: factory.mode,
1310
+ uiSelectedModel,
1311
+ systemDefaultModel,
1312
+ overrideModel
1313
+ });
1314
+ const built = buildAgent(factory, resolvedModel, {
1315
+ categories,
1316
+ disabledSkills,
1317
+ resolveSkills
1318
+ });
1319
+ if (override) {
1320
+ if (override.prompt_append) {
1321
+ built.prompt = (built.prompt ? built.prompt + `
1322
+
1323
+ ` : "") + override.prompt_append;
1324
+ }
1325
+ if (override.temperature !== undefined) {
1326
+ built.temperature = override.temperature;
1327
+ }
1328
+ }
1329
+ result[name] = built;
1330
+ }
1331
+ return result;
1332
+ }
1333
+
1334
+ // src/create-managers.ts
1335
+ function createManagers(options) {
1336
+ const { pluginConfig, resolveSkills } = options;
1337
+ const agents = createBuiltinAgents({
1338
+ disabledAgents: pluginConfig.disabled_agents,
1339
+ agentOverrides: pluginConfig.agents,
1340
+ resolveSkills
1341
+ });
1342
+ const configHandler = new ConfigHandler({ pluginConfig });
1343
+ const backgroundManager = new BackgroundManager({
1344
+ maxConcurrent: pluginConfig.background?.defaultConcurrency ?? 5
1345
+ });
1346
+ const skillMcpManager = new SkillMcpManager;
1347
+ return { configHandler, backgroundManager, skillMcpManager, agents };
1348
+ }
1349
+
1350
+ // src/features/skill-loader/loader.ts
1351
+ import * as path3 from "path";
1352
+ import * as os2 from "os";
1353
+
1354
+ // src/features/skill-loader/discovery.ts
1355
+ import * as fs2 from "fs";
1356
+ import * as path2 from "path";
1357
+ function parseFrontmatter(text) {
1358
+ const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/;
1359
+ const match = frontmatterRegex.exec(text);
1360
+ if (!match) {
1361
+ return { metadata: {}, content: text };
1362
+ }
1363
+ const yamlBlock = match[1];
1364
+ const body = match[2];
1365
+ const metadata = {};
1366
+ try {
1367
+ const lines = yamlBlock.split(/\r?\n/);
1368
+ let i = 0;
1369
+ while (i < lines.length) {
1370
+ const line = lines[i];
1371
+ const keyValueMatch = /^(\w[\w-]*):\s*(.*)$/.exec(line);
1372
+ if (!keyValueMatch) {
1373
+ i++;
1374
+ continue;
1375
+ }
1376
+ const key = keyValueMatch[1];
1377
+ const rawValue = keyValueMatch[2].trim();
1378
+ if (rawValue.startsWith("[") && rawValue.endsWith("]")) {
1379
+ const items = rawValue.slice(1, -1).split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, "")).filter((s) => s.length > 0);
1380
+ setMetadataField(metadata, key, items);
1381
+ i++;
1382
+ continue;
1383
+ }
1384
+ if (rawValue === "") {
1385
+ const listItems = [];
1386
+ i++;
1387
+ while (i < lines.length && /^\s+-\s+(.+)$/.test(lines[i])) {
1388
+ const itemMatch = /^\s+-\s+(.+)$/.exec(lines[i]);
1389
+ if (itemMatch) {
1390
+ listItems.push(itemMatch[1].trim().replace(/^['"]|['"]$/g, ""));
1391
+ }
1392
+ i++;
1393
+ }
1394
+ if (listItems.length > 0) {
1395
+ setMetadataField(metadata, key, listItems);
1396
+ }
1397
+ continue;
1398
+ }
1399
+ setMetadataField(metadata, key, rawValue.replace(/^['"]|['"]$/g, ""));
1400
+ i++;
1401
+ }
1402
+ } catch (err) {
1403
+ log("Failed to parse YAML frontmatter", { error: String(err) });
1404
+ return { metadata: {}, content: text };
1405
+ }
1406
+ return { metadata, content: body };
1407
+ }
1408
+ function setMetadataField(metadata, key, value) {
1409
+ switch (key) {
1410
+ case "name":
1411
+ if (typeof value === "string")
1412
+ metadata.name = value;
1413
+ break;
1414
+ case "description":
1415
+ if (typeof value === "string")
1416
+ metadata.description = value;
1417
+ break;
1418
+ case "model":
1419
+ if (typeof value === "string")
1420
+ metadata.model = value;
1421
+ break;
1422
+ case "tools":
1423
+ metadata.tools = value;
1424
+ break;
1425
+ default:
1426
+ break;
1427
+ }
1428
+ }
1429
+ function scanDirectory(options) {
1430
+ const { directory, scope } = options;
1431
+ if (!fs2.existsSync(directory)) {
1432
+ return [];
1433
+ }
1434
+ let entries;
1435
+ try {
1436
+ entries = fs2.readdirSync(directory, { withFileTypes: true });
1437
+ } catch (err) {
1438
+ log("Failed to read skills directory", { directory, error: String(err) });
1439
+ return [];
1440
+ }
1441
+ const skills = [];
1442
+ for (const entry of entries) {
1443
+ const fullPath = path2.join(directory, entry.name);
1444
+ if (entry.isDirectory()) {
1445
+ const skillFile = path2.join(fullPath, "SKILL.md");
1446
+ if (fs2.existsSync(skillFile)) {
1447
+ const skill = loadSkillFile(skillFile, scope);
1448
+ if (skill)
1449
+ skills.push(skill);
1450
+ }
1451
+ continue;
1452
+ }
1453
+ if (entry.isFile() && entry.name === "SKILL.md") {
1454
+ const skill = loadSkillFile(fullPath, scope);
1455
+ if (skill)
1456
+ skills.push(skill);
1457
+ }
1458
+ }
1459
+ return skills;
1460
+ }
1461
+ function loadSkillFile(filePath, scope) {
1462
+ let text;
1463
+ try {
1464
+ text = fs2.readFileSync(filePath, "utf8");
1465
+ } catch (err) {
1466
+ log("Failed to read skill file", { filePath, error: String(err) });
1467
+ return null;
1468
+ }
1469
+ const { metadata, content } = parseFrontmatter(text);
1470
+ if (!metadata.name) {
1471
+ log("Skill file missing name in frontmatter — skipping", { filePath });
1472
+ return null;
1473
+ }
1474
+ return { name: metadata.name, description: metadata.description ?? "", content, scope, path: filePath, model: metadata.model };
1475
+ }
1476
+
1477
+ // src/features/skill-loader/merger.ts
1478
+ var SCOPE_PRIORITY = { project: 3, user: 2, builtin: 1 };
1479
+ function mergeSkills(skills) {
1480
+ const skillMap = new Map;
1481
+ for (const skill of skills) {
1482
+ const existing = skillMap.get(skill.name);
1483
+ if (!existing || (SCOPE_PRIORITY[skill.scope] ?? 0) > (SCOPE_PRIORITY[existing.scope] ?? 0)) {
1484
+ skillMap.set(skill.name, skill);
1485
+ }
1486
+ }
1487
+ return Array.from(skillMap.values());
1488
+ }
1489
+
1490
+ // src/features/skill-loader/builtin-skills.ts
1491
+ function createBuiltinSkills() {
1492
+ return [];
1493
+ }
1494
+
1495
+ // src/features/skill-loader/loader.ts
1496
+ function loadSkills(options = {}) {
1497
+ const { directory, disabledSkills = [] } = options;
1498
+ const projectDir = path3.join(directory ?? process.cwd(), ".opencode", "skills");
1499
+ const userDir = path3.join(os2.homedir(), ".config", "opencode", "weave-opencode", "skills");
1500
+ const projectSkills = scanDirectory({ directory: projectDir, scope: "project" });
1501
+ const userSkills = scanDirectory({ directory: userDir, scope: "user" });
1502
+ const builtinSkills = createBuiltinSkills();
1503
+ const all = [...projectSkills, ...userSkills, ...builtinSkills];
1504
+ const merged = mergeSkills(all);
1505
+ if (disabledSkills.length === 0)
1506
+ return { skills: merged };
1507
+ const disabledSet = new Set(disabledSkills);
1508
+ return { skills: merged.filter((s) => !disabledSet.has(s.name)) };
1509
+ }
1510
+ // src/features/skill-loader/resolver.ts
1511
+ function resolveSkill(name, result) {
1512
+ return result.skills.find((s) => s.name === name)?.content ?? "";
1513
+ }
1514
+ function resolveMultipleSkills(skillNames, disabledSkills, discovered) {
1515
+ if (!discovered)
1516
+ return "";
1517
+ const parts = [];
1518
+ for (const name of skillNames) {
1519
+ if (disabledSkills?.has(name))
1520
+ continue;
1521
+ const content = resolveSkill(name, discovered);
1522
+ if (content) {
1523
+ parts.push(content);
1524
+ }
1525
+ }
1526
+ return parts.join(`
1527
+
1528
+ `);
1529
+ }
1530
+ function createSkillResolver(discovered) {
1531
+ return (skillNames, disabledSkills) => {
1532
+ return resolveMultipleSkills(skillNames, disabledSkills, discovered);
1533
+ };
1534
+ }
1535
+ // src/create-tools.ts
1536
+ async function createTools(options) {
1537
+ const { ctx, pluginConfig } = options;
1538
+ const skillResult = loadSkills({
1539
+ directory: ctx.directory,
1540
+ disabledSkills: pluginConfig.disabled_skills ?? []
1541
+ });
1542
+ const resolveSkillsFn = createSkillResolver(skillResult);
1543
+ const tools = {};
1544
+ return {
1545
+ tools,
1546
+ availableSkills: skillResult.skills,
1547
+ resolveSkillsFn
1548
+ };
1549
+ }
1550
+
1551
+ // src/hooks/context-window-monitor.ts
1552
+ function checkContextWindow(state, thresholds = { warningPct: 0.8, criticalPct: 0.95 }) {
1553
+ const usagePct = state.maxTokens > 0 ? state.usedTokens / state.maxTokens : 0;
1554
+ if (usagePct >= thresholds.criticalPct) {
1555
+ const message = buildRecoveryMessage(state, usagePct);
1556
+ log(`[context-window] CRITICAL ${(usagePct * 100).toFixed(1)}% used in session ${state.sessionId}`);
1557
+ return { action: "recover", usagePct, message };
1558
+ }
1559
+ if (usagePct >= thresholds.warningPct) {
1560
+ log(`[context-window] WARNING ${(usagePct * 100).toFixed(1)}% used in session ${state.sessionId}`);
1561
+ return { action: "warn", usagePct, message: buildWarningMessage(usagePct) };
1562
+ }
1563
+ return { action: "none", usagePct };
1564
+ }
1565
+ function buildWarningMessage(usagePct) {
1566
+ const pct = (usagePct * 100).toFixed(0);
1567
+ return `⚠️ Context window at ${pct}%. Consider wrapping up the current task or spawning a background agent for remaining work.
1568
+
1569
+ Update the sidebar: use todowrite to create or update a todo (in_progress, high priority): "Context: ${pct}% — wrap up soon"`;
1570
+ }
1571
+ function buildRecoveryMessage(state, usagePct) {
1572
+ const pct = (usagePct * 100).toFixed(0);
1573
+ return `\uD83D\uDEA8 Context window at ${pct}% (${state.usedTokens}/${state.maxTokens} tokens).
1574
+
1575
+ IMMEDIATE ACTION REQUIRED:
1576
+ 1. Save your current progress and findings to a notepad or file
1577
+ 2. Summarize completed work and remaining tasks
1578
+ 3. If work remains: spawn a background agent or ask the user to continue in a new session
1579
+ 4. Do NOT attempt large new tasks — wrap up gracefully
1580
+
1581
+ Update the sidebar: use todowrite to create a todo (in_progress, high priority): "CONTEXT ${pct}% — save & stop"`;
1582
+ }
1583
+
1584
+ // src/hooks/write-existing-file-guard.ts
1585
+ import * as fs3 from "fs";
1586
+ function createWriteGuardState() {
1587
+ return { readFiles: new Set };
1588
+ }
1589
+ function trackFileRead(state, filePath) {
1590
+ state.readFiles.add(filePath);
1591
+ }
1592
+ function checkWriteAllowed(state, filePath) {
1593
+ if (!fs3.existsSync(filePath)) {
1594
+ return { allowed: true };
1595
+ }
1596
+ if (state.readFiles.has(filePath)) {
1597
+ return { allowed: true };
1598
+ }
1599
+ const warning = `⚠️ Write guard: Attempting to write to '${filePath}' without reading it first. Read the file before overwriting to avoid data loss.`;
1600
+ log(`[write-guard] BLOCKED write to unread file: ${filePath}`);
1601
+ return { allowed: false, warning };
1602
+ }
1603
+ function createWriteGuard(state) {
1604
+ return {
1605
+ trackRead: (filePath) => trackFileRead(state, filePath),
1606
+ checkWrite: (filePath) => checkWriteAllowed(state, filePath)
1607
+ };
1608
+ }
1609
+
1610
+ // src/hooks/rules-injector.ts
1611
+ import * as fs4 from "fs";
1612
+ import * as path4 from "path";
1613
+ var RULES_FILENAMES = ["AGENTS.md", ".rules", "CLAUDE.md"];
1614
+ function findRulesFile(directory) {
1615
+ for (const filename of RULES_FILENAMES) {
1616
+ const candidate = path4.join(directory, filename);
1617
+ if (fs4.existsSync(candidate)) {
1618
+ return candidate;
1619
+ }
1620
+ }
1621
+ return;
1622
+ }
1623
+ function loadRulesForDirectory(directory) {
1624
+ const rulesFile = findRulesFile(directory);
1625
+ if (!rulesFile)
1626
+ return;
1627
+ try {
1628
+ const content = fs4.readFileSync(rulesFile, "utf8");
1629
+ log(`[rules-injector] Loaded rules from ${rulesFile}`);
1630
+ return content;
1631
+ } catch {
1632
+ log(`[rules-injector] Failed to read rules file: ${rulesFile}`);
1633
+ return;
1634
+ }
1635
+ }
1636
+ function shouldInjectRules(toolName) {
1637
+ return toolName === "read" || toolName === "write" || toolName === "edit";
1638
+ }
1639
+ function getDirectoryFromFilePath(filePath) {
1640
+ return path4.dirname(path4.resolve(filePath));
1641
+ }
1642
+ function buildRulesInjection(rulesContent, directory) {
1643
+ return `<rules source="${directory}">
1644
+ ${rulesContent}
1645
+ </rules>`;
1646
+ }
1647
+ function getRulesForFile(filePath) {
1648
+ const dir = getDirectoryFromFilePath(filePath);
1649
+ const content = loadRulesForDirectory(dir);
1650
+ if (!content)
1651
+ return;
1652
+ return buildRulesInjection(content, dir);
1653
+ }
1654
+
1655
+ // src/hooks/first-message-variant.ts
1656
+ var appliedSessions = new Set;
1657
+ var createdSessions = new Set;
1658
+ function markSessionCreated(sessionId) {
1659
+ createdSessions.add(sessionId);
1660
+ }
1661
+ function markApplied(sessionId) {
1662
+ appliedSessions.add(sessionId);
1663
+ }
1664
+ function shouldApplyVariant(sessionId) {
1665
+ return createdSessions.has(sessionId) && !appliedSessions.has(sessionId);
1666
+ }
1667
+ function clearSession(sessionId) {
1668
+ appliedSessions.delete(sessionId);
1669
+ createdSessions.delete(sessionId);
1670
+ }
1671
+
1672
+ // src/hooks/keyword-detector.ts
1673
+ var DEFAULT_KEYWORD_ACTIONS = [
1674
+ {
1675
+ keyword: "ultrawork",
1676
+ injection: `[ULTRAWORK MODE ACTIVATED]
1677
+ Maximum effort engaged. Use ALL available agents in parallel. No shortcuts. Complete the task fully and deeply before responding.`
1678
+ },
1679
+ {
1680
+ keyword: "ulw",
1681
+ injection: `[ULTRAWORK MODE ACTIVATED]
1682
+ Maximum effort engaged. Use ALL available agents in parallel. No shortcuts. Complete the task fully and deeply before responding.`
1683
+ }
1684
+ ];
1685
+ function detectKeywords(message, actions = DEFAULT_KEYWORD_ACTIONS) {
1686
+ const lower = message.toLowerCase();
1687
+ return actions.filter((a) => lower.includes(a.keyword.toLowerCase()));
1688
+ }
1689
+ function buildKeywordInjection(detected) {
1690
+ if (detected.length === 0)
1691
+ return;
1692
+ return detected.map((a) => a.injection).join(`
1693
+
1694
+ `);
1695
+ }
1696
+ function processMessageForKeywords(message, sessionId, actions) {
1697
+ const detected = detectKeywords(message, actions);
1698
+ if (detected.length > 0) {
1699
+ log(`[keyword-detector] Detected keywords in session ${sessionId}: ${detected.map((a) => a.keyword).join(", ")}`);
1700
+ }
1701
+ return buildKeywordInjection(detected);
1702
+ }
1703
+
1704
+ // src/hooks/pattern-md-only.ts
1705
+ var WRITE_TOOLS = new Set(["write", "edit"]);
1706
+ var WEAVE_DIR_SEGMENT = ".weave";
1707
+ function checkPatternWrite(agentName, toolName, filePath) {
1708
+ if (agentName !== "pattern") {
1709
+ return { allowed: true };
1710
+ }
1711
+ if (!WRITE_TOOLS.has(toolName)) {
1712
+ return { allowed: true };
1713
+ }
1714
+ const normalizedPath = filePath.replace(/\\/g, "/");
1715
+ if (!normalizedPath.includes(`${WEAVE_DIR_SEGMENT}/`)) {
1716
+ return {
1717
+ allowed: false,
1718
+ reason: `Pattern agent can only write to .weave/ directory. Attempted: ${filePath}`
1719
+ };
1720
+ }
1721
+ if (!normalizedPath.endsWith(".md")) {
1722
+ return {
1723
+ allowed: false,
1724
+ reason: `Pattern agent can only write .md files. Attempted: ${filePath}`
1725
+ };
1726
+ }
1727
+ return { allowed: true };
1728
+ }
1729
+
1730
+ // src/features/work-state/constants.ts
1731
+ var WEAVE_DIR = ".weave";
1732
+ var WORK_STATE_FILE = "state.json";
1733
+ var WORK_STATE_PATH = `${WEAVE_DIR}/${WORK_STATE_FILE}`;
1734
+ var PLANS_DIR = `${WEAVE_DIR}/plans`;
1735
+ // src/features/work-state/storage.ts
1736
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync, unlinkSync, mkdirSync, readdirSync as readdirSync2, statSync } from "fs";
1737
+ import { join as join6, basename } from "path";
1738
+ var UNCHECKED_RE = /^[-*]\s*\[\s*\]/gm;
1739
+ var CHECKED_RE = /^[-*]\s*\[[xX]\]/gm;
1740
+ function readWorkState(directory) {
1741
+ const filePath = join6(directory, WEAVE_DIR, WORK_STATE_FILE);
1742
+ try {
1743
+ if (!existsSync5(filePath))
1744
+ return null;
1745
+ const raw = readFileSync4(filePath, "utf-8");
1746
+ const parsed = JSON.parse(raw);
1747
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
1748
+ return null;
1749
+ if (typeof parsed.active_plan !== "string")
1750
+ return null;
1751
+ if (!Array.isArray(parsed.session_ids)) {
1752
+ parsed.session_ids = [];
1753
+ }
1754
+ return parsed;
1755
+ } catch {
1756
+ return null;
1757
+ }
1758
+ }
1759
+ function writeWorkState(directory, state) {
1760
+ try {
1761
+ const dir = join6(directory, WEAVE_DIR);
1762
+ if (!existsSync5(dir)) {
1763
+ mkdirSync(dir, { recursive: true });
1764
+ }
1765
+ writeFileSync(join6(dir, WORK_STATE_FILE), JSON.stringify(state, null, 2), "utf-8");
1766
+ return true;
1767
+ } catch {
1768
+ return false;
1769
+ }
1770
+ }
1771
+ function clearWorkState(directory) {
1772
+ const filePath = join6(directory, WEAVE_DIR, WORK_STATE_FILE);
1773
+ try {
1774
+ if (existsSync5(filePath)) {
1775
+ unlinkSync(filePath);
1776
+ }
1777
+ return true;
1778
+ } catch {
1779
+ return false;
1780
+ }
1781
+ }
1782
+ function appendSessionId(directory, sessionId) {
1783
+ const state = readWorkState(directory);
1784
+ if (!state)
1785
+ return null;
1786
+ if (!state.session_ids.includes(sessionId)) {
1787
+ state.session_ids.push(sessionId);
1788
+ writeWorkState(directory, state);
1789
+ }
1790
+ return state;
1791
+ }
1792
+ function createWorkState(planPath, sessionId, agent) {
1793
+ return {
1794
+ active_plan: planPath,
1795
+ started_at: new Date().toISOString(),
1796
+ session_ids: [sessionId],
1797
+ plan_name: getPlanName(planPath),
1798
+ ...agent !== undefined ? { agent } : {}
1799
+ };
1800
+ }
1801
+ function findPlans(directory) {
1802
+ const plansDir = join6(directory, PLANS_DIR);
1803
+ try {
1804
+ if (!existsSync5(plansDir))
1805
+ return [];
1806
+ const files = readdirSync2(plansDir).filter((f) => f.endsWith(".md")).map((f) => {
1807
+ const fullPath = join6(plansDir, f);
1808
+ const stat = statSync(fullPath);
1809
+ return { path: fullPath, mtime: stat.mtimeMs };
1810
+ }).sort((a, b) => b.mtime - a.mtime).map((f) => f.path);
1811
+ return files;
1812
+ } catch {
1813
+ return [];
1814
+ }
1815
+ }
1816
+ function getPlanProgress(planPath) {
1817
+ if (!existsSync5(planPath)) {
1818
+ return { total: 0, completed: 0, isComplete: true };
1819
+ }
1820
+ try {
1821
+ const content = readFileSync4(planPath, "utf-8");
1822
+ const unchecked = content.match(UNCHECKED_RE) || [];
1823
+ const checked = content.match(CHECKED_RE) || [];
1824
+ const total = unchecked.length + checked.length;
1825
+ const completed = checked.length;
1826
+ return {
1827
+ total,
1828
+ completed,
1829
+ isComplete: total === 0 || completed === total
1830
+ };
1831
+ } catch {
1832
+ return { total: 0, completed: 0, isComplete: true };
1833
+ }
1834
+ }
1835
+ function getPlanName(planPath) {
1836
+ return basename(planPath, ".md");
1837
+ }
1838
+ // src/hooks/start-work-hook.ts
1839
+ function handleStartWork(input) {
1840
+ const { promptText, sessionId, directory } = input;
1841
+ if (!promptText.includes("<session-context>")) {
1842
+ return { contextInjection: null, switchAgent: null };
1843
+ }
1844
+ const explicitPlanName = extractPlanName(promptText);
1845
+ const existingState = readWorkState(directory);
1846
+ const allPlans = findPlans(directory);
1847
+ if (explicitPlanName) {
1848
+ return handleExplicitPlan(explicitPlanName, allPlans, sessionId, directory);
1849
+ }
1850
+ if (existingState) {
1851
+ const progress = getPlanProgress(existingState.active_plan);
1852
+ if (!progress.isComplete) {
1853
+ appendSessionId(directory, sessionId);
1854
+ return {
1855
+ switchAgent: "tapestry",
1856
+ contextInjection: buildResumeContext(existingState.active_plan, existingState.plan_name, progress)
1857
+ };
1858
+ }
1859
+ }
1860
+ return handlePlanDiscovery(allPlans, sessionId, directory);
1861
+ }
1862
+ function extractPlanName(promptText) {
1863
+ const match = promptText.match(/<user-request>\s*([\s\S]*?)\s*<\/user-request>/i);
1864
+ if (!match)
1865
+ return null;
1866
+ const cleaned = match[1].trim();
1867
+ return cleaned || null;
1868
+ }
1869
+ function handleExplicitPlan(requestedName, allPlans, sessionId, directory) {
1870
+ const matched = findPlanByName(allPlans, requestedName);
1871
+ if (!matched) {
1872
+ const incompletePlans = allPlans.filter((p) => !getPlanProgress(p).isComplete);
1873
+ const listing = incompletePlans.length > 0 ? incompletePlans.map((p) => ` - ${getPlanName(p)}`).join(`
1874
+ `) : " (none)";
1875
+ return {
1876
+ switchAgent: "tapestry",
1877
+ contextInjection: `## Plan Not Found
1878
+ No plan matching "${requestedName}" was found.
1879
+
1880
+ Available incomplete plans:
1881
+ ${listing}
1882
+
1883
+ Tell the user which plans are available and ask them to specify one.`
1884
+ };
1885
+ }
1886
+ const progress = getPlanProgress(matched);
1887
+ if (progress.isComplete) {
1888
+ return {
1889
+ switchAgent: "tapestry",
1890
+ contextInjection: `## Plan Already Complete
1891
+ The plan "${getPlanName(matched)}" has all ${progress.total} tasks completed.
1892
+ Tell the user this plan is already done and suggest creating a new one with Pattern.`
1893
+ };
1894
+ }
1895
+ clearWorkState(directory);
1896
+ const state = createWorkState(matched, sessionId, "tapestry");
1897
+ writeWorkState(directory, state);
1898
+ return {
1899
+ switchAgent: "tapestry",
1900
+ contextInjection: buildFreshContext(matched, getPlanName(matched), progress)
1901
+ };
1902
+ }
1903
+ function handlePlanDiscovery(allPlans, sessionId, directory) {
1904
+ if (allPlans.length === 0) {
1905
+ return {
1906
+ switchAgent: "tapestry",
1907
+ contextInjection: "## No Plans Found\nNo plan files found at `.weave/plans/`.\nTell the user to switch to Pattern agent to create a work plan first."
1908
+ };
1909
+ }
1910
+ const incompletePlans = allPlans.filter((p) => !getPlanProgress(p).isComplete);
1911
+ if (incompletePlans.length === 0) {
1912
+ return {
1913
+ switchAgent: "tapestry",
1914
+ contextInjection: `## All Plans Complete
1915
+ All existing plans have been completed.
1916
+ Tell the user to switch to Pattern agent to create a new plan.`
1917
+ };
1918
+ }
1919
+ if (incompletePlans.length === 1) {
1920
+ const plan = incompletePlans[0];
1921
+ const progress = getPlanProgress(plan);
1922
+ const state = createWorkState(plan, sessionId, "tapestry");
1923
+ writeWorkState(directory, state);
1924
+ return {
1925
+ switchAgent: "tapestry",
1926
+ contextInjection: buildFreshContext(plan, getPlanName(plan), progress)
1927
+ };
1928
+ }
1929
+ const listing = incompletePlans.map((p) => {
1930
+ const progress = getPlanProgress(p);
1931
+ return ` - **${getPlanName(p)}** (${progress.completed}/${progress.total} tasks done)`;
1932
+ }).join(`
1933
+ `);
1934
+ return {
1935
+ switchAgent: "tapestry",
1936
+ contextInjection: `## Multiple Plans Found
1937
+ There are ${incompletePlans.length} incomplete plans:
1938
+ ${listing}
1939
+
1940
+ Ask the user which plan to work on. They can run \`/start-work [plan-name]\` to select one.`
1941
+ };
1942
+ }
1943
+ function findPlanByName(plans, requestedName) {
1944
+ const lower = requestedName.toLowerCase();
1945
+ const exact = plans.find((p) => getPlanName(p).toLowerCase() === lower);
1946
+ if (exact)
1947
+ return exact;
1948
+ const partial = plans.find((p) => getPlanName(p).toLowerCase().includes(lower));
1949
+ return partial || null;
1950
+ }
1951
+ function buildFreshContext(planPath, planName, progress) {
1952
+ return `## Starting Plan: ${planName}
1953
+ **Plan file**: ${planPath}
1954
+ **Progress**: ${progress.completed}/${progress.total} tasks completed
1955
+
1956
+ Read the plan file now and begin executing from the first unchecked \`- [ ]\` task.
1957
+
1958
+ **SIDEBAR TODOS — DO THIS FIRST:**
1959
+ Before starting any work, use todowrite to populate the sidebar:
1960
+ 1. Create a summary todo (in_progress): "${planName} ${progress.completed}/${progress.total}"
1961
+ 2. Create a todo for the first unchecked task (in_progress)
1962
+ 3. Create todos for the next 2-3 tasks (pending)
1963
+ Keep each todo under 35 chars. Update as you complete tasks.`;
1964
+ }
1965
+ function buildResumeContext(planPath, planName, progress) {
1966
+ const remaining = progress.total - progress.completed;
1967
+ return `## Resuming Plan: ${planName}
1968
+ **Plan file**: ${planPath}
1969
+ **Progress**: ${progress.completed}/${progress.total} tasks completed
1970
+ **Status**: RESUMING — continuing from where the previous session left off.
1971
+
1972
+ Read the plan file now and continue from the first unchecked \`- [ ]\` task.
1973
+
1974
+ **SIDEBAR TODOS — RESTORE STATE:**
1975
+ Previous session's todos are lost. Use todowrite to restore the sidebar:
1976
+ 1. Create a summary todo (in_progress): "${planName} ${progress.completed}/${progress.total}"
1977
+ 2. Create a todo for the next unchecked task (in_progress)
1978
+ 3. Create todos for the following 2-3 tasks (pending)
1979
+ Keep each todo under 35 chars. ${remaining} task${remaining !== 1 ? "s" : ""} remaining.`;
1980
+ }
1981
+
1982
+ // src/hooks/work-continuation.ts
1983
+ function checkContinuation(input) {
1984
+ const { directory } = input;
1985
+ const state = readWorkState(directory);
1986
+ if (!state) {
1987
+ return { continuationPrompt: null };
1988
+ }
1989
+ const progress = getPlanProgress(state.active_plan);
1990
+ if (progress.isComplete) {
1991
+ return { continuationPrompt: null };
1992
+ }
1993
+ const remaining = progress.total - progress.completed;
1994
+ return {
1995
+ continuationPrompt: `You have an active work plan with incomplete tasks. Continue working.
1996
+
1997
+ **Plan**: ${state.plan_name}
1998
+ **File**: ${state.active_plan}
1999
+ **Progress**: ${progress.completed}/${progress.total} tasks completed (${remaining} remaining)
2000
+
2001
+ 1. Read the plan file NOW to check exact current progress
2002
+ 2. Use todowrite to restore sidebar: summary todo "${state.plan_name} ${progress.completed}/${progress.total}" (in_progress) + next task (in_progress) + 2-3 upcoming (pending). Max 35 chars each.
2003
+ 3. Find the first unchecked \`- [ ]\` task
2004
+ 4. Execute it, verify it, mark \`- [ ]\` → \`- [x]\`
2005
+ 5. Update sidebar todos as you complete tasks
2006
+ 6. Do not stop until all tasks are complete`
2007
+ };
2008
+ }
2009
+
2010
+ // src/hooks/verification-reminder.ts
2011
+ function buildVerificationReminder(input) {
2012
+ const planContext = input.planName && input.progress ? `
2013
+ **Plan**: ${input.planName} (${input.progress.completed}/${input.progress.total} tasks done)` : "";
2014
+ return {
2015
+ verificationPrompt: `## Verification Required
2016
+ ${planContext}
2017
+
2018
+ Before marking this task complete, verify the work:
2019
+
2020
+ 1. **Read the changes**: \`git diff --stat\` then Read each changed file
2021
+ 2. **Run checks**: Run relevant tests, check for linting/type errors
2022
+ 3. **Validate behavior**: Does the code actually do what was requested?
2023
+ 4. **Gate decision**: Can you explain what every changed line does?
2024
+
2025
+ If uncertain about quality, delegate to \`weft\` agent for a formal review:
2026
+ \`call_weave_agent(agent="weft", prompt="Review the changes for [task description]")\`
2027
+
2028
+ If changes touch auth, crypto, tokens, or input validation, delegate to \`warp\` agent for a security audit:
2029
+ \`call_weave_agent(agent="warp", prompt="Security audit the changes for [task description]")\`
2030
+
2031
+ Only mark complete when ALL checks pass.`
2032
+ };
2033
+ }
2034
+
2035
+ // src/hooks/create-hooks.ts
2036
+ function createHooks(args) {
2037
+ const { isHookEnabled, directory } = args;
2038
+ const writeGuardState = createWriteGuardState();
2039
+ const writeGuard = createWriteGuard(writeGuardState);
2040
+ const contextWindowThresholds = {
2041
+ warningPct: 0.8,
2042
+ criticalPct: 0.95
2043
+ };
2044
+ return {
2045
+ checkContextWindow: isHookEnabled("context-window-monitor") ? (state) => checkContextWindow(state, contextWindowThresholds) : null,
2046
+ writeGuard: isHookEnabled("write-existing-file-guard") ? writeGuard : null,
2047
+ shouldInjectRules: isHookEnabled("rules-injector") ? shouldInjectRules : null,
2048
+ getRulesForFile: isHookEnabled("rules-injector") ? getRulesForFile : null,
2049
+ firstMessageVariant: isHookEnabled("first-message-variant") ? { shouldApplyVariant, markApplied, markSessionCreated, clearSession } : null,
2050
+ processMessageForKeywords: isHookEnabled("keyword-detector") ? processMessageForKeywords : null,
2051
+ patternMdOnly: isHookEnabled("pattern-md-only") ? checkPatternWrite : null,
2052
+ startWork: isHookEnabled("start-work") ? (promptText, sessionId) => handleStartWork({ promptText, sessionId, directory }) : null,
2053
+ workContinuation: isHookEnabled("work-continuation") ? (sessionId) => checkContinuation({ sessionId, directory }) : null,
2054
+ verificationReminder: isHookEnabled("verification-reminder") ? buildVerificationReminder : null
2055
+ };
2056
+ }
2057
+
2058
+ // src/plugin/plugin-interface.ts
2059
+ function createPluginInterface(args) {
2060
+ const { pluginConfig, hooks, tools, configHandler, agents } = args;
2061
+ return {
2062
+ tool: tools,
2063
+ config: async (config) => {
2064
+ const result = await configHandler.handle({
2065
+ pluginConfig,
2066
+ agents,
2067
+ availableTools: []
2068
+ });
2069
+ config.agent = result.agents;
2070
+ config.command = result.commands;
2071
+ if (result.defaultAgent) {
2072
+ config.default_agent = result.defaultAgent;
2073
+ }
2074
+ },
2075
+ "chat.message": async (input, _output) => {
2076
+ const { sessionID } = input;
2077
+ if (hooks.checkContextWindow) {
2078
+ hooks.checkContextWindow({
2079
+ usedTokens: 0,
2080
+ maxTokens: 0,
2081
+ sessionId: sessionID
2082
+ });
2083
+ }
2084
+ if (hooks.firstMessageVariant) {
2085
+ if (hooks.firstMessageVariant.shouldApplyVariant(sessionID)) {
2086
+ hooks.firstMessageVariant.markApplied(sessionID);
2087
+ }
2088
+ }
2089
+ if (hooks.processMessageForKeywords) {
2090
+ hooks.processMessageForKeywords("", sessionID);
2091
+ }
2092
+ if (hooks.startWork) {
2093
+ const parts = _output.parts;
2094
+ const message = _output.message;
2095
+ if (parts) {
2096
+ const timestamp = new Date().toISOString();
2097
+ for (const part of parts) {
2098
+ if (part.type === "text" && part.text) {
2099
+ part.text = part.text.replace(/\$SESSION_ID/g, sessionID).replace(/\$TIMESTAMP/g, timestamp);
2100
+ }
2101
+ }
2102
+ }
2103
+ const promptText = parts?.filter((p) => p.type === "text" && p.text).map((p) => p.text).join(`
2104
+ `).trim() ?? "";
2105
+ const result = hooks.startWork(promptText, sessionID);
2106
+ if (result.switchAgent && message) {
2107
+ message.agent = getAgentDisplayName(result.switchAgent);
2108
+ }
2109
+ if (result.contextInjection && parts) {
2110
+ const idx = parts.findIndex((p) => p.type === "text" && p.text);
2111
+ if (idx >= 0 && parts[idx].text) {
2112
+ parts[idx].text += `
2113
+
2114
+ ---
2115
+ ${result.contextInjection}`;
2116
+ } else {
2117
+ parts.push({ type: "text", text: result.contextInjection });
2118
+ }
2119
+ }
2120
+ }
2121
+ },
2122
+ "chat.params": async (_input, _output) => {},
2123
+ "chat.headers": async (_input, _output) => {},
2124
+ event: async (input) => {
2125
+ const { event } = input;
2126
+ if (hooks.firstMessageVariant) {
2127
+ if (event.type === "session.created") {
2128
+ const evt = event;
2129
+ hooks.firstMessageVariant.markSessionCreated(evt.properties.info.id);
2130
+ }
2131
+ if (event.type === "session.deleted") {
2132
+ const evt = event;
2133
+ hooks.firstMessageVariant.clearSession(evt.properties.info.id);
2134
+ }
2135
+ }
2136
+ if (hooks.workContinuation && event.type === "session.idle") {
2137
+ const evt = event;
2138
+ const sessionId = evt.properties?.sessionID ?? "";
2139
+ if (sessionId) {
2140
+ const result = hooks.workContinuation(sessionId);
2141
+ if (result.continuationPrompt) {}
2142
+ }
2143
+ }
2144
+ },
2145
+ "tool.execute.before": async (input, _output) => {
2146
+ const args2 = _output.args;
2147
+ const filePath = args2?.file_path ?? args2?.path ?? "";
2148
+ if (filePath && hooks.shouldInjectRules && hooks.getRulesForFile) {
2149
+ if (hooks.shouldInjectRules(input.tool)) {
2150
+ hooks.getRulesForFile(filePath);
2151
+ }
2152
+ }
2153
+ if (filePath && hooks.writeGuard) {
2154
+ if (input.tool === "read") {
2155
+ hooks.writeGuard.trackRead(filePath);
2156
+ }
2157
+ }
2158
+ if (filePath && hooks.patternMdOnly) {
2159
+ const agentName = input.agent;
2160
+ if (agentName) {
2161
+ const check = hooks.patternMdOnly(agentName, input.tool, filePath);
2162
+ if (!check.allowed) {
2163
+ throw new Error(check.reason ?? "Pattern agent is restricted to .md files in .weave/");
2164
+ }
2165
+ }
2166
+ }
2167
+ },
2168
+ "tool.execute.after": async (_input, _output) => {}
2169
+ };
2170
+ }
2171
+
2172
+ // src/index.ts
2173
+ var WeavePlugin = async (ctx) => {
2174
+ const pluginConfig = loadWeaveConfig(ctx.directory, ctx);
2175
+ const disabledHooks = new Set(pluginConfig.disabled_hooks ?? []);
2176
+ const isHookEnabled = (name) => !disabledHooks.has(name);
2177
+ const toolsResult = await createTools({ ctx, pluginConfig });
2178
+ const managers = createManagers({ ctx, pluginConfig, resolveSkills: toolsResult.resolveSkillsFn });
2179
+ const hooks = createHooks({ pluginConfig, isHookEnabled, directory: ctx.directory });
2180
+ return createPluginInterface({
2181
+ pluginConfig,
2182
+ hooks,
2183
+ tools: toolsResult.tools,
2184
+ configHandler: managers.configHandler,
2185
+ agents: managers.agents
2186
+ });
2187
+ };
2188
+ var src_default = WeavePlugin;
2189
+ export {
2190
+ src_default as default
2191
+ };