@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/relations.js
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { boardById, childTicketsFor, relationRows, ticketById, unresolvedBlockers } from "./queries.js";
|
|
2
|
-
import { tx } from "./db.js";
|
|
3
|
-
import { requireBoardAccess, requirePermission } from "./auth.js";
|
|
4
|
-
import { recordEvent } from "./events.js";
|
|
5
|
-
import { httpError, id, now, requiredString } from "./util.js";
|
|
6
|
-
|
|
7
|
-
export function createRelation(body, ctx) {
|
|
8
|
-
const { db, board, actor } = ctx;
|
|
9
|
-
const sourceId = requiredString(body.source_ticket_id, "source_ticket_id");
|
|
10
|
-
const source = ticketById(db, sourceId);
|
|
11
|
-
if (!source || source.board_id !== board.id) throw httpError(404, "ticket_not_found");
|
|
12
|
-
|
|
13
|
-
const target = ticketById(db, requiredString(body.target_ticket_id, "target_ticket_id"));
|
|
14
|
-
// Relations are only meaningful within a single board's DB. Cross-board
|
|
15
|
-
// relations are not supported by the schema (foreign keys live in this DB
|
|
16
|
-
// only) and would invent silent invariants.
|
|
17
|
-
if (!target || target.board_id !== board.id) throw httpError(404, "ticket_not_found");
|
|
18
|
-
|
|
19
|
-
requireBoardAccess(actor, boardById(db, source.board_id));
|
|
20
|
-
requirePermission(actor, "write");
|
|
21
|
-
|
|
22
|
-
if (source.id === target.id) throw httpError(400, "relation_self");
|
|
23
|
-
if (source.parent_ticket_id === target.id || target.parent_ticket_id === source.id) {
|
|
24
|
-
throw httpError(400, "relation_redundant_with_parent");
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const relationType = body.type || "relates_to";
|
|
28
|
-
// Hierarchy is stored on the ticket row (parent_ticket_id), not in the
|
|
29
|
-
// relations table. The `child_of` / `parent_of` types only appear as
|
|
30
|
-
// read-only synthetic entries in getTicketRelations output — pointing
|
|
31
|
-
// callers at the right write path keeps the two models disjoint.
|
|
32
|
-
if (relationType === "child_of" || relationType === "parent_of") {
|
|
33
|
-
throw httpError(400, "hierarchy_uses_parent_ticket_id");
|
|
34
|
-
}
|
|
35
|
-
// Blocker relations are meaningless against a target that's already in a
|
|
36
|
-
// done lane — the row would be a stale blocker on creation. Reject so
|
|
37
|
-
// callers fix the data instead of silently creating dead relations.
|
|
38
|
-
if (relationType === "blocked_by" && target.state_role === "done") {
|
|
39
|
-
throw httpError(400, "blocker_target_already_done");
|
|
40
|
-
}
|
|
41
|
-
if (relationType === "blocks" && source.state_role === "done") {
|
|
42
|
-
throw httpError(400, "blocker_source_already_done");
|
|
43
|
-
}
|
|
44
|
-
const existing = db
|
|
45
|
-
.prepare("SELECT * FROM relations WHERE source_ticket_id = ? AND target_ticket_id = ? AND type = ?")
|
|
46
|
-
.get(source.id, target.id, relationType);
|
|
47
|
-
// Don't silently 201 on a no-op: an INSERT OR IGNORE that hit the UNIQUE
|
|
48
|
-
// constraint would have returned the pre-existing row, hiding from the
|
|
49
|
-
// caller that nothing was created.
|
|
50
|
-
if (existing) throw httpError(409, "relation_exists");
|
|
51
|
-
|
|
52
|
-
const relationId = id();
|
|
53
|
-
return tx(db, () => {
|
|
54
|
-
db.prepare(
|
|
55
|
-
"INSERT INTO relations (id, source_ticket_id, target_ticket_id, type, created_at) VALUES (?, ?, ?, ?, ?)"
|
|
56
|
-
).run(relationId, source.id, target.id, relationType, now());
|
|
57
|
-
recordEvent(db, source.board_id, "relation_created", source.id, actor.name, {
|
|
58
|
-
target_ticket_id: target.id,
|
|
59
|
-
type: relationType
|
|
60
|
-
});
|
|
61
|
-
return db.prepare("SELECT * FROM relations WHERE id = ?").get(relationId);
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function deleteRelation(relationId, ctx) {
|
|
66
|
-
const { db, board, actor } = ctx;
|
|
67
|
-
const row = db.prepare("SELECT * FROM relations WHERE id = ?").get(relationId);
|
|
68
|
-
if (!row) throw httpError(404, "relation_not_found");
|
|
69
|
-
const source = ticketById(db, row.source_ticket_id);
|
|
70
|
-
const target = ticketById(db, row.target_ticket_id);
|
|
71
|
-
// Relation must live in the resolved board (sanity check — same DB by
|
|
72
|
-
// definition since router resolved this board, but keep the assertion).
|
|
73
|
-
if (source && source.board_id !== board.id) throw httpError(404, "relation_not_found");
|
|
74
|
-
if (source) requireBoardAccess(actor, boardById(db, source.board_id));
|
|
75
|
-
if (target) requireBoardAccess(actor, boardById(db, target.board_id));
|
|
76
|
-
requirePermission(actor, "write");
|
|
77
|
-
return tx(db, () => {
|
|
78
|
-
db.prepare("DELETE FROM relations WHERE id = ?").run(relationId);
|
|
79
|
-
recordEvent(db, source?.board_id || board.id, "relation_deleted", row.source_ticket_id, actor.name, {
|
|
80
|
-
target_ticket_id: row.target_ticket_id,
|
|
81
|
-
type: row.type
|
|
82
|
-
});
|
|
83
|
-
return { ok: true };
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function getTicketRelations(ticketId, ctx) {
|
|
88
|
-
const { db, board, actor } = ctx;
|
|
89
|
-
const ticket = ticketById(db, ticketId);
|
|
90
|
-
if (!ticket || ticket.board_id !== board.id) throw httpError(404, "ticket_not_found");
|
|
91
|
-
requireBoardAccess(actor, boardById(db, ticket.board_id));
|
|
92
|
-
const compactOther = (other) => other && {
|
|
93
|
-
id: other.id,
|
|
94
|
-
number: other.number,
|
|
95
|
-
title: other.title,
|
|
96
|
-
type: other.type,
|
|
97
|
-
state_id: other.state_id,
|
|
98
|
-
state_name: other.state_name,
|
|
99
|
-
state_role: other.state_role,
|
|
100
|
-
parent_ticket_id: other.parent_ticket_id
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const relations = relationRows(db, ticketId).map((row) => ({
|
|
104
|
-
id: row.id,
|
|
105
|
-
type: row.type,
|
|
106
|
-
direction: row.direction,
|
|
107
|
-
source: "relation",
|
|
108
|
-
other_ticket: compactOther(row.other_ticket)
|
|
109
|
-
}));
|
|
110
|
-
|
|
111
|
-
// Hierarchy is the other relationship model in the system. We don't store
|
|
112
|
-
// it in the `relations` table (parent_ticket_id is a column on the ticket
|
|
113
|
-
// row, kept disjoint from peer relations), but agents asking "what's linked
|
|
114
|
-
// to this ticket?" want a single answer. Synthesize read-only `child_of` /
|
|
115
|
-
// `parent_of` entries here. Marked source='hierarchy' so callers know they
|
|
116
|
-
// can't delete via DELETE /api/relations/:id; mutate via PATCH on the
|
|
117
|
-
// ticket's parent_ticket_id instead.
|
|
118
|
-
if (ticket.parent_ticket_id) {
|
|
119
|
-
const parent = ticketById(db, ticket.parent_ticket_id);
|
|
120
|
-
if (parent && !parent.archived_at) {
|
|
121
|
-
relations.push({
|
|
122
|
-
id: null,
|
|
123
|
-
type: "child_of",
|
|
124
|
-
direction: "outgoing",
|
|
125
|
-
source: "hierarchy",
|
|
126
|
-
other_ticket: compactOther(parent)
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
for (const child of childTicketsFor(db, ticketId)) {
|
|
131
|
-
relations.push({
|
|
132
|
-
id: null,
|
|
133
|
-
type: "parent_of",
|
|
134
|
-
direction: "outgoing",
|
|
135
|
-
source: "hierarchy",
|
|
136
|
-
other_ticket: compactOther(child)
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return { ticket_id: ticket.id, relations };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export function getTicketBlockers(ticketId, ctx) {
|
|
144
|
-
const { db, board, actor } = ctx;
|
|
145
|
-
const ticket = ticketById(db, ticketId);
|
|
146
|
-
if (!ticket || ticket.board_id !== board.id) throw httpError(404, "ticket_not_found");
|
|
147
|
-
requireBoardAccess(actor, boardById(db, ticket.board_id));
|
|
148
|
-
const blockers = unresolvedBlockers(db, ticketId).map((row) => ({
|
|
149
|
-
id: row.id,
|
|
150
|
-
number: row.number,
|
|
151
|
-
title: row.title,
|
|
152
|
-
type: row.type,
|
|
153
|
-
state_id: row.state_id,
|
|
154
|
-
state_name: row.state_name,
|
|
155
|
-
state_role: row.state_role,
|
|
156
|
-
via_epic_id: row.via_epic_id || null
|
|
157
|
-
}));
|
|
158
|
-
return {
|
|
159
|
-
ticket_id: ticket.id,
|
|
160
|
-
can_start: blockers.length === 0,
|
|
161
|
-
blockers
|
|
162
|
-
};
|
|
163
|
-
}
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { boardById, ticketById } from "./queries.js";
|
|
2
|
-
import { requireBoardAccess, requirePermission } from "./auth.js";
|
|
3
|
-
import { tx } from "./db.js";
|
|
4
|
-
import { recordEvent } from "./events.js";
|
|
5
|
-
import { bumpTicketUpdatedAt } from "./tickets.js";
|
|
6
|
-
import { httpError, id, now, requiredString } from "./util.js";
|
|
7
|
-
|
|
8
|
-
const VERDICTS = new Set(["PASS", "BLOCK", "QUESTION"]);
|
|
9
|
-
const JSON_ARRAY_FIELDS = ["blocking_findings", "optional_findings", "evidence_commands"];
|
|
10
|
-
|
|
11
|
-
export function normalizeReviewVerdict(value) {
|
|
12
|
-
const verdict = String(value || "").trim().toUpperCase();
|
|
13
|
-
if (!VERDICTS.has(verdict)) throw httpError(400, "invalid_review_verdict");
|
|
14
|
-
return verdict;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function normalizeJsonArray(value, field) {
|
|
18
|
-
if (value === undefined || value === null) return [];
|
|
19
|
-
if (!Array.isArray(value)) throw httpError(400, `invalid_${field}`);
|
|
20
|
-
return value;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function optionalString(value) {
|
|
24
|
-
if (value === undefined || value === null) return "";
|
|
25
|
-
return String(value).trim();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function hydrateReviewVerdict(row) {
|
|
29
|
-
if (!row) return null;
|
|
30
|
-
const hydrated = { ...row };
|
|
31
|
-
for (const field of JSON_ARRAY_FIELDS) {
|
|
32
|
-
const jsonField = `${field}_json`;
|
|
33
|
-
hydrated[field] = JSON.parse(row[jsonField] || "[]");
|
|
34
|
-
delete hydrated[jsonField];
|
|
35
|
-
}
|
|
36
|
-
hydrated.supersedes_prior_review_id = hydrated.supersedes_prior_review_id || null;
|
|
37
|
-
return hydrated;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function reviewVerdictById(db, reviewId) {
|
|
41
|
-
return hydrateReviewVerdict(db.prepare("SELECT * FROM review_verdicts WHERE id = ?").get(reviewId));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function requireTicketForReview(ticketId, ctx) {
|
|
45
|
-
const { db, board, actor } = ctx;
|
|
46
|
-
const ticket = ticketById(db, ticketId);
|
|
47
|
-
if (!ticket || ticket.board_id !== board.id) throw httpError(404, "ticket_not_found");
|
|
48
|
-
requireBoardAccess(actor, boardById(db, ticket.board_id));
|
|
49
|
-
return ticket;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function createReviewVerdict(ticketId, body = {}, ctx) {
|
|
53
|
-
const { db, actor } = ctx;
|
|
54
|
-
const ticket = requireTicketForReview(ticketId, ctx);
|
|
55
|
-
requirePermission(actor, "write");
|
|
56
|
-
|
|
57
|
-
const verdict = normalizeReviewVerdict(requiredString(body.verdict, "verdict"));
|
|
58
|
-
const blockingFindings = normalizeJsonArray(body.blocking_findings, "blocking_findings");
|
|
59
|
-
const optionalFindings = normalizeJsonArray(body.optional_findings, "optional_findings");
|
|
60
|
-
const evidenceCommands = normalizeJsonArray(body.evidence_commands, "evidence_commands");
|
|
61
|
-
const supersedesPriorReviewId = optionalString(body.supersedes_prior_review_id) || null;
|
|
62
|
-
|
|
63
|
-
if (supersedesPriorReviewId) {
|
|
64
|
-
const prior = reviewVerdictById(db, supersedesPriorReviewId);
|
|
65
|
-
if (!prior) throw httpError(400, "invalid_supersedes_prior_review_id");
|
|
66
|
-
if (prior.ticket_id !== ticket.id) throw httpError(400, "superseded_review_ticket_mismatch");
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const reviewId = id();
|
|
70
|
-
return tx(db, () => {
|
|
71
|
-
const time = now();
|
|
72
|
-
db.prepare(
|
|
73
|
-
`INSERT INTO review_verdicts
|
|
74
|
-
(id, ticket_id, verdict, blocking_findings_json, optional_findings_json,
|
|
75
|
-
evidence_commands_json, reviewer_profile, reviewer_session_id,
|
|
76
|
-
reviewed_commit_sha, dispatch_run_id, supersedes_prior_review_id,
|
|
77
|
-
created_by, created_at)
|
|
78
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
79
|
-
).run(
|
|
80
|
-
reviewId,
|
|
81
|
-
ticket.id,
|
|
82
|
-
verdict,
|
|
83
|
-
JSON.stringify(blockingFindings),
|
|
84
|
-
JSON.stringify(optionalFindings),
|
|
85
|
-
JSON.stringify(evidenceCommands),
|
|
86
|
-
optionalString(body.reviewer_profile),
|
|
87
|
-
optionalString(body.reviewer_session_id),
|
|
88
|
-
optionalString(body.reviewed_commit_sha),
|
|
89
|
-
optionalString(body.dispatch_run_id),
|
|
90
|
-
supersedesPriorReviewId,
|
|
91
|
-
actor.name,
|
|
92
|
-
time
|
|
93
|
-
);
|
|
94
|
-
db.prepare("UPDATE tickets SET updated_at = ? WHERE id = ?").run(time, ticket.id);
|
|
95
|
-
recordEvent(db, ticket.board_id, "review_verdict_created", ticket.id, actor.name, {
|
|
96
|
-
review_id: reviewId,
|
|
97
|
-
verdict,
|
|
98
|
-
supersedes_prior_review_id: supersedesPriorReviewId,
|
|
99
|
-
reviewed_commit_sha: optionalString(body.reviewed_commit_sha),
|
|
100
|
-
dispatch_run_id: optionalString(body.dispatch_run_id),
|
|
101
|
-
actor_type: actor.type,
|
|
102
|
-
actor_id: actor.id
|
|
103
|
-
});
|
|
104
|
-
bumpTicketUpdatedAt(db, ticket.parent_ticket_id, time);
|
|
105
|
-
return getReviewVerdict(reviewId, ctx);
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function getReviewVerdict(reviewId, ctx) {
|
|
110
|
-
const { db, board, actor } = ctx;
|
|
111
|
-
const review = reviewVerdictById(db, reviewId);
|
|
112
|
-
if (!review) throw httpError(404, "review_verdict_not_found");
|
|
113
|
-
const ticket = ticketById(db, review.ticket_id);
|
|
114
|
-
if (!ticket || ticket.board_id !== board.id) throw httpError(404, "review_verdict_not_found");
|
|
115
|
-
requireBoardAccess(actor, boardById(db, ticket.board_id));
|
|
116
|
-
return review;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export function listReviewVerdicts(ticketId, ctx, options = {}) {
|
|
120
|
-
const { db } = ctx;
|
|
121
|
-
const ticket = requireTicketForReview(ticketId, ctx);
|
|
122
|
-
const limit = normalizeLimit(options.limit);
|
|
123
|
-
return db
|
|
124
|
-
.prepare(
|
|
125
|
-
`SELECT * FROM review_verdicts
|
|
126
|
-
WHERE ticket_id = ?
|
|
127
|
-
ORDER BY created_at DESC, rowid DESC
|
|
128
|
-
LIMIT ?`
|
|
129
|
-
)
|
|
130
|
-
.all(ticket.id, limit)
|
|
131
|
-
.map(hydrateReviewVerdict);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function normalizeLimit(value) {
|
|
135
|
-
const n = Number(value ?? 50);
|
|
136
|
-
if (!Number.isInteger(n) || n <= 0) return 50;
|
|
137
|
-
return Math.min(n, 200);
|
|
138
|
-
}
|