@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 +7 -25
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/schema.d.ts +0 -9
- package/dist/features/skill-loader/index.d.ts +1 -2
- package/dist/features/skill-loader/loader.d.ts +2 -1
- package/dist/features/skill-loader/opencode-client.d.ts +2 -0
- package/dist/features/skill-loader/types.d.ts +1 -8
- package/dist/features/work-state/index.d.ts +1 -1
- package/dist/features/work-state/storage.d.ts +6 -1
- package/dist/features/work-state/types.d.ts +2 -0
- package/dist/index.js +171 -56
- package/package.json +1 -1
- package/dist/features/skill-loader/builtin-skills.d.ts +0 -2
- package/dist/features/skill-loader/merger.d.ts +0 -2
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:
|
|
25
|
-
- [Step 2:
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
package/dist/config/loader.d.ts
CHANGED
|
@@ -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;
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -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
|
|
7
|
+
export declare function loadSkills(options: LoadSkillsOptions): Promise<SkillDiscoveryResult>;
|
|
@@ -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
|
|
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,
|
|
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
|
-
|
|
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:
|
|
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,
|
|
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
|
|
1541
|
-
const
|
|
1542
|
-
const projectDir = path3.join(directory
|
|
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
|
|
1547
|
-
|
|
1548
|
-
|
|
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
|
|
1645
|
+
return { skills };
|
|
1551
1646
|
const disabledSet = new Set(disabledSkills);
|
|
1552
|
-
return { skills:
|
|
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