@comfanion/usethis_todo 0.1.15-dev.12 → 0.1.15-dev.14

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.
Files changed (3) hide show
  1. package/index.ts +4 -1
  2. package/package.json +1 -1
  3. package/tools.ts +44 -41
package/index.ts CHANGED
@@ -2,7 +2,7 @@ import type { Plugin } from "@opencode-ai/plugin"
2
2
  import path from "path"
3
3
  import fs from "fs/promises"
4
4
 
5
- import { write, read, read_five, read_by_id, update, setNativeStorageBase } from "./tools"
5
+ import { write, read, read_five, read_by_id, update, setNativeStorageBase, setClient } from "./tools"
6
6
 
7
7
  interface TodoPruneState {
8
8
  lastToolCallId: string | null
@@ -11,6 +11,9 @@ interface TodoPruneState {
11
11
  const pruneStates = new Map<string, TodoPruneState>()
12
12
 
13
13
  const UsethisTodoPlugin: Plugin = async ({ directory, client }) => {
14
+ // Pass client to tools for event emitting
15
+ setClient(client)
16
+
14
17
  // Resolve the authoritative state path from OpenCode server (non-blocking).
15
18
  // Must NOT await — server may block until plugin init completes → deadlock.
16
19
  client.path.get().then((pathInfo: any) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comfanion/usethis_todo",
3
- "version": "0.1.15-dev.12",
3
+ "version": "0.1.15-dev.14",
4
4
  "description": "OpenCode plugin: enhanced TODO tools (dual storage + dependency graph)",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
package/tools.ts CHANGED
@@ -103,10 +103,11 @@ interface TodoGraph {
103
103
  }
104
104
 
105
105
  // ============================================================================
106
- // Storage — Unified Global
106
+ // Storage — Dual Write
107
107
  // ============================================================================
108
108
 
109
109
  let _nativeStorageBase: string | null = null
110
+ let _client: any = null
110
111
 
111
112
  /**
112
113
  * Set the native storage base path (OpenCode's state directory).
@@ -116,6 +117,10 @@ export function setNativeStorageBase(statePath: string): void {
116
117
  _nativeStorageBase = statePath
117
118
  }
118
119
 
120
+ export function setClient(client: any): void {
121
+ _client = client
122
+ }
123
+
119
124
  // Resolve project directory (context.directory may be undefined via MCP)
120
125
  function dir(directory?: string): string {
121
126
  return directory || process.env.OPENCODE_PROJECT_DIR || process.cwd()
@@ -150,26 +155,22 @@ async function getNativeDataDirs(): Promise<string[]> {
150
155
  return [...dirs]
151
156
  }
152
157
 
153
- async function getStoragePath(sid: string): Promise<string> {
158
+ async function getStoragePaths(sid: string): Promise<string[]> {
154
159
  const file = `${sid || "current"}.json`
160
+ const paths: string[] = []
155
161
 
156
- // 1. Prefer authoritative path from OpenCode server
162
+ // 1. Authoritative path from OpenCode server
157
163
  if (_nativeStorageBase) {
158
- return path.join(_nativeStorageBase, "storage", "todo", file)
164
+ paths.push(path.join(_nativeStorageBase, "storage", "todo", file))
159
165
  }
160
166
 
161
- // 2. Fallback: guess from well-known data dirs (first one that exists or default)
167
+ // 2. Well-known data dirs (fallbacks)
162
168
  const baseDirs = await getNativeDataDirs()
163
- // Try to find one that exists
164
169
  for (const base of baseDirs) {
165
- try {
166
- await fs.access(base)
167
- return path.join(base, "opencode", "storage", "todo", file)
168
- } catch {}
170
+ paths.push(path.join(base, "opencode", "storage", "todo", file))
169
171
  }
170
172
 
171
- // Default to first one
172
- return path.join(baseDirs[0], "opencode", "storage", "todo", file)
173
+ return [...new Set(paths)] // Unique paths
173
174
  }
174
175
 
175
176
  // ============================================================================
@@ -217,21 +218,21 @@ function toNativePriority(priority: string): string {
217
218
 
218
219
  async function readTodos(sid: string, directory?: string): Promise<Todo[]> {
219
220
  try {
220
- const storagePath = await getStoragePath(sid)
221
- const raw = JSON.parse(await fs.readFile(storagePath, "utf-8"))
222
- if (!Array.isArray(raw)) return []
223
-
224
- return raw.map((t: any) => {
225
- // Restore our rich status from shadow field if present
226
- const realStatus = fromNativeStatus(t.status, t.usethisStatus)
227
- const realPriority = t.usethisPriority || t.priority // Fallback if not shadowed
228
-
229
- return normalizeTodo({
230
- ...t,
231
- status: realStatus,
232
- priority: realPriority
233
- })
234
- })
221
+ const storagePaths = await getStoragePaths(sid)
222
+ // Try to read from any available path, starting with authoritative
223
+ for (const storagePath of storagePaths) {
224
+ try {
225
+ const raw = JSON.parse(await fs.readFile(storagePath, "utf-8"))
226
+ if (Array.isArray(raw)) {
227
+ return raw.map((t: any) => {
228
+ const realStatus = fromNativeStatus(t.status, t.usethisStatus)
229
+ const realPriority = t.usethisPriority || t.priority
230
+ return normalizeTodo({ ...t, status: realStatus, priority: realPriority })
231
+ })
232
+ }
233
+ } catch { continue }
234
+ }
235
+ return []
235
236
  } catch {
236
237
  return []
237
238
  }
@@ -243,25 +244,17 @@ function getEnhancedPath(sid: string, directory?: string): string {
243
244
  }
244
245
 
245
246
  async function writeTodos(todos: Todo[], sid: string, directory?: string): Promise<void> {
246
- const storagePath = await getStoragePath(sid)
247
+ const storagePaths = await getStoragePaths(sid)
247
248
  const enhancedPath = getEnhancedPath(sid, directory)
248
249
 
249
- // Prepare for storage:
250
- // 1. Set native-compatible fields (status, priority)
251
- // 2. Store real values in shadow fields (usethisStatus, usethisPriority)
252
- // 3. Keep all other fields (blockedBy, etc)
253
-
254
250
  const storageTodos = todos.map(t => {
255
251
  const nativeStatus = toNativeStatus(t.status)
256
252
  const nativePriority = toNativePriority(t.priority)
257
253
 
258
254
  return {
259
255
  ...t,
260
- // Native fields for Sidebar compatibility
261
256
  status: nativeStatus,
262
257
  priority: nativePriority,
263
-
264
- // Shadow fields (our source of truth)
265
258
  usethisStatus: t.status,
266
259
  usethisPriority: t.priority
267
260
  }
@@ -269,16 +262,26 @@ async function writeTodos(todos: Todo[], sid: string, directory?: string): Promi
269
262
 
270
263
  const json = JSON.stringify(storageTodos, null, 2)
271
264
 
272
- // 1. Write to Global Storage (for Sidebar)
273
- await fs.mkdir(path.dirname(storagePath), { recursive: true })
274
- await fs.writeFile(storagePath, json, "utf-8")
265
+ // 1. Write to Global Storages (for Sidebar)
266
+ await Promise.allSettled(
267
+ storagePaths.map(async (p) => {
268
+ try {
269
+ await fs.mkdir(path.dirname(p), { recursive: true })
270
+ await fs.writeFile(p, json, "utf-8")
271
+ await logAction(directory || "", "debug", `Writing to global: ${p}`)
272
+ } catch (e) {
273
+ await logAction(directory || "", "error", `Global write failed for ${p}: ${String(e)}`)
274
+ }
275
+ })
276
+ )
275
277
 
276
278
  // 2. Write to Local Storage (for User visibility/Git)
277
279
  try {
278
280
  await fs.mkdir(path.dirname(enhancedPath), { recursive: true })
279
281
  await fs.writeFile(enhancedPath, json, "utf-8")
280
- } catch {
281
- // ignore local write errors (non-fatal)
282
+ await logAction(directory || "", "debug", `Writing to local: ${enhancedPath}`)
283
+ } catch (e) {
284
+ await logAction(directory || "", "error", `Local write failed: ${String(e)}`)
282
285
  }
283
286
  }
284
287