@opencode_weave/weave 0.5.2 → 0.6.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/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 +194 -83
- 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/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);
|
|
@@ -573,15 +567,8 @@ For complex tasks that benefit from structured planning before execution:
|
|
|
573
567
|
- /start-work loads the plan, creates work state at \`.weave/state.json\`, and switches to Tapestry
|
|
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
|
-
|
|
577
|
-
|
|
578
|
-
a. Run \`git diff --stat\` to identify all changed files
|
|
579
|
-
b. Delegate to Weft (quality review) AND Warp (security audit) in parallel
|
|
580
|
-
c. Warp self-triages: if no security-relevant changes, it fast-exits with APPROVE — so always invoke it
|
|
581
|
-
d. If Weft or Warp REJECT → address blocking issues, then re-run the rejecting reviewer
|
|
582
|
-
e. Only report success to the user after BOTH Weft and Warp APPROVE
|
|
583
|
-
- This step has NO skip conditions. Not for small changes, not for user request, not for time pressure.
|
|
584
|
-
- Skipping this step is a workflow violation.
|
|
570
|
+
|
|
571
|
+
Note: Tapestry runs Weft and Warp reviews directly after completing all tasks — Loom does not need to gate this.
|
|
585
572
|
|
|
586
573
|
When to use this workflow vs. direct execution:
|
|
587
574
|
- USE plan workflow: Large features, multi-file refactors, anything with 5+ steps or architectural decisions
|
|
@@ -591,11 +578,9 @@ When to use this workflow vs. direct execution:
|
|
|
591
578
|
<ReviewWorkflow>
|
|
592
579
|
Two review modes — different rules for each:
|
|
593
580
|
|
|
594
|
-
**Post-Plan-Execution Review
|
|
595
|
-
-
|
|
596
|
-
-
|
|
597
|
-
- Warp self-triages: fast-exits with APPROVE if no security-relevant changes detected
|
|
598
|
-
- Both must APPROVE before reporting success to the user
|
|
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.
|
|
599
584
|
|
|
600
585
|
**Ad-Hoc Review (non-plan work):**
|
|
601
586
|
- Delegate to Weft to review the changes
|
|
@@ -642,7 +627,6 @@ var TAPESTRY_DEFAULTS = {
|
|
|
642
627
|
temperature: 0.1,
|
|
643
628
|
description: "Tapestry (Execution Orchestrator)",
|
|
644
629
|
tools: {
|
|
645
|
-
task: false,
|
|
646
630
|
call_weave_agent: false
|
|
647
631
|
},
|
|
648
632
|
prompt: `<Role>
|
|
@@ -700,16 +684,51 @@ When activated by /start-work with a plan file:
|
|
|
700
684
|
3. For each task:
|
|
701
685
|
a. Read the task description, files, and acceptance criteria
|
|
702
686
|
b. Execute the work (write code, run commands, create files)
|
|
703
|
-
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.
|
|
704
688
|
d. Mark complete: use Edit tool to change \`- [ ]\` to \`- [x]\` in the plan file
|
|
705
689
|
e. Report: "Completed task N/M: [title]"
|
|
706
690
|
4. CONTINUE to the next unchecked task
|
|
707
|
-
5. When ALL checkboxes are checked,
|
|
708
|
-
"All tasks complete. **Post-execution review required** — Loom must run Weft and Warp before reporting success."
|
|
691
|
+
5. When ALL checkboxes are checked, follow the <PostExecutionReview> protocol below before reporting final summary.
|
|
709
692
|
|
|
710
693
|
NEVER stop mid-plan unless explicitly told to or completely blocked.
|
|
711
694
|
</PlanExecution>
|
|
712
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
|
+
|
|
713
732
|
<Execution>
|
|
714
733
|
- Work through tasks top to bottom
|
|
715
734
|
- Verify each step before marking complete
|
|
@@ -1378,6 +1397,14 @@ function createBuiltinAgents(options = {}) {
|
|
|
1378
1397
|
resolveSkills
|
|
1379
1398
|
});
|
|
1380
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
|
+
}
|
|
1381
1408
|
if (override.prompt_append) {
|
|
1382
1409
|
built.prompt = (built.prompt ? built.prompt + `
|
|
1383
1410
|
|
|
@@ -1412,6 +1439,55 @@ function createManagers(options) {
|
|
|
1412
1439
|
import * as path3 from "path";
|
|
1413
1440
|
import * as os2 from "os";
|
|
1414
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, { signal: AbortSignal.timeout(3000) });
|
|
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
|
+
|
|
1415
1491
|
// src/features/skill-loader/discovery.ts
|
|
1416
1492
|
import * as fs2 from "fs";
|
|
1417
1493
|
import * as path2 from "path";
|
|
@@ -1535,38 +1611,40 @@ function loadSkillFile(filePath, scope) {
|
|
|
1535
1611
|
return { name: metadata.name, description: metadata.description ?? "", content, scope, path: filePath, model: metadata.model };
|
|
1536
1612
|
}
|
|
1537
1613
|
|
|
1538
|
-
// src/features/skill-loader/merger.ts
|
|
1539
|
-
var SCOPE_PRIORITY = { project: 3, user: 2, builtin: 1 };
|
|
1540
|
-
function mergeSkills(skills) {
|
|
1541
|
-
const skillMap = new Map;
|
|
1542
|
-
for (const skill of skills) {
|
|
1543
|
-
const existing = skillMap.get(skill.name);
|
|
1544
|
-
if (!existing || (SCOPE_PRIORITY[skill.scope] ?? 0) > (SCOPE_PRIORITY[existing.scope] ?? 0)) {
|
|
1545
|
-
skillMap.set(skill.name, skill);
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
return Array.from(skillMap.values());
|
|
1549
|
-
}
|
|
1550
|
-
|
|
1551
|
-
// src/features/skill-loader/builtin-skills.ts
|
|
1552
|
-
function createBuiltinSkills() {
|
|
1553
|
-
return [];
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
1614
|
// src/features/skill-loader/loader.ts
|
|
1557
|
-
function
|
|
1558
|
-
const
|
|
1559
|
-
const projectDir = path3.join(directory
|
|
1560
|
-
const userDir = path3.join(os2.homedir(), ".config", "opencode", "weave-opencode", "skills");
|
|
1561
|
-
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");
|
|
1562
1618
|
const userSkills = scanDirectory({ directory: userDir, scope: "user" });
|
|
1563
|
-
const
|
|
1564
|
-
|
|
1565
|
-
|
|
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
|
+
}
|
|
1566
1644
|
if (disabledSkills.length === 0)
|
|
1567
|
-
return { skills
|
|
1645
|
+
return { skills };
|
|
1568
1646
|
const disabledSet = new Set(disabledSkills);
|
|
1569
|
-
return { skills:
|
|
1647
|
+
return { skills: skills.filter((s) => !disabledSet.has(s.name)) };
|
|
1570
1648
|
}
|
|
1571
1649
|
// src/features/skill-loader/resolver.ts
|
|
1572
1650
|
function resolveSkill(name, result) {
|
|
@@ -1596,7 +1674,8 @@ function createSkillResolver(discovered) {
|
|
|
1596
1674
|
// src/create-tools.ts
|
|
1597
1675
|
async function createTools(options) {
|
|
1598
1676
|
const { ctx, pluginConfig } = options;
|
|
1599
|
-
const skillResult = loadSkills({
|
|
1677
|
+
const skillResult = await loadSkills({
|
|
1678
|
+
serverUrl: ctx.serverUrl,
|
|
1600
1679
|
directory: ctx.directory,
|
|
1601
1680
|
disabledSkills: pluginConfig.disabled_skills ?? []
|
|
1602
1681
|
});
|
|
@@ -1796,6 +1875,7 @@ var PLANS_DIR = `${WEAVE_DIR}/plans`;
|
|
|
1796
1875
|
// src/features/work-state/storage.ts
|
|
1797
1876
|
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync, unlinkSync, mkdirSync, readdirSync as readdirSync2, statSync } from "fs";
|
|
1798
1877
|
import { join as join6, basename } from "path";
|
|
1878
|
+
import { execSync } from "child_process";
|
|
1799
1879
|
var UNCHECKED_RE = /^[-*]\s*\[\s*\]/gm;
|
|
1800
1880
|
var CHECKED_RE = /^[-*]\s*\[[xX]\]/gm;
|
|
1801
1881
|
function readWorkState(directory) {
|
|
@@ -1850,15 +1930,29 @@ function appendSessionId(directory, sessionId) {
|
|
|
1850
1930
|
}
|
|
1851
1931
|
return state;
|
|
1852
1932
|
}
|
|
1853
|
-
function createWorkState(planPath, sessionId, agent) {
|
|
1933
|
+
function createWorkState(planPath, sessionId, agent, directory) {
|
|
1934
|
+
const startSha = directory ? getHeadSha(directory) : undefined;
|
|
1854
1935
|
return {
|
|
1855
1936
|
active_plan: planPath,
|
|
1856
1937
|
started_at: new Date().toISOString(),
|
|
1857
1938
|
session_ids: [sessionId],
|
|
1858
1939
|
plan_name: getPlanName(planPath),
|
|
1859
|
-
...agent !== undefined ? { agent } : {}
|
|
1940
|
+
...agent !== undefined ? { agent } : {},
|
|
1941
|
+
...startSha !== undefined ? { start_sha: startSha } : {}
|
|
1860
1942
|
};
|
|
1861
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
|
+
}
|
|
1862
1956
|
function findPlans(directory) {
|
|
1863
1957
|
const plansDir = join6(directory, PLANS_DIR);
|
|
1864
1958
|
try {
|
|
@@ -2205,7 +2299,7 @@ Tell the user to fix the plan file and run /start-work again.`
|
|
|
2205
2299
|
};
|
|
2206
2300
|
}
|
|
2207
2301
|
appendSessionId(directory, sessionId);
|
|
2208
|
-
const resumeContext = buildResumeContext(existingState.active_plan, existingState.plan_name, progress);
|
|
2302
|
+
const resumeContext = buildResumeContext(existingState.active_plan, existingState.plan_name, progress, existingState.start_sha);
|
|
2209
2303
|
if (validation.warnings.length > 0) {
|
|
2210
2304
|
return {
|
|
2211
2305
|
switchAgent: "tapestry",
|
|
@@ -2269,9 +2363,9 @@ Tell the user to fix these issues in the plan file and try again.`
|
|
|
2269
2363
|
};
|
|
2270
2364
|
}
|
|
2271
2365
|
clearWorkState(directory);
|
|
2272
|
-
const state = createWorkState(matched, sessionId, "tapestry");
|
|
2366
|
+
const state = createWorkState(matched, sessionId, "tapestry", directory);
|
|
2273
2367
|
writeWorkState(directory, state);
|
|
2274
|
-
const freshContext = buildFreshContext(matched, getPlanName(matched), progress);
|
|
2368
|
+
const freshContext = buildFreshContext(matched, getPlanName(matched), progress, state.start_sha);
|
|
2275
2369
|
if (validation.warnings.length > 0) {
|
|
2276
2370
|
return {
|
|
2277
2371
|
switchAgent: "tapestry",
|
|
@@ -2317,9 +2411,9 @@ ${formatValidationResults(validation)}
|
|
|
2317
2411
|
Tell the user to fix these issues in the plan file and try again.`
|
|
2318
2412
|
};
|
|
2319
2413
|
}
|
|
2320
|
-
const state = createWorkState(plan, sessionId, "tapestry");
|
|
2414
|
+
const state = createWorkState(plan, sessionId, "tapestry", directory);
|
|
2321
2415
|
writeWorkState(directory, state);
|
|
2322
|
-
const freshContext = buildFreshContext(plan, getPlanName(plan), progress);
|
|
2416
|
+
const freshContext = buildFreshContext(plan, getPlanName(plan), progress, state.start_sha);
|
|
2323
2417
|
if (validation.warnings.length > 0) {
|
|
2324
2418
|
return {
|
|
2325
2419
|
switchAgent: "tapestry",
|
|
@@ -2375,10 +2469,12 @@ function formatValidationResults(result) {
|
|
|
2375
2469
|
return lines.join(`
|
|
2376
2470
|
`);
|
|
2377
2471
|
}
|
|
2378
|
-
function buildFreshContext(planPath, planName, progress) {
|
|
2472
|
+
function buildFreshContext(planPath, planName, progress, startSha) {
|
|
2473
|
+
const shaLine = startSha ? `
|
|
2474
|
+
**Start SHA**: ${startSha}` : "";
|
|
2379
2475
|
return `## Starting Plan: ${planName}
|
|
2380
2476
|
**Plan file**: ${planPath}
|
|
2381
|
-
**Progress**: ${progress.completed}/${progress.total} tasks completed
|
|
2477
|
+
**Progress**: ${progress.completed}/${progress.total} tasks completed${shaLine}
|
|
2382
2478
|
|
|
2383
2479
|
Read the plan file now and begin executing from the first unchecked \`- [ ]\` task.
|
|
2384
2480
|
|
|
@@ -2389,12 +2485,14 @@ Before starting any work, use todowrite to populate the sidebar:
|
|
|
2389
2485
|
3. Create todos for the next 2-3 tasks (pending)
|
|
2390
2486
|
Keep each todo under 35 chars. Update as you complete tasks.`;
|
|
2391
2487
|
}
|
|
2392
|
-
function buildResumeContext(planPath, planName, progress) {
|
|
2488
|
+
function buildResumeContext(planPath, planName, progress, startSha) {
|
|
2393
2489
|
const remaining = progress.total - progress.completed;
|
|
2490
|
+
const shaLine = startSha ? `
|
|
2491
|
+
**Start SHA**: ${startSha}` : "";
|
|
2394
2492
|
return `## Resuming Plan: ${planName}
|
|
2395
2493
|
**Plan file**: ${planPath}
|
|
2396
2494
|
**Progress**: ${progress.completed}/${progress.total} tasks completed
|
|
2397
|
-
**Status**: RESUMING — continuing from where the previous session left off
|
|
2495
|
+
**Status**: RESUMING — continuing from where the previous session left off.${shaLine}
|
|
2398
2496
|
|
|
2399
2497
|
Read the plan file now and continue from the first unchecked \`- [ ]\` task.
|
|
2400
2498
|
|
|
@@ -2508,6 +2606,7 @@ function clearSession2(sessionId) {
|
|
|
2508
2606
|
// src/plugin/plugin-interface.ts
|
|
2509
2607
|
function createPluginInterface(args) {
|
|
2510
2608
|
const { pluginConfig, hooks, tools, configHandler, agents, client } = args;
|
|
2609
|
+
let pendingInterrupt = false;
|
|
2511
2610
|
return {
|
|
2512
2611
|
tool: tools,
|
|
2513
2612
|
config: async (config) => {
|
|
@@ -2613,25 +2712,37 @@ ${result.contextInjection}`;
|
|
|
2613
2712
|
}
|
|
2614
2713
|
}
|
|
2615
2714
|
}
|
|
2616
|
-
if (
|
|
2715
|
+
if (event.type === "tui.command.execute") {
|
|
2617
2716
|
const evt = event;
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2717
|
+
if (evt.properties?.command === "session.interrupt") {
|
|
2718
|
+
pendingInterrupt = true;
|
|
2719
|
+
log("[work-continuation] User interrupt detected — will suppress next continuation");
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
if (hooks.workContinuation && event.type === "session.idle") {
|
|
2723
|
+
if (pendingInterrupt) {
|
|
2724
|
+
pendingInterrupt = false;
|
|
2725
|
+
log("[work-continuation] Skipping continuation — session was interrupted by user");
|
|
2726
|
+
} else {
|
|
2727
|
+
const evt = event;
|
|
2728
|
+
const sessionId = evt.properties?.sessionID ?? "";
|
|
2729
|
+
if (sessionId) {
|
|
2730
|
+
const result = hooks.workContinuation(sessionId);
|
|
2731
|
+
if (result.continuationPrompt && client) {
|
|
2732
|
+
try {
|
|
2733
|
+
await client.session.promptAsync({
|
|
2734
|
+
path: { id: sessionId },
|
|
2735
|
+
body: {
|
|
2736
|
+
parts: [{ type: "text", text: result.continuationPrompt }]
|
|
2737
|
+
}
|
|
2738
|
+
});
|
|
2739
|
+
log("[work-continuation] Injected continuation prompt", { sessionId });
|
|
2740
|
+
} catch (err) {
|
|
2741
|
+
log("[work-continuation] Failed to inject continuation", { sessionId, error: String(err) });
|
|
2742
|
+
}
|
|
2743
|
+
} else if (result.continuationPrompt) {
|
|
2744
|
+
log("[work-continuation] continuationPrompt available but no client", { sessionId });
|
|
2632
2745
|
}
|
|
2633
|
-
} else if (result.continuationPrompt) {
|
|
2634
|
-
log("[work-continuation] continuationPrompt available but no client", { sessionId });
|
|
2635
2746
|
}
|
|
2636
2747
|
}
|
|
2637
2748
|
}
|
package/package.json
CHANGED