kanbo-cli 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +173 -0
- package/LICENSE +75 -21
- package/README.md +19 -48
- package/dist/{better-sqlite3-fU3G4xnX.cjs → better-sqlite3-DPdSZ6sH.cjs} +244 -247
- package/dist/cli-main.cjs +51208 -0
- package/dist/cli.cjs +32 -45216
- package/dist/dist-IHRTF-b5.cjs +2552 -0
- package/dist/lib/{tool-names-WfH4IURz.d.cts → approval-BsBk2xVr.d.cts} +72 -3
- package/dist/lib/{tool-names-3q_GEZej.d.mts → approval-LRZ6ontC.d.mts} +72 -3
- package/dist/lib/{board-store-BvWn9xTB.d.mts → board-store-D_Mpfb84.d.mts} +75 -2
- package/dist/lib/{board-store-BfszzAJB.d.cts → board-store-Dx-mw2I6.d.cts} +75 -2
- package/dist/lib/index.cjs +8 -2
- package/dist/lib/index.d.cts +202 -91
- package/dist/lib/index.d.mts +202 -91
- package/dist/lib/index.mjs +3 -3
- package/dist/lib/{manifest-RDyoGrVR.cjs → manifest-QPoQl8tn.cjs} +6881 -1556
- package/dist/lib/{manifest-BwpC-4BO.mjs → manifest-v2e9x_C8.mjs} +6795 -1556
- package/dist/lib/mcp/index.cjs +156 -21
- package/dist/lib/mcp/index.d.cts +140 -5
- package/dist/lib/mcp/index.d.mts +140 -5
- package/dist/lib/mcp/index.mjs +152 -22
- package/dist/lib/{migrate-DnMjTCvu.cjs → migrate-Dca5WdYd.cjs} +144 -14
- package/dist/lib/{migrate-BhYi9cXw.mjs → migrate-DtBeW3Fs.mjs} +134 -16
- package/dist/lib/{migrations-path-Ciwan537.mjs → migrations-path-ClaYRNwk.mjs} +5 -7
- package/dist/lib/{migrations-path-CZxVOgeD.cjs → migrations-path-DBCE17dT.cjs} +5 -7
- package/dist/lib/postgres/index.cjs +2 -4
- package/dist/lib/postgres/index.d.cts +2 -3
- package/dist/lib/postgres/index.d.mts +2 -3
- package/dist/lib/postgres/index.mjs +2 -4
- package/dist/lib/{roles.sql-CvyCGt38.mjs → roles.sql-BoOZSkBp.mjs} +96 -5
- package/dist/lib/{roles.sql-B9rt-x4o.cjs → roles.sql-E58x7mLS.cjs} +95 -4
- package/dist/lib/schema-D0MyXcej.d.cts +1 -2
- package/dist/lib/schema-D0MyXcej.d.mts +1 -2
- package/dist/lib/schema-epoch-ZyumVL_A.d.cts +1 -2
- package/dist/lib/schema-epoch-ZyumVL_A.d.mts +1 -2
- package/dist/lib/sqlite/index.cjs +2 -2
- package/dist/lib/sqlite/index.d.cts +3 -4
- package/dist/lib/sqlite/index.d.mts +3 -4
- package/dist/lib/sqlite/index.mjs +2 -2
- package/dist/lib/sqlite/schema.cjs +0 -2
- package/dist/lib/sqlite/schema.mjs +0 -2
- package/package.json +14 -11
- package/dist/better-sqlite3-fU3G4xnX.cjs.map +0 -1
- package/dist/cli.cjs.map +0 -1
- package/dist/dist-BsCwQFEl.cjs +0 -989
- package/dist/dist-BsCwQFEl.cjs.map +0 -1
- package/dist/lib/manifest-BwpC-4BO.mjs.map +0 -1
- package/dist/lib/manifest-RDyoGrVR.cjs.map +0 -1
- package/dist/lib/mcp/index.cjs.map +0 -1
- package/dist/lib/mcp/index.mjs.map +0 -1
- package/dist/lib/migrate-BhYi9cXw.mjs.map +0 -1
- package/dist/lib/migrate-DnMjTCvu.cjs.map +0 -1
- package/dist/lib/migrations-path-CZxVOgeD.cjs.map +0 -1
- package/dist/lib/migrations-path-Ciwan537.mjs.map +0 -1
- package/dist/lib/postgres/index.cjs.map +0 -1
- package/dist/lib/postgres/index.mjs.map +0 -1
- package/dist/lib/roles.sql-B9rt-x4o.cjs.map +0 -1
- package/dist/lib/roles.sql-CvyCGt38.mjs.map +0 -1
- package/dist/lib/sqlite/schema.cjs.map +0 -1
- package/dist/lib/sqlite/schema.mjs.map +0 -1
- package/drizzle-postgres/0000_board.sql +0 -135
- package/drizzle-postgres/0001_history_order.sql +0 -2
- package/drizzle-postgres/0002_issue_pull_requests.sql +0 -16
- package/drizzle-postgres/0003_milestone_start_date.sql +0 -1
- package/drizzle-postgres/0004_status_entry_rules.sql +0 -1
- package/drizzle-postgres/0005_run_external_session_ref.sql +0 -1
- package/drizzle-postgres/README.md +0 -61
- package/drizzle-postgres/meta/0000_snapshot.json +0 -1078
- package/drizzle-postgres/meta/0001_snapshot.json +0 -1090
- package/drizzle-postgres/meta/0002_snapshot.json +0 -1239
- package/drizzle-postgres/meta/0003_snapshot.json +0 -1245
- package/drizzle-postgres/meta/0004_snapshot.json +0 -1251
- package/drizzle-postgres/meta/0005_snapshot.json +0 -1257
- package/drizzle-postgres/meta/_journal.json +0 -48
- package/drizzle-sqlite/0000_board.sql +0 -133
- package/drizzle-sqlite/0001_issue_pull_requests.sql +0 -15
- package/drizzle-sqlite/0002_milestone_start_date.sql +0 -1
- package/drizzle-sqlite/0003_status_entry_rules.sql +0 -1
- package/drizzle-sqlite/0004_run_external_session_ref.sql +0 -1
- package/drizzle-sqlite/README.md +0 -67
- package/drizzle-sqlite/meta/0000_snapshot.json +0 -946
- package/drizzle-sqlite/meta/0001_snapshot.json +0 -1054
- package/drizzle-sqlite/meta/0002_snapshot.json +0 -1061
- package/drizzle-sqlite/meta/0003_snapshot.json +0 -1068
- package/drizzle-sqlite/meta/0004_snapshot.json +0 -1075
- package/drizzle-sqlite/meta/_journal.json +0 -41
package/dist/lib/mcp/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as BoardError } from "../migrations-path-
|
|
1
|
+
import { A as projectKanboSprint, C as pageOf, D as projectKanboFieldChange, E as projectKanboColumn, O as projectKanboPullRequest, S as matchesKanboCardQuery, T as projectKanboCardComment, b as DEFAULT_KANBO_COMMENT_LIMIT, c as registerKanboTools, d as KANBO_MCP_INSTRUCTIONS, f as KANBO_CAPABILITIES_MARKDOWN_RESOURCE_URI, i as KANBO_TOOL_NAMES, it as normalizeStatusName, j as projectKanboSubCard, k as projectKanboRun, l as KANBO_TOOLS, m as registerKanboCapabilitiesResources, o as KANBO_MCP_SERVER_NAME, p as KANBO_CAPABILITIES_RESOURCE_URI, r as KANBO_TOOL_CLI_EQUIVALENTS, s as createKanboMcpServer, st as previewComment, u as buildKanboTools, w as projectKanboCard, x as KANBO_CARD_INCLUDES, y as DEFAULT_KANBO_CARD_INCLUDES } from "../manifest-v2e9x_C8.mjs";
|
|
2
|
+
import { a as BoardError } from "../migrations-path-ClaYRNwk.mjs";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
//#region src/mcp/http-transport.ts
|
|
5
5
|
const IssueSchema = z.object({
|
|
@@ -87,6 +87,26 @@ const SprintSchema = z.object({
|
|
|
87
87
|
})
|
|
88
88
|
});
|
|
89
89
|
const PrimeSchema = z.object({ text: z.string() });
|
|
90
|
+
const ACTOR_KINDS = [
|
|
91
|
+
"user",
|
|
92
|
+
"agent",
|
|
93
|
+
"provider-target",
|
|
94
|
+
"system"
|
|
95
|
+
];
|
|
96
|
+
/** A comment as the server lists it; who wrote it by the stored kind, `system.*` markers included. */
|
|
97
|
+
const CardCommentSchema = z.object({
|
|
98
|
+
id: z.string(),
|
|
99
|
+
authorKind: z.string(),
|
|
100
|
+
content: z.string(),
|
|
101
|
+
createdAt: z.number()
|
|
102
|
+
});
|
|
103
|
+
const FieldChangeSchema = z.object({
|
|
104
|
+
field: z.string(),
|
|
105
|
+
fromValue: z.string().nullable(),
|
|
106
|
+
toValue: z.string().nullable(),
|
|
107
|
+
actorKind: z.enum(ACTOR_KINDS),
|
|
108
|
+
createdAt: z.number()
|
|
109
|
+
});
|
|
90
110
|
function createHttpTransport(input) {
|
|
91
111
|
const { request } = input;
|
|
92
112
|
async function readColumns() {
|
|
@@ -110,36 +130,135 @@ function createHttpTransport(input) {
|
|
|
110
130
|
});
|
|
111
131
|
return column;
|
|
112
132
|
}
|
|
133
|
+
async function listCards(filters) {
|
|
134
|
+
const query = new URLSearchParams({
|
|
135
|
+
workspaceId: input.workspaceId(),
|
|
136
|
+
...filters
|
|
137
|
+
});
|
|
138
|
+
return z.array(IssueSchema).parse(await request("GET", `/issues/?${query.toString()}`));
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The cards a query picks. A server of the `/issues` routes filters on one
|
|
142
|
+
* column and on a parent, and no more; the rest of the query is applied here,
|
|
143
|
+
* to the cards it answered with, so the answer is the one the board file gives.
|
|
144
|
+
*
|
|
145
|
+
* The server filters on a parent's id only, and an agent may name the parent
|
|
146
|
+
* the way it names any card — `WOR-001`, `WOR-1`, `1`. The id is asked first;
|
|
147
|
+
* when no card answers it, the board is read whole to learn which card the
|
|
148
|
+
* reference names, and a reference that names none is refused as `issue_not_found`.
|
|
149
|
+
*/
|
|
150
|
+
async function cardPage(cardQuery) {
|
|
151
|
+
const columns = await readColumns();
|
|
152
|
+
const statusIds = cardQuery.columns === void 0 ? null : new Set(cardQuery.columns.map((column) => requireColumn(columns, column).id));
|
|
153
|
+
const filters = statusIds?.size === 1 ? { statusId: [...statusIds][0] } : {};
|
|
154
|
+
let parent = cardQuery.parent;
|
|
155
|
+
let rows = await listCards(parent === void 0 ? filters : {
|
|
156
|
+
...filters,
|
|
157
|
+
parentIssueId: parent
|
|
158
|
+
});
|
|
159
|
+
if (parent !== void 0 && rows.length === 0) {
|
|
160
|
+
const board = await listCards({});
|
|
161
|
+
const named = findCardRow(board, parent);
|
|
162
|
+
if (!named) throw new BoardError("issue_not_found", { issueId: parent });
|
|
163
|
+
parent = named.id;
|
|
164
|
+
rows = board;
|
|
165
|
+
}
|
|
166
|
+
const matched = rows.filter((row) => matchesKanboCardQuery(row, {
|
|
167
|
+
...cardQuery,
|
|
168
|
+
parent,
|
|
169
|
+
returned: void 0
|
|
170
|
+
}, statusIds));
|
|
171
|
+
const picked = cardQuery.returned === void 0 ? matched : (await withAttention(matched)).filter((row) => matchesKanboCardQuery(row, cardQuery, null));
|
|
172
|
+
const offset = cardQuery.offset ?? 0;
|
|
173
|
+
return {
|
|
174
|
+
cards: (await withAttention(picked.slice(offset, cardQuery.limit === void 0 ? void 0 : offset + cardQuery.limit))).map((row) => projectKanboCard(row, columns)),
|
|
175
|
+
total: picked.length,
|
|
176
|
+
offset
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* The cards with what a list says beside the ones a person has to look at or
|
|
181
|
+
* sent back — their comments read one card at a time, since the route lists
|
|
182
|
+
* none, and only for cards that are waiting, or neither waiting nor running.
|
|
183
|
+
*/
|
|
184
|
+
async function withAttention(rows) {
|
|
185
|
+
return await Promise.all(rows.map(async (row) => {
|
|
186
|
+
if (row.attention || row.waitingFor !== "human" && row.activeRun !== null) return row;
|
|
187
|
+
const comments = z.array(CardCommentSchema).parse(await request("GET", `/issues/${encodeURIComponent(row.id)}/comments`));
|
|
188
|
+
const decision = comments.filter((comment) => comment.authorKind === "system.approved" || comment.authorKind === "system.returned").at(-1);
|
|
189
|
+
const returned = row.waitingFor !== "human" && decision?.authorKind === "system.returned";
|
|
190
|
+
const latest = comments.filter((comment) => comment.authorKind !== "system.run").at(-1);
|
|
191
|
+
const lastComment = (row.waitingFor === "human" || returned) && latest ? previewComment({
|
|
192
|
+
...latest,
|
|
193
|
+
authorKind: latest.authorKind
|
|
194
|
+
}) : null;
|
|
195
|
+
return {
|
|
196
|
+
...row,
|
|
197
|
+
attention: {
|
|
198
|
+
returned,
|
|
199
|
+
lastComment
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
/** The ready cards, a page of them; the server answers the whole queue. */
|
|
205
|
+
async function readyPage({ returnedLimit, ...page }) {
|
|
206
|
+
const query = new URLSearchParams({ workspaceId: input.workspaceId() });
|
|
207
|
+
const cards = z.array(IssueSchema).parse(await request("GET", `/issues/ready?${query.toString()}`));
|
|
208
|
+
const columns = await readColumns();
|
|
209
|
+
const offset = page.offset ?? 0;
|
|
210
|
+
const ready = {
|
|
211
|
+
cards: cards.slice(offset, page.limit === void 0 ? void 0 : offset + page.limit).map((row) => projectKanboCard(row, columns)),
|
|
212
|
+
total: cards.length,
|
|
213
|
+
offset
|
|
214
|
+
};
|
|
215
|
+
return returnedLimit === void 0 ? ready : {
|
|
216
|
+
...ready,
|
|
217
|
+
returned: await cardPage({
|
|
218
|
+
returned: true,
|
|
219
|
+
limit: returnedLimit
|
|
220
|
+
})
|
|
221
|
+
};
|
|
222
|
+
}
|
|
113
223
|
return {
|
|
114
224
|
prime: async () => PrimeSchema.parse(await request("GET", `/issues/prime?workspaceId=${encodeURIComponent(input.workspaceId())}`)),
|
|
115
|
-
ready: async ({ limit }) => {
|
|
116
|
-
|
|
117
|
-
if (limit !== void 0) query.set("limit", String(limit));
|
|
118
|
-
const cards = z.array(IssueSchema).parse(await request("GET", `/issues/ready?${query.toString()}`));
|
|
119
|
-
const columns = await readColumns();
|
|
120
|
-
return cards.map((row) => projectKanboCard(row, columns));
|
|
121
|
-
},
|
|
225
|
+
ready: async ({ limit }) => (await readyPage({ limit })).cards,
|
|
226
|
+
readyPage,
|
|
122
227
|
columns: readColumns,
|
|
123
228
|
sprints: async () => z.array(SprintSchema).parse(await request("GET", `/issues/sprints?workspaceId=${encodeURIComponent(input.workspaceId())}`)).map(projectKanboSprint),
|
|
124
|
-
cardGet: async ({ card }) => {
|
|
229
|
+
cardGet: async ({ card, include = DEFAULT_KANBO_CARD_INCLUDES, commentLimit = 10 }) => {
|
|
125
230
|
const found = IssueSchema.parse(await request("GET", `/issues/${encodeURIComponent(card)}`));
|
|
126
|
-
const
|
|
231
|
+
const wants = new Set(include);
|
|
232
|
+
const path = `/issues/${encodeURIComponent(found.id)}`;
|
|
127
233
|
const query = new URLSearchParams({
|
|
128
234
|
workspaceId: input.workspaceId(),
|
|
129
235
|
parentIssueId: found.id
|
|
130
236
|
});
|
|
131
|
-
const subCards =
|
|
237
|
+
const [columns, subCards, comments, runs, history, pullRequests] = await Promise.all([
|
|
238
|
+
readColumns(),
|
|
239
|
+
wants.has("subCards") ? request("GET", `/issues/?${query.toString()}`).then((rows) => z.array(IssueSchema).parse(rows)) : null,
|
|
240
|
+
wants.has("comments") ? request("GET", `${path}/comments`).then((rows) => z.array(CardCommentSchema).parse(rows)) : null,
|
|
241
|
+
wants.has("runs") ? request("GET", `${path}/runs`).then((rows) => z.array(RunSchema).parse(rows)) : null,
|
|
242
|
+
wants.has("history") ? request("GET", `${path}/field-changes`).then((rows) => z.array(FieldChangeSchema).parse(rows)) : null,
|
|
243
|
+
wants.has("prs") ? request("GET", `${path}/pull-requests`).then((rows) => z.array(PullRequestSchema).parse(rows)) : null
|
|
244
|
+
]);
|
|
132
245
|
return {
|
|
133
246
|
...projectKanboCard(found, columns),
|
|
134
|
-
subCards: subCards.map((row) => projectKanboSubCard(row, columns))
|
|
247
|
+
...subCards ? { subCards: subCards.map((row) => projectKanboSubCard(row, columns)) } : {},
|
|
248
|
+
...comments ? {
|
|
249
|
+
comments: comments.slice(-commentLimit).map(projectKanboCardComment),
|
|
250
|
+
commentCount: comments.length
|
|
251
|
+
} : {},
|
|
252
|
+
...runs ? { runs: runs.map(projectKanboRun) } : {},
|
|
253
|
+
...history ? { history: history.map(projectKanboFieldChange) } : {},
|
|
254
|
+
...pullRequests ? { pullRequests: pullRequests.map(projectKanboPullRequest) } : {}
|
|
135
255
|
};
|
|
136
256
|
},
|
|
137
|
-
cardList: async ({ column, limit }) => {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
},
|
|
257
|
+
cardList: async ({ column, limit }) => (await cardPage({
|
|
258
|
+
columns: column ? [column] : void 0,
|
|
259
|
+
limit
|
|
260
|
+
})).cards,
|
|
261
|
+
cardPage,
|
|
143
262
|
cardCreate: async ({ title, description, column, parent, executionMode }) => await projectOne(await request("POST", "/issues/", {
|
|
144
263
|
workspaceId: input.workspaceId(),
|
|
145
264
|
...title !== void 0 ? { title } : {},
|
|
@@ -167,7 +286,18 @@ function createHttpTransport(input) {
|
|
|
167
286
|
})))
|
|
168
287
|
};
|
|
169
288
|
}
|
|
289
|
+
/**
|
|
290
|
+
* The card a reference names among cards already read, the way the board file
|
|
291
|
+
* resolves one: its id, its number, or its key without the padding (`WOR-1`).
|
|
292
|
+
*/
|
|
293
|
+
function findCardRow(rows, reference) {
|
|
294
|
+
const trimmed = reference.trim();
|
|
295
|
+
const byId = rows.find((row) => row.id === trimmed);
|
|
296
|
+
if (byId) return byId;
|
|
297
|
+
const key = /^(?:(.+)-)?(\d+)$/.exec(trimmed);
|
|
298
|
+
if (!key) return;
|
|
299
|
+
const [, prefix, digits] = key;
|
|
300
|
+
return rows.find((row) => row.number === Number(digits) && (prefix === void 0 || row.id.slice(0, row.id.lastIndexOf("-")).toUpperCase() === prefix.toUpperCase()));
|
|
301
|
+
}
|
|
170
302
|
//#endregion
|
|
171
|
-
export { KANBO_CAPABILITIES_MARKDOWN_RESOURCE_URI, KANBO_CAPABILITIES_RESOURCE_URI, KANBO_MCP_INSTRUCTIONS, KANBO_MCP_SERVER_NAME, KANBO_TOOLS, KANBO_TOOL_CLI_EQUIVALENTS, KANBO_TOOL_NAMES, buildKanboTools, createHttpTransport, createKanboMcpServer, registerKanboCapabilitiesResources, registerKanboTools };
|
|
172
|
-
|
|
173
|
-
//# sourceMappingURL=index.mjs.map
|
|
303
|
+
export { DEFAULT_KANBO_CARD_INCLUDES, DEFAULT_KANBO_COMMENT_LIMIT, KANBO_CAPABILITIES_MARKDOWN_RESOURCE_URI, KANBO_CAPABILITIES_RESOURCE_URI, KANBO_CARD_INCLUDES, KANBO_MCP_INSTRUCTIONS, KANBO_MCP_SERVER_NAME, KANBO_TOOLS, KANBO_TOOL_CLI_EQUIVALENTS, KANBO_TOOL_NAMES, buildKanboTools, createHttpTransport, createKanboMcpServer, matchesKanboCardQuery, pageOf, registerKanboCapabilitiesResources, registerKanboTools };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_migrations_path = require("./migrations-path-
|
|
1
|
+
const require_migrations_path = require("./migrations-path-DBCE17dT.cjs");
|
|
2
2
|
const require_sqlite_schema = require("./sqlite/schema.cjs");
|
|
3
3
|
let drizzle_orm = require("drizzle-orm");
|
|
4
4
|
let drizzle_orm_sqlite_core = require("drizzle-orm/sqlite-core");
|
|
@@ -6,6 +6,17 @@ let node_module = require("node:module");
|
|
|
6
6
|
let node_path = require("node:path");
|
|
7
7
|
let node_url = require("node:url");
|
|
8
8
|
let drizzle_orm_better_sqlite3_migrator = require("drizzle-orm/better-sqlite3/migrator");
|
|
9
|
+
//#region src/domain/fold-case.ts
|
|
10
|
+
/**
|
|
11
|
+
* Text as a search compares it: every letter lowercased by Unicode's rules, not
|
|
12
|
+
* only A–Z — `Über` and `über` are one word. What Postgres `ilike` does on
|
|
13
|
+
* either side of a board search; SQLite's own `like` folds ASCII alone, so a
|
|
14
|
+
* board file compares through this function instead (`kanbo_fold`).
|
|
15
|
+
*/
|
|
16
|
+
function foldCase(text) {
|
|
17
|
+
return text.toLowerCase();
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
9
20
|
//#region src/sqlite/transaction.ts
|
|
10
21
|
/**
|
|
11
22
|
* The better-sqlite3 handle Drizzle keeps beside the query builder. `drizzle()`
|
|
@@ -162,6 +173,82 @@ function selectRunProjection(database, scope) {
|
|
|
162
173
|
}));
|
|
163
174
|
}
|
|
164
175
|
/**
|
|
176
|
+
* One page of the cards a query picks, and how many it picks in all, in one
|
|
177
|
+
* statement: `count(*) over ()` is taken over every picked row before `limit`
|
|
178
|
+
* and `offset` cut the page out of them. Only a page past the last card — no
|
|
179
|
+
* row to carry the count — asks a second time.
|
|
180
|
+
*/
|
|
181
|
+
function selectCardPage(database, query) {
|
|
182
|
+
if (query.statusIds?.length === 0 || query.priorities?.length === 0) return {
|
|
183
|
+
cards: [],
|
|
184
|
+
total: 0
|
|
185
|
+
};
|
|
186
|
+
if (query.text !== void 0) registerFoldFunction(database);
|
|
187
|
+
const where = (0, drizzle_orm.and)(...cardQueryPredicates(query));
|
|
188
|
+
const rows = database.select({
|
|
189
|
+
...(0, drizzle_orm.getTableColumns)(require_sqlite_schema.issues),
|
|
190
|
+
pickedTotal: drizzle_orm.sql`count(*) over ()`
|
|
191
|
+
}).from(require_sqlite_schema.issues).where(where).orderBy(require_sqlite_schema.issues.order, (0, drizzle_orm.desc)(require_sqlite_schema.issues.createdAt), require_sqlite_schema.issues.id).limit(query.limit ?? Number.MAX_SAFE_INTEGER).offset(query.offset ?? 0).all();
|
|
192
|
+
if (rows.length === 0) return {
|
|
193
|
+
cards: [],
|
|
194
|
+
total: (query.offset ?? 0) > 0 ? database.select({ count: drizzle_orm.sql`count(*)` }).from(require_sqlite_schema.issues).where(where).get()?.count ?? 0 : 0
|
|
195
|
+
};
|
|
196
|
+
return {
|
|
197
|
+
cards: rows.map(({ pickedTotal: _total, ...card }) => card),
|
|
198
|
+
total: rows[0].pickedTotal
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/** The connections `kanbo_fold` is registered on already. */
|
|
202
|
+
const foldingConnections = /* @__PURE__ */ new WeakSet();
|
|
203
|
+
/**
|
|
204
|
+
* Teach the connection `kanbo_fold(text)`, the search's `foldCase`, once.
|
|
205
|
+
*
|
|
206
|
+
* SQLite's `like` and `lower()` fold A–Z and nothing else, so `über` would not
|
|
207
|
+
* find `Über` on a board file while Postgres `ilike` and the HTTP transport find
|
|
208
|
+
* it. The function is registered on the connection the statement runs on —
|
|
209
|
+
* the host may have opened it, not this package — and stays one statement.
|
|
210
|
+
*/
|
|
211
|
+
function registerFoldFunction(database) {
|
|
212
|
+
const connection = readSqliteConnection(database);
|
|
213
|
+
if (foldingConnections.has(connection)) return;
|
|
214
|
+
connection.function("kanbo_fold", { deterministic: true }, (value) => typeof value === "string" ? foldCase(value) : value);
|
|
215
|
+
foldingConnections.add(connection);
|
|
216
|
+
}
|
|
217
|
+
function cardQueryPredicates(query) {
|
|
218
|
+
const running = drizzle_orm.sql`${require_sqlite_schema.issues.id} in (select ${require_sqlite_schema.issueRuns.issueId} from ${require_sqlite_schema.issueRuns} where ${require_sqlite_schema.issueRuns.state} = 'running')`;
|
|
219
|
+
const text = foldCase(query.text ?? "");
|
|
220
|
+
return [
|
|
221
|
+
(0, drizzle_orm.eq)(require_sqlite_schema.issues.workspaceId, query.workspaceId),
|
|
222
|
+
query.statusIds === void 0 ? void 0 : (0, drizzle_orm.inArray)(require_sqlite_schema.issues.statusId, [...query.statusIds]),
|
|
223
|
+
query.waitingForPerson === void 0 ? void 0 : query.waitingForPerson ? (0, drizzle_orm.eq)(require_sqlite_schema.issues.waitingFor, "human") : (0, drizzle_orm.isNull)(require_sqlite_schema.issues.waitingFor),
|
|
224
|
+
query.parentIssueId === void 0 ? void 0 : (0, drizzle_orm.eq)(require_sqlite_schema.issues.parentIssueId, query.parentIssueId),
|
|
225
|
+
query.hasActiveRun === void 0 ? void 0 : query.hasActiveRun ? running : drizzle_orm.sql`not ${running}`,
|
|
226
|
+
query.returned === void 0 ? void 0 : query.returned ? returnedCard(running) : drizzle_orm.sql`not ${returnedCard(running)}`,
|
|
227
|
+
query.text === void 0 ? void 0 : drizzle_orm.sql`(instr(kanbo_fold(${require_sqlite_schema.issues.id}), ${text}) > 0 or instr(kanbo_fold(${require_sqlite_schema.issues.title}), ${text}) > 0 or instr(kanbo_fold(coalesce(${require_sqlite_schema.issues.description}, '')), ${text}) > 0)`,
|
|
228
|
+
query.updatedSince === void 0 ? void 0 : (0, drizzle_orm.gte)(require_sqlite_schema.issues.updatedAt, query.updatedSince),
|
|
229
|
+
...(query.labels ?? []).map((label) => drizzle_orm.sql`exists (select 1 from json_each(${require_sqlite_schema.issues.labels}) where value = ${label})`),
|
|
230
|
+
query.priorities === void 0 ? void 0 : (0, drizzle_orm.inArray)(require_sqlite_schema.issues.priority, [...query.priorities])
|
|
231
|
+
];
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* A card a person sent back that nobody picked up again (`BoardCardQuery.returned`):
|
|
235
|
+
* not waiting, not running, and its last decision a return — no approval or
|
|
236
|
+
* return written after it, `rowid` breaking a tie inside one second the way
|
|
237
|
+
* `readApproval`'s comment order does.
|
|
238
|
+
*/
|
|
239
|
+
function returnedCard(running) {
|
|
240
|
+
return drizzle_orm.sql`(${require_sqlite_schema.issues.waitingFor} is null and not ${running} and exists (
|
|
241
|
+
select 1 from ${require_sqlite_schema.issueComments} as decision
|
|
242
|
+
where decision.issue_id = ${require_sqlite_schema.issues.id} and decision.author_kind = 'system.returned'
|
|
243
|
+
and not exists (
|
|
244
|
+
select 1 from ${require_sqlite_schema.issueComments} as later
|
|
245
|
+
where later.issue_id = decision.issue_id
|
|
246
|
+
and later.author_kind in ('system.approved', 'system.returned')
|
|
247
|
+
and (later.created_at > decision.created_at or (later.created_at = decision.created_at and later.rowid > decision.rowid))
|
|
248
|
+
)
|
|
249
|
+
))`;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
165
252
|
* Bind the store's statements to one SQLite handle. `resolveDatabase` is called
|
|
166
253
|
* per statement rather than captured, so the store follows a host that swaps its
|
|
167
254
|
* database out (a server that resets its infrastructure between tests, say).
|
|
@@ -173,6 +260,7 @@ function createSqliteBoardStoreCore(resolveDatabase) {
|
|
|
173
260
|
listAllInBoardOrder: () => resolveDatabase().select().from(require_sqlite_schema.issueStatuses).orderBy(require_sqlite_schema.issueStatuses.workspaceId, require_sqlite_schema.issueStatuses.order).all(),
|
|
174
261
|
listAll: () => resolveDatabase().select().from(require_sqlite_schema.issueStatuses).all(),
|
|
175
262
|
findById: (statusId) => resolveDatabase().select().from(require_sqlite_schema.issueStatuses).where((0, drizzle_orm.eq)(require_sqlite_schema.issueStatuses.id, statusId)).get() ?? null,
|
|
263
|
+
lockById: (statusId) => resolveDatabase().select().from(require_sqlite_schema.issueStatuses).where((0, drizzle_orm.eq)(require_sqlite_schema.issueStatuses.id, statusId)).get() ?? null,
|
|
176
264
|
findInWorkspace: (workspaceId, statusId) => resolveDatabase().select().from(require_sqlite_schema.issueStatuses).where(drizzle_orm.sql`${require_sqlite_schema.issueStatuses.workspaceId} = ${workspaceId} AND ${require_sqlite_schema.issueStatuses.id} = ${statusId}`).get() ?? null,
|
|
177
265
|
countByWorkspace: (workspaceId) => {
|
|
178
266
|
return resolveDatabase().select({ count: drizzle_orm.sql`count(*)` }).from(require_sqlite_schema.issueStatuses).where((0, drizzle_orm.eq)(require_sqlite_schema.issueStatuses.workspaceId, workspaceId)).get()?.count ?? 0;
|
|
@@ -205,11 +293,17 @@ function createSqliteBoardStoreCore(resolveDatabase) {
|
|
|
205
293
|
issues: {
|
|
206
294
|
listInBoardOrder: (workspaceId) => {
|
|
207
295
|
const predicates = [workspaceId ? (0, drizzle_orm.eq)(require_sqlite_schema.issues.workspaceId, workspaceId) : void 0].filter((predicate) => predicate !== void 0);
|
|
208
|
-
return resolveDatabase().select().from(require_sqlite_schema.issues).where(predicates.length > 0 ? (0, drizzle_orm.and)(...predicates) : void 0).orderBy(require_sqlite_schema.issues.order, (0, drizzle_orm.desc)(require_sqlite_schema.issues.createdAt)).all();
|
|
296
|
+
return resolveDatabase().select().from(require_sqlite_schema.issues).where(predicates.length > 0 ? (0, drizzle_orm.and)(...predicates) : void 0).orderBy(require_sqlite_schema.issues.order, (0, drizzle_orm.desc)(require_sqlite_schema.issues.createdAt), require_sqlite_schema.issues.id).all();
|
|
209
297
|
},
|
|
210
298
|
listNewestFirst: () => resolveDatabase().select().from(require_sqlite_schema.issues).orderBy((0, drizzle_orm.desc)(require_sqlite_schema.issues.createdAt)).all(),
|
|
211
299
|
listAll: () => resolveDatabase().select().from(require_sqlite_schema.issues).all(),
|
|
212
300
|
listByWorkspace: (workspaceId) => resolveDatabase().select().from(require_sqlite_schema.issues).where((0, drizzle_orm.eq)(require_sqlite_schema.issues.workspaceId, workspaceId)).all(),
|
|
301
|
+
listPage: (query) => selectCardPage(resolveDatabase(), query),
|
|
302
|
+
countByMilestone: (workspaceId) => resolveDatabase().select({
|
|
303
|
+
milestoneId: drizzle_orm.sql`${require_sqlite_schema.issues.milestoneId}`,
|
|
304
|
+
statusId: require_sqlite_schema.issues.statusId,
|
|
305
|
+
count: drizzle_orm.sql`count(*)`
|
|
306
|
+
}).from(require_sqlite_schema.issues).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(require_sqlite_schema.issues.workspaceId, workspaceId), (0, drizzle_orm.isNotNull)(require_sqlite_schema.issues.milestoneId))).groupBy(require_sqlite_schema.issues.milestoneId, require_sqlite_schema.issues.statusId).all(),
|
|
213
307
|
listByIds: (issueIds) => resolveDatabase().select().from(require_sqlite_schema.issues).where((0, drizzle_orm.inArray)(require_sqlite_schema.issues.id, issueIds)).all(),
|
|
214
308
|
findById: (issueId) => resolveDatabase().select().from(require_sqlite_schema.issues).where((0, drizzle_orm.eq)(require_sqlite_schema.issues.id, issueId)).get() ?? null,
|
|
215
309
|
findInWorkspace: (workspaceId, issueId) => resolveDatabase().select().from(require_sqlite_schema.issues).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(require_sqlite_schema.issues.id, issueId), (0, drizzle_orm.eq)(require_sqlite_schema.issues.workspaceId, workspaceId))).get() ?? null,
|
|
@@ -248,6 +342,15 @@ function createSqliteBoardStoreCore(resolveDatabase) {
|
|
|
248
342
|
},
|
|
249
343
|
comments: {
|
|
250
344
|
listByIssue: (issueId) => resolveDatabase().select().from(require_sqlite_schema.issueComments).where((0, drizzle_orm.eq)(require_sqlite_schema.issueComments.issueId, issueId)).orderBy((0, drizzle_orm.asc)(require_sqlite_schema.issueComments.createdAt), COMMENT_INSERTION_ORDER).all(),
|
|
345
|
+
listLatestByIssues: (issueIds, authorKinds) => {
|
|
346
|
+
if (issueIds.length === 0 || authorKinds?.length === 0) return [];
|
|
347
|
+
const database = resolveDatabase();
|
|
348
|
+
const ranked = database.select({
|
|
349
|
+
id: require_sqlite_schema.issueComments.id,
|
|
350
|
+
commentRank: drizzle_orm.sql`row_number() over (partition by ${require_sqlite_schema.issueComments.issueId} order by ${require_sqlite_schema.issueComments.createdAt} desc, ${require_sqlite_schema.issueComments}.rowid desc)`.as("comment_rank")
|
|
351
|
+
}).from(require_sqlite_schema.issueComments).where((0, drizzle_orm.and)((0, drizzle_orm.inArray)(require_sqlite_schema.issueComments.issueId, [...issueIds]), authorKinds === void 0 ? void 0 : (0, drizzle_orm.inArray)(require_sqlite_schema.issueComments.authorKind, [...authorKinds]))).as("ranked");
|
|
352
|
+
return database.select((0, drizzle_orm.getTableColumns)(require_sqlite_schema.issueComments)).from(require_sqlite_schema.issueComments).innerJoin(ranked, (0, drizzle_orm.eq)(ranked.id, require_sqlite_schema.issueComments.id)).where((0, drizzle_orm.eq)(ranked.commentRank, 1)).all();
|
|
353
|
+
},
|
|
251
354
|
findById: (commentId) => resolveDatabase().select().from(require_sqlite_schema.issueComments).where((0, drizzle_orm.eq)(require_sqlite_schema.issueComments.id, commentId)).get() ?? null,
|
|
252
355
|
findByDedupeKey: (issueId, dedupeKey) => resolveDatabase().select().from(require_sqlite_schema.issueComments).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(require_sqlite_schema.issueComments.issueId, issueId), (0, drizzle_orm.eq)(require_sqlite_schema.issueComments.dedupeKey, dedupeKey))).get() ?? null,
|
|
253
356
|
listByDedupeKeyPrefix: (issueId, prefix) => resolveDatabase().select().from(require_sqlite_schema.issueComments).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(require_sqlite_schema.issueComments.issueId, issueId), drizzle_orm.sql`substr(${require_sqlite_schema.issueComments.dedupeKey}, 1, ${prefix.length}) = ${prefix}`)).orderBy((0, drizzle_orm.asc)(require_sqlite_schema.issueComments.createdAt), COMMENT_INSERTION_ORDER).all(),
|
|
@@ -354,6 +457,7 @@ function createSqliteBoardStore(options) {
|
|
|
354
457
|
listAllInBoardOrder: async () => core.statuses.listAllInBoardOrder(),
|
|
355
458
|
listAll: async () => core.statuses.listAll(),
|
|
356
459
|
findById: async (statusId) => core.statuses.findById(statusId),
|
|
460
|
+
lockById: async (statusId) => core.statuses.lockById(statusId),
|
|
357
461
|
findInWorkspace: async (workspaceId, statusId) => core.statuses.findInWorkspace(workspaceId, statusId),
|
|
358
462
|
countByWorkspace: async (workspaceId) => core.statuses.countByWorkspace(workspaceId),
|
|
359
463
|
create: async (values) => core.statuses.create(values),
|
|
@@ -375,6 +479,8 @@ function createSqliteBoardStore(options) {
|
|
|
375
479
|
listNewestFirst: async () => core.issues.listNewestFirst(),
|
|
376
480
|
listAll: async () => core.issues.listAll(),
|
|
377
481
|
listByWorkspace: async (workspaceId) => core.issues.listByWorkspace(workspaceId),
|
|
482
|
+
listPage: async (query) => core.issues.listPage(query),
|
|
483
|
+
countByMilestone: async (workspaceId) => core.issues.countByMilestone(workspaceId),
|
|
378
484
|
listByIds: async (issueIds) => core.issues.listByIds(issueIds),
|
|
379
485
|
findById: async (issueId) => core.issues.findById(issueId),
|
|
380
486
|
findInWorkspace: async (workspaceId, issueId) => core.issues.findInWorkspace(workspaceId, issueId),
|
|
@@ -391,6 +497,7 @@ function createSqliteBoardStore(options) {
|
|
|
391
497
|
},
|
|
392
498
|
comments: {
|
|
393
499
|
listByIssue: async (issueId) => core.comments.listByIssue(issueId),
|
|
500
|
+
listLatestByIssues: async (issueIds, authorKinds) => core.comments.listLatestByIssues(issueIds, authorKinds),
|
|
394
501
|
findById: async (commentId) => core.comments.findById(commentId),
|
|
395
502
|
findByDedupeKey: async (issueId, dedupeKey) => core.comments.findByDedupeKey(issueId, dedupeKey),
|
|
396
503
|
listByDedupeKeyPrefix: async (issueId, prefix) => core.comments.listByDedupeKeyPrefix(issueId, prefix),
|
|
@@ -448,25 +555,35 @@ function createSqliteBoardStore(options) {
|
|
|
448
555
|
};
|
|
449
556
|
return store;
|
|
450
557
|
}
|
|
558
|
+
/** The logger a board handle is opened with: the counter while one is on, otherwise none. */
|
|
559
|
+
function boardQueryLogger() {}
|
|
451
560
|
//#endregion
|
|
452
561
|
//#region src/sqlite/open-database.ts
|
|
453
562
|
const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
454
563
|
/** The tables a board file must already carry for this package to read it. */
|
|
455
564
|
const REQUIRED_BOARD_TABLES = ["issue_runs", "kanban_meta"];
|
|
456
|
-
/** What a caller is told when the native module a board file needs is not installed. */
|
|
457
|
-
const BETTER_SQLITE3_MISSING_MESSAGE = "Install better-sqlite3 to use a board file";
|
|
458
565
|
/**
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
|
|
463
|
-
|
|
566
|
+
* What a caller is told when the native module a board file needs does not
|
|
567
|
+
* load: which Node, system and processor it was tried on — a prebuilt module
|
|
568
|
+
* exists for most, not all — what the loader said, and how to get it back.
|
|
569
|
+
*/
|
|
570
|
+
function betterSqlite3MissingMessage(cause) {
|
|
571
|
+
const said = cause instanceof Error ? cause.message.split("\n")[0] : String(cause);
|
|
572
|
+
return `Could not load better-sqlite3, which kanbo opens board files with, on Node ${process.version} (${process.platform}-${process.arch}): ${said}. Reinstall kanbo with npm install -g kanbo-cli, which brings it along; a program that uses kanbo as a library installs better-sqlite3 itself.`;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* `better-sqlite3` is an optional dependency (ruling 5): an install where it
|
|
576
|
+
* could not be built carries no native module, and a Postgres-only or
|
|
577
|
+
* capabilities-only use — or importing `kanbo/sqlite` for a type or for
|
|
578
|
+
* `runSqliteTransaction` — should not demand one. So the require happens here,
|
|
579
|
+
* the one place a board file is actually opened, rather than at the top of
|
|
580
|
+
* this module.
|
|
464
581
|
*/
|
|
465
582
|
function loadBetterSqlite3() {
|
|
466
583
|
try {
|
|
467
584
|
return require$1("better-sqlite3");
|
|
468
585
|
} catch (error) {
|
|
469
|
-
throw new Error(
|
|
586
|
+
throw new Error(betterSqlite3MissingMessage(error), { cause: error });
|
|
470
587
|
}
|
|
471
588
|
}
|
|
472
589
|
/**
|
|
@@ -479,7 +596,7 @@ function loadBetterSqlite3() {
|
|
|
479
596
|
* this module, `kanbo capabilities` and `kanbo/mcp` included, and the
|
|
480
597
|
* friendly message above becomes unreachable. A dynamic import keeps the whole
|
|
481
598
|
* chain behind the one call that actually opens a file, which is what makes the
|
|
482
|
-
* optional
|
|
599
|
+
* optional dependency honest. `createRequire` would be the smaller change and is the
|
|
483
600
|
* wrong one: it would load drizzle's CJS copy beside the ESM one this package
|
|
484
601
|
* already holds, and it would resolve from `node_modules`, which the packaged
|
|
485
602
|
* `dist/cli.cjs` deliberately does not have beside it.
|
|
@@ -512,7 +629,10 @@ async function openBoardDatabase(path) {
|
|
|
512
629
|
connection.pragma("busy_timeout = 5000");
|
|
513
630
|
connection.pragma("synchronous = NORMAL");
|
|
514
631
|
return {
|
|
515
|
-
database: drizzle(connection, {
|
|
632
|
+
database: drizzle(connection, {
|
|
633
|
+
schema: require_sqlite_schema.boardSqliteSchema,
|
|
634
|
+
logger: void 0
|
|
635
|
+
}),
|
|
516
636
|
connection,
|
|
517
637
|
close: () => connection.close()
|
|
518
638
|
};
|
|
@@ -657,12 +777,24 @@ Object.defineProperty(exports, "assertBoardSchema", {
|
|
|
657
777
|
return assertBoardSchema;
|
|
658
778
|
}
|
|
659
779
|
});
|
|
780
|
+
Object.defineProperty(exports, "boardQueryLogger", {
|
|
781
|
+
enumerable: true,
|
|
782
|
+
get: function() {
|
|
783
|
+
return boardQueryLogger;
|
|
784
|
+
}
|
|
785
|
+
});
|
|
660
786
|
Object.defineProperty(exports, "createSqliteBoardStore", {
|
|
661
787
|
enumerable: true,
|
|
662
788
|
get: function() {
|
|
663
789
|
return createSqliteBoardStore;
|
|
664
790
|
}
|
|
665
791
|
});
|
|
792
|
+
Object.defineProperty(exports, "foldCase", {
|
|
793
|
+
enumerable: true,
|
|
794
|
+
get: function() {
|
|
795
|
+
return foldCase;
|
|
796
|
+
}
|
|
797
|
+
});
|
|
666
798
|
Object.defineProperty(exports, "getBoardSqliteMigrationsPath", {
|
|
667
799
|
enumerable: true,
|
|
668
800
|
get: function() {
|
|
@@ -705,5 +837,3 @@ Object.defineProperty(exports, "runSqliteTransaction", {
|
|
|
705
837
|
return runSqliteTransaction;
|
|
706
838
|
}
|
|
707
839
|
});
|
|
708
|
-
|
|
709
|
-
//# sourceMappingURL=migrate-DnMjTCvu.cjs.map
|