@howaboua/opencode-roadmap-plugin 0.1.5 → 0.1.6
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 +85 -14
- package/dist/src/descriptions/createroadmap.txt +20 -16
- package/dist/src/descriptions/loader.js +8 -2
- package/dist/src/descriptions/readroadmap.txt +19 -2
- package/dist/src/descriptions/updateroadmap.txt +8 -6
- package/dist/src/errors/loader.js +8 -1
- package/dist/src/storage.d.ts +1 -0
- package/dist/src/storage.js +59 -23
- package/dist/src/tools/createroadmap.d.ts +4 -0
- package/dist/src/tools/createroadmap.js +14 -3
- package/dist/src/tools/readroadmap.d.ts +4 -0
- package/dist/src/tools/readroadmap.js +4 -0
- package/dist/src/tools/updateroadmap.d.ts +4 -0
- package/dist/src/tools/updateroadmap.js +9 -2
- package/dist/src/types.d.ts +16 -16
- package/dist/src/types.js +1 -1
- package/package.json +4 -2
- package/dist/descriptions/index.d.ts +0 -1
- package/dist/descriptions/index.js +0 -1
- package/dist/descriptions/loader.d.ts +0 -1
- package/dist/descriptions/loader.js +0 -17
- package/dist/errors/loader.d.ts +0 -2
- package/dist/errors/loader.js +0 -24
- package/dist/storage.d.ts +0 -25
- package/dist/storage.js +0 -214
- package/dist/tools/createroadmap.d.ts +0 -2
- package/dist/tools/createroadmap.js +0 -135
- package/dist/tools/readroadmap.d.ts +0 -2
- package/dist/tools/readroadmap.js +0 -90
- package/dist/tools/updateroadmap.d.ts +0 -2
- package/dist/tools/updateroadmap.js +0 -107
- package/dist/types.d.ts +0 -151
- package/dist/types.js +0 -18
package/README.md
CHANGED
|
@@ -1,29 +1,100 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Opencode Roadmap Plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Persistent project roadmaps for OpenCode. Coordinates work across sessions and parallel Task tool subagents.
|
|
4
|
+
|
|
5
|
+
## Why Use This?
|
|
6
|
+
|
|
7
|
+
OpenCode's built-in todo is session-scoped—it disappears when you restart. Task tool subagents are stateless—they can't see each other's work.
|
|
8
|
+
|
|
9
|
+
This plugin solves both:
|
|
10
|
+
|
|
11
|
+
- **Persists to disk** — survives restarts, available across sessions
|
|
12
|
+
- **Shared context** — subagents read the same roadmap to understand the bigger picture
|
|
13
|
+
- **Concurrent awareness** — agents see what's `in_progress` and avoid conflicts
|
|
14
|
+
|
|
15
|
+

