@starlove/orbit 1.4.0 → 2.0.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/Dockerfile +10 -8
- package/README.md +146 -164
- package/dist/full/csp.txt +1 -0
- package/dist/full/fonts/JetBrains_Mono/JetBrainsMono-subset.woff2 +0 -0
- package/dist/full/fonts/JetBrains_Mono/README.txt +5 -0
- package/dist/full/fonts/Mona_Sans/MonaSans-subset.woff2 +0 -0
- package/{public/fonts/Open_Sans → dist/full/fonts/Mona_Sans}/OFL.txt +93 -93
- package/dist/full/fonts/Mona_Sans/README.txt +10 -0
- package/{public/fonts/JetBrains_Mono → dist/full/fonts/Newsreader}/OFL.txt +93 -93
- package/dist/full/fonts/Newsreader/README.txt +8 -0
- package/dist/full/fonts/Newsreader/newsreader.woff2 +0 -0
- package/dist/full/index.html +36 -653
- package/dist/full/js/app.BmuponPJ.js +32 -0
- package/dist/full/js/app.BmuponPJ.js.map +1 -0
- package/dist/full/js/chunk.1fJGQGbW.js +2 -0
- package/dist/full/js/chunk.1fJGQGbW.js.map +1 -0
- package/dist/full/js/chunk.5D7JO2zp.js +2 -0
- package/dist/full/js/chunk.5D7JO2zp.js.map +1 -0
- package/dist/full/js/chunk.BrNn_D79.js +2 -0
- package/dist/full/js/chunk.BrNn_D79.js.map +1 -0
- package/dist/full/js/chunk.CokJXIqM.js +2 -0
- package/dist/full/js/chunk.CokJXIqM.js.map +1 -0
- package/dist/full/js/chunk.D_Haqy6R.js +2 -0
- package/dist/full/js/chunk.D_Haqy6R.js.map +1 -0
- package/dist/full/js/chunk.RO0jsH6I.js +15 -0
- package/dist/full/js/chunk.RO0jsH6I.js.map +1 -0
- package/dist/full/js/chunk.dqWOEtBM.js +2 -0
- package/dist/full/js/chunk.dqWOEtBM.js.map +1 -0
- package/dist/full/js/warm.f260bbbc.js +15 -0
- package/dist/full/orbits-favicon.png +0 -0
- package/dist/full/orbits-logo-bg-wide.png +0 -0
- package/dist/full/orbits-logo-bg.png +0 -0
- package/dist/full/orbits-logo.png +0 -0
- package/dist/full/styles.2b154ffe.css +1 -0
- package/dist/full/vendor/starlove-ui/theme-init.js +66 -0
- package/dist/full/version.json +2 -2
- package/docs/AGENT_GUIDE.md +114 -0
- package/docs/UPDATING.md +249 -64
- package/lib/adapters/indexeddb/blobs.d.ts +3 -0
- package/lib/adapters/indexeddb/blobs.js +54 -0
- package/lib/adapters/indexeddb/database.d.ts +60 -0
- package/lib/adapters/indexeddb/database.js +126 -0
- package/lib/adapters/indexeddb/index.d.ts +5 -0
- package/lib/adapters/indexeddb/index.js +3 -0
- package/lib/adapters/indexeddb/locks.d.ts +14 -0
- package/lib/adapters/indexeddb/locks.js +40 -0
- package/lib/adapters/indexeddb/store.d.ts +40 -0
- package/lib/adapters/indexeddb/store.js +424 -0
- package/lib/adapters/indexeddb/tables.d.ts +43 -0
- package/lib/adapters/indexeddb/tables.js +127 -0
- package/lib/adapters/memory/blobs.d.ts +3 -0
- package/lib/adapters/memory/blobs.js +44 -0
- package/lib/adapters/memory/board.d.ts +41 -0
- package/lib/adapters/memory/board.js +521 -0
- package/lib/adapters/memory/index.d.ts +2 -0
- package/lib/adapters/memory/index.js +2 -0
- package/lib/adapters/memory/search.d.ts +6 -0
- package/lib/adapters/memory/search.js +30 -0
- package/lib/adapters/memory/store.d.ts +34 -0
- package/lib/adapters/memory/store.js +266 -0
- package/lib/adapters/sqlite/backups.d.ts +53 -0
- package/lib/adapters/sqlite/backups.js +232 -0
- package/lib/adapters/sqlite/blobs.d.ts +14 -0
- package/lib/adapters/sqlite/blobs.js +109 -0
- package/lib/adapters/sqlite/board.d.ts +77 -0
- package/lib/adapters/sqlite/board.js +328 -0
- package/lib/adapters/sqlite/database.d.ts +67 -0
- package/lib/adapters/sqlite/database.js +285 -0
- package/lib/adapters/sqlite/index.d.ts +3 -0
- package/lib/adapters/sqlite/index.js +2 -0
- package/lib/adapters/sqlite/lock.d.ts +11 -0
- package/lib/adapters/sqlite/lock.js +34 -0
- package/lib/adapters/sqlite/records.d.ts +54 -0
- package/lib/adapters/sqlite/records.js +546 -0
- package/lib/adapters/sqlite/schema.d.ts +54 -0
- package/lib/adapters/sqlite/schema.js +276 -0
- package/lib/adapters/sqlite/store.d.ts +39 -0
- package/lib/adapters/sqlite/store.js +385 -0
- package/lib/browser/in-page.d.ts +31 -0
- package/lib/browser/in-page.js +46 -0
- package/lib/browser/old-preview.d.ts +13 -0
- package/lib/browser/old-preview.js +80 -0
- package/lib/browser/preview-board.d.ts +8 -0
- package/lib/browser/preview-board.js +69 -0
- package/lib/cli/main.d.ts +7 -0
- package/lib/cli/main.js +343 -0
- package/lib/cli/orbit.d.ts +8 -0
- package/lib/cli/orbit.js +25 -0
- package/lib/client/http.d.ts +38 -0
- package/lib/client/http.js +334 -0
- package/lib/client/local.d.ts +26 -0
- package/lib/client/local.js +190 -0
- package/lib/client/object-url.d.ts +8 -0
- package/lib/client/object-url.js +21 -0
- package/lib/client/types.d.ts +268 -0
- package/lib/client/types.js +8 -0
- package/lib/commands/agent-tools.d.ts +31 -0
- package/lib/commands/agent-tools.js +100 -0
- package/lib/commands/index.d.ts +56 -0
- package/lib/commands/index.js +298 -0
- package/lib/commands/parse.d.ts +73 -0
- package/lib/commands/parse.js +231 -0
- package/lib/commands/present.d.ts +134 -0
- package/lib/commands/present.js +198 -0
- package/lib/commands/session.d.ts +85 -0
- package/lib/commands/session.js +138 -0
- package/lib/commands/verbs.d.ts +35 -0
- package/lib/commands/verbs.js +842 -0
- package/lib/core/actors.d.ts +18 -0
- package/lib/core/actors.js +45 -0
- package/lib/core/agent.d.ts +128 -0
- package/lib/core/agent.js +323 -0
- package/lib/core/boards.d.ts +113 -0
- package/lib/core/boards.js +288 -0
- package/lib/core/bootstrap.d.ts +47 -0
- package/lib/core/bootstrap.js +100 -0
- package/lib/core/bytes.d.ts +10 -0
- package/lib/core/bytes.js +52 -0
- package/lib/core/card-paths.d.ts +25 -0
- package/lib/core/card-paths.js +44 -0
- package/lib/core/context.d.ts +60 -0
- package/lib/core/context.js +42 -0
- package/lib/core/cross-board.d.ts +81 -0
- package/lib/core/cross-board.js +228 -0
- package/lib/core/describe.d.ts +21 -0
- package/lib/core/describe.js +81 -0
- package/lib/core/errors.d.ts +159 -0
- package/lib/core/errors.js +160 -0
- package/lib/core/files.d.ts +79 -0
- package/lib/core/files.js +262 -0
- package/lib/core/history.d.ts +20 -0
- package/lib/core/history.js +53 -0
- package/lib/core/image-types.d.ts +16 -0
- package/lib/core/image-types.js +36 -0
- package/lib/core/model.d.ts +263 -0
- package/lib/core/model.js +34 -0
- package/lib/core/nebula-link.d.ts +14 -0
- package/lib/core/nebula-link.js +39 -0
- package/lib/core/relations.d.ts +58 -0
- package/lib/core/relations.js +112 -0
- package/lib/core/search.d.ts +38 -0
- package/lib/core/search.js +92 -0
- package/lib/core/sessions.d.ts +44 -0
- package/lib/core/sessions.js +158 -0
- package/lib/core/snapshot-schema.d.ts +295 -0
- package/lib/core/snapshot-schema.js +82 -0
- package/lib/core/snapshots.d.ts +75 -0
- package/lib/core/snapshots.js +409 -0
- package/lib/core/states.d.ts +25 -0
- package/lib/core/states.js +126 -0
- package/lib/core/text.d.ts +40 -0
- package/lib/core/text.js +113 -0
- package/lib/core/tickets.d.ts +69 -0
- package/lib/core/tickets.js +311 -0
- package/lib/core/trello.d.ts +17 -0
- package/lib/core/trello.js +269 -0
- package/lib/core/verdicts.d.ts +28 -0
- package/lib/core/verdicts.js +92 -0
- package/lib/core/views.d.ts +95 -0
- package/lib/core/views.js +186 -0
- package/lib/http/app.d.ts +46 -0
- package/lib/http/app.js +491 -0
- package/lib/mcp-server.d.ts +10 -0
- package/lib/mcp-server.js +120 -0
- package/lib/node/assets.d.ts +7 -0
- package/lib/node/assets.js +68 -0
- package/lib/node/data-folder.d.ts +21 -0
- package/lib/node/data-folder.js +34 -0
- package/lib/node/env.d.ts +38 -0
- package/lib/node/env.js +49 -0
- package/lib/node/host-check.d.ts +24 -0
- package/lib/node/host-check.js +53 -0
- package/lib/node/host-services.d.ts +9 -0
- package/lib/node/host-services.js +150 -0
- package/lib/node/local.d.ts +22 -0
- package/lib/node/local.js +61 -0
- package/lib/node/migrate.d.ts +53 -0
- package/lib/node/migrate.js +635 -0
- package/lib/node/package.d.ts +7 -0
- package/lib/node/package.js +14 -0
- package/lib/node/repo-board.d.ts +49 -0
- package/lib/node/repo-board.js +245 -0
- package/lib/node/serve.d.ts +8 -0
- package/lib/node/serve.js +106 -0
- package/lib/orbit.d.ts +47 -0
- package/lib/orbit.js +44 -0
- package/lib/quiet-sqlite-warning.d.ts +1 -0
- package/lib/quiet-sqlite-warning.js +18 -0
- package/lib/store/blob-path.d.ts +6 -0
- package/lib/store/blob-path.js +12 -0
- package/lib/store/conformance.d.ts +52 -0
- package/lib/store/conformance.js +1449 -0
- package/lib/store/search-words.d.ts +10 -0
- package/lib/store/search-words.js +19 -0
- package/lib/store/types.d.ts +452 -0
- package/lib/store/types.js +34 -0
- package/llms.txt +63 -0
- package/package.json +109 -25
- package/src/cli/orbit.js +11 -658
- package/src/mcp-server.js +16 -876
- package/SKILL-ORBIT.md +0 -151
- package/build.mjs +0 -210
- package/dist/full/js/app.62905fcb.js +0 -952
- package/dist/full/js/app.62905fcb.js.map +0 -7
- package/dist/full/styles.4e60d824.css +0 -1
- package/docs/AGENT_PROTOCOL.md +0 -424
- package/docs/MCP_SETUP.md +0 -54
- package/docs/ORBIT_DISPATCH.md +0 -280
- package/docs/SS-Orbit-Dark.png +0 -0
- package/docs/SS-Orbit-Light.png +0 -0
- package/docs/mcp-client.example.json +0 -13
- package/docs/setup/orbit-init-wizard.md +0 -174
- package/public/apple-touch-icon.png +0 -0
- package/public/cursors/live_arrow.cur +0 -0
- package/public/cursors/live_link.cur +0 -0
- package/public/cursors/live_move.cur +0 -0
- package/public/cursors/live_select.cur +0 -0
- package/public/cursors/live_unavail.cur +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Bold.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-BoldItalic.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Italic.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Light.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-LightItalic.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Medium.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-MediumItalic.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Regular.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-SemiBold.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-SemiBoldItalic.ttf +0 -0
- package/public/fonts/Chakra_Petch/OFL.txt +0 -93
- package/public/fonts/Inter/Inter-Italic-VariableFont_opsz,wght.ttf +0 -0
- package/public/fonts/Inter/Inter-VariableFont_opsz,wght.ttf +0 -0
- package/public/fonts/Inter/OFL.txt +0 -93
- package/public/fonts/Inter/README.txt +0 -118
- package/public/fonts/JetBrains_Mono/JetBrainsMono-Italic-VariableFont_wght.ttf +0 -0
- package/public/fonts/JetBrains_Mono/JetBrainsMono-VariableFont_wght.ttf +0 -0
- package/public/fonts/JetBrains_Mono/README.txt +0 -79
- package/public/fonts/OpenRing-Regular.ttf +0 -0
- package/public/fonts/Open_Sans/OpenSans-Italic-VariableFont_wdth,wght.ttf +0 -0
- package/public/fonts/Open_Sans/OpenSans-VariableFont_wdth,wght.ttf +0 -0
- package/public/fonts/Open_Sans/README.txt +0 -100
- package/public/fonts/Orbitron/OFL.txt +0 -93
- package/public/fonts/Orbitron/Orbitron-VariableFont_wght.ttf +0 -0
- package/public/fonts/Orbitron/README.txt +0 -68
- package/public/fonts/Rajdhani/OFL.txt +0 -93
- package/public/fonts/Rajdhani/Rajdhani-Bold.ttf +0 -0
- package/public/fonts/Rajdhani/Rajdhani-Light.ttf +0 -0
- package/public/fonts/Rajdhani/Rajdhani-Medium.ttf +0 -0
- package/public/fonts/Rajdhani/Rajdhani-Regular.ttf +0 -0
- package/public/fonts/Rajdhani/Rajdhani-SemiBold.ttf +0 -0
- package/public/fonts/runescape-uf.ttf +0 -0
- package/public/index.html +0 -769
- package/public/js/actor-labels.js +0 -65
- package/public/js/api.js +0 -39
- package/public/js/app.js +0 -45
- package/public/js/artifact-drawer.js +0 -79
- package/public/js/board-export.js +0 -161
- package/public/js/board-menu.js +0 -374
- package/public/js/card-actions.js +0 -545
- package/public/js/config.js +0 -20
- package/public/js/create-card.js +0 -190
- package/public/js/dom.js +0 -19
- package/public/js/drawer.js +0 -273
- package/public/js/font-preference.js +0 -229
- package/public/js/format.js +0 -433
- package/public/js/help-menu.js +0 -31
- package/public/js/icons.js +0 -16
- package/public/js/kanban.js +0 -694
- package/public/js/local-backend.js +0 -1015
- package/public/js/main.js +0 -161
- package/public/js/modal.js +0 -33
- package/public/js/motion-preference.js +0 -64
- package/public/js/panel.js +0 -58
- package/public/js/router.js +0 -134
- package/public/js/search.js +0 -56
- package/public/js/settings-tabs.js +0 -7
- package/public/js/settings.js +0 -1634
- package/public/js/sse.js +0 -159
- package/public/js/starscape.js +0 -332
- package/public/js/state.js +0 -129
- package/public/js/text-editing.js +0 -128
- package/public/js/theme-preference.js +0 -95
- package/public/js/ticket-detail.js +0 -1516
- package/public/js/ticket-typeahead.js +0 -49
- package/public/js/toast.js +0 -89
- package/public/js/topbar-notes.js +0 -155
- package/public/js/transport.js +0 -22
- package/public/js/typeahead.js +0 -206
- package/public/js/unread.js +0 -121
- package/public/js/update-check.js +0 -58
- package/public/js/url-routes.js +0 -94
- package/public/orbits-favicon.png +0 -0
- package/public/orbits-logo-bg-wide.png +0 -0
- package/public/orbits-logo-bg.png +0 -0
- package/public/orbits-logo.png +0 -0
- package/public/styles.css +0 -7709
- package/src/core/agent.js +0 -387
- package/src/core/agents-md.js +0 -109
- package/src/core/artifacts.js +0 -149
- package/src/core/attachments.js +0 -238
- package/src/core/auth.js +0 -85
- package/src/core/backups.js +0 -142
- package/src/core/board.js +0 -148
- package/src/core/boards.js +0 -409
- package/src/core/bootstrap.js +0 -170
- package/src/core/css-bundle.js +0 -30
- package/src/core/db.js +0 -315
- package/src/core/delete-board.js +0 -163
- package/src/core/dispatch.js +0 -1007
- package/src/core/events.js +0 -314
- package/src/core/http.js +0 -115
- package/src/core/migrate.js +0 -198
- package/src/core/paths.js +0 -55
- package/src/core/provision-repo-board.js +0 -119
- package/src/core/queries.js +0 -246
- package/src/core/registry.js +0 -211
- package/src/core/relations.js +0 -163
- package/src/core/review-verdicts.js +0 -138
- package/src/core/router.js +0 -556
- package/src/core/search.js +0 -183
- package/src/core/seed.js +0 -234
- package/src/core/snapshots.js +0 -692
- package/src/core/states.js +0 -167
- package/src/core/system-picker.js +0 -109
- package/src/core/tickets.js +0 -400
- package/src/core/util.js +0 -106
- package/src/core/version-check.js +0 -154
- package/src/mcp/orbit-client.js +0 -287
- package/src/server.js +0 -115
package/src/core/snapshots.js
DELETED
|
@@ -1,692 +0,0 @@
|
|
|
1
|
-
import { resetBoard, tx, getRegistry } from "./db.js";
|
|
2
|
-
import { backupBoardDatabase } from "./backups.js";
|
|
3
|
-
import { boardById, labelsForTicket } from "./queries.js";
|
|
4
|
-
import { requireBoardAccess } from "./auth.js";
|
|
5
|
-
import { recordEvent } from "./events.js";
|
|
6
|
-
import { computeNewBoardDbPath, ensureBoardDbFileAndSchema, syncRegistryFromBoardDb } from "./registry.js";
|
|
7
|
-
import {
|
|
8
|
-
httpError,
|
|
9
|
-
id,
|
|
10
|
-
normalizePriority,
|
|
11
|
-
normalizeProjectEntryType,
|
|
12
|
-
normalizeTicketType,
|
|
13
|
-
now,
|
|
14
|
-
normalizePath,
|
|
15
|
-
requiredString,
|
|
16
|
-
slugify
|
|
17
|
-
} from "./util.js";
|
|
18
|
-
import { reindexAllTickets } from "./tickets.js";
|
|
19
|
-
import {
|
|
20
|
-
attachmentRowsForBoard,
|
|
21
|
-
importAttachmentRows,
|
|
22
|
-
serializeAttachmentForExport
|
|
23
|
-
} from "./attachments.js";
|
|
24
|
-
|
|
25
|
-
export function exportBoard(boardId, ctx, options = {}) {
|
|
26
|
-
const { db, board, actor } = ctx;
|
|
27
|
-
if (boardId !== board.id) throw httpError(404, "board_not_found");
|
|
28
|
-
const innerBoard = boardById(db, boardId);
|
|
29
|
-
if (!innerBoard) throw httpError(404, "board_not_found");
|
|
30
|
-
requireBoardAccess(actor, innerBoard);
|
|
31
|
-
|
|
32
|
-
const states = db.prepare("SELECT * FROM states WHERE board_id = ? ORDER BY position").all(boardId);
|
|
33
|
-
const tickets = db
|
|
34
|
-
.prepare("SELECT * FROM tickets WHERE board_id = ? ORDER BY number ASC")
|
|
35
|
-
.all(boardId)
|
|
36
|
-
.map((ticket) => ({ ...ticket, labels: labelsForTicket(db, ticket.id) }));
|
|
37
|
-
const ticketIds = tickets.map((ticket) => ticket.id);
|
|
38
|
-
const comments = ticketIds.length
|
|
39
|
-
? db
|
|
40
|
-
.prepare("SELECT * FROM comments WHERE ticket_id IN (SELECT id FROM tickets WHERE board_id = ?) ORDER BY created_at ASC")
|
|
41
|
-
.all(boardId)
|
|
42
|
-
: [];
|
|
43
|
-
const reviewVerdicts = ticketIds.length
|
|
44
|
-
? db
|
|
45
|
-
.prepare("SELECT * FROM review_verdicts WHERE ticket_id IN (SELECT id FROM tickets WHERE board_id = ?) ORDER BY created_at ASC")
|
|
46
|
-
.all(boardId)
|
|
47
|
-
: [];
|
|
48
|
-
const relations = ticketIds.length
|
|
49
|
-
? db
|
|
50
|
-
.prepare(
|
|
51
|
-
`SELECT *
|
|
52
|
-
FROM relations
|
|
53
|
-
WHERE source_ticket_id IN (SELECT id FROM tickets WHERE board_id = ?)
|
|
54
|
-
OR target_ticket_id IN (SELECT id FROM tickets WHERE board_id = ?)
|
|
55
|
-
ORDER BY created_at ASC`
|
|
56
|
-
)
|
|
57
|
-
.all(boardId, boardId)
|
|
58
|
-
: [];
|
|
59
|
-
const entries = db
|
|
60
|
-
.prepare("SELECT * FROM board_entries WHERE board_id = ? ORDER BY created_at ASC")
|
|
61
|
-
.all(boardId);
|
|
62
|
-
const labels = db.prepare("SELECT * FROM labels WHERE board_id = ? ORDER BY name ASC").all(boardId);
|
|
63
|
-
const events = db
|
|
64
|
-
.prepare(
|
|
65
|
-
`SELECT *
|
|
66
|
-
FROM events
|
|
67
|
-
WHERE ticket_id IS NULL
|
|
68
|
-
OR ticket_id IN (SELECT id FROM tickets WHERE board_id = ?)
|
|
69
|
-
ORDER BY created_at ASC`
|
|
70
|
-
)
|
|
71
|
-
.all(boardId);
|
|
72
|
-
const attachments = attachmentRowsForBoard(db, boardId).map((row) =>
|
|
73
|
-
serializeAttachmentForExport(board, row, Boolean(options.includeAttachments))
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
return {
|
|
77
|
-
format: "orbit-board-export",
|
|
78
|
-
version: 9,
|
|
79
|
-
exported_at: now(),
|
|
80
|
-
include_attachments: Boolean(options.includeAttachments),
|
|
81
|
-
board: { ...innerBoard },
|
|
82
|
-
states,
|
|
83
|
-
labels,
|
|
84
|
-
tickets,
|
|
85
|
-
comments,
|
|
86
|
-
review_verdicts: reviewVerdicts,
|
|
87
|
-
relations,
|
|
88
|
-
attachments,
|
|
89
|
-
board_entries: entries,
|
|
90
|
-
events
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function importBoardSnapshot(body, ctx) {
|
|
95
|
-
const { db, actor, board } = ctx;
|
|
96
|
-
const snapshot = normalizeImportSnapshot(body?.snapshot || body, ctx);
|
|
97
|
-
const format = snapshot?.format;
|
|
98
|
-
if (!snapshot || format !== "orbit-board-export") throw httpError(400, "invalid_import_snapshot");
|
|
99
|
-
if (!snapshot.board) throw httpError(400, "snapshot_missing_board");
|
|
100
|
-
|
|
101
|
-
const replaceExisting = Boolean(body?.replace_existing);
|
|
102
|
-
const existingCount = db.prepare("SELECT COUNT(*) AS count FROM boards").get().count;
|
|
103
|
-
if (existingCount > 0 && !replaceExisting) {
|
|
104
|
-
throw httpError(409, "import_requires_empty_board_or_replace_existing");
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Snapshot the existing DB file before a destructive replace, outside the
|
|
108
|
-
// transaction (it copies the file on disk, not rows).
|
|
109
|
-
if (existingCount > 0 && replaceExisting) {
|
|
110
|
-
backupBoardDatabase(ctx.board, db, "pre-import-replace");
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const targetBoardId = board.id;
|
|
114
|
-
const importedBoard = {
|
|
115
|
-
...snapshot.board,
|
|
116
|
-
id: targetBoardId
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const result = tx(db, () => {
|
|
120
|
-
if (existingCount > 0 && replaceExisting) resetBoard(db);
|
|
121
|
-
|
|
122
|
-
db.prepare(
|
|
123
|
-
`INSERT INTO boards
|
|
124
|
-
(id, slug, name, repo_url, system_path, default_branch,
|
|
125
|
-
project_notes, agent_instructions, ai_enabled, created_at, updated_at)
|
|
126
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
127
|
-
).run(
|
|
128
|
-
importedBoard.id,
|
|
129
|
-
importedBoard.slug,
|
|
130
|
-
importedBoard.name,
|
|
131
|
-
importedBoard.repo_url || "",
|
|
132
|
-
importedBoard.system_path || "",
|
|
133
|
-
importedBoard.default_branch || "main",
|
|
134
|
-
importedBoard.project_notes || "",
|
|
135
|
-
importedBoard.agent_instructions || "",
|
|
136
|
-
importedBoard.ai_enabled === 0 ? 0 : 1,
|
|
137
|
-
importedBoard.created_at || now(),
|
|
138
|
-
importedBoard.updated_at || now()
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
for (const state of snapshot.states || []) {
|
|
142
|
-
db.prepare(
|
|
143
|
-
`INSERT INTO states
|
|
144
|
-
(id, board_id, name, position, is_default, role, created_at)
|
|
145
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
146
|
-
).run(
|
|
147
|
-
state.id,
|
|
148
|
-
targetBoardId,
|
|
149
|
-
state.name,
|
|
150
|
-
state.position,
|
|
151
|
-
state.is_default || 0,
|
|
152
|
-
state.role || null,
|
|
153
|
-
state.created_at || now()
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
for (const label of snapshot.labels || []) {
|
|
158
|
-
db.prepare("INSERT INTO labels (id, board_id, name, color, created_at) VALUES (?, ?, ?, ?, ?)").run(
|
|
159
|
-
label.id,
|
|
160
|
-
targetBoardId,
|
|
161
|
-
label.name,
|
|
162
|
-
label.color || "#64748b",
|
|
163
|
-
label.created_at || now()
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
for (const ticket of snapshot.tickets || []) {
|
|
168
|
-
db.prepare(
|
|
169
|
-
`INSERT INTO tickets
|
|
170
|
-
(id, board_id, number, title, description, type, parent_ticket_id, ai_plan,
|
|
171
|
-
plan_artifact_path, implementation_summary, implementation_artifact_path,
|
|
172
|
-
implementation_updates, state_id, priority,
|
|
173
|
-
created_by, created_at, updated_at, archived_at)
|
|
174
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
175
|
-
).run(
|
|
176
|
-
ticket.id,
|
|
177
|
-
targetBoardId,
|
|
178
|
-
ticket.number,
|
|
179
|
-
ticket.title,
|
|
180
|
-
ticket.description || "",
|
|
181
|
-
normalizeTicketType(ticket.type || "task"),
|
|
182
|
-
ticket.parent_ticket_id || null,
|
|
183
|
-
ticket.ai_plan || "",
|
|
184
|
-
ticket.plan_artifact_path || "",
|
|
185
|
-
ticket.implementation_summary || "",
|
|
186
|
-
ticket.implementation_artifact_path || "",
|
|
187
|
-
ticket.implementation_updates || "",
|
|
188
|
-
ticket.state_id,
|
|
189
|
-
normalizePriority(ticket.priority),
|
|
190
|
-
ticket.created_by || "import",
|
|
191
|
-
ticket.created_at || now(),
|
|
192
|
-
ticket.updated_at || now(),
|
|
193
|
-
ticket.archived_at || null
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
for (const label of ticket.labels || []) {
|
|
197
|
-
db.prepare("INSERT OR IGNORE INTO ticket_labels (ticket_id, label_id) VALUES (?, ?)").run(ticket.id, label.id);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
for (const comment of snapshot.comments || []) {
|
|
202
|
-
db.prepare(
|
|
203
|
-
"INSERT INTO comments (id, ticket_id, author, kind, body, omitted_for_ai, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)"
|
|
204
|
-
).run(
|
|
205
|
-
comment.id,
|
|
206
|
-
comment.ticket_id,
|
|
207
|
-
comment.author || "import",
|
|
208
|
-
comment.kind || "comment",
|
|
209
|
-
comment.body || "",
|
|
210
|
-
comment.omitted_for_ai ? 1 : 0,
|
|
211
|
-
comment.created_at || now()
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
for (const review of snapshot.review_verdicts || []) {
|
|
216
|
-
db.prepare(
|
|
217
|
-
`INSERT INTO review_verdicts
|
|
218
|
-
(id, ticket_id, verdict, blocking_findings_json, optional_findings_json,
|
|
219
|
-
evidence_commands_json, reviewer_profile, reviewer_session_id,
|
|
220
|
-
reviewed_commit_sha, dispatch_run_id, supersedes_prior_review_id,
|
|
221
|
-
created_by, created_at)
|
|
222
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
223
|
-
).run(
|
|
224
|
-
review.id,
|
|
225
|
-
review.ticket_id,
|
|
226
|
-
review.verdict,
|
|
227
|
-
review.blocking_findings_json || "[]",
|
|
228
|
-
review.optional_findings_json || "[]",
|
|
229
|
-
review.evidence_commands_json || "[]",
|
|
230
|
-
review.reviewer_profile || "",
|
|
231
|
-
review.reviewer_session_id || "",
|
|
232
|
-
review.reviewed_commit_sha || "",
|
|
233
|
-
review.dispatch_run_id || "",
|
|
234
|
-
review.supersedes_prior_review_id || null,
|
|
235
|
-
review.created_by || "import",
|
|
236
|
-
review.created_at || now()
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
for (const relation of snapshot.relations || []) {
|
|
241
|
-
db.prepare(
|
|
242
|
-
"INSERT OR IGNORE INTO relations (id, source_ticket_id, target_ticket_id, type, created_at) VALUES (?, ?, ?, ?, ?)"
|
|
243
|
-
).run(
|
|
244
|
-
relation.id,
|
|
245
|
-
relation.source_ticket_id,
|
|
246
|
-
relation.target_ticket_id,
|
|
247
|
-
relation.type || "relates_to",
|
|
248
|
-
relation.created_at || now()
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
importAttachmentRows(snapshot, ctx, targetBoardId);
|
|
253
|
-
|
|
254
|
-
for (const entry of snapshot.board_entries || []) {
|
|
255
|
-
db.prepare(
|
|
256
|
-
`INSERT INTO board_entries
|
|
257
|
-
(id, board_id, type, title, body, ticket_id, created_by, created_at, updated_at, struck_at)
|
|
258
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
259
|
-
).run(
|
|
260
|
-
entry.id,
|
|
261
|
-
targetBoardId,
|
|
262
|
-
normalizeProjectEntryType(entry.type),
|
|
263
|
-
requiredString(entry.title, "entry_title"),
|
|
264
|
-
entry.body || "",
|
|
265
|
-
entry.ticket_id || null,
|
|
266
|
-
entry.created_by || "import",
|
|
267
|
-
entry.created_at || now(),
|
|
268
|
-
entry.updated_at || now(),
|
|
269
|
-
entry.struck_at || null
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
for (const event of snapshot.events || []) {
|
|
274
|
-
db.prepare(
|
|
275
|
-
"INSERT INTO events (id, ticket_id, actor, type, body_json, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
276
|
-
).run(
|
|
277
|
-
event.id,
|
|
278
|
-
event.ticket_id || null,
|
|
279
|
-
event.actor || "import",
|
|
280
|
-
event.type || "imported_event",
|
|
281
|
-
event.body_json || "{}",
|
|
282
|
-
event.created_at || now()
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Reindex inside the transaction: a committed import must never leave the
|
|
287
|
-
// FTS table empty/stale (previously this ran after COMMIT and a throw here
|
|
288
|
-
// left search broken on otherwise-imported data).
|
|
289
|
-
reindexAllTickets(db);
|
|
290
|
-
recordEvent(db, targetBoardId, "board_imported", null, actor.name, {
|
|
291
|
-
board_id: targetBoardId,
|
|
292
|
-
replace_existing: replaceExisting
|
|
293
|
-
});
|
|
294
|
-
return {
|
|
295
|
-
ok: true,
|
|
296
|
-
imported_board_id: targetBoardId,
|
|
297
|
-
imported_board_slug: importedBoard.slug,
|
|
298
|
-
replace_existing: replaceExisting,
|
|
299
|
-
source_format: snapshot.source_format || "orbit-board-export",
|
|
300
|
-
imported_counts: snapshot.imported_counts || {
|
|
301
|
-
states: (snapshot.states || []).length,
|
|
302
|
-
tickets: (snapshot.tickets || []).length,
|
|
303
|
-
comments: (snapshot.comments || []).length,
|
|
304
|
-
labels: (snapshot.labels || []).length
|
|
305
|
-
}
|
|
306
|
-
};
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
// Registry sync writes the separate registry DB (a different connection), so
|
|
310
|
-
// it runs after the board-DB transaction commits, not inside it.
|
|
311
|
-
syncRegistryFromBoardDb(board, db);
|
|
312
|
-
return result;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
export function importBoardSnapshotAsNewBoard(body, ctx) {
|
|
316
|
-
const rawSnapshot = body?.snapshot || body;
|
|
317
|
-
const actor = ctx.actor;
|
|
318
|
-
const sourceBoard = ctx.board || {};
|
|
319
|
-
const repoPath = normalizePath(body?.repo_path || body?.system_path || sourceBoard.repo_path || sourceBoard.system_path || process.cwd());
|
|
320
|
-
const sourceFormatSnapshot = normalizeImportSnapshot(rawSnapshot, {
|
|
321
|
-
...ctx,
|
|
322
|
-
board: {
|
|
323
|
-
id: id(),
|
|
324
|
-
slug: "",
|
|
325
|
-
name: "",
|
|
326
|
-
repo_path: repoPath,
|
|
327
|
-
system_path: repoPath,
|
|
328
|
-
repo_url: sourceBoard.repo_url || "",
|
|
329
|
-
default_branch: sourceBoard.default_branch || "main"
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
const format = sourceFormatSnapshot?.format;
|
|
333
|
-
if (!sourceFormatSnapshot || format !== "orbit-board-export") throw httpError(400, "invalid_import_snapshot");
|
|
334
|
-
if (!sourceFormatSnapshot.board) throw httpError(400, "snapshot_missing_board");
|
|
335
|
-
|
|
336
|
-
const boardName = cleanText(sourceFormatSnapshot.board.name) || cleanText(rawSnapshot?.name) || "Imported Board";
|
|
337
|
-
const boardSlug = uniqueImportedBoardSlug(body?.slug || sourceFormatSnapshot.board.slug || rawSnapshot?.name || boardName);
|
|
338
|
-
const boardId = id();
|
|
339
|
-
const dbPath = computeNewBoardDbPath(repoPath, boardSlug);
|
|
340
|
-
const db = ensureBoardDbFileAndSchema(dbPath);
|
|
341
|
-
const newBoardCtx = {
|
|
342
|
-
actor,
|
|
343
|
-
board: {
|
|
344
|
-
id: boardId,
|
|
345
|
-
slug: boardSlug,
|
|
346
|
-
name: boardName,
|
|
347
|
-
repo_path: repoPath,
|
|
348
|
-
db_path: dbPath,
|
|
349
|
-
system_path: repoPath,
|
|
350
|
-
repo_url: sourceBoard.repo_url || sourceFormatSnapshot.board.repo_url || "",
|
|
351
|
-
default_branch: sourceBoard.default_branch || sourceFormatSnapshot.board.default_branch || "main"
|
|
352
|
-
},
|
|
353
|
-
db
|
|
354
|
-
};
|
|
355
|
-
const normalizedSnapshot = normalizeImportSnapshot(rawSnapshot, newBoardCtx);
|
|
356
|
-
const snapshotForNewBoard = {
|
|
357
|
-
...normalizedSnapshot,
|
|
358
|
-
board: {
|
|
359
|
-
...normalizedSnapshot.board,
|
|
360
|
-
id: boardId,
|
|
361
|
-
slug: boardSlug,
|
|
362
|
-
name: cleanText(normalizedSnapshot.board?.name) || boardName,
|
|
363
|
-
repo_url: normalizedSnapshot.board?.repo_url || sourceBoard.repo_url || "",
|
|
364
|
-
system_path: normalizedSnapshot.board?.system_path || repoPath,
|
|
365
|
-
default_branch: normalizedSnapshot.board?.default_branch || sourceBoard.default_branch || "main"
|
|
366
|
-
}
|
|
367
|
-
};
|
|
368
|
-
|
|
369
|
-
return {
|
|
370
|
-
...importBoardSnapshot({ snapshot: snapshotForNewBoard, replace_existing: false }, newBoardCtx),
|
|
371
|
-
created_new_board: true
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
function uniqueImportedBoardSlug(value) {
|
|
376
|
-
const base = slugify(value) || `imported-board-${id().slice(0, 8)}`;
|
|
377
|
-
const reg = getRegistry();
|
|
378
|
-
if (!reg.prepare("SELECT id FROM boards WHERE slug = ?").get(base)) return base;
|
|
379
|
-
for (let i = 2; i < 1000; i += 1) {
|
|
380
|
-
const candidate = `${base}-${i}`;
|
|
381
|
-
if (!reg.prepare("SELECT id FROM boards WHERE slug = ?").get(candidate)) return candidate;
|
|
382
|
-
}
|
|
383
|
-
return `${base}-${id().slice(0, 8)}`;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
function normalizeImportSnapshot(snapshot, ctx) {
|
|
387
|
-
if (snapshot?.format === "orbit-board-export") return snapshot;
|
|
388
|
-
if (looksLikeTrelloExport(snapshot)) return trelloExportToOrbitSnapshot(snapshot, ctx);
|
|
389
|
-
return snapshot;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
function looksLikeTrelloExport(value) {
|
|
393
|
-
return Boolean(
|
|
394
|
-
value &&
|
|
395
|
-
typeof value === "object" &&
|
|
396
|
-
Array.isArray(value.cards) &&
|
|
397
|
-
Array.isArray(value.lists) &&
|
|
398
|
-
(typeof value.id === "string" || typeof value.name === "string")
|
|
399
|
-
);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
const TRELLO_LABEL_COLORS = {
|
|
403
|
-
green: "#61bd4f",
|
|
404
|
-
yellow: "#f2d600",
|
|
405
|
-
orange: "#ff9f1a",
|
|
406
|
-
red: "#eb5a46",
|
|
407
|
-
purple: "#c377e0",
|
|
408
|
-
blue: "#0079bf",
|
|
409
|
-
sky: "#00c2e0",
|
|
410
|
-
lime: "#51e898",
|
|
411
|
-
pink: "#ff78cb",
|
|
412
|
-
black: "#344563"
|
|
413
|
-
};
|
|
414
|
-
|
|
415
|
-
function trelloExportToOrbitSnapshot(trello, ctx) {
|
|
416
|
-
const time = now();
|
|
417
|
-
const targetBoard = ctx.board || {};
|
|
418
|
-
const boardName = cleanText(trello.name) || "Imported Trello Board";
|
|
419
|
-
const boardSlug = targetBoard.slug || slugify(boardName) || `trello-${id().slice(0, 8)}`;
|
|
420
|
-
const labelsById = buildTrelloLabelMap(trello, time);
|
|
421
|
-
const checklistsByCard = groupTrelloChecklists(trello);
|
|
422
|
-
const commentsByCard = groupTrelloComments(trello);
|
|
423
|
-
const sortedLists = [...(trello.lists || [])].sort(compareTrelloPosition);
|
|
424
|
-
const states = buildTrelloStates(sortedLists, time);
|
|
425
|
-
const fallbackStateId = states[0]?.id;
|
|
426
|
-
const stateIdsByTrelloListId = new Map(sortedLists.map((list, index) => [list.id, states[index]?.id || fallbackStateId]));
|
|
427
|
-
const sortedCards = [...(trello.cards || [])].sort(compareTrelloPosition);
|
|
428
|
-
const tickets = [];
|
|
429
|
-
const comments = [];
|
|
430
|
-
|
|
431
|
-
sortedCards.forEach((card, index) => {
|
|
432
|
-
const ticketId = trelloScopedId("card", card.id || `${index + 1}`);
|
|
433
|
-
const stateId = stateIdsByTrelloListId.get(card.idList) || fallbackStateId;
|
|
434
|
-
if (!stateId) return;
|
|
435
|
-
const cardUpdatedAt = cleanText(card.dateLastActivity) || time;
|
|
436
|
-
const cardCreatedAt = dateFromTrelloObjectId(card.id) || cardUpdatedAt;
|
|
437
|
-
const cardComments = commentsByCard.get(card.id) || [];
|
|
438
|
-
const ticketLabels = trelloLabelsForCard(card, labelsById);
|
|
439
|
-
const importedDescription = renderTrelloCardDescription(card, {
|
|
440
|
-
listName: listNameForCard(trello.lists, card.idList),
|
|
441
|
-
checklists: checklistsByCard.get(card.id) || []
|
|
442
|
-
});
|
|
443
|
-
tickets.push({
|
|
444
|
-
id: ticketId,
|
|
445
|
-
number: index + 1,
|
|
446
|
-
title: cleanText(card.name) || `Untitled Trello card ${index + 1}`,
|
|
447
|
-
description: importedDescription,
|
|
448
|
-
type: "task",
|
|
449
|
-
parent_ticket_id: null,
|
|
450
|
-
ai_plan: "",
|
|
451
|
-
implementation_summary: "",
|
|
452
|
-
implementation_updates: "",
|
|
453
|
-
state_id: stateId,
|
|
454
|
-
priority: priorityFromTrelloCard(card),
|
|
455
|
-
created_by: "trello-import",
|
|
456
|
-
created_at: cardCreatedAt,
|
|
457
|
-
updated_at: cardUpdatedAt,
|
|
458
|
-
archived_at: card.closed ? cardUpdatedAt : null,
|
|
459
|
-
labels: ticketLabels
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
cardComments.forEach((action, commentIndex) => {
|
|
463
|
-
const commentId = trelloScopedId("comment", action.id || `${card.id}-${commentIndex + 1}`);
|
|
464
|
-
comments.push({
|
|
465
|
-
id: commentId,
|
|
466
|
-
ticket_id: ticketId,
|
|
467
|
-
author: trelloMemberName(action.memberCreator),
|
|
468
|
-
kind: "human_comment",
|
|
469
|
-
body: cleanText(action.data?.text) || "",
|
|
470
|
-
omitted_for_ai: 0,
|
|
471
|
-
created_at: cleanText(action.date) || cardUpdatedAt
|
|
472
|
-
});
|
|
473
|
-
});
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
return {
|
|
477
|
-
format: "orbit-board-export",
|
|
478
|
-
source_format: "trello-board-export",
|
|
479
|
-
version: 9,
|
|
480
|
-
exported_at: time,
|
|
481
|
-
include_attachments: false,
|
|
482
|
-
imported_counts: {
|
|
483
|
-
states: states.length,
|
|
484
|
-
tickets: tickets.length,
|
|
485
|
-
comments: comments.length,
|
|
486
|
-
labels: labelsById.size
|
|
487
|
-
},
|
|
488
|
-
board: {
|
|
489
|
-
id: targetBoard.id || trelloScopedId("board", trello.id || id()),
|
|
490
|
-
slug: boardSlug,
|
|
491
|
-
name: boardName,
|
|
492
|
-
repo_url: targetBoard.repo_url || "",
|
|
493
|
-
system_path: targetBoard.system_path || targetBoard.repo_path || "",
|
|
494
|
-
default_branch: targetBoard.default_branch || "main",
|
|
495
|
-
project_notes: cleanText(trello.desc) ? `Imported from Trello.\n\n${cleanText(trello.desc)}` : "Imported from Trello.",
|
|
496
|
-
agent_instructions: "",
|
|
497
|
-
ai_enabled: 1,
|
|
498
|
-
created_at: cleanText(trello.dateLastActivity) || time,
|
|
499
|
-
updated_at: time
|
|
500
|
-
},
|
|
501
|
-
states,
|
|
502
|
-
labels: [...labelsById.values()],
|
|
503
|
-
tickets,
|
|
504
|
-
comments: comments.filter((comment) => comment.body),
|
|
505
|
-
review_verdicts: [],
|
|
506
|
-
relations: [],
|
|
507
|
-
attachments: [],
|
|
508
|
-
board_entries: [],
|
|
509
|
-
events: []
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
function buildTrelloStates(lists = [], time) {
|
|
514
|
-
const sorted = [...lists].sort(compareTrelloPosition);
|
|
515
|
-
const rolesSeen = new Set();
|
|
516
|
-
const firstOpenIndex = sorted.findIndex((list) => !list.closed);
|
|
517
|
-
const defaultIndex = firstOpenIndex >= 0 ? firstOpenIndex : 0;
|
|
518
|
-
return sorted.map((list, index) => {
|
|
519
|
-
const role = uniqueTrelloStateRole(inferTrelloStateRole(list.name), rolesSeen);
|
|
520
|
-
return {
|
|
521
|
-
id: trelloScopedId("list", list.id || `${index + 1}`),
|
|
522
|
-
name: cleanText(list.name) || `Trello List ${index + 1}`,
|
|
523
|
-
position: index,
|
|
524
|
-
is_default: index === defaultIndex ? 1 : 0,
|
|
525
|
-
role,
|
|
526
|
-
created_at: dateFromTrelloObjectId(list.id) || time
|
|
527
|
-
};
|
|
528
|
-
});
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
function buildTrelloLabelMap(trello, time) {
|
|
532
|
-
const labels = new Map();
|
|
533
|
-
const addLabel = (raw) => {
|
|
534
|
-
if (!raw) return;
|
|
535
|
-
const key = raw.id || `${raw.color || "label"}-${raw.name || ""}`;
|
|
536
|
-
const name = trelloLabelName(raw, trello.labelNames);
|
|
537
|
-
if (!name || labels.has(key)) return;
|
|
538
|
-
labels.set(key, {
|
|
539
|
-
id: trelloScopedId("label", key),
|
|
540
|
-
name,
|
|
541
|
-
color: TRELLO_LABEL_COLORS[String(raw.color || "").toLowerCase()] || "#64748b",
|
|
542
|
-
created_at: dateFromTrelloObjectId(raw.id) || time
|
|
543
|
-
});
|
|
544
|
-
};
|
|
545
|
-
for (const label of trello.labels || []) addLabel(label);
|
|
546
|
-
for (const card of trello.cards || []) for (const label of card.labels || []) addLabel(label);
|
|
547
|
-
return labels;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
function groupTrelloChecklists(trello) {
|
|
551
|
-
const grouped = new Map();
|
|
552
|
-
const add = (cardId, checklist) => {
|
|
553
|
-
if (!cardId || !checklist) return;
|
|
554
|
-
if (!grouped.has(cardId)) grouped.set(cardId, []);
|
|
555
|
-
grouped.get(cardId).push(checklist);
|
|
556
|
-
};
|
|
557
|
-
for (const checklist of trello.checklists || []) add(checklist.idCard, checklist);
|
|
558
|
-
for (const card of trello.cards || []) for (const checklist of card.checklists || []) add(card.id, checklist);
|
|
559
|
-
return grouped;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
function groupTrelloComments(trello) {
|
|
563
|
-
const grouped = new Map();
|
|
564
|
-
for (const action of trello.actions || []) {
|
|
565
|
-
if (action.type !== "commentCard") continue;
|
|
566
|
-
const cardId = action.data?.card?.id;
|
|
567
|
-
if (!cardId) continue;
|
|
568
|
-
if (!grouped.has(cardId)) grouped.set(cardId, []);
|
|
569
|
-
grouped.get(cardId).push(action);
|
|
570
|
-
}
|
|
571
|
-
for (const actions of grouped.values()) actions.sort((a, b) => String(a.date || "").localeCompare(String(b.date || "")));
|
|
572
|
-
return grouped;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
function trelloLabelsForCard(card, labelsById) {
|
|
576
|
-
const result = [];
|
|
577
|
-
const seen = new Set();
|
|
578
|
-
const addByRawId = (rawId) => {
|
|
579
|
-
const label = labelsById.get(rawId);
|
|
580
|
-
if (label && !seen.has(label.id)) {
|
|
581
|
-
seen.add(label.id);
|
|
582
|
-
result.push(label);
|
|
583
|
-
}
|
|
584
|
-
};
|
|
585
|
-
for (const label of card.labels || []) addByRawId(label.id || `${label.color || "label"}-${label.name || ""}`);
|
|
586
|
-
for (const labelId of card.idLabels || []) addByRawId(labelId);
|
|
587
|
-
return result;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
function renderTrelloCardDescription(card, { listName, checklists }) {
|
|
591
|
-
const parts = [];
|
|
592
|
-
const desc = cleanText(card.desc);
|
|
593
|
-
if (desc) parts.push(desc);
|
|
594
|
-
const meta = [];
|
|
595
|
-
if (card.idShort) meta.push(`Trello card #${card.idShort}`);
|
|
596
|
-
if (listName) meta.push(`List: ${listName}`);
|
|
597
|
-
if (card.shortUrl || card.url) meta.push(`URL: ${card.shortUrl || card.url}`);
|
|
598
|
-
if (card.due) meta.push(`Due: ${card.due}${card.dueComplete ? " (complete)" : ""}`);
|
|
599
|
-
if (card.closed) meta.push("Archived in Trello: yes");
|
|
600
|
-
if (meta.length) parts.push(["Imported from Trello:", ...meta.map((line) => `- ${line}`)].join("\n"));
|
|
601
|
-
|
|
602
|
-
const checklistText = renderTrelloChecklists(checklists);
|
|
603
|
-
if (checklistText) parts.push(checklistText);
|
|
604
|
-
const attachmentText = renderTrelloAttachments(card.attachments || []);
|
|
605
|
-
if (attachmentText) parts.push(attachmentText);
|
|
606
|
-
return parts.join("\n\n");
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
function renderTrelloChecklists(checklists = []) {
|
|
610
|
-
const rendered = checklists
|
|
611
|
-
.map((checklist) => {
|
|
612
|
-
const items = [...(checklist.checkItems || [])].sort(compareTrelloPosition);
|
|
613
|
-
if (!items.length) return "";
|
|
614
|
-
return [`Checklist: ${cleanText(checklist.name) || "Untitled"}`, ...items.map((item) => `- [${item.state === "complete" ? "x" : " "}] ${cleanText(item.name) || "Untitled item"}`)].join("\n");
|
|
615
|
-
})
|
|
616
|
-
.filter(Boolean);
|
|
617
|
-
return rendered.join("\n\n");
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
function renderTrelloAttachments(attachments = []) {
|
|
621
|
-
const rendered = attachments
|
|
622
|
-
.map((attachment) => {
|
|
623
|
-
const name = cleanText(attachment.name) || cleanText(attachment.fileName) || "Attachment";
|
|
624
|
-
const url = cleanText(attachment.url);
|
|
625
|
-
return url ? `- ${name}: ${url}` : `- ${name}`;
|
|
626
|
-
})
|
|
627
|
-
.filter(Boolean);
|
|
628
|
-
return rendered.length ? ["Trello attachments:", ...rendered].join("\n") : "";
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
function trelloLabelName(label, labelNames = {}) {
|
|
632
|
-
const explicit = cleanText(label.name);
|
|
633
|
-
if (explicit) return explicit;
|
|
634
|
-
const color = cleanText(label.color);
|
|
635
|
-
const namedColor = color ? cleanText(labelNames[color]) : "";
|
|
636
|
-
if (namedColor) return namedColor;
|
|
637
|
-
return color ? `Trello ${color}` : "";
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
function listNameForCard(lists = [], idList) {
|
|
641
|
-
return cleanText(lists.find((list) => list.id === idList)?.name);
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
function priorityFromTrelloCard(card) {
|
|
645
|
-
const names = (card.labels || []).map((label) => cleanText(label.name || label.color).toLowerCase()).join(" ");
|
|
646
|
-
if (/urgent|critical|blocker|high|red/.test(names)) return 4;
|
|
647
|
-
if (/low|someday|backlog/.test(names)) return 1;
|
|
648
|
-
return 2;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
function inferTrelloStateRole(name) {
|
|
652
|
-
const normalized = cleanText(name).toLowerCase();
|
|
653
|
-
if (!normalized) return null;
|
|
654
|
-
if (/^ai ready$|ai[- ]ready/.test(normalized)) return "ai_ready";
|
|
655
|
-
if (/review|qa|verify|validation/.test(normalized)) return "review";
|
|
656
|
-
if (/doing|in progress|active|working|wip/.test(normalized)) return "in_progress";
|
|
657
|
-
if (/done|complete|completed|shipped|closed/.test(normalized)) return "done";
|
|
658
|
-
return null;
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
function uniqueTrelloStateRole(role, rolesSeen) {
|
|
662
|
-
if (!role || rolesSeen.has(role)) return null;
|
|
663
|
-
rolesSeen.add(role);
|
|
664
|
-
return role;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
function trelloMemberName(member) {
|
|
668
|
-
return cleanText(member?.fullName) || cleanText(member?.username) || "Trello";
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
function compareTrelloPosition(a, b) {
|
|
672
|
-
const pa = Number(a?.pos ?? Number.MAX_SAFE_INTEGER);
|
|
673
|
-
const pb = Number(b?.pos ?? Number.MAX_SAFE_INTEGER);
|
|
674
|
-
if (pa !== pb) return pa - pb;
|
|
675
|
-
return String(a?.id || "").localeCompare(String(b?.id || ""));
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
function dateFromTrelloObjectId(value) {
|
|
679
|
-
const idValue = String(value || "");
|
|
680
|
-
if (!/^[0-9a-fA-F]{8}/.test(idValue)) return null;
|
|
681
|
-
const seconds = Number.parseInt(idValue.slice(0, 8), 16);
|
|
682
|
-
if (!Number.isFinite(seconds)) return null;
|
|
683
|
-
return new Date(seconds * 1000).toISOString();
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
function cleanText(value) {
|
|
687
|
-
return String(value ?? "").replace(/\r\n/g, "\n").trim();
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
function trelloScopedId(prefix, value) {
|
|
691
|
-
return `trello-${prefix}-${String(value || id()).replace(/[^a-zA-Z0-9_-]/g, "-")}`;
|
|
692
|
-
}
|