opencode-extended-sidebar 0.2.90 → 0.2.91
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/.oesignore +17 -17
- package/CHANGELOG.md +96 -95
- package/LICENSE +21 -21
- package/README.md +150 -235
- package/oes.json +13 -11
- package/package.json +100 -100
- package/scripts/install-git-hooks.mjs +29 -29
- package/src/pware.oc.core/constants/index.ts +18 -18
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventKind.ts +29 -29
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventName.ts +15 -13
- package/src/pware.oc.core/constants/pware.oc.core.constants.eventType.ts +241 -241
- package/src/pware.oc.core/constants/pware.oc.core.constants.myWork.ts +55 -55
- package/src/pware.oc.core/constants/pware.oc.core.constants.partType.ts +65 -65
- package/src/pware.oc.core/constants/pware.oc.core.constants.phase.ts +58 -58
- package/src/pware.oc.core/constants/pware.oc.core.constants.pulse.ts +63 -63
- package/src/pware.oc.core/constants/pware.oc.core.constants.rowKind.ts +34 -34
- package/src/pware.oc.core/constants/pware.oc.core.constants.status.ts +74 -74
- package/src/pware.oc.core/constants/pware.oc.core.constants.toolName.ts +69 -69
- package/src/pware.oc.core/git/index.ts +7 -7
- package/src/pware.oc.core/git/pware.oc.core.git.ts +237 -237
- package/src/pware.oc.core/git/pware.oc.core.gitignore.ts +87 -87
- package/src/pware.oc.core/index.ts +19 -18
- package/src/pware.oc.core/pware.oc.core.bus.ts +42 -42
- package/src/pware.oc.core/pware.oc.core.cache.ts +48 -48
- package/src/pware.oc.core/pware.oc.core.clipboard.ts +97 -97
- package/src/pware.oc.core/pware.oc.core.debug.ts +280 -280
- package/src/pware.oc.core/pware.oc.core.events.ts +79 -79
- package/src/pware.oc.core/pware.oc.core.glyph.ts +135 -135
- package/src/pware.oc.core/pware.oc.core.layout.ts +234 -234
- package/src/pware.oc.core/pware.oc.core.oes.ts +99 -89
- package/src/pware.oc.core/pware.oc.core.paths.ts +240 -240
- package/src/pware.oc.core/pware.oc.core.preview.ts +74 -74
- package/src/pware.oc.core/pware.oc.core.pulse.ts +726 -726
- package/src/pware.oc.core/pware.oc.core.runtimeConfig.ts +41 -0
- package/src/pware.oc.core/pware.oc.core.sqlite.ts +164 -175
- package/src/pware.oc.core/pware.oc.core.sqliteGateway.ts +89 -0
- package/src/pware.oc.core/pware.oc.core.status.ts +214 -214
- package/src/pware.oc.core/pware.oc.core.timing.ts +63 -63
- package/src/pware.oc.core/pware.oc.core.width.ts +215 -215
- package/src/pware.oc.omo/constants/index.ts +15 -15
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.backgroundTask.ts +18 -18
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.boulderStatus.ts +49 -49
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.docKind.ts +29 -29
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.eventName.ts +11 -11
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.planStatus.ts +60 -60
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.reviewStatus.ts +49 -49
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.startWork.ts +25 -25
- package/src/pware.oc.omo/constants/pware.oc.omo.constants.verdict.ts +38 -38
- package/src/pware.oc.omo/index.ts +7 -7
- package/src/pware.oc.omo/resolver/index.ts +19 -19
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approval.ts +179 -179
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalGroup.ts +116 -116
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.approvalState.ts +34 -34
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.boulder.ts +503 -503
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.config.ts +40 -40
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.doc.ts +269 -269
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.draftFile.ts +18 -18
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.notepadsFile.ts +18 -18
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.plan.ts +155 -155
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.planFile.ts +363 -361
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.proofFile.ts +18 -18
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.rulesFile.ts +11 -11
- package/src/pware.oc.omo/resolver/pware.oc.omo.resolver.runContinuationFile.ts +14 -14
- package/src/pware.oc.opencode/constants/index.ts +10 -10
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.eventName.ts +15 -15
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.fileTouch.ts +18 -18
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.questionKind.ts +45 -45
- package/src/pware.oc.opencode/constants/pware.oc.opencode.constants.sessionStatus.ts +58 -58
- package/src/pware.oc.opencode/index.ts +8 -8
- package/src/pware.oc.opencode/pware.oc.opencode.events.ts +182 -182
- package/src/pware.oc.opencode/pware.oc.opencode.files.ts +399 -399
- package/src/pware.oc.opencode/resolver/index.ts +223 -170
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.file.ts +150 -162
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.question.ts +149 -185
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.session.ts +248 -211
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.todo.ts +22 -26
- package/src/pware.oc.opencode/resolver/pware.oc.opencode.resolver.tool.ts +224 -236
- package/src/pware.oc.perf/index.ts +14 -14
- package/src/pware.oc.perf/pware.oc.perf.asciichart.d.ts +15 -15
- package/src/pware.oc.perf/pware.oc.perf.charts.ts +284 -284
- package/src/pware.oc.perf/pware.oc.perf.reader.ts +962 -927
- package/src/pware.oc.perf/pware.oc.perf.realtime.ts +88 -88
- package/src/pware.oc.perf/pware.oc.perf.realtimeBlock.ts +118 -118
- package/src/pware.oc.perf/pware.oc.perf.realtimeCpuRam.ts +79 -79
- package/src/pware.oc.perf/pware.oc.perf.realtimeSampler.ts +71 -71
- package/src/pware.oc.perf/pware.oc.perf.realtimeTimeline.ts +214 -214
- package/src/pware.oc.perf/pware.oc.perf.self.ts +179 -179
- package/src/pware.oc.perf/pware.oc.perf.view.tsx +463 -462
- package/src/pware.oc.runtime/index.ts +13 -13
- package/src/pware.oc.runtime/pware.oc.runtime.monitor.ts +142 -142
- package/src/pware.oc.runtime/pware.oc.runtime.mywork-enrich.ts +87 -79
- package/src/pware.oc.runtime/pware.oc.runtime.mywork.ts +282 -253
- package/src/pware.oc.runtime/pware.oc.runtime.omoRead.ts +37 -0
- package/src/pware.oc.runtime/pware.oc.runtime.questions.ts +83 -95
- package/src/pware.oc.runtime/pware.oc.runtime.snapshotClient.ts +130 -129
- package/src/pware.oc.runtime/pware.oc.runtime.source.ts +105 -105
- package/src/pware.oc.runtime/pware.oc.runtime.worker.ts +44 -43
- package/src/pware.oc.runtime/resolver/index.ts +268 -224
- package/src/pware.oc.runtime/resolver/pware.oc.runtime.resolver.delegate.ts +146 -146
- package/src/pware.oc.ui/index.ts +12 -12
- package/src/pware.oc.ui/pware.oc.ui.chrome.tsx +381 -339
- package/src/pware.oc.ui/pware.oc.ui.glyphs.tsx +144 -144
- package/src/pware.oc.ui/pware.oc.ui.host.tsx +111 -111
- package/src/pware.oc.ui/pware.oc.ui.live.tsx +82 -82
- package/src/pware.oc.ui/pware.oc.ui.menudialogs.tsx +843 -843
- package/src/pware.oc.ui/pware.oc.ui.sections.tsx +552 -526
- package/src/pware.oc.ui/pware.oc.ui.sidebar.tsx +1718 -1629
- package/src/pware.oc.ui.tsx +98 -98
|
@@ -1,361 +1,363 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* pware.oc.omo.resolver.planFile
|
|
3
|
-
*
|
|
4
|
-
* Omo-file → writer-session index. Reads the OpenCode SQLite database through
|
|
5
|
-
* a raw `SqlDb` handle to map `.omo/` / `.sisyphus/` document files to the
|
|
6
|
-
* session that last wrote them. Lives in the omo layer because omo may read
|
|
7
|
-
* opencode data (one-way relaxation): the index needs the session/part rows,
|
|
8
|
-
* not any opencode-layer code.
|
|
9
|
-
*
|
|
10
|
-
* One generic engine (`omoFileIndex` / `sessionForOmoFile`) serves every
|
|
11
|
-
* document kind; the plan/draft index (`planSessionIndex` /
|
|
12
|
-
* `sessionForPlanFile`) is a legacy specialisation that folds plans and drafts
|
|
13
|
-
* into one map so a session's latest plan is preferred over its latest draft.
|
|
14
|
-
*/
|
|
15
|
-
import { createStampCache } from "../../pware.oc.core/pware.oc.core.cache.js"
|
|
16
|
-
import { basenameOf, str } from "../../pware.oc.core/pware.oc.core.paths.js"
|
|
17
|
-
import type { SqlDb } from "../../pware.oc.core/pware.oc.core.sqlite.js"
|
|
18
|
-
import { DOC_KIND_PLAN, type DocKind } from "../constants/pware.oc.omo.constants.docKind.js"
|
|
19
|
-
import {
|
|
20
|
-
listOmoFiles,
|
|
21
|
-
type DocView,
|
|
22
|
-
type ListOmoFilesOptions,
|
|
23
|
-
} from "./pware.oc.omo.resolver.doc.js"
|
|
24
|
-
|
|
25
|
-
/** Every OMO on-disk document kind (the subdirectory under `.omo/` / `.sisyphus/`). */
|
|
26
|
-
export const OMO_FILE_KINDS = [
|
|
27
|
-
"plan",
|
|
28
|
-
"draft",
|
|
29
|
-
"notepad",
|
|
30
|
-
"proof",
|
|
31
|
-
"rule",
|
|
32
|
-
"run-continuation",
|
|
33
|
-
] as const
|
|
34
|
-
|
|
35
|
-
/** An OMO on-disk document kind. */
|
|
36
|
-
export type OmoFileKind = (typeof OMO_FILE_KINDS)[number]
|
|
37
|
-
|
|
38
|
-
/** One case-insensitive regex per kind matching the omo/sisyphus subdir. */
|
|
39
|
-
const OMO_FILE_KIND_RE: Record<OmoFileKind, RegExp> = {
|
|
40
|
-
plan: /\.(omo|sisyphus)\/plans\//i,
|
|
41
|
-
draft: /\.(omo|sisyphus)\/drafts\//i,
|
|
42
|
-
notepad: /\.(omo|sisyphus)\/notepads\//i,
|
|
43
|
-
proof: /\.(omo|sisyphus)\/evidence\//i,
|
|
44
|
-
rule: /\.(omo|sisyphus)\/rules\//i,
|
|
45
|
-
"run-continuation": /\.(omo|sisyphus)\/run-continuation\//i,
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export type PlanSessionIndex = {
|
|
49
|
-
fileWriter: Map<string, { sessionId: string; lastAt: number }>
|
|
50
|
-
sessionPlan: Map<string, { rel: string; lastAt: number; isPlan: boolean }>
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export type OmoFileIndex = {
|
|
54
|
-
fileWriter: Map<string, { sessionId: string; lastAt: number }>
|
|
55
|
-
sessionFiles: Map<string, { rel: string; lastAt: number }>
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const PLAN_FILE_RE = /\.(omo|sisyphus)\/(drafts|plans)\//i
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Normalise `fp` to a project-relative posix path, match it against `regex`,
|
|
62
|
-
* and slice to the marker start. `projectRoot` strips the absolute prefix;
|
|
63
|
-
* without it the fp is used as-is and the marker may sit mid-path. Null when
|
|
64
|
-
* the path does not match `regex`.
|
|
65
|
-
*/
|
|
66
|
-
function omoRelOf(
|
|
67
|
-
fp: string,
|
|
68
|
-
projectRoot: string | null | undefined,
|
|
69
|
-
regex: RegExp,
|
|
70
|
-
): string | null {
|
|
71
|
-
let rel = fp.replace(/\\/g, "/").trim()
|
|
72
|
-
if (!rel) return null
|
|
73
|
-
if (projectRoot) {
|
|
74
|
-
const root = projectRoot.replace(/\\/g, "/").replace(/\/+$/, "")
|
|
75
|
-
if (rel.toLowerCase().startsWith(root.toLowerCase() + "/")) {
|
|
76
|
-
rel = rel.slice(root.length + 1)
|
|
77
|
-
} else if (rel.toLowerCase() === root.toLowerCase()) {
|
|
78
|
-
rel = ""
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
const m = rel.match(regex)
|
|
82
|
-
if (!m) return null
|
|
83
|
-
const start = m.index ?? 0
|
|
84
|
-
return start > 0 ? rel.slice(start) : rel
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* If `fp` is a plan/draft target under `.omo/` or `.sisyphus/`, return its
|
|
89
|
-
* project-relative path and whether it is under `plans/`. `projectRoot`
|
|
90
|
-
* strips the absolute prefix; without it the fp is used as-is and the marker
|
|
91
|
-
* may sit mid-path. Null for anything else (plain source files included).
|
|
92
|
-
*/
|
|
93
|
-
function planFileOf(
|
|
94
|
-
fp: string,
|
|
95
|
-
projectRoot: string | null | undefined,
|
|
96
|
-
): { rel: string; isPlan: boolean } | null {
|
|
97
|
-
const rel = omoRelOf(fp, projectRoot, PLAN_FILE_RE)
|
|
98
|
-
if (!rel) return null
|
|
99
|
-
const m = rel.match(PLAN_FILE_RE)
|
|
100
|
-
return { rel, isPlan: m?.[2]?.toLowerCase() === "plans" }
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** `MAX(part.time_updated)` over the part/session join, or null on error. */
|
|
104
|
-
function partsStampInfo(
|
|
105
|
-
db: SqlDb,
|
|
106
|
-
projectId: string | null | undefined,
|
|
107
|
-
): { id: string; max: number } | null {
|
|
108
|
-
try {
|
|
109
|
-
const where = projectId ? "session.project_id = ?" : "1=1"
|
|
110
|
-
const args = projectId ? [projectId] : []
|
|
111
|
-
const p = db.get<{ m: number | null }>(
|
|
112
|
-
`SELECT MAX(part.time_updated) AS m
|
|
113
|
-
FROM part JOIN session ON session.id = part.session_id
|
|
114
|
-
WHERE ${where}`,
|
|
115
|
-
...args,
|
|
116
|
-
)
|
|
117
|
-
return { id: projectId ?? "*", max: p?.m ?? 0 }
|
|
118
|
-
} catch {
|
|
119
|
-
return null
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function planIndexStamp(db: SqlDb, projectId: string | null | undefined): string {
|
|
124
|
-
const info = partsStampInfo(db, projectId)
|
|
125
|
-
return info ? `${info.id}|${info.max}` : "x"
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function omoFileStamp(db: SqlDb, projectId: string | null | undefined, kind: OmoFileKind): string {
|
|
129
|
-
const info = partsStampInfo(db, projectId)
|
|
130
|
-
return info ? `${info.id}|${kind}|${info.max}` : "x"
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/** One write-ish part row: session, resolved filePath, latest write time, and
|
|
134
|
-
* whether the session ever `write`-created the file. */
|
|
135
|
-
type FilePartRow = {
|
|
136
|
-
session_id: string | null
|
|
137
|
-
fp: string | null
|
|
138
|
-
last: number | null
|
|
139
|
-
is_writer: number | null
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function queryFileParts(db: SqlDb, projectId: string | null | undefined): FilePartRow[] {
|
|
143
|
-
const where = projectId ? "session.project_id = ? AND" : ""
|
|
144
|
-
const args = projectId ? [projectId] : []
|
|
145
|
-
return db.all<FilePartRow>(
|
|
146
|
-
`SELECT part.session_id,
|
|
147
|
-
COALESCE(
|
|
148
|
-
json_extract(part.data,'$.state.input.filePath'),
|
|
149
|
-
json_extract(part.data,'$.input.filePath'),
|
|
150
|
-
json_extract(part.data,'$.filePath')
|
|
151
|
-
) AS fp,
|
|
152
|
-
MAX(part.time_updated) AS last,
|
|
153
|
-
MAX(CASE WHEN json_extract(part.data,'$.tool')='write' THEN 1 ELSE 0 END) AS is_writer
|
|
154
|
-
FROM part
|
|
155
|
-
JOIN session ON session.id = part.session_id
|
|
156
|
-
WHERE ${where} json_extract(part.data,'$.type')='tool'
|
|
157
|
-
AND json_extract(part.data,'$.tool') IN ('write','edit','multiedit','apply_edit','applyedit')
|
|
158
|
-
GROUP BY part.session_id, fp`,
|
|
159
|
-
...args,
|
|
160
|
-
)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function buildPlanSessionIndex(
|
|
164
|
-
db: SqlDb,
|
|
165
|
-
projectId: string | null | undefined,
|
|
166
|
-
projectRoot: string | null | undefined,
|
|
167
|
-
): PlanSessionIndex {
|
|
168
|
-
const fileWriter = new Map<string, { sessionId: string; lastAt: number }>()
|
|
169
|
-
const writerPref = new Map<string, number>() // basename → is_writer of the current entry
|
|
170
|
-
const sessionPlan = new Map<string, { rel: string; lastAt: number; isPlan: boolean }>()
|
|
171
|
-
try {
|
|
172
|
-
for (const row of queryFileParts(db, projectId)) {
|
|
173
|
-
const sessionId = str(row.session_id)
|
|
174
|
-
const fp = str(row.fp)
|
|
175
|
-
const last = typeof row.last === "number" && row.last > 0 ? row.last : 0
|
|
176
|
-
if (!sessionId || !fp) continue
|
|
177
|
-
const info = planFileOf(fp, projectRoot)
|
|
178
|
-
if (!info) continue
|
|
179
|
-
const base = basenameOf(info.rel)
|
|
180
|
-
const isWriter = row.is_writer === 1 ? 1 : 0
|
|
181
|
-
const writer = fileWriter.get(base)
|
|
182
|
-
if (!writer) {
|
|
183
|
-
fileWriter.set(base, { sessionId, lastAt: last })
|
|
184
|
-
writerPref.set(base, isWriter)
|
|
185
|
-
} else {
|
|
186
|
-
const cur = writerPref.get(base) ?? 0
|
|
187
|
-
// A `write` creator beats a session that only edited the file, even
|
|
188
|
-
// when the edit came later; equal preference falls back to the later
|
|
189
|
-
// timestamp.
|
|
190
|
-
if (isWriter > cur || (isWriter === cur && last > writer.lastAt)) {
|
|
191
|
-
fileWriter.set(base, { sessionId, lastAt: last })
|
|
192
|
-
writerPref.set(base, isWriter)
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
const plan = sessionPlan.get(sessionId)
|
|
196
|
-
if (!plan) {
|
|
197
|
-
sessionPlan.set(sessionId, { rel: info.rel, lastAt: last, isPlan: info.isPlan })
|
|
198
|
-
} else if (last > plan.lastAt) {
|
|
199
|
-
sessionPlan.set(sessionId, { rel: info.rel, lastAt: last, isPlan: info.isPlan })
|
|
200
|
-
} else if (last === plan.lastAt && info.isPlan && !plan.isPlan) {
|
|
201
|
-
sessionPlan.set(sessionId, { rel: info.rel, lastAt: last, isPlan: info.isPlan })
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
} catch {
|
|
205
|
-
// soft-fail → empty index
|
|
206
|
-
}
|
|
207
|
-
return { fileWriter, sessionPlan }
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function buildOmoFileIndex(
|
|
211
|
-
db: SqlDb,
|
|
212
|
-
projectId: string | null | undefined,
|
|
213
|
-
projectRoot: string | null | undefined,
|
|
214
|
-
kind: OmoFileKind,
|
|
215
|
-
): OmoFileIndex {
|
|
216
|
-
const fileWriter = new Map<string, { sessionId: string; lastAt: number }>()
|
|
217
|
-
const writerPref = new Map<string, number>() // basename → is_writer of the current entry
|
|
218
|
-
const sessionFiles = new Map<string, { rel: string; lastAt: number }>()
|
|
219
|
-
const regex = OMO_FILE_KIND_RE[kind]
|
|
220
|
-
try {
|
|
221
|
-
for (const row of queryFileParts(db, projectId)) {
|
|
222
|
-
const sessionId = str(row.session_id)
|
|
223
|
-
const fp = str(row.fp)
|
|
224
|
-
const last = typeof row.last === "number" && row.last > 0 ? row.last : 0
|
|
225
|
-
if (!sessionId || !fp) continue
|
|
226
|
-
const rel = omoRelOf(fp, projectRoot, regex)
|
|
227
|
-
if (!rel) continue
|
|
228
|
-
const base = basenameOf(rel)
|
|
229
|
-
const isWriter = row.is_writer === 1 ? 1 : 0
|
|
230
|
-
const writer = fileWriter.get(base)
|
|
231
|
-
if (!writer) {
|
|
232
|
-
fileWriter.set(base, { sessionId, lastAt: last })
|
|
233
|
-
writerPref.set(base, isWriter)
|
|
234
|
-
} else {
|
|
235
|
-
const cur = writerPref.get(base) ?? 0
|
|
236
|
-
// A `write` creator beats a session that only edited the file, even
|
|
237
|
-
// when the edit came later; equal preference falls back to the later
|
|
238
|
-
// timestamp.
|
|
239
|
-
if (isWriter > cur || (isWriter === cur && last > writer.lastAt)) {
|
|
240
|
-
fileWriter.set(base, { sessionId, lastAt: last })
|
|
241
|
-
writerPref.set(base, isWriter)
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
const curFile = sessionFiles.get(sessionId)
|
|
245
|
-
if (!curFile || last > curFile.lastAt) {
|
|
246
|
-
sessionFiles.set(sessionId, { rel, lastAt: last })
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
} catch {
|
|
250
|
-
// soft-fail → empty index
|
|
251
|
-
}
|
|
252
|
-
return { fileWriter, sessionFiles }
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const planIndexCache = createStampCache<PlanSessionIndex>()
|
|
256
|
-
const omoIndexCache = createStampCache<OmoFileIndex>()
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* In-memory bidirectional plan-file index for one project, built from a single
|
|
260
|
-
* write-ish `part` join. `fileWriter` maps a plan/draft basename to its latest
|
|
261
|
-
* writer session; `sessionPlan` maps a session to its latest plan/draft file,
|
|
262
|
-
* preferring `plans/` over `drafts/` on an equal timestamp. Stamp-cached on the
|
|
263
|
-
* project's `MAX(part.time_updated)` so it rebuilds only when parts change.
|
|
264
|
-
*/
|
|
265
|
-
export function planSessionIndex(
|
|
266
|
-
db: SqlDb,
|
|
267
|
-
projectId: string | null | undefined,
|
|
268
|
-
projectRoot: string | null | undefined,
|
|
269
|
-
): PlanSessionIndex {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
)
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
*
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
1
|
+
/**
|
|
2
|
+
* pware.oc.omo.resolver.planFile
|
|
3
|
+
*
|
|
4
|
+
* Omo-file → writer-session index. Reads the OpenCode SQLite database through
|
|
5
|
+
* a raw `SqlDb` handle to map `.omo/` / `.sisyphus/` document files to the
|
|
6
|
+
* session that last wrote them. Lives in the omo layer because omo may read
|
|
7
|
+
* opencode data (one-way relaxation): the index needs the session/part rows,
|
|
8
|
+
* not any opencode-layer code.
|
|
9
|
+
*
|
|
10
|
+
* One generic engine (`omoFileIndex` / `sessionForOmoFile`) serves every
|
|
11
|
+
* document kind; the plan/draft index (`planSessionIndex` /
|
|
12
|
+
* `sessionForPlanFile`) is a legacy specialisation that folds plans and drafts
|
|
13
|
+
* into one map so a session's latest plan is preferred over its latest draft.
|
|
14
|
+
*/
|
|
15
|
+
import { createStampCache } from "../../pware.oc.core/pware.oc.core.cache.js"
|
|
16
|
+
import { basenameOf, str } from "../../pware.oc.core/pware.oc.core.paths.js"
|
|
17
|
+
import type { SqlDb } from "../../pware.oc.core/pware.oc.core.sqlite.js"
|
|
18
|
+
import { DOC_KIND_PLAN, type DocKind } from "../constants/pware.oc.omo.constants.docKind.js"
|
|
19
|
+
import {
|
|
20
|
+
listOmoFiles,
|
|
21
|
+
type DocView,
|
|
22
|
+
type ListOmoFilesOptions,
|
|
23
|
+
} from "./pware.oc.omo.resolver.doc.js"
|
|
24
|
+
|
|
25
|
+
/** Every OMO on-disk document kind (the subdirectory under `.omo/` / `.sisyphus/`). */
|
|
26
|
+
export const OMO_FILE_KINDS = [
|
|
27
|
+
"plan",
|
|
28
|
+
"draft",
|
|
29
|
+
"notepad",
|
|
30
|
+
"proof",
|
|
31
|
+
"rule",
|
|
32
|
+
"run-continuation",
|
|
33
|
+
] as const
|
|
34
|
+
|
|
35
|
+
/** An OMO on-disk document kind. */
|
|
36
|
+
export type OmoFileKind = (typeof OMO_FILE_KINDS)[number]
|
|
37
|
+
|
|
38
|
+
/** One case-insensitive regex per kind matching the omo/sisyphus subdir. */
|
|
39
|
+
const OMO_FILE_KIND_RE: Record<OmoFileKind, RegExp> = {
|
|
40
|
+
plan: /\.(omo|sisyphus)\/plans\//i,
|
|
41
|
+
draft: /\.(omo|sisyphus)\/drafts\//i,
|
|
42
|
+
notepad: /\.(omo|sisyphus)\/notepads\//i,
|
|
43
|
+
proof: /\.(omo|sisyphus)\/evidence\//i,
|
|
44
|
+
rule: /\.(omo|sisyphus)\/rules\//i,
|
|
45
|
+
"run-continuation": /\.(omo|sisyphus)\/run-continuation\//i,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type PlanSessionIndex = {
|
|
49
|
+
fileWriter: Map<string, { sessionId: string; lastAt: number }>
|
|
50
|
+
sessionPlan: Map<string, { rel: string; lastAt: number; isPlan: boolean }>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type OmoFileIndex = {
|
|
54
|
+
fileWriter: Map<string, { sessionId: string; lastAt: number }>
|
|
55
|
+
sessionFiles: Map<string, { rel: string; lastAt: number }>
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const PLAN_FILE_RE = /\.(omo|sisyphus)\/(drafts|plans)\//i
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Normalise `fp` to a project-relative posix path, match it against `regex`,
|
|
62
|
+
* and slice to the marker start. `projectRoot` strips the absolute prefix;
|
|
63
|
+
* without it the fp is used as-is and the marker may sit mid-path. Null when
|
|
64
|
+
* the path does not match `regex`.
|
|
65
|
+
*/
|
|
66
|
+
function omoRelOf(
|
|
67
|
+
fp: string,
|
|
68
|
+
projectRoot: string | null | undefined,
|
|
69
|
+
regex: RegExp,
|
|
70
|
+
): string | null {
|
|
71
|
+
let rel = fp.replace(/\\/g, "/").trim()
|
|
72
|
+
if (!rel) return null
|
|
73
|
+
if (projectRoot) {
|
|
74
|
+
const root = projectRoot.replace(/\\/g, "/").replace(/\/+$/, "")
|
|
75
|
+
if (rel.toLowerCase().startsWith(root.toLowerCase() + "/")) {
|
|
76
|
+
rel = rel.slice(root.length + 1)
|
|
77
|
+
} else if (rel.toLowerCase() === root.toLowerCase()) {
|
|
78
|
+
rel = ""
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const m = rel.match(regex)
|
|
82
|
+
if (!m) return null
|
|
83
|
+
const start = m.index ?? 0
|
|
84
|
+
return start > 0 ? rel.slice(start) : rel
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* If `fp` is a plan/draft target under `.omo/` or `.sisyphus/`, return its
|
|
89
|
+
* project-relative path and whether it is under `plans/`. `projectRoot`
|
|
90
|
+
* strips the absolute prefix; without it the fp is used as-is and the marker
|
|
91
|
+
* may sit mid-path. Null for anything else (plain source files included).
|
|
92
|
+
*/
|
|
93
|
+
function planFileOf(
|
|
94
|
+
fp: string,
|
|
95
|
+
projectRoot: string | null | undefined,
|
|
96
|
+
): { rel: string; isPlan: boolean } | null {
|
|
97
|
+
const rel = omoRelOf(fp, projectRoot, PLAN_FILE_RE)
|
|
98
|
+
if (!rel) return null
|
|
99
|
+
const m = rel.match(PLAN_FILE_RE)
|
|
100
|
+
return { rel, isPlan: m?.[2]?.toLowerCase() === "plans" }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** `MAX(part.time_updated)` over the part/session join, or null on error. */
|
|
104
|
+
function partsStampInfo(
|
|
105
|
+
db: SqlDb,
|
|
106
|
+
projectId: string | null | undefined,
|
|
107
|
+
): { id: string; max: number } | null {
|
|
108
|
+
try {
|
|
109
|
+
const where = projectId ? "session.project_id = ?" : "1=1"
|
|
110
|
+
const args = projectId ? [projectId] : []
|
|
111
|
+
const p = db.get<{ m: number | null }>(
|
|
112
|
+
`SELECT MAX(part.time_updated) AS m
|
|
113
|
+
FROM part JOIN session ON session.id = part.session_id
|
|
114
|
+
WHERE ${where}`,
|
|
115
|
+
...args,
|
|
116
|
+
)
|
|
117
|
+
return { id: projectId ?? "*", max: p?.m ?? 0 }
|
|
118
|
+
} catch {
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function planIndexStamp(db: SqlDb, projectId: string | null | undefined): string {
|
|
124
|
+
const info = partsStampInfo(db, projectId)
|
|
125
|
+
return info ? `${info.id}|${info.max}` : "x"
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function omoFileStamp(db: SqlDb, projectId: string | null | undefined, kind: OmoFileKind): string {
|
|
129
|
+
const info = partsStampInfo(db, projectId)
|
|
130
|
+
return info ? `${info.id}|${kind}|${info.max}` : "x"
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** One write-ish part row: session, resolved filePath, latest write time, and
|
|
134
|
+
* whether the session ever `write`-created the file. */
|
|
135
|
+
type FilePartRow = {
|
|
136
|
+
session_id: string | null
|
|
137
|
+
fp: string | null
|
|
138
|
+
last: number | null
|
|
139
|
+
is_writer: number | null
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function queryFileParts(db: SqlDb, projectId: string | null | undefined): FilePartRow[] {
|
|
143
|
+
const where = projectId ? "session.project_id = ? AND" : ""
|
|
144
|
+
const args = projectId ? [projectId] : []
|
|
145
|
+
return db.all<FilePartRow>(
|
|
146
|
+
`SELECT part.session_id,
|
|
147
|
+
COALESCE(
|
|
148
|
+
json_extract(part.data,'$.state.input.filePath'),
|
|
149
|
+
json_extract(part.data,'$.input.filePath'),
|
|
150
|
+
json_extract(part.data,'$.filePath')
|
|
151
|
+
) AS fp,
|
|
152
|
+
MAX(part.time_updated) AS last,
|
|
153
|
+
MAX(CASE WHEN json_extract(part.data,'$.tool')='write' THEN 1 ELSE 0 END) AS is_writer
|
|
154
|
+
FROM part
|
|
155
|
+
JOIN session ON session.id = part.session_id
|
|
156
|
+
WHERE ${where} json_extract(part.data,'$.type')='tool'
|
|
157
|
+
AND json_extract(part.data,'$.tool') IN ('write','edit','multiedit','apply_edit','applyedit')
|
|
158
|
+
GROUP BY part.session_id, fp`,
|
|
159
|
+
...args,
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function buildPlanSessionIndex(
|
|
164
|
+
db: SqlDb,
|
|
165
|
+
projectId: string | null | undefined,
|
|
166
|
+
projectRoot: string | null | undefined,
|
|
167
|
+
): PlanSessionIndex {
|
|
168
|
+
const fileWriter = new Map<string, { sessionId: string; lastAt: number }>()
|
|
169
|
+
const writerPref = new Map<string, number>() // basename → is_writer of the current entry
|
|
170
|
+
const sessionPlan = new Map<string, { rel: string; lastAt: number; isPlan: boolean }>()
|
|
171
|
+
try {
|
|
172
|
+
for (const row of queryFileParts(db, projectId)) {
|
|
173
|
+
const sessionId = str(row.session_id)
|
|
174
|
+
const fp = str(row.fp)
|
|
175
|
+
const last = typeof row.last === "number" && row.last > 0 ? row.last : 0
|
|
176
|
+
if (!sessionId || !fp) continue
|
|
177
|
+
const info = planFileOf(fp, projectRoot)
|
|
178
|
+
if (!info) continue
|
|
179
|
+
const base = basenameOf(info.rel)
|
|
180
|
+
const isWriter = row.is_writer === 1 ? 1 : 0
|
|
181
|
+
const writer = fileWriter.get(base)
|
|
182
|
+
if (!writer) {
|
|
183
|
+
fileWriter.set(base, { sessionId, lastAt: last })
|
|
184
|
+
writerPref.set(base, isWriter)
|
|
185
|
+
} else {
|
|
186
|
+
const cur = writerPref.get(base) ?? 0
|
|
187
|
+
// A `write` creator beats a session that only edited the file, even
|
|
188
|
+
// when the edit came later; equal preference falls back to the later
|
|
189
|
+
// timestamp.
|
|
190
|
+
if (isWriter > cur || (isWriter === cur && last > writer.lastAt)) {
|
|
191
|
+
fileWriter.set(base, { sessionId, lastAt: last })
|
|
192
|
+
writerPref.set(base, isWriter)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const plan = sessionPlan.get(sessionId)
|
|
196
|
+
if (!plan) {
|
|
197
|
+
sessionPlan.set(sessionId, { rel: info.rel, lastAt: last, isPlan: info.isPlan })
|
|
198
|
+
} else if (last > plan.lastAt) {
|
|
199
|
+
sessionPlan.set(sessionId, { rel: info.rel, lastAt: last, isPlan: info.isPlan })
|
|
200
|
+
} else if (last === plan.lastAt && info.isPlan && !plan.isPlan) {
|
|
201
|
+
sessionPlan.set(sessionId, { rel: info.rel, lastAt: last, isPlan: info.isPlan })
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
} catch {
|
|
205
|
+
// soft-fail → empty index
|
|
206
|
+
}
|
|
207
|
+
return { fileWriter, sessionPlan }
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function buildOmoFileIndex(
|
|
211
|
+
db: SqlDb,
|
|
212
|
+
projectId: string | null | undefined,
|
|
213
|
+
projectRoot: string | null | undefined,
|
|
214
|
+
kind: OmoFileKind,
|
|
215
|
+
): OmoFileIndex {
|
|
216
|
+
const fileWriter = new Map<string, { sessionId: string; lastAt: number }>()
|
|
217
|
+
const writerPref = new Map<string, number>() // basename → is_writer of the current entry
|
|
218
|
+
const sessionFiles = new Map<string, { rel: string; lastAt: number }>()
|
|
219
|
+
const regex = OMO_FILE_KIND_RE[kind]
|
|
220
|
+
try {
|
|
221
|
+
for (const row of queryFileParts(db, projectId)) {
|
|
222
|
+
const sessionId = str(row.session_id)
|
|
223
|
+
const fp = str(row.fp)
|
|
224
|
+
const last = typeof row.last === "number" && row.last > 0 ? row.last : 0
|
|
225
|
+
if (!sessionId || !fp) continue
|
|
226
|
+
const rel = omoRelOf(fp, projectRoot, regex)
|
|
227
|
+
if (!rel) continue
|
|
228
|
+
const base = basenameOf(rel)
|
|
229
|
+
const isWriter = row.is_writer === 1 ? 1 : 0
|
|
230
|
+
const writer = fileWriter.get(base)
|
|
231
|
+
if (!writer) {
|
|
232
|
+
fileWriter.set(base, { sessionId, lastAt: last })
|
|
233
|
+
writerPref.set(base, isWriter)
|
|
234
|
+
} else {
|
|
235
|
+
const cur = writerPref.get(base) ?? 0
|
|
236
|
+
// A `write` creator beats a session that only edited the file, even
|
|
237
|
+
// when the edit came later; equal preference falls back to the later
|
|
238
|
+
// timestamp.
|
|
239
|
+
if (isWriter > cur || (isWriter === cur && last > writer.lastAt)) {
|
|
240
|
+
fileWriter.set(base, { sessionId, lastAt: last })
|
|
241
|
+
writerPref.set(base, isWriter)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const curFile = sessionFiles.get(sessionId)
|
|
245
|
+
if (!curFile || last > curFile.lastAt) {
|
|
246
|
+
sessionFiles.set(sessionId, { rel, lastAt: last })
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
} catch {
|
|
250
|
+
// soft-fail → empty index
|
|
251
|
+
}
|
|
252
|
+
return { fileWriter, sessionFiles }
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const planIndexCache = createStampCache<PlanSessionIndex>()
|
|
256
|
+
const omoIndexCache = createStampCache<OmoFileIndex>()
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* In-memory bidirectional plan-file index for one project, built from a single
|
|
260
|
+
* write-ish `part` join. `fileWriter` maps a plan/draft basename to its latest
|
|
261
|
+
* writer session; `sessionPlan` maps a session to its latest plan/draft file,
|
|
262
|
+
* preferring `plans/` over `drafts/` on an equal timestamp. Stamp-cached on the
|
|
263
|
+
* project's `MAX(part.time_updated)` so it rebuilds only when parts change.
|
|
264
|
+
*/
|
|
265
|
+
export function planSessionIndex(
|
|
266
|
+
db: SqlDb,
|
|
267
|
+
projectId: string | null | undefined,
|
|
268
|
+
projectRoot: string | null | undefined,
|
|
269
|
+
): PlanSessionIndex {
|
|
270
|
+
const key = `${db.path ?? ""}|${projectRoot ?? ""}|${planIndexStamp(db, projectId)}`
|
|
271
|
+
return planIndexCache.get(key, () =>
|
|
272
|
+
buildPlanSessionIndex(db, projectId, projectRoot),
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* The session that last wrote a `.omo/` plan/draft file, if any. Matches
|
|
278
|
+
* write-ish tool parts by the file's basename, so the planner session that
|
|
279
|
+
* created a pending approval is the one "Continue" should jump to.
|
|
280
|
+
*/
|
|
281
|
+
export function sessionForPlanFile(db: SqlDb, relPath: string | null | undefined): string | null {
|
|
282
|
+
const base = basenameOf(relPath ?? "")
|
|
283
|
+
if (!base || base === "file") return null
|
|
284
|
+
return planSessionIndex(db, null, null).fileWriter.get(base)?.sessionId ?? null
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* In-memory bidirectional index for one OMO document kind, built from a single
|
|
289
|
+
* write-ish `part` join. `fileWriter` maps a file basename to its latest writer
|
|
290
|
+
* session; `sessionFiles` maps a session to its latest file of that kind.
|
|
291
|
+
* Stamp-cached on the project's `MAX(part.time_updated)` so it rebuilds only
|
|
292
|
+
* when parts change.
|
|
293
|
+
*/
|
|
294
|
+
export function omoFileIndex(
|
|
295
|
+
db: SqlDb,
|
|
296
|
+
projectId: string | null | undefined,
|
|
297
|
+
projectRoot: string | null | undefined,
|
|
298
|
+
kind: OmoFileKind,
|
|
299
|
+
): OmoFileIndex {
|
|
300
|
+
const key = `${db.path ?? ""}|${projectRoot ?? ""}|${omoFileStamp(db, projectId, kind)}`
|
|
301
|
+
return omoIndexCache.get(key, () =>
|
|
302
|
+
buildOmoFileIndex(db, projectId, projectRoot, kind),
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* The writer session for a rel path from an already-built index. The per-file
|
|
308
|
+
* half of `sessionForOmoFile`, split out so a caller can filter a whole list
|
|
309
|
+
* against one index build instead of one stamp lookup per file.
|
|
310
|
+
*/
|
|
311
|
+
export function omoWriterSession(
|
|
312
|
+
index: OmoFileIndex,
|
|
313
|
+
relPath: string | null | undefined,
|
|
314
|
+
): string | null {
|
|
315
|
+
const base = basenameOf(relPath ?? "")
|
|
316
|
+
if (!base || base === "file") return null
|
|
317
|
+
return index.fileWriter.get(base)?.sessionId ?? null
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* The session that last wrote a `.omo/` file of `kind`, if any. Matches
|
|
322
|
+
* write-ish tool parts by the file's basename.
|
|
323
|
+
*/
|
|
324
|
+
export function sessionForOmoFile(
|
|
325
|
+
db: SqlDb,
|
|
326
|
+
relPath: string | null | undefined,
|
|
327
|
+
kind: OmoFileKind,
|
|
328
|
+
): string | null {
|
|
329
|
+
return omoWriterSession(omoFileIndex(db, null, null, kind), relPath)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** A per-kind omo-file resolver: the index builder + the writer-session lookup. */
|
|
333
|
+
export function makeOmoFileResolver(kind: OmoFileKind): {
|
|
334
|
+
sessionIndex: (
|
|
335
|
+
db: SqlDb,
|
|
336
|
+
projectId: string | null | undefined,
|
|
337
|
+
projectRoot: string | null | undefined,
|
|
338
|
+
) => OmoFileIndex
|
|
339
|
+
sessionForFile: (db: SqlDb, relPath: string | null | undefined) => string | null
|
|
340
|
+
} {
|
|
341
|
+
return {
|
|
342
|
+
sessionIndex: (db, projectId, projectRoot) => omoFileIndex(db, projectId, projectRoot, kind),
|
|
343
|
+
sessionForFile: (db, relPath) => sessionForOmoFile(db, relPath, kind),
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** A per-doc-kind `.list` wrapper — the `XxxFile.list` shape. */
|
|
348
|
+
export function makeOmoFileList(docKind: DocKind): {
|
|
349
|
+
list: (
|
|
350
|
+
projectRoot: string | null | undefined,
|
|
351
|
+
sessionId?: string | null,
|
|
352
|
+
opts?: ListOmoFilesOptions,
|
|
353
|
+
) => DocView[]
|
|
354
|
+
} {
|
|
355
|
+
return {
|
|
356
|
+
list(projectRoot, sessionId = null, opts = {}) {
|
|
357
|
+
return listOmoFiles(docKind, projectRoot, { ...opts, sessionId })
|
|
358
|
+
},
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** Plan files under `.omo/plans/`, optionally filtered by writer session. */
|
|
363
|
+
export const PlanFile = makeOmoFileList(DOC_KIND_PLAN)
|