@opencode_weave/weave 0.4.2 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,6 +35,7 @@ Weave is a lean OpenCode plugin with multi-agent orchestration. It provides a co
35
35
  - [Background Agents](#background-agents)
36
36
  - [Tool Permissions](#tool-permissions)
37
37
  - [Development](#development)
38
+ - [Acknowledgments](#acknowledgments)
38
39
  - [License](#license)
39
40
 
40
41
  ## Overview
@@ -312,6 +313,10 @@ Tool access is controlled per-agent to ensure safety and specialized focus. For
312
313
  - **Typecheck**: `bun run typecheck`
313
314
  - **Clean**: `bun run clean`
314
315
 
316
+ ## Acknowledgments
317
+
318
+ Weave was inspired by [Oh My OpenCode](https://github.com/code-yeongyu/oh-my-opencode) by [@code-yeongyu](https://github.com/code-yeongyu) — a pioneering OpenCode plugin that proved multi-agent orchestration, discipline agents, and structured plan-execute workflows could radically improve the developer experience. Many of Weave's core ideas — from category-based task dispatch to background agent parallelism — trace their roots to patterns Oh My OpenCode established. We're grateful for the trailblazing work and the vibrant community around it.
319
+
315
320
  ## License
316
321
 
317
322
  MIT
@@ -1,3 +1,5 @@
1
1
  export type { WorkState, PlanProgress } from "./types";
2
+ export type { ValidationResult, ValidationIssue, ValidationSeverity, ValidationCategory } from "./validation-types";
2
3
  export { WEAVE_DIR, WORK_STATE_FILE, WORK_STATE_PATH, PLANS_DIR } from "./constants";
3
4
  export { readWorkState, writeWorkState, clearWorkState, appendSessionId, createWorkState, findPlans, getPlanProgress, getPlanName, } from "./storage";
5
+ export { validatePlan } from "./validation";
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The 6 validation categories checked by validatePlan().
3
+ */
4
+ export type ValidationCategory = "structure" | "checkboxes" | "file-references" | "numbering" | "effort-estimate" | "verification";
5
+ /** Severity level for a validation issue. */
6
+ export type ValidationSeverity = "error" | "warning";
7
+ /**
8
+ * A single validation issue found in a plan file.
9
+ */
10
+ export interface ValidationIssue {
11
+ severity: ValidationSeverity;
12
+ category: ValidationCategory;
13
+ message: string;
14
+ }
15
+ /**
16
+ * The result of validating a plan file.
17
+ * `valid` is false if there are any blocking errors.
18
+ */
19
+ export interface ValidationResult {
20
+ /** False when there is at least one error (blocking). True when only warnings or clean. */
21
+ valid: boolean;
22
+ /** Blocking issues — prevent /start-work from proceeding */
23
+ errors: ValidationIssue[];
24
+ /** Non-blocking issues — surfaced to user but don't block execution */
25
+ warnings: ValidationIssue[];
26
+ }
@@ -0,0 +1,9 @@
1
+ import type { ValidationResult } from "./validation-types";
2
+ /**
3
+ * Validates a plan file's structure and content before /start-work execution.
4
+ *
5
+ * @param planPath Absolute path to the plan markdown file
6
+ * @param projectDir Absolute path to the project root (used for file-reference checks)
7
+ * @returns ValidationResult with errors (blocking) and warnings (non-blocking)
8
+ */
9
+ export declare function validatePlan(planPath: string, projectDir: string): ValidationResult;
@@ -2,6 +2,7 @@
2
2
  * Start-work hook: detects the /start-work command, resolves the target plan,
3
3
  * creates/updates work state, and returns context for injection into the prompt.
4
4
  */
5
+ import type { ValidationResult } from "../features/work-state";
5
6
  export interface StartWorkInput {
6
7
  promptText: string;
7
8
  sessionId: string;
@@ -18,3 +19,7 @@ export interface StartWorkResult {
18
19
  * Returns null contextInjection if this message is not a /start-work command.
19
20
  */
20
21
  export declare function handleStartWork(input: StartWorkInput): StartWorkResult;
22
+ /**
23
+ * Format validation errors and warnings as a markdown string.
24
+ */
25
+ export declare function formatValidationResults(result: ValidationResult): string;
package/dist/index.js CHANGED
@@ -525,7 +525,7 @@ FORMAT RULES:
525
525
  - Use /start-work to hand off to Tapestry for todo-list driven execution of multi-step plans
526
526
  - Use shuttle for category-specific specialized work
527
527
  - Use Weft for reviewing completed work or validating plans before execution
528
- - Use Warp for security audits when changes touch auth, crypto, tokens, or input validation
528
+ - MUST use Warp for security audits when changes touch auth, crypto, certificates, tokens, signatures, or input validation — not optional
529
529
  - Delegate aggressively to keep your context lean
530
530
  </Delegation>
531
531
 
@@ -568,6 +568,7 @@ For complex tasks that benefit from structured planning before execution:
568
568
  - SKIP ONLY IF: User explicitly says "skip review"
569
569
  - Weft reads the plan, verifies file references, checks executability
570
570
  - If Weft rejects, send issues back to Pattern for revision
571
+ - MANDATORY: If the plan touches security-relevant areas (crypto, auth, certificates, tokens, signatures, or input validation) → also run Warp on the plan
571
572
  3. EXECUTE: Tell the user to run \`/start-work\` to begin execution
572
573
  - /start-work loads the plan, creates work state at \`.weave/state.json\`, and switches to Tapestry
573
574
  - Tapestry reads the plan and works through tasks, marking checkboxes as it goes
@@ -596,11 +597,12 @@ When to skip Weft:
596
597
  - User explicitly says "skip review"
597
598
  - Simple question-answering (no code changes)
598
599
 
599
- For security-relevant changes, also delegate to Warp:
600
+ MANDATORY If ANY changed file touches crypto, auth, certificates, tokens, signatures, or input validation:
601
+ → MUST run Warp in parallel with Weft. This is NOT optional.
602
+ → Failure to invoke Warp for security-relevant changes is a workflow violation.
600
603
  - Warp is read-only and skeptical-biased — it rejects when security is at risk
601
604
  - Warp self-triages: if no security-relevant changes, it fast-exits with APPROVE
602
605
  - If Warp rejects: address the specific security issues before shipping
603
- - Run Warp in parallel with Weft for comprehensive coverage
604
606
  </ReviewWorkflow>
605
607
 
606
608
  <Style>
@@ -1877,6 +1879,286 @@ function getPlanProgress(planPath) {
1877
1879
  function getPlanName(planPath) {
1878
1880
  return basename(planPath, ".md");
1879
1881
  }
1882
+ // src/features/work-state/validation.ts
1883
+ import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
1884
+ import { resolve as resolve2, sep } from "path";
1885
+ function validatePlan(planPath, projectDir) {
1886
+ const errors = [];
1887
+ const warnings = [];
1888
+ const resolvedPlanPath = resolve2(planPath);
1889
+ const allowedDir = resolve2(projectDir, PLANS_DIR);
1890
+ if (!resolvedPlanPath.startsWith(allowedDir + sep) && resolvedPlanPath !== allowedDir) {
1891
+ errors.push({
1892
+ severity: "error",
1893
+ category: "structure",
1894
+ message: `Plan path is outside the allowed directory (${PLANS_DIR}/): ${planPath}`
1895
+ });
1896
+ return { valid: false, errors, warnings };
1897
+ }
1898
+ if (!existsSync6(resolvedPlanPath)) {
1899
+ errors.push({
1900
+ severity: "error",
1901
+ category: "structure",
1902
+ message: `Plan file not found: ${planPath}`
1903
+ });
1904
+ return { valid: false, errors, warnings };
1905
+ }
1906
+ const content = readFileSync5(resolvedPlanPath, "utf-8");
1907
+ validateStructure(content, errors, warnings);
1908
+ validateCheckboxes(content, errors, warnings);
1909
+ validateFileReferences(content, projectDir, warnings);
1910
+ validateNumbering(content, errors, warnings);
1911
+ validateEffortEstimate(content, warnings);
1912
+ validateVerificationSection(content, errors);
1913
+ return {
1914
+ valid: errors.length === 0,
1915
+ errors,
1916
+ warnings
1917
+ };
1918
+ }
1919
+ function extractSection(content, heading) {
1920
+ const lines = content.split(`
1921
+ `);
1922
+ let startIdx = -1;
1923
+ for (let i = 0;i < lines.length; i++) {
1924
+ if (lines[i].trim() === heading) {
1925
+ startIdx = i + 1;
1926
+ break;
1927
+ }
1928
+ }
1929
+ if (startIdx === -1)
1930
+ return null;
1931
+ const sectionLines = [];
1932
+ for (let i = startIdx;i < lines.length; i++) {
1933
+ if (/^## /.test(lines[i]))
1934
+ break;
1935
+ sectionLines.push(lines[i]);
1936
+ }
1937
+ return sectionLines.join(`
1938
+ `);
1939
+ }
1940
+ function hasSection(content, heading) {
1941
+ return content.split(`
1942
+ `).some((line) => line.trim() === heading);
1943
+ }
1944
+ function validateStructure(content, errors, warnings) {
1945
+ const requiredSections = [
1946
+ ["## TL;DR", "Missing required section: ## TL;DR"],
1947
+ ["## TODOs", "Missing required section: ## TODOs"],
1948
+ ["## Verification", "Missing required section: ## Verification"]
1949
+ ];
1950
+ for (const [heading, message] of requiredSections) {
1951
+ if (!hasSection(content, heading)) {
1952
+ errors.push({ severity: "error", category: "structure", message });
1953
+ }
1954
+ }
1955
+ const optionalSections = [
1956
+ ["## Context", "Missing optional section: ## Context"],
1957
+ ["## Objectives", "Missing optional section: ## Objectives"]
1958
+ ];
1959
+ for (const [heading, message] of optionalSections) {
1960
+ if (!hasSection(content, heading)) {
1961
+ warnings.push({ severity: "warning", category: "structure", message });
1962
+ }
1963
+ }
1964
+ }
1965
+ function validateCheckboxes(content, errors, warnings) {
1966
+ const todosSection = extractSection(content, "## TODOs");
1967
+ if (todosSection === null) {
1968
+ return;
1969
+ }
1970
+ const checkboxPattern = /^- \[[ x]\] /m;
1971
+ if (!checkboxPattern.test(todosSection)) {
1972
+ errors.push({
1973
+ severity: "error",
1974
+ category: "checkboxes",
1975
+ message: "## TODOs section contains no checkboxes (- [ ] or - [x])"
1976
+ });
1977
+ return;
1978
+ }
1979
+ const lines = todosSection.split(`
1980
+ `);
1981
+ let taskIndex = 0;
1982
+ for (let i = 0;i < lines.length; i++) {
1983
+ const line = lines[i];
1984
+ if (/^- \[[ x]\] /.test(line)) {
1985
+ taskIndex++;
1986
+ const taskLabel = `Task ${taskIndex}`;
1987
+ const bodyLines = [];
1988
+ let j = i + 1;
1989
+ while (j < lines.length && !/^- \[[ x]\] /.test(lines[j])) {
1990
+ bodyLines.push(lines[j]);
1991
+ j++;
1992
+ }
1993
+ const body = bodyLines.join(`
1994
+ `);
1995
+ if (!/\*\*What\*\*/.test(body)) {
1996
+ warnings.push({
1997
+ severity: "warning",
1998
+ category: "checkboxes",
1999
+ message: `${taskLabel} is missing **What** sub-field`
2000
+ });
2001
+ }
2002
+ if (!/\*\*Files\*\*/.test(body)) {
2003
+ warnings.push({
2004
+ severity: "warning",
2005
+ category: "checkboxes",
2006
+ message: `${taskLabel} is missing **Files** sub-field`
2007
+ });
2008
+ }
2009
+ if (!/\*\*Acceptance\*\*/.test(body)) {
2010
+ warnings.push({
2011
+ severity: "warning",
2012
+ category: "checkboxes",
2013
+ message: `${taskLabel} is missing **Acceptance** sub-field`
2014
+ });
2015
+ }
2016
+ }
2017
+ }
2018
+ }
2019
+ var NEW_FILE_INDICATORS = [
2020
+ /^\s*create\s+/i,
2021
+ /^\s*new:\s*/i,
2022
+ /\(new\)/i,
2023
+ /^\s*add\s+/i
2024
+ ];
2025
+ function isNewFile(rawPath) {
2026
+ return NEW_FILE_INDICATORS.some((re) => re.test(rawPath));
2027
+ }
2028
+ function extractFilePath(raw) {
2029
+ let cleaned = raw.replace(/^\s*(create|modify|new:|add)\s+/i, "").replace(/\(new\)/gi, "").trim();
2030
+ const firstToken = cleaned.split(/\s+/)[0];
2031
+ if (firstToken && (firstToken.includes("/") || firstToken.endsWith(".ts") || firstToken.endsWith(".js") || firstToken.endsWith(".json") || firstToken.endsWith(".md"))) {
2032
+ cleaned = firstToken;
2033
+ } else if (!cleaned.includes("/")) {
2034
+ return null;
2035
+ }
2036
+ return cleaned || null;
2037
+ }
2038
+ function validateFileReferences(content, projectDir, warnings) {
2039
+ const todosSection = extractSection(content, "## TODOs");
2040
+ if (todosSection === null)
2041
+ return;
2042
+ const lines = todosSection.split(`
2043
+ `);
2044
+ for (const line of lines) {
2045
+ const filesMatch = /^\s*\*\*Files\*\*:?\s*(.+)$/.exec(line);
2046
+ if (!filesMatch)
2047
+ continue;
2048
+ const rawValue = filesMatch[1].trim();
2049
+ const parts = rawValue.split(",");
2050
+ for (const part of parts) {
2051
+ const trimmed = part.trim();
2052
+ if (!trimmed)
2053
+ continue;
2054
+ const newFile = isNewFile(trimmed);
2055
+ const filePath = extractFilePath(trimmed);
2056
+ if (!filePath)
2057
+ continue;
2058
+ if (newFile)
2059
+ continue;
2060
+ if (filePath.startsWith("/")) {
2061
+ warnings.push({
2062
+ severity: "warning",
2063
+ category: "file-references",
2064
+ message: `Absolute file path not allowed in plan references: ${filePath}`
2065
+ });
2066
+ continue;
2067
+ }
2068
+ const resolvedProject = resolve2(projectDir);
2069
+ const absolutePath = resolve2(projectDir, filePath);
2070
+ if (!absolutePath.startsWith(resolvedProject + sep) && absolutePath !== resolvedProject) {
2071
+ warnings.push({
2072
+ severity: "warning",
2073
+ category: "file-references",
2074
+ message: `File reference escapes project directory (path traversal): ${filePath}`
2075
+ });
2076
+ continue;
2077
+ }
2078
+ if (!existsSync6(absolutePath)) {
2079
+ warnings.push({
2080
+ severity: "warning",
2081
+ category: "file-references",
2082
+ message: `Referenced file does not exist (may be created by an earlier task): ${filePath}`
2083
+ });
2084
+ }
2085
+ }
2086
+ }
2087
+ }
2088
+ function validateNumbering(content, errors, warnings) {
2089
+ const todosSection = extractSection(content, "## TODOs");
2090
+ if (todosSection === null)
2091
+ return;
2092
+ const numbers = [];
2093
+ const seen = new Set;
2094
+ for (const line of todosSection.split(`
2095
+ `)) {
2096
+ const match = /^- \[[ x]\] (\d+)\./.exec(line);
2097
+ if (!match)
2098
+ continue;
2099
+ const n = parseInt(match[1], 10);
2100
+ if (seen.has(n)) {
2101
+ errors.push({
2102
+ severity: "error",
2103
+ category: "numbering",
2104
+ message: `Duplicate task number: ${n}`
2105
+ });
2106
+ } else {
2107
+ seen.add(n);
2108
+ numbers.push(n);
2109
+ }
2110
+ }
2111
+ if (numbers.length < 2)
2112
+ return;
2113
+ const sorted = [...numbers].sort((a, b) => a - b);
2114
+ for (let i = 1;i < sorted.length; i++) {
2115
+ if (sorted[i] !== sorted[i - 1] + 1) {
2116
+ warnings.push({
2117
+ severity: "warning",
2118
+ category: "numbering",
2119
+ message: `Gap in task numbering: expected ${sorted[i - 1] + 1} but found ${sorted[i]}`
2120
+ });
2121
+ }
2122
+ }
2123
+ }
2124
+ var VALID_EFFORT_VALUES = ["quick", "short", "medium", "large", "xl"];
2125
+ function validateEffortEstimate(content, warnings) {
2126
+ const tldrSection = extractSection(content, "## TL;DR");
2127
+ if (tldrSection === null) {
2128
+ return;
2129
+ }
2130
+ const effortMatch = /\*\*Estimated Effort\*\*:?\s*(.+)/i.exec(tldrSection);
2131
+ if (!effortMatch) {
2132
+ warnings.push({
2133
+ severity: "warning",
2134
+ category: "effort-estimate",
2135
+ message: "Missing **Estimated Effort** in ## TL;DR section"
2136
+ });
2137
+ return;
2138
+ }
2139
+ const value = effortMatch[1].trim().toLowerCase();
2140
+ if (!VALID_EFFORT_VALUES.includes(value)) {
2141
+ warnings.push({
2142
+ severity: "warning",
2143
+ category: "effort-estimate",
2144
+ message: `Invalid effort estimate value: "${effortMatch[1].trim()}". Expected one of: Quick, Short, Medium, Large, XL`
2145
+ });
2146
+ }
2147
+ }
2148
+ function validateVerificationSection(content, errors) {
2149
+ const verificationSection = extractSection(content, "## Verification");
2150
+ if (verificationSection === null) {
2151
+ return;
2152
+ }
2153
+ const hasCheckbox = /^- \[[ x]\] /m.test(verificationSection);
2154
+ if (!hasCheckbox) {
2155
+ errors.push({
2156
+ severity: "error",
2157
+ category: "verification",
2158
+ message: "## Verification section contains no checkboxes — at least one verifiable condition is required"
2159
+ });
2160
+ }
2161
+ }
1880
2162
  // src/hooks/start-work-hook.ts
1881
2163
  function handleStartWork(input) {
1882
2164
  const { promptText, sessionId, directory } = input;
@@ -1892,10 +2174,33 @@ function handleStartWork(input) {
1892
2174
  if (existingState) {
1893
2175
  const progress = getPlanProgress(existingState.active_plan);
1894
2176
  if (!progress.isComplete) {
2177
+ const validation = validatePlan(existingState.active_plan, directory);
2178
+ if (!validation.valid) {
2179
+ clearWorkState(directory);
2180
+ return {
2181
+ switchAgent: "tapestry",
2182
+ contextInjection: `## Plan Validation Failed
2183
+ The active plan "${existingState.plan_name}" has structural issues. Work state has been cleared.
2184
+
2185
+ ${formatValidationResults(validation)}
2186
+
2187
+ Tell the user to fix the plan file and run /start-work again.`
2188
+ };
2189
+ }
1895
2190
  appendSessionId(directory, sessionId);
2191
+ const resumeContext = buildResumeContext(existingState.active_plan, existingState.plan_name, progress);
2192
+ if (validation.warnings.length > 0) {
2193
+ return {
2194
+ switchAgent: "tapestry",
2195
+ contextInjection: `${resumeContext}
2196
+
2197
+ ### Validation Warnings
2198
+ ${formatValidationResults(validation)}`
2199
+ };
2200
+ }
1896
2201
  return {
1897
2202
  switchAgent: "tapestry",
1898
- contextInjection: buildResumeContext(existingState.active_plan, existingState.plan_name, progress)
2203
+ contextInjection: resumeContext
1899
2204
  };
1900
2205
  }
1901
2206
  }
@@ -1934,12 +2239,34 @@ The plan "${getPlanName(matched)}" has all ${progress.total} tasks completed.
1934
2239
  Tell the user this plan is already done and suggest creating a new one with Pattern.`
1935
2240
  };
1936
2241
  }
2242
+ const validation = validatePlan(matched, directory);
2243
+ if (!validation.valid) {
2244
+ return {
2245
+ switchAgent: "tapestry",
2246
+ contextInjection: `## Plan Validation Failed
2247
+ The plan "${getPlanName(matched)}" has structural issues that must be fixed before execution can begin.
2248
+
2249
+ ${formatValidationResults(validation)}
2250
+
2251
+ Tell the user to fix these issues in the plan file and try again.`
2252
+ };
2253
+ }
1937
2254
  clearWorkState(directory);
1938
2255
  const state = createWorkState(matched, sessionId, "tapestry");
1939
2256
  writeWorkState(directory, state);
2257
+ const freshContext = buildFreshContext(matched, getPlanName(matched), progress);
2258
+ if (validation.warnings.length > 0) {
2259
+ return {
2260
+ switchAgent: "tapestry",
2261
+ contextInjection: `${freshContext}
2262
+
2263
+ ### Validation Warnings
2264
+ ${formatValidationResults(validation)}`
2265
+ };
2266
+ }
1940
2267
  return {
1941
2268
  switchAgent: "tapestry",
1942
- contextInjection: buildFreshContext(matched, getPlanName(matched), progress)
2269
+ contextInjection: freshContext
1943
2270
  };
1944
2271
  }
1945
2272
  function handlePlanDiscovery(allPlans, sessionId, directory) {
@@ -1961,11 +2288,33 @@ Tell the user to switch to Pattern agent to create a new plan.`
1961
2288
  if (incompletePlans.length === 1) {
1962
2289
  const plan = incompletePlans[0];
1963
2290
  const progress = getPlanProgress(plan);
2291
+ const validation = validatePlan(plan, directory);
2292
+ if (!validation.valid) {
2293
+ return {
2294
+ switchAgent: "tapestry",
2295
+ contextInjection: `## Plan Validation Failed
2296
+ The plan "${getPlanName(plan)}" has structural issues that must be fixed before execution can begin.
2297
+
2298
+ ${formatValidationResults(validation)}
2299
+
2300
+ Tell the user to fix these issues in the plan file and try again.`
2301
+ };
2302
+ }
1964
2303
  const state = createWorkState(plan, sessionId, "tapestry");
1965
2304
  writeWorkState(directory, state);
2305
+ const freshContext = buildFreshContext(plan, getPlanName(plan), progress);
2306
+ if (validation.warnings.length > 0) {
2307
+ return {
2308
+ switchAgent: "tapestry",
2309
+ contextInjection: `${freshContext}
2310
+
2311
+ ### Validation Warnings
2312
+ ${formatValidationResults(validation)}`
2313
+ };
2314
+ }
1966
2315
  return {
1967
2316
  switchAgent: "tapestry",
1968
- contextInjection: buildFreshContext(plan, getPlanName(plan), progress)
2317
+ contextInjection: freshContext
1969
2318
  };
1970
2319
  }
1971
2320
  const listing = incompletePlans.map((p) => {
@@ -1990,6 +2339,25 @@ function findPlanByName(plans, requestedName) {
1990
2339
  const partial = plans.find((p) => getPlanName(p).toLowerCase().includes(lower));
1991
2340
  return partial || null;
1992
2341
  }
2342
+ function formatValidationResults(result) {
2343
+ const lines = [];
2344
+ if (result.errors.length > 0) {
2345
+ lines.push("**Errors (blocking):**");
2346
+ for (const err of result.errors) {
2347
+ lines.push(`- [${err.category}] ${err.message}`);
2348
+ }
2349
+ }
2350
+ if (result.warnings.length > 0) {
2351
+ if (result.errors.length > 0)
2352
+ lines.push("");
2353
+ lines.push("**Warnings:**");
2354
+ for (const warn of result.warnings) {
2355
+ lines.push(`- [${warn.category}] ${warn.message}`);
2356
+ }
2357
+ }
2358
+ return lines.join(`
2359
+ `);
2360
+ }
1993
2361
  function buildFreshContext(planPath, planName, progress) {
1994
2362
  return `## Starting Plan: ${planName}
1995
2363
  **Plan file**: ${planPath}
@@ -2067,7 +2435,7 @@ Before marking this task complete, verify the work:
2067
2435
  If uncertain about quality, delegate to \`weft\` agent for a formal review:
2068
2436
  \`call_weave_agent(agent="weft", prompt="Review the changes for [task description]")\`
2069
2437
 
2070
- If changes touch auth, crypto, tokens, or input validation, delegate to \`warp\` agent for a security audit:
2438
+ MANDATORY: If changes touch auth, crypto, certificates, tokens, signatures, or input validation, you MUST delegate to \`warp\` agent for a security audit — this is NOT optional:
2071
2439
  \`call_weave_agent(agent="warp", prompt="Security audit the changes for [task description]")\`
2072
2440
 
2073
2441
  Only mark complete when ALL checks pass.`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencode_weave/weave",
3
- "version": "0.4.2",
3
+ "version": "0.5.1",
4
4
  "description": "Weave — lean OpenCode plugin with multi-agent orchestration",
5
5
  "author": "Weave",
6
6
  "license": "MIT",