|
|
4
16
|
|
|
5
17
|
## Installation
|
|
6
18
|
|
|
7
|
-
Add to your
|
|
19
|
+
Add to your `opencode.json`:
|
|
8
20
|
|
|
9
21
|
```json
|
|
10
22
|
{
|
|
11
|
-
"plugin": ["@howaboua/opencode-roadmap-plugin"]
|
|
23
|
+
"plugin": ["@howaboua/opencode-roadmap-plugin@latest"]
|
|
12
24
|
}
|
|
13
25
|
```
|
|
14
26
|
|
|
15
|
-
|
|
27
|
+
OpenCode installs it automatically on next launch.
|
|
28
|
+
|
|
29
|
+
## Tools
|
|
30
|
+
|
|
31
|
+
### `createroadmap`
|
|
32
|
+
|
|
33
|
+
Create or extend a project roadmap.
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
"Create a roadmap for building user auth with login, signup, and password reset"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- Features group related work (`"1"`, `"2"`, `"3"`)
|
|
40
|
+
- Actions are concrete tasks (`"1.01"`, `"1.02"`) within features
|
|
41
|
+
- New actions always start as `pending`
|
|
42
|
+
- Append-only: existing IDs never change
|
|
43
|
+
|
|
44
|
+
### `readroadmap`
|
|
45
|
+
|
|
46
|
+
View current state and progress.
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
"Show me the roadmap"
|
|
50
|
+
"What's the status of feature 2?"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Before delegating work to Task tool subagents, instruct them to read the roadmap first so they understand their assigned action within the broader plan.
|
|
16
54
|
|
|
17
|
-
|
|
18
|
-
- `updateroadmap`: advance action status forward only (`pending` → `in_progress` → `completed`), optional description update.
|
|
19
|
-
- `readroadmap`: summarize roadmap, optionally filtered by feature/action.
|
|
20
|
-
- Storage: JSON on disk with auto-archive when all actions complete.
|
|
21
|
-
- Validation: Zod schemas enforce shape; errors surface readable templates.
|
|
55
|
+
### `updateroadmap`
|
|
22
56
|
|
|
23
|
-
|
|
57
|
+
Change action status or description.
|
|
24
58
|
|
|
25
|
-
```bash
|
|
26
|
-
npm run build # Emit dist/ (ESM + d.ts)
|
|
27
59
|
```
|
|
60
|
+
"Mark action 1.01 as in_progress"
|
|
61
|
+
"Action 2.03 is completed"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Statuses:** `pending` → `in_progress` → `completed` | `cancelled`
|
|
65
|
+
|
|
66
|
+
Transitions are flexible—you can revert if plans change. Only `cancelled` is terminal.
|
|
67
|
+
|
|
68
|
+
Auto-archives the roadmap when all actions reach `completed`.
|
|
69
|
+
|
|
70
|
+
## Coordinating Parallel Work
|
|
71
|
+
|
|
72
|
+
When multiple subagents work simultaneously:
|
|
73
|
+
|
|
74
|
+
1. Each reads the roadmap to see what's `in_progress`
|
|
75
|
+
2. Agents stay focused on their assigned action only
|
|
76
|
+
3. They avoid modifying files that belong to another `in_progress` action
|
|
77
|
+
4. Errors outside their scope get noted, not fixed
|
|
78
|
+
|
|
79
|
+
This prevents conflicts when subagents run in parallel.
|
|
80
|
+
|
|
81
|
+
## Workflow Example
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
You: "Plan out building a REST API with auth, users, and posts endpoints"
|
|
85
|
+
|
|
86
|
+
AI: Creates roadmap with 3 features, ~12 actions
|
|
87
|
+
|
|
88
|
+
You: "Implement feature 1"
|
|
89
|
+
|
|
90
|
+
AI: Reads roadmap → sees Feature 1 has 4 actions → uses todowrite for immediate steps → delegates to subagents → each subagent reads roadmap first → updates status when done
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Storage
|
|
94
|
+
|
|
95
|
+
- **Active:** `roadmap.json` in project root
|
|
96
|
+
- **Archived:** `roadmap.archive.<timestamp>.json` when complete
|
|
97
|
+
|
|
98
|
+
## License
|
|
28
99
|
|
|
29
|
-
|
|
100
|
+
MIT
|
|
@@ -1,20 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
Establish a durable project roadmap saved to disk. Provides shared context across sessions and Task tool subagents.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
Use when:
|
|
4
|
+
- User says "roadmap", "plan the project", "phases", or "milestones"
|
|
5
|
+
- Work will be delegated to Task tool subagents that need shared context
|
|
6
|
+
- Deliverables group naturally into distinct features
|
|
7
|
+
|
|
8
|
+
When launching Task tool subagents, instruct them to call readroadmap first to understand their work within the broader plan.
|
|
9
|
+
|
|
10
|
+
Structure: Features contain Actions. IDs are immutable once created.
|
|
11
|
+
|
|
12
|
+
Format:
|
|
13
|
+
- Feature: number "1", "2", title, description
|
|
14
|
+
- Action: number "1.01", "1.02" (prefix matches parent feature), description, status "pending"
|
|
9
15
|
|
|
10
16
|
Example:
|
|
11
17
|
{
|
|
12
|
-
"features": [
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
]
|
|
20
|
-
}
|
|
18
|
+
"features": [{
|
|
19
|
+
"number": "1", "title": "Auth", "description": "User authentication system",
|
|
20
|
+
"actions": [
|
|
21
|
+
{ "number": "1.01", "description": "Design JWT token flow and refresh strategy", "status": "pending" }
|
|
22
|
+
]
|
|
23
|
+
}]
|
|
24
|
+
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads tool description text files from the descriptions directory.
|
|
3
|
+
* ESM-compatible using import.meta.url for path resolution.
|
|
4
|
+
*/
|
|
1
5
|
import { promises as fs } from "fs";
|
|
2
|
-
import { join } from "path";
|
|
6
|
+
import { dirname, join } from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
3
10
|
export async function loadDescription(filename) {
|
|
4
|
-
// Assets are colocated with the compiled loader (copied into dist/src/descriptions).
|
|
5
11
|
const filePath = join(__dirname, filename);
|
|
6
12
|
try {
|
|
7
13
|
return await fs.readFile(filePath, "utf-8");
|
|
@@ -1,2 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
Load the persisted roadmap from disk to understand project state.
|
|
2
|
+
|
|
3
|
+
When to use:
|
|
4
|
+
1. Before starting a major feature - read roadmap first, then plan immediate steps with todowrite
|
|
5
|
+
2. When launching Task tool subagents - instruct them to call readroadmap to understand their assigned work
|
|
6
|
+
3. To check overall progress across features
|
|
7
|
+
|
|
8
|
+
Concurrent work awareness:
|
|
9
|
+
- Actions marked "in_progress" may have another agent actively working on them
|
|
10
|
+
- Stay focused on YOUR assigned action only - do not fix unrelated codebase errors
|
|
11
|
+
- Avoid modifying files that belong to another in_progress action
|
|
12
|
+
- If you encounter errors in code outside your scope, note them but do not fix
|
|
13
|
+
|
|
14
|
+
Returns: feature list, action statuses, completion percentages.
|
|
15
|
+
|
|
16
|
+
Filter options:
|
|
17
|
+
- featureNumber: show one feature and its actions
|
|
18
|
+
- actionNumber: show one action's details
|
|
19
|
+
- (omit both): full roadmap overview
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
Advance action state within the persisted roadmap.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
2. Immutable IDs: Cannot change action numbers or move actions.
|
|
6
|
-
3. Side Effect: Automatically archives roadmap file when ALL actions are completed.
|
|
3
|
+
Statuses: pending, in_progress, completed, cancelled
|
|
4
|
+
Transitions: Flexible (can revert if needed), except cancelled is terminal.
|
|
7
5
|
|
|
8
|
-
|
|
6
|
+
Update after completing work on an action. When delegating to Task tool subagents, instruct them to call updateroadmap when they finish their assigned action.
|
|
7
|
+
|
|
8
|
+
Archives roadmap automatically when all actions reach completed.
|
|
9
|
+
|
|
10
|
+
Input: actionNumber (required), status (optional), description (optional).
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads error message templates from the errors directory.
|
|
3
|
+
* Supports template variable substitution for dynamic error messages.
|
|
4
|
+
*/
|
|
1
5
|
import { promises as fs } from "fs";
|
|
2
|
-
import { join } from "path";
|
|
6
|
+
import { dirname, join } from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
3
10
|
const ERROR_CACHE = {};
|
|
4
11
|
export async function loadErrorTemplate(filename) {
|
|
5
12
|
if (ERROR_CACHE[filename])
|
package/dist/src/storage.d.ts
CHANGED
package/dist/src/storage.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File-based storage for roadmap data with atomic writes and validation.
|
|
3
|
+
* Handles concurrent access via file locking and provides safe read/write operations.
|
|
4
|
+
*/
|
|
1
5
|
import { promises as fs } from "fs";
|
|
2
6
|
import { join } from "path";
|
|
7
|
+
import { z } from "zod";
|
|
3
8
|
import { Roadmap as RoadmapSchema } from "./types.js";
|
|
4
9
|
const ROADMAP_FILE = "roadmap.json";
|
|
10
|
+
const LOCK_FILE = `${ROADMAP_FILE}.lock`;
|
|
11
|
+
const LOCK_TIMEOUT_MS = 5000;
|
|
12
|
+
const LOCK_RETRY_MS = 50;
|
|
5
13
|
export class FileStorage {
|
|
6
14
|
directory;
|
|
7
15
|
constructor(directory) {
|
|
@@ -31,6 +39,10 @@ export class FileStorage {
|
|
|
31
39
|
if (error instanceof SyntaxError) {
|
|
32
40
|
throw new Error("Roadmap file contains invalid JSON. File may be corrupted.");
|
|
33
41
|
}
|
|
42
|
+
if (error instanceof z.ZodError) {
|
|
43
|
+
const issues = error.errors.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ");
|
|
44
|
+
throw new Error(`Roadmap file has invalid structure: ${issues}`);
|
|
45
|
+
}
|
|
34
46
|
if (error instanceof Error && error.message.includes("ENOENT")) {
|
|
35
47
|
return null;
|
|
36
48
|
}
|
|
@@ -40,28 +52,50 @@ export class FileStorage {
|
|
|
40
52
|
throw new Error("Unknown error while reading roadmap");
|
|
41
53
|
}
|
|
42
54
|
}
|
|
43
|
-
async
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
const data = JSON.stringify(roadmap, null, 2);
|
|
48
|
-
await fs.writeFile(tempPath, data, "utf-8");
|
|
49
|
-
await fs.rename(tempPath, filePath);
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
55
|
+
async acquireLock() {
|
|
56
|
+
const lockPath = join(this.directory, LOCK_FILE);
|
|
57
|
+
const start = Date.now();
|
|
58
|
+
while (Date.now() - start < LOCK_TIMEOUT_MS) {
|
|
52
59
|
try {
|
|
53
|
-
await fs.
|
|
60
|
+
await fs.writeFile(lockPath, String(process.pid), { flag: "wx" });
|
|
61
|
+
return async () => {
|
|
62
|
+
await fs.unlink(lockPath).catch(() => { });
|
|
63
|
+
};
|
|
54
64
|
}
|
|
55
65
|
catch {
|
|
56
|
-
|
|
66
|
+
await new Promise((r) => setTimeout(r, LOCK_RETRY_MS));
|
|
57
67
|
}
|
|
58
|
-
|
|
59
|
-
|
|
68
|
+
}
|
|
69
|
+
throw new Error("Could not acquire lock on roadmap file. Another operation may be in progress.");
|
|
70
|
+
}
|
|
71
|
+
async write(roadmap) {
|
|
72
|
+
await fs.mkdir(this.directory, { recursive: true }).catch(() => { });
|
|
73
|
+
const unlock = await this.acquireLock();
|
|
74
|
+
try {
|
|
75
|
+
const filePath = join(this.directory, ROADMAP_FILE);
|
|
76
|
+
const randomSuffix = Math.random().toString(36).slice(2, 8);
|
|
77
|
+
const tempPath = join(this.directory, `${ROADMAP_FILE}.tmp.${Date.now()}.${randomSuffix}`);
|
|
78
|
+
try {
|
|
79
|
+
const data = JSON.stringify(roadmap, null, 2);
|
|
80
|
+
await fs.writeFile(tempPath, data, "utf-8");
|
|
81
|
+
await fs.rename(tempPath, filePath);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
await fs.unlink(tempPath).catch(() => { });
|
|
85
|
+
if (error instanceof Error) {
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
throw new Error("Unknown error while writing roadmap");
|
|
60
89
|
}
|
|
61
|
-
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
await unlock();
|
|
62
93
|
}
|
|
63
94
|
}
|
|
64
95
|
async archive() {
|
|
96
|
+
if (!(await this.exists())) {
|
|
97
|
+
throw new Error("Cannot archive: roadmap file does not exist");
|
|
98
|
+
}
|
|
65
99
|
const filePath = join(this.directory, ROADMAP_FILE);
|
|
66
100
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
67
101
|
const archiveFilename = `roadmap.archive.${timestamp}.json`;
|
|
@@ -112,7 +146,7 @@ export class RoadmapValidator {
|
|
|
112
146
|
}
|
|
113
147
|
// Check action-feature mismatch
|
|
114
148
|
if (featureNumber) {
|
|
115
|
-
const actionFeaturePrefix = action.number.split(
|
|
149
|
+
const actionFeaturePrefix = action.number.split(".")[0];
|
|
116
150
|
if (actionFeaturePrefix !== featureNumber) {
|
|
117
151
|
errors.push({
|
|
118
152
|
code: "ACTION_FEATURE_MISMATCH",
|
|
@@ -192,16 +226,18 @@ export class RoadmapValidator {
|
|
|
192
226
|
return null;
|
|
193
227
|
}
|
|
194
228
|
static validateStatusProgression(currentStatus, newStatus) {
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
229
|
+
const validStatuses = ["pending", "in_progress", "completed", "cancelled"];
|
|
230
|
+
if (!validStatuses.includes(newStatus)) {
|
|
231
|
+
return {
|
|
232
|
+
code: "INVALID_STATUS",
|
|
233
|
+
message: `Invalid status "${newStatus}". Valid: ${validStatuses.join(", ")}`,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
// Allow any transition except from cancelled (terminal state for abandoned work)
|
|
237
|
+
if (currentStatus === "cancelled") {
|
|
202
238
|
return {
|
|
203
239
|
code: "INVALID_STATUS_TRANSITION",
|
|
204
|
-
message: `
|
|
240
|
+
message: `Cannot change status of cancelled action. Create a new action instead.`,
|
|
205
241
|
};
|
|
206
242
|
}
|
|
207
243
|
return null;
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool for creating and appending to project roadmaps.
|
|
3
|
+
* Supports merge logic for adding new features/actions to existing roadmaps.
|
|
4
|
+
*/
|
|
1
5
|
import { type ToolDefinition } from "@opencode-ai/plugin";
|
|
2
6
|
export declare function createCreateRoadmapTool(directory: string): Promise<ToolDefinition>;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool for creating and appending to project roadmaps.
|
|
3
|
+
* Supports merge logic for adding new features/actions to existing roadmaps.
|
|
4
|
+
*/
|
|
1
5
|
import { tool } from "@opencode-ai/plugin";
|
|
2
6
|
import { FileStorage, RoadmapValidator } from "../storage.js";
|
|
3
7
|
import { loadDescription } from "../descriptions/index.js";
|
|
@@ -72,13 +76,14 @@ export async function createCreateRoadmapTool(directory) {
|
|
|
72
76
|
const existingFeature = roadmap.features.find((f) => f.number === inputFeature.number);
|
|
73
77
|
if (existingFeature) {
|
|
74
78
|
// Feature exists: Validate Immutability
|
|
75
|
-
if (existingFeature.title !== inputFeature.title ||
|
|
79
|
+
if (existingFeature.title !== inputFeature.title ||
|
|
80
|
+
existingFeature.description !== inputFeature.description) {
|
|
76
81
|
const msg = await getErrorMessage("immutable_feature", {
|
|
77
82
|
id: inputFeature.number,
|
|
78
83
|
oldTitle: existingFeature.title,
|
|
79
84
|
oldDesc: existingFeature.description,
|
|
80
85
|
newTitle: inputFeature.title,
|
|
81
|
-
newDesc: inputFeature.description
|
|
86
|
+
newDesc: inputFeature.description,
|
|
82
87
|
});
|
|
83
88
|
throw new Error(msg);
|
|
84
89
|
}
|
|
@@ -117,10 +122,16 @@ export async function createCreateRoadmapTool(directory) {
|
|
|
117
122
|
}
|
|
118
123
|
// Final Sort of Features
|
|
119
124
|
roadmap.features.sort((a, b) => parseInt(a.number) - parseInt(b.number));
|
|
125
|
+
// Safety check: ensure no feature ended up with zero actions after merge
|
|
126
|
+
for (const feature of roadmap.features) {
|
|
127
|
+
if (feature.actions.length === 0) {
|
|
128
|
+
throw new Error(`Feature "${feature.number}" has no actions. This indicates a merge error.`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
120
131
|
// Final Validation of the Merged Roadmap
|
|
121
132
|
const finalErrors = RoadmapValidator.validateFeatureSequence(roadmap.features);
|
|
122
133
|
if (finalErrors.length > 0) {
|
|
123
|
-
throw new Error(`Resulting roadmap would be invalid:\n${finalErrors.map(e => e.message).join("\n")}`);
|
|
134
|
+
throw new Error(`Resulting roadmap would be invalid:\n${finalErrors.map((e) => e.message).join("\n")}`);
|
|
124
135
|
}
|
|
125
136
|
await storage.write(roadmap);
|
|
126
137
|
const totalActions = roadmap.features.reduce((sum, feature) => sum + feature.actions.length, 0);
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool for reading roadmap state, progress, and details.
|
|
3
|
+
* Supports filtering by feature or action number.
|
|
4
|
+
*/
|
|
1
5
|
import { type ToolDefinition } from "@opencode-ai/plugin";
|
|
2
6
|
export declare function createReadRoadmapTool(directory: string): Promise<ToolDefinition>;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool for reading roadmap state, progress, and details.
|
|
3
|
+
* Supports filtering by feature or action number.
|
|
4
|
+
*/
|
|
1
5
|
import { tool } from "@opencode-ai/plugin";
|
|
2
6
|
import { FileStorage, RoadmapValidator } from "../storage.js";
|
|
3
7
|
import { loadDescription } from "../descriptions/index.js";
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool for updating action status and descriptions in a roadmap.
|
|
3
|
+
* Enforces forward-only status progression and archives when complete.
|
|
4
|
+
*/
|
|
1
5
|
import { type ToolDefinition } from "@opencode-ai/plugin";
|
|
2
6
|
export declare function createUpdateRoadmapTool(directory: string): Promise<ToolDefinition>;
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool for updating action status and descriptions in a roadmap.
|
|
3
|
+
* Enforces forward-only status progression and archives when complete.
|
|
4
|
+
*/
|
|
1
5
|
import { tool } from "@opencode-ai/plugin";
|
|
2
6
|
import { FileStorage, RoadmapValidator } from "../storage.js";
|
|
3
7
|
import { loadDescription } from "../descriptions/index.js";
|
|
@@ -12,9 +16,9 @@ export async function createUpdateRoadmapTool(directory) {
|
|
|
12
16
|
.optional()
|
|
13
17
|
.describe("New action description (full overwrite). If not provided, only status is updated."),
|
|
14
18
|
status: tool.schema
|
|
15
|
-
.enum(["pending", "in_progress", "completed"])
|
|
19
|
+
.enum(["pending", "in_progress", "completed", "cancelled"])
|
|
16
20
|
.optional()
|
|
17
|
-
.describe("New action status
|
|
21
|
+
.describe("New action status. Flexible transitions allowed except from cancelled."),
|
|
18
22
|
},
|
|
19
23
|
async execute(args) {
|
|
20
24
|
const storage = new FileStorage(directory);
|
|
@@ -78,6 +82,9 @@ export async function createUpdateRoadmapTool(directory) {
|
|
|
78
82
|
if (args.status !== undefined && oldStatus !== args.status) {
|
|
79
83
|
changes.push(`status: "${oldStatus}" → "${args.status}"`);
|
|
80
84
|
}
|
|
85
|
+
if (changes.length === 0) {
|
|
86
|
+
return `Action ${args.actionNumber} unchanged. Provided values match current state.`;
|
|
87
|
+
}
|
|
81
88
|
// Check if all actions are completed
|
|
82
89
|
let allCompleted = true;
|
|
83
90
|
for (const feature of roadmap.features) {
|
package/dist/src/types.d.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
export declare const ActionStatus: z.ZodEnum<["pending", "in_progress", "completed"]>;
|
|
2
|
+
export declare const ActionStatus: z.ZodEnum<["pending", "in_progress", "completed", "cancelled"]>;
|
|
3
3
|
export type ActionStatus = z.infer<typeof ActionStatus>;
|
|
4
4
|
export declare const Action: z.ZodObject<{
|
|
5
5
|
number: z.ZodString;
|
|
6
6
|
description: z.ZodString;
|
|
7
|
-
status: z.ZodEnum<["pending", "in_progress", "completed"]>;
|
|
7
|
+
status: z.ZodEnum<["pending", "in_progress", "completed", "cancelled"]>;
|
|
8
8
|
}, "strip", z.ZodTypeAny, {
|
|
9
9
|
number: string;
|
|
10
|
-
status: "pending" | "in_progress" | "completed";
|
|
10
|
+
status: "pending" | "in_progress" | "completed" | "cancelled";
|
|
11
11
|
description: string;
|
|
12
12
|
}, {
|
|
13
13
|
number: string;
|
|
14
|
-
status: "pending" | "in_progress" | "completed";
|
|
14
|
+
status: "pending" | "in_progress" | "completed" | "cancelled";
|
|
15
15
|
description: string;
|
|
16
16
|
}>;
|
|
17
17
|
export type Action = z.infer<typeof Action>;
|
|
@@ -22,14 +22,14 @@ export declare const Feature: z.ZodObject<{
|
|
|
22
22
|
actions: z.ZodArray<z.ZodObject<{
|
|
23
23
|
number: z.ZodString;
|
|
24
24
|
description: z.ZodString;
|
|
25
|
-
status: z.ZodEnum<["pending", "in_progress", "completed"]>;
|
|
25
|
+
status: z.ZodEnum<["pending", "in_progress", "completed", "cancelled"]>;
|
|
26
26
|
}, "strip", z.ZodTypeAny, {
|
|
27
27
|
number: string;
|
|
28
|
-
status: "pending" | "in_progress" | "completed";
|
|
28
|
+
status: "pending" | "in_progress" | "completed" | "cancelled";
|
|
29
29
|
description: string;
|
|
30
30
|
}, {
|
|
31
31
|
number: string;
|
|
32
|
-
status: "pending" | "in_progress" | "completed";
|
|
32
|
+
status: "pending" | "in_progress" | "completed" | "cancelled";
|
|
33
33
|
description: string;
|
|
34
34
|
}>, "many">;
|
|
35
35
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -38,7 +38,7 @@ export declare const Feature: z.ZodObject<{
|
|
|
38
38
|
title: string;
|
|
39
39
|
actions: {
|
|
40
40
|
number: string;
|
|
41
|
-
status: "pending" | "in_progress" | "completed";
|
|
41
|
+
status: "pending" | "in_progress" | "completed" | "cancelled";
|
|
42
42
|
description: string;
|
|
43
43
|
}[];
|
|
44
44
|
}, {
|
|
@@ -47,7 +47,7 @@ export declare const Feature: z.ZodObject<{
|
|
|
47
47
|
title: string;
|
|
48
48
|
actions: {
|
|
49
49
|
number: string;
|
|
50
|
-
status: "pending" | "in_progress" | "completed";
|
|
50
|
+
status: "pending" | "in_progress" | "completed" | "cancelled";
|
|
51
51
|
description: string;
|
|
52
52
|
}[];
|
|
53
53
|
}>;
|
|
@@ -60,14 +60,14 @@ export declare const Roadmap: z.ZodObject<{
|
|
|
60
60
|
actions: z.ZodArray<z.ZodObject<{
|
|
61
61
|
number: z.ZodString;
|
|
62
62
|
description: z.ZodString;
|
|
63
|
-
status: z.ZodEnum<["pending", "in_progress", "completed"]>;
|
|
63
|
+
status: z.ZodEnum<["pending", "in_progress", "completed", "cancelled"]>;
|
|
64
64
|
}, "strip", z.ZodTypeAny, {
|
|
65
65
|
number: string;
|
|
66
|
-
status: "pending" | "in_progress" | "completed";
|
|
66
|
+
status: "pending" | "in_progress" | "completed" | "cancelled";
|
|
67
67
|
description: string;
|
|
68
68
|
}, {
|
|
69
69
|
number: string;
|
|
70
|
-
status: "pending" | "in_progress" | "completed";
|
|
70
|
+
status: "pending" | "in_progress" | "completed" | "cancelled";
|
|
71
71
|
description: string;
|
|
72
72
|
}>, "many">;
|
|
73
73
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -76,7 +76,7 @@ export declare const Roadmap: z.ZodObject<{
|
|
|
76
76
|
title: string;
|
|
77
77
|
actions: {
|
|
78
78
|
number: string;
|
|
79
|
-
status: "pending" | "in_progress" | "completed";
|
|
79
|
+
status: "pending" | "in_progress" | "completed" | "cancelled";
|
|
80
80
|
description: string;
|
|
81
81
|
}[];
|
|
82
82
|
}, {
|
|
@@ -85,7 +85,7 @@ export declare const Roadmap: z.ZodObject<{
|
|
|
85
85
|
title: string;
|
|
86
86
|
actions: {
|
|
87
87
|
number: string;
|
|
88
|
-
status: "pending" | "in_progress" | "completed";
|
|
88
|
+
status: "pending" | "in_progress" | "completed" | "cancelled";
|
|
89
89
|
description: string;
|
|
90
90
|
}[];
|
|
91
91
|
}>, "many">;
|
|
@@ -96,7 +96,7 @@ export declare const Roadmap: z.ZodObject<{
|
|
|
96
96
|
title: string;
|
|
97
97
|
actions: {
|
|
98
98
|
number: string;
|
|
99
|
-
status: "pending" | "in_progress" | "completed";
|
|
99
|
+
status: "pending" | "in_progress" | "completed" | "cancelled";
|
|
100
100
|
description: string;
|
|
101
101
|
}[];
|
|
102
102
|
}[];
|
|
@@ -107,7 +107,7 @@ export declare const Roadmap: z.ZodObject<{
|
|
|
107
107
|
title: string;
|
|
108
108
|
actions: {
|
|
109
109
|
number: string;
|
|
110
|
-
status: "pending" | "in_progress" | "completed";
|
|
110
|
+
status: "pending" | "in_progress" | "completed" | "cancelled";
|
|
111
111
|
description: string;
|
|
112
112
|
}[];
|
|
113
113
|
}[];
|
package/dist/src/types.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@howaboua/opencode-roadmap-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Strategic roadmap planning and multi-agent coordination for OpenCode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -32,7 +32,6 @@
|
|
|
32
32
|
"typescript": "^5.0.0"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"typescript": "^5.0.0",
|
|
36
35
|
"zod": "^3.22.0"
|
|
37
36
|
},
|
|
38
37
|
"scripts": {
|
|
@@ -41,6 +40,9 @@
|
|
|
41
40
|
"lint": "eslint . --ext .ts",
|
|
42
41
|
"prepublishOnly": "npm run build"
|
|
43
42
|
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
44
46
|
"opencode": {
|
|
45
47
|
"type": "plugin",
|
|
46
48
|
"tools": [
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { loadDescription } from "./loader";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { loadDescription } from "./loader.js";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function loadDescription(filename: string): Promise<string>;
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { promises as fs } from "fs";
|
|
2
|
-
import { join } from "path";
|
|
3
|
-
export async function loadDescription(filename) {
|
|
4
|
-
// In the compiled output, __dirname is .../dist/descriptions, but the assets are in .../src/descriptions.
|
|
5
|
-
// This path adjustment ensures the assets are found regardless of the build process.
|
|
6
|
-
const descriptionsDir = join(__dirname, "..", "..", "src", "descriptions");
|
|
7
|
-
const filePath = join(descriptionsDir, filename);
|
|
8
|
-
try {
|
|
9
|
-
return await fs.readFile(filePath, "utf-8");
|
|
10
|
-
}
|
|
11
|
-
catch (error) {
|
|
12
|
-
if (error.code === "ENOENT") {
|
|
13
|
-
throw new Error(`Description file not found: ${filename}. Looked in: ${filePath}. Please ensure asset files are correctly located.`);
|
|
14
|
-
}
|
|
15
|
-
throw error;
|
|
16
|
-
}
|
|
17
|
-
}
|
package/dist/errors/loader.d.ts
DELETED