@opencode_weave/weave 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,9 +21,8 @@ Weave is a lean OpenCode plugin with multi-agent orchestration. It provides a co
21
21
  - [Quick Tasks (No Plan Needed)](#quick-tasks-no-plan-needed)
22
22
  - [Installation](#installation)
23
23
  - [Prerequisites](#prerequisites)
24
- - [Step 1: Install](#step-1-install)
25
- - [Step 2: Register in opencode.json](#step-2-register-in-opencodejson)
26
- - [Step 3: Restart OpenCode](#step-3-restart-opencode)
24
+ - [Step 1: Add to opencode.json](#step-1-add-to-opencodejson)
25
+ - [Step 2: Restart OpenCode](#step-2-restart-opencode)
27
26
  - [Troubleshooting](#troubleshooting)
28
27
  - [Uninstalling](#uninstalling)
29
28
  - [Configuration](#configuration)
@@ -149,17 +148,8 @@ This package is published on [npm](https://www.npmjs.com/package/@opencode_weave
149
148
  ### Prerequisites
150
149
 
151
150
  - [OpenCode](https://opencode.ai)
152
- - Bun or Node.js
153
151
 
154
- ### Step 1: Install
155
-
156
- ```bash
157
- bun add @opencode_weave/weave
158
- # or
159
- npm install @opencode_weave/weave
160
- ```
161
-
162
- ### Step 2: Register in opencode.json
152
+ ### Step 1: Add to opencode.json
163
153
 
164
154
  Add the plugin to your `opencode.json` file:
165
155
 
@@ -169,9 +159,9 @@ Add the plugin to your `opencode.json` file:
169
159
  }
170
160
  ```
171
161
 
172
- ### Step 3: Restart OpenCode
162
+ ### Step 2: Restart OpenCode
173
163
 
174
- The plugin loads automatically upon restart and works with zero configuration out of the box.
164
+ OpenCode automatically installs npm plugins at startup — no manual `bun add` or `npm install` required. The plugin loads automatically upon restart and works with zero configuration out of the box.
175
165
 
176
166
  ### Troubleshooting
177
167
 
@@ -194,15 +184,7 @@ Delete the `@opencode_weave/weave` entry from the `plugin` array in your `openco
194
184
  }
195
185
  ```
196
186
 
197
- ### Step 2: Uninstall the package
198
-
199
- ```bash
200
- bun remove @opencode_weave/weave
201
- # or
202
- npm uninstall @opencode_weave/weave
203
- ```
204
-
205
- ### Step 3: Clean up project artifacts (optional)
187
+ ### Step 2: Clean up project artifacts (optional)
206
188
 
207
189
  Weave may have created plan and state files during usage. Remove them if no longer needed:
208
190
 
@@ -216,7 +198,7 @@ You can also remove any project-level configuration if present:
216
198
  rm -f .opencode/weave-opencode.jsonc .opencode/weave-opencode.json
217
199
  ```
218
200
 
219
- ### Step 4: Clean up user-level configuration (optional)
201
+ ### Step 3: Clean up user-level configuration (optional)
220
202
 
221
203
  If you no longer use Weave in any project, remove the global configuration:
222
204
 
@@ -1,2 +1,2 @@
1
1
  import { type WeaveConfig } from "./schema";
2
- export declare function loadWeaveConfig(directory: string, _ctx?: unknown): WeaveConfig;
2
+ export declare function loadWeaveConfig(directory: string, _ctx?: unknown, _homeDir?: string): WeaveConfig;
@@ -78,10 +78,6 @@ export declare const TmuxConfigSchema: z.ZodObject<{
78
78
  }>>;
79
79
  main_pane_size: z.ZodOptional<z.ZodNumber>;
80
80
  }, z.core.$strip>;
81
- export declare const SkillsConfigSchema: z.ZodObject<{
82
- paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
83
- recursive: z.ZodOptional<z.ZodBoolean>;
84
- }, z.core.$strip>;
85
81
  export declare const ExperimentalConfigSchema: z.ZodObject<{
86
82
  plugin_load_timeout_ms: z.ZodOptional<z.ZodNumber>;
87
83
  context_window_warning_threshold: z.ZodOptional<z.ZodNumber>;
@@ -141,10 +137,6 @@ export declare const WeaveConfigSchema: z.ZodObject<{
141
137
  }>>;
142
138
  main_pane_size: z.ZodOptional<z.ZodNumber>;
143
139
  }, z.core.$strip>>;
144
- skills: z.ZodOptional<z.ZodObject<{
145
- paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
146
- recursive: z.ZodOptional<z.ZodBoolean>;
147
- }, z.core.$strip>>;
148
140
  experimental: z.ZodOptional<z.ZodObject<{
149
141
  plugin_load_timeout_ms: z.ZodOptional<z.ZodNumber>;
150
142
  context_window_warning_threshold: z.ZodOptional<z.ZodNumber>;
@@ -157,6 +149,5 @@ export type CategoryConfig = z.infer<typeof CategoryConfigSchema>;
157
149
  export type CategoriesConfig = z.infer<typeof CategoriesConfigSchema>;
158
150
  export type BackgroundConfig = z.infer<typeof BackgroundConfigSchema>;
159
151
  export type TmuxConfig = z.infer<typeof TmuxConfigSchema>;
160
- export type SkillsConfig = z.infer<typeof SkillsConfigSchema>;
161
152
  export type ExperimentalConfig = z.infer<typeof ExperimentalConfigSchema>;
162
153
  export type WeaveConfig = z.infer<typeof WeaveConfigSchema>;
@@ -1,7 +1,6 @@
1
1
  export { loadSkills } from "./loader";
2
2
  export type { LoadSkillsOptions } from "./loader";
3
3
  export { createResolveSkillsFn, createSkillResolver, resolveMultipleSkills, resolveSkill } from "./resolver";
4
+ export { fetchSkillsFromOpenCode } from "./opencode-client";
4
5
  export { scanDirectory, parseFrontmatter } from "./discovery";
5
- export { mergeSkills } from "./merger";
6
- export { createBuiltinSkills } from "./builtin-skills";
7
6
  export type { SkillScope, SkillMetadata, LoadedSkill, SkillDiscoveryResult } from "./types";
@@ -1,6 +1,7 @@
1
1
  import type { SkillDiscoveryResult } from './types';
2
2
  export interface LoadSkillsOptions {
3
+ serverUrl: string | URL;
3
4
  directory?: string;
4
5
  disabledSkills?: string[];
5
6
  }
6
- export declare function loadSkills(options?: LoadSkillsOptions): SkillDiscoveryResult;
7
+ export declare function loadSkills(options: LoadSkillsOptions): Promise<SkillDiscoveryResult>;
@@ -0,0 +1,2 @@
1
+ import type { LoadedSkill } from "./types";
2
+ export declare function fetchSkillsFromOpenCode(serverUrl: string | URL, directory: string): Promise<LoadedSkill[]>;
@@ -4,19 +4,12 @@ export interface SkillMetadata {
4
4
  description?: string;
5
5
  model?: string;
6
6
  tools?: string | string[];
7
- mcp?: {
8
- name: string;
9
- type: "stdio" | "http";
10
- command?: string;
11
- args?: string[];
12
- url?: string;
13
- };
14
7
  }
15
8
  export interface LoadedSkill {
16
9
  name: string;
17
10
  description: string;
18
11
  content: string;
19
- scope: SkillScope;
12
+ scope?: SkillScope;
20
13
  path?: string;
21
14
  model?: string;
22
15
  }
@@ -1,5 +1,5 @@
1
1
  export type { WorkState, PlanProgress } from "./types";
2
2
  export type { ValidationResult, ValidationIssue, ValidationSeverity, ValidationCategory } from "./validation-types";
3
3
  export { WEAVE_DIR, WORK_STATE_FILE, WORK_STATE_PATH, PLANS_DIR } from "./constants";
4
- export { readWorkState, writeWorkState, clearWorkState, appendSessionId, createWorkState, findPlans, getPlanProgress, getPlanName, } from "./storage";
4
+ export { readWorkState, writeWorkState, clearWorkState, appendSessionId, createWorkState, findPlans, getPlanProgress, getPlanName, getHeadSha, } from "./storage";
5
5
  export { validatePlan } from "./validation";
@@ -21,7 +21,12 @@ export declare function appendSessionId(directory: string, sessionId: string): W
21
21
  /**
22
22
  * Create a fresh WorkState for a plan file.
23
23
  */
24
- export declare function createWorkState(planPath: string, sessionId: string, agent?: string): WorkState;
24
+ export declare function createWorkState(planPath: string, sessionId: string, agent?: string, directory?: string): WorkState;
25
+ /**
26
+ * Get the current HEAD SHA of the git repo at the given directory.
27
+ * Returns undefined if not a git repo or git is unavailable.
28
+ */
29
+ export declare function getHeadSha(directory: string): string | undefined;
25
30
  /**
26
31
  * Find all plan files in .weave/plans/, sorted by modification time (newest first).
27
32
  * Returns absolute paths.
@@ -13,6 +13,8 @@ export interface WorkState {
13
13
  plan_name: string;
14
14
  /** Agent type to use when resuming (e.g., "tapestry") */
15
15
  agent?: string;
16
+ /** Git HEAD SHA at the time work started (absent if not a git repo) */
17
+ start_sha?: string;
16
18
  }
17
19
  /**
18
20
  * Progress snapshot from counting checkboxes in a plan file.
package/dist/index.js CHANGED
@@ -46,10 +46,6 @@ var TmuxConfigSchema = z.object({
46
46
  layout: z.enum(["main-horizontal", "main-vertical", "tiled", "even-horizontal", "even-vertical"]).optional(),
47
47
  main_pane_size: z.number().optional()
48
48
  });
49
- var SkillsConfigSchema = z.object({
50
- paths: z.array(z.string()).optional(),
51
- recursive: z.boolean().optional()
52
- });
53
49
  var ExperimentalConfigSchema = z.object({
54
50
  plugin_load_timeout_ms: z.number().min(1000).optional(),
55
51
  context_window_warning_threshold: z.number().min(0).max(1).optional(),
@@ -65,7 +61,6 @@ var WeaveConfigSchema = z.object({
65
61
  disabled_skills: z.array(z.string()).optional(),
66
62
  background: BackgroundConfigSchema.optional(),
67
63
  tmux: TmuxConfigSchema.optional(),
68
- skills: SkillsConfigSchema.optional(),
69
64
  experimental: ExperimentalConfigSchema.optional()
70
65
  });
71
66
 
@@ -100,7 +95,6 @@ function mergeConfigs(user, project) {
100
95
  disabled_skills: mergeStringArrays(user.disabled_skills, project.disabled_skills),
101
96
  background: project.background ?? user.background,
102
97
  tmux: project.tmux ?? user.tmux,
103
- skills: project.skills ?? user.skills,
104
98
  experimental: user.experimental || project.experimental ? { ...user.experimental, ...project.experimental } : undefined
105
99
  };
106
100
  }
@@ -152,8 +146,8 @@ function detectConfigFile(basePath) {
152
146
  return jsonPath;
153
147
  return null;
154
148
  }
155
- function loadWeaveConfig(directory, _ctx) {
156
- const userBasePath = join2(homedir(), ".config", "opencode", "weave-opencode");
149
+ function loadWeaveConfig(directory, _ctx, _homeDir) {
150
+ const userBasePath = join2(_homeDir ?? homedir(), ".config", "opencode", "weave-opencode");
157
151
  const projectBasePath = join2(directory, ".opencode", "weave-opencode");
158
152
  const userConfigPath = detectConfigFile(userBasePath);
159
153
  const projectConfigPath = detectConfigFile(projectBasePath);
@@ -525,7 +519,7 @@ FORMAT RULES:
525
519
  - Use /start-work to hand off to Tapestry for todo-list driven execution of multi-step plans
526
520
  - Use shuttle for category-specific specialized work
527
521
  - Use Weft for reviewing completed work or validating plans before execution
528
- - MUST use Warp for security audits when changes touch auth, crypto, certificates, tokens, signatures, or input validation — not optional
522
+ - MUST use Warp for security audits when changes touch auth, crypto, certificates, tokens, signatures, input validation, secrets, passwords, sessions, CORS, CSP, .env files, or OAuth/OIDC/SAML flows — not optional. When in doubt, invoke Warp — false positives (fast APPROVE) are cheap.
529
523
  - Delegate aggressively to keep your context lean
530
524
  </Delegation>
531
525
 
@@ -574,25 +568,32 @@ For complex tasks that benefit from structured planning before execution:
574
568
  - Tapestry reads the plan and works through tasks, marking checkboxes as it goes
575
569
  4. RESUME: If work was interrupted, \`/start-work\` resumes from the last unchecked task
576
570
 
571
+ Note: Tapestry runs Weft and Warp reviews directly after completing all tasks — Loom does not need to gate this.
572
+
577
573
  When to use this workflow vs. direct execution:
578
574
  - USE plan workflow: Large features, multi-file refactors, anything with 5+ steps or architectural decisions
579
575
  - SKIP plan workflow: Quick fixes, single-file changes, simple questions
580
576
  </PlanWorkflow>
581
577
 
582
578
  <ReviewWorkflow>
583
- After significant implementation work completes:
579
+ Two review modes different rules for each:
580
+
581
+ **Post-Plan-Execution Review:**
582
+ - Handled directly by Tapestry — Tapestry invokes Weft and Warp after completing all tasks.
583
+ - Loom does not need to intervene.
584
+
585
+ **Ad-Hoc Review (non-plan work):**
584
586
  - Delegate to Weft to review the changes
585
587
  - Weft is read-only and approval-biased — it rejects only for real problems
586
588
  - If Weft approves: proceed confidently
587
589
  - If Weft rejects: address the specific blocking issues, then re-review
588
590
 
589
- When to invoke Weft:
590
- - After completing a multi-step plan
591
+ When to invoke ad-hoc Weft:
591
592
  - After any task that touches 3+ files
592
593
  - Before shipping to the user when quality matters
593
594
  - When you're unsure if work meets acceptance criteria
594
595
 
595
- When to skip Weft:
596
+ When to skip ad-hoc Weft:
596
597
  - Single-file trivial changes
597
598
  - User explicitly says "skip review"
598
599
  - Simple question-answering (no code changes)
@@ -626,7 +627,6 @@ var TAPESTRY_DEFAULTS = {
626
627
  temperature: 0.1,
627
628
  description: "Tapestry (Execution Orchestrator)",
628
629
  tools: {
629
- task: false,
630
630
  call_weave_agent: false
631
631
  },
632
632
  prompt: `<Role>
@@ -684,15 +684,51 @@ When activated by /start-work with a plan file:
684
684
  3. For each task:
685
685
  a. Read the task description, files, and acceptance criteria
686
686
  b. Execute the work (write code, run commands, create files)
687
- c. Verify: Read changed files, run tests, check acceptance criteria. If uncertain about quality, note that Loom should invoke Weft for formal review.
687
+ c. Verify: Follow the <Verification> protocol below ALL checks must pass before marking complete. If uncertain about quality, note that Loom should invoke Weft for formal review.
688
688
  d. Mark complete: use Edit tool to change \`- [ ]\` to \`- [x]\` in the plan file
689
689
  e. Report: "Completed task N/M: [title]"
690
690
  4. CONTINUE to the next unchecked task
691
- 5. When ALL checkboxes are checked, report final summary
691
+ 5. When ALL checkboxes are checked, follow the <PostExecutionReview> protocol below before reporting final summary.
692
692
 
693
693
  NEVER stop mid-plan unless explicitly told to or completely blocked.
694
694
  </PlanExecution>
695
695
 
696
+ <Verification>
697
+ After completing work for each task — BEFORE marking \`- [ ]\` → \`- [x]\`:
698
+
699
+ 1. **Inspect changes**:
700
+ - Review your Edit/Write tool call history to identify all files you modified
701
+ - Read EVERY changed file to confirm correctness
702
+ - Cross-check: does the code actually implement what the task required?
703
+
704
+ 2. **Validate acceptance criteria**:
705
+ - Re-read the task's acceptance criteria from the plan
706
+ - Verify EACH criterion is met — exactly, not approximately
707
+ - If any criterion is unmet: address it, then re-verify
708
+
709
+ 3. **Accumulate learnings** (if \`.weave/learnings/{plan-name}.md\` exists or plan has multiple tasks):
710
+ - After verification passes, append 1-3 bullet points of key findings
711
+ - Before starting the NEXT task, read the learnings file for context from previous tasks
712
+
713
+ **Gate**: Only mark complete when ALL checks pass. If ANY check fails, fix first.
714
+ </Verification>
715
+
716
+ <PostExecutionReview>
717
+ After ALL plan tasks are checked off, run this mandatory review gate:
718
+
719
+ 1. Identify all changed files:
720
+ - If a **Start SHA** was provided in the session context, run \`git diff --name-only <start-sha>..HEAD\` to get the complete list of changed files (this captures all changes including intermediate commits)
721
+ - If no Start SHA is available (non-git workspace), use the plan's \`**Files**:\` fields as the review scope
722
+ 2. Delegate to Weft (quality review) AND Warp (security audit) in parallel using the Task tool:
723
+ - Weft: subagent_type "weft" — reviews code quality
724
+ - Warp: subagent_type "warp" — audits security (self-triages; fast-exits with APPROVE if no security-relevant changes)
725
+ - Include the list of changed files in your prompt to each reviewer
726
+ 3. Report the review results to the user:
727
+ - Summarize Weft's and Warp's findings (APPROVE or REJECT with details)
728
+ - If either reviewer REJECTS, present the blocking issues to the user for decision — do NOT attempt to fix them yourself
729
+ - Tapestry follows the plan; review findings require user approval before any further changes
730
+ </PostExecutionReview>
731
+
696
732
  <Execution>
697
733
  - Work through tasks top to bottom
698
734
  - Verify each step before marking complete
@@ -1361,6 +1397,14 @@ function createBuiltinAgents(options = {}) {
1361
1397
  resolveSkills
1362
1398
  });
1363
1399
  if (override) {
1400
+ if (override.skills?.length && resolveSkills) {
1401
+ const skillContent = resolveSkills(override.skills, disabledSkills);
1402
+ if (skillContent) {
1403
+ built.prompt = skillContent + (built.prompt ? `
1404
+
1405
+ ` + built.prompt : "");
1406
+ }
1407
+ }
1364
1408
  if (override.prompt_append) {
1365
1409
  built.prompt = (built.prompt ? built.prompt + `
1366
1410
 
@@ -1395,6 +1439,55 @@ function createManagers(options) {
1395
1439
  import * as path3 from "path";
1396
1440
  import * as os2 from "os";
1397
1441
 
1442
+ // src/features/skill-loader/opencode-client.ts
1443
+ function deriveScope(location) {
1444
+ if (location.includes(".opencode"))
1445
+ return "project";
1446
+ return "user";
1447
+ }
1448
+ async function fetchSkillsFromOpenCode(serverUrl, directory) {
1449
+ const base = serverUrl.toString().replace(/\/$/, "");
1450
+ const url = `${base}/skill?directory=${encodeURIComponent(directory)}`;
1451
+ let response;
1452
+ try {
1453
+ response = await fetch(url);
1454
+ } catch (err) {
1455
+ log("Failed to fetch skills from OpenCode — skills will not be loaded", { url, error: String(err) });
1456
+ return [];
1457
+ }
1458
+ if (!response.ok) {
1459
+ log("OpenCode /skill endpoint returned non-OK status — skills will not be loaded", {
1460
+ url,
1461
+ status: response.status
1462
+ });
1463
+ return [];
1464
+ }
1465
+ let data;
1466
+ try {
1467
+ data = await response.json();
1468
+ } catch (err) {
1469
+ log("Failed to parse skills response from OpenCode", { url, error: String(err) });
1470
+ return [];
1471
+ }
1472
+ if (!Array.isArray(data)) {
1473
+ log("Unexpected skills response shape from OpenCode — expected array", { url });
1474
+ return [];
1475
+ }
1476
+ const skills = [];
1477
+ for (const item of data) {
1478
+ if (!item.name)
1479
+ continue;
1480
+ skills.push({
1481
+ name: item.name,
1482
+ description: item.description ?? "",
1483
+ content: item.content ?? "",
1484
+ scope: deriveScope(item.location ?? ""),
1485
+ path: item.location
1486
+ });
1487
+ }
1488
+ return skills;
1489
+ }
1490
+
1398
1491
  // src/features/skill-loader/discovery.ts
1399
1492
  import * as fs2 from "fs";
1400
1493
  import * as path2 from "path";
@@ -1518,38 +1611,40 @@ function loadSkillFile(filePath, scope) {
1518
1611
  return { name: metadata.name, description: metadata.description ?? "", content, scope, path: filePath, model: metadata.model };
1519
1612
  }
1520
1613
 
1521
- // src/features/skill-loader/merger.ts
1522
- var SCOPE_PRIORITY = { project: 3, user: 2, builtin: 1 };
1523
- function mergeSkills(skills) {
1524
- const skillMap = new Map;
1525
- for (const skill of skills) {
1526
- const existing = skillMap.get(skill.name);
1527
- if (!existing || (SCOPE_PRIORITY[skill.scope] ?? 0) > (SCOPE_PRIORITY[existing.scope] ?? 0)) {
1528
- skillMap.set(skill.name, skill);
1529
- }
1530
- }
1531
- return Array.from(skillMap.values());
1532
- }
1533
-
1534
- // src/features/skill-loader/builtin-skills.ts
1535
- function createBuiltinSkills() {
1536
- return [];
1537
- }
1538
-
1539
1614
  // src/features/skill-loader/loader.ts
1540
- function loadSkills(options = {}) {
1541
- const { directory, disabledSkills = [] } = options;
1542
- const projectDir = path3.join(directory ?? process.cwd(), ".opencode", "skills");
1543
- const userDir = path3.join(os2.homedir(), ".config", "opencode", "weave-opencode", "skills");
1544
- const projectSkills = scanDirectory({ directory: projectDir, scope: "project" });
1615
+ function scanFilesystemSkills(directory) {
1616
+ const userDir = path3.join(os2.homedir(), ".config", "opencode", "skills");
1617
+ const projectDir = path3.join(directory, ".opencode", "skills");
1545
1618
  const userSkills = scanDirectory({ directory: userDir, scope: "user" });
1546
- const builtinSkills = createBuiltinSkills();
1547
- const all = [...projectSkills, ...userSkills, ...builtinSkills];
1548
- const merged = mergeSkills(all);
1619
+ const projectSkills = scanDirectory({ directory: projectDir, scope: "project" });
1620
+ return [...projectSkills, ...userSkills];
1621
+ }
1622
+ function mergeSkillSources(apiSkills, fsSkills) {
1623
+ const seen = new Set(apiSkills.map((s) => s.name));
1624
+ const merged = [...apiSkills];
1625
+ for (const skill of fsSkills) {
1626
+ if (!seen.has(skill.name)) {
1627
+ merged.push(skill);
1628
+ seen.add(skill.name);
1629
+ }
1630
+ }
1631
+ return merged;
1632
+ }
1633
+ async function loadSkills(options) {
1634
+ const { serverUrl, directory = process.cwd(), disabledSkills = [] } = options;
1635
+ const apiSkills = await fetchSkillsFromOpenCode(serverUrl, directory);
1636
+ const fsSkills = scanFilesystemSkills(directory);
1637
+ const skills = mergeSkillSources(apiSkills, fsSkills);
1638
+ if (apiSkills.length === 0 && fsSkills.length > 0) {
1639
+ log("OpenCode API returned no skills — using filesystem fallback", {
1640
+ fsSkillCount: fsSkills.length,
1641
+ fsSkillNames: fsSkills.map((s) => s.name)
1642
+ });
1643
+ }
1549
1644
  if (disabledSkills.length === 0)
1550
- return { skills: merged };
1645
+ return { skills };
1551
1646
  const disabledSet = new Set(disabledSkills);
1552
- return { skills: merged.filter((s) => !disabledSet.has(s.name)) };
1647
+ return { skills: skills.filter((s) => !disabledSet.has(s.name)) };
1553
1648
  }
1554
1649
  // src/features/skill-loader/resolver.ts
1555
1650
  function resolveSkill(name, result) {
@@ -1579,7 +1674,8 @@ function createSkillResolver(discovered) {
1579
1674
  // src/create-tools.ts
1580
1675
  async function createTools(options) {
1581
1676
  const { ctx, pluginConfig } = options;
1582
- const skillResult = loadSkills({
1677
+ const skillResult = await loadSkills({
1678
+ serverUrl: ctx.serverUrl,
1583
1679
  directory: ctx.directory,
1584
1680
  disabledSkills: pluginConfig.disabled_skills ?? []
1585
1681
  });
@@ -1779,6 +1875,7 @@ var PLANS_DIR = `${WEAVE_DIR}/plans`;
1779
1875
  // src/features/work-state/storage.ts
1780
1876
  import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync, unlinkSync, mkdirSync, readdirSync as readdirSync2, statSync } from "fs";
1781
1877
  import { join as join6, basename } from "path";
1878
+ import { execSync } from "child_process";
1782
1879
  var UNCHECKED_RE = /^[-*]\s*\[\s*\]/gm;
1783
1880
  var CHECKED_RE = /^[-*]\s*\[[xX]\]/gm;
1784
1881
  function readWorkState(directory) {
@@ -1833,15 +1930,29 @@ function appendSessionId(directory, sessionId) {
1833
1930
  }
1834
1931
  return state;
1835
1932
  }
1836
- function createWorkState(planPath, sessionId, agent) {
1933
+ function createWorkState(planPath, sessionId, agent, directory) {
1934
+ const startSha = directory ? getHeadSha(directory) : undefined;
1837
1935
  return {
1838
1936
  active_plan: planPath,
1839
1937
  started_at: new Date().toISOString(),
1840
1938
  session_ids: [sessionId],
1841
1939
  plan_name: getPlanName(planPath),
1842
- ...agent !== undefined ? { agent } : {}
1940
+ ...agent !== undefined ? { agent } : {},
1941
+ ...startSha !== undefined ? { start_sha: startSha } : {}
1843
1942
  };
1844
1943
  }
1944
+ function getHeadSha(directory) {
1945
+ try {
1946
+ const sha = execSync("git rev-parse HEAD", {
1947
+ cwd: directory,
1948
+ encoding: "utf-8",
1949
+ stdio: ["pipe", "pipe", "pipe"]
1950
+ }).trim();
1951
+ return sha || undefined;
1952
+ } catch {
1953
+ return;
1954
+ }
1955
+ }
1845
1956
  function findPlans(directory) {
1846
1957
  const plansDir = join6(directory, PLANS_DIR);
1847
1958
  try {
@@ -2188,7 +2299,7 @@ Tell the user to fix the plan file and run /start-work again.`
2188
2299
  };
2189
2300
  }
2190
2301
  appendSessionId(directory, sessionId);
2191
- const resumeContext = buildResumeContext(existingState.active_plan, existingState.plan_name, progress);
2302
+ const resumeContext = buildResumeContext(existingState.active_plan, existingState.plan_name, progress, existingState.start_sha);
2192
2303
  if (validation.warnings.length > 0) {
2193
2304
  return {
2194
2305
  switchAgent: "tapestry",
@@ -2252,9 +2363,9 @@ Tell the user to fix these issues in the plan file and try again.`
2252
2363
  };
2253
2364
  }
2254
2365
  clearWorkState(directory);
2255
- const state = createWorkState(matched, sessionId, "tapestry");
2366
+ const state = createWorkState(matched, sessionId, "tapestry", directory);
2256
2367
  writeWorkState(directory, state);
2257
- const freshContext = buildFreshContext(matched, getPlanName(matched), progress);
2368
+ const freshContext = buildFreshContext(matched, getPlanName(matched), progress, state.start_sha);
2258
2369
  if (validation.warnings.length > 0) {
2259
2370
  return {
2260
2371
  switchAgent: "tapestry",
@@ -2300,9 +2411,9 @@ ${formatValidationResults(validation)}
2300
2411
  Tell the user to fix these issues in the plan file and try again.`
2301
2412
  };
2302
2413
  }
2303
- const state = createWorkState(plan, sessionId, "tapestry");
2414
+ const state = createWorkState(plan, sessionId, "tapestry", directory);
2304
2415
  writeWorkState(directory, state);
2305
- const freshContext = buildFreshContext(plan, getPlanName(plan), progress);
2416
+ const freshContext = buildFreshContext(plan, getPlanName(plan), progress, state.start_sha);
2306
2417
  if (validation.warnings.length > 0) {
2307
2418
  return {
2308
2419
  switchAgent: "tapestry",
@@ -2358,10 +2469,12 @@ function formatValidationResults(result) {
2358
2469
  return lines.join(`
2359
2470
  `);
2360
2471
  }
2361
- function buildFreshContext(planPath, planName, progress) {
2472
+ function buildFreshContext(planPath, planName, progress, startSha) {
2473
+ const shaLine = startSha ? `
2474
+ **Start SHA**: ${startSha}` : "";
2362
2475
  return `## Starting Plan: ${planName}
2363
2476
  **Plan file**: ${planPath}
2364
- **Progress**: ${progress.completed}/${progress.total} tasks completed
2477
+ **Progress**: ${progress.completed}/${progress.total} tasks completed${shaLine}
2365
2478
 
2366
2479
  Read the plan file now and begin executing from the first unchecked \`- [ ]\` task.
2367
2480
 
@@ -2372,12 +2485,14 @@ Before starting any work, use todowrite to populate the sidebar:
2372
2485
  3. Create todos for the next 2-3 tasks (pending)
2373
2486
  Keep each todo under 35 chars. Update as you complete tasks.`;
2374
2487
  }
2375
- function buildResumeContext(planPath, planName, progress) {
2488
+ function buildResumeContext(planPath, planName, progress, startSha) {
2376
2489
  const remaining = progress.total - progress.completed;
2490
+ const shaLine = startSha ? `
2491
+ **Start SHA**: ${startSha}` : "";
2377
2492
  return `## Resuming Plan: ${planName}
2378
2493
  **Plan file**: ${planPath}
2379
2494
  **Progress**: ${progress.completed}/${progress.total} tasks completed
2380
- **Status**: RESUMING — continuing from where the previous session left off.
2495
+ **Status**: RESUMING — continuing from where the previous session left off.${shaLine}
2381
2496
 
2382
2497
  Read the plan file now and continue from the first unchecked \`- [ ]\` task.
2383
2498
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencode_weave/weave",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "Weave — lean OpenCode plugin with multi-agent orchestration",
5
5
  "author": "Weave",
6
6
  "license": "MIT",
@@ -1,2 +0,0 @@
1
- import type { LoadedSkill } from "./types";
2
- export declare function createBuiltinSkills(): LoadedSkill[];
@@ -1,2 +0,0 @@
1
- import type { LoadedSkill } from './types';
2
- export declare function mergeSkills(skills: LoadedSkill[]): LoadedSkill[];