@nestos/kanban 0.0.0-stage → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +165 -2
  3. package/dist/activity-BbC4sgKL.d.cts +258 -0
  4. package/dist/activity-Czlan5xW.d.ts +258 -0
  5. package/dist/activity-module-DaO1Rry0.d.cts +22 -0
  6. package/dist/activity-module-T22wliZu.d.ts +22 -0
  7. package/dist/activity-ports-Dqxa86zT.d.cts +12 -0
  8. package/dist/activity-ports-cxfrelaL.d.ts +12 -0
  9. package/dist/activity.cjs +16 -0
  10. package/dist/activity.cjs.map +1 -0
  11. package/dist/activity.d.cts +12 -0
  12. package/dist/activity.d.ts +12 -0
  13. package/dist/activity.js +16 -0
  14. package/dist/activity.js.map +1 -0
  15. package/dist/chunk-3JYLJPE5.js +95 -0
  16. package/dist/chunk-3JYLJPE5.js.map +1 -0
  17. package/dist/chunk-3LEIHB7Y.js +221 -0
  18. package/dist/chunk-3LEIHB7Y.js.map +1 -0
  19. package/dist/chunk-4XF6LQSU.cjs +9 -0
  20. package/dist/chunk-4XF6LQSU.cjs.map +1 -0
  21. package/dist/chunk-5Q7WB63A.cjs +282 -0
  22. package/dist/chunk-5Q7WB63A.cjs.map +1 -0
  23. package/dist/chunk-7KDOTBRX.cjs +58 -0
  24. package/dist/chunk-7KDOTBRX.cjs.map +1 -0
  25. package/dist/chunk-AHHEGTUA.js +148 -0
  26. package/dist/chunk-AHHEGTUA.js.map +1 -0
  27. package/dist/chunk-AOHQCX7K.cjs +95 -0
  28. package/dist/chunk-AOHQCX7K.cjs.map +1 -0
  29. package/dist/chunk-DINLR6E6.cjs +443 -0
  30. package/dist/chunk-DINLR6E6.cjs.map +1 -0
  31. package/dist/chunk-EYTVC2H5.cjs +1612 -0
  32. package/dist/chunk-EYTVC2H5.cjs.map +1 -0
  33. package/dist/chunk-FEE2YBZ7.js +1972 -0
  34. package/dist/chunk-FEE2YBZ7.js.map +1 -0
  35. package/dist/chunk-FNO5K3A7.js +282 -0
  36. package/dist/chunk-FNO5K3A7.js.map +1 -0
  37. package/dist/chunk-IHHDXZPZ.js +382 -0
  38. package/dist/chunk-IHHDXZPZ.js.map +1 -0
  39. package/dist/chunk-J3EAIVA3.js +280 -0
  40. package/dist/chunk-J3EAIVA3.js.map +1 -0
  41. package/dist/chunk-KH3WF3K4.js +443 -0
  42. package/dist/chunk-KH3WF3K4.js.map +1 -0
  43. package/dist/chunk-M35X5PHO.js +196 -0
  44. package/dist/chunk-M35X5PHO.js.map +1 -0
  45. package/dist/chunk-QRGZD2JU.cjs +1972 -0
  46. package/dist/chunk-QRGZD2JU.cjs.map +1 -0
  47. package/dist/chunk-RBOER6EW.cjs +221 -0
  48. package/dist/chunk-RBOER6EW.cjs.map +1 -0
  49. package/dist/chunk-RQP77ZTY.js +9 -0
  50. package/dist/chunk-RQP77ZTY.js.map +1 -0
  51. package/dist/chunk-RSH6QSEC.cjs +196 -0
  52. package/dist/chunk-RSH6QSEC.cjs.map +1 -0
  53. package/dist/chunk-VMRDXOLX.cjs +148 -0
  54. package/dist/chunk-VMRDXOLX.cjs.map +1 -0
  55. package/dist/chunk-X4UHZX6F.js +58 -0
  56. package/dist/chunk-X4UHZX6F.js.map +1 -0
  57. package/dist/chunk-XBSGUVCY.cjs +382 -0
  58. package/dist/chunk-XBSGUVCY.cjs.map +1 -0
  59. package/dist/chunk-XKDZHAUE.js +1612 -0
  60. package/dist/chunk-XKDZHAUE.js.map +1 -0
  61. package/dist/chunk-ZUZGAO6G.cjs +280 -0
  62. package/dist/chunk-ZUZGAO6G.cjs.map +1 -0
  63. package/dist/content-C5aJZFlw.d.cts +35 -0
  64. package/dist/content-hLH5BNud.d.ts +35 -0
  65. package/dist/contracts/index.cjs +31 -0
  66. package/dist/contracts/index.cjs.map +1 -0
  67. package/dist/contracts/index.d.cts +5 -0
  68. package/dist/contracts/index.d.ts +5 -0
  69. package/dist/contracts/index.js +31 -0
  70. package/dist/contracts/index.js.map +1 -0
  71. package/dist/discovery-9E-LfwIw.d.cts +61 -0
  72. package/dist/discovery-D60yrjej.d.ts +61 -0
  73. package/dist/discovery.cjs +377 -0
  74. package/dist/discovery.cjs.map +1 -0
  75. package/dist/discovery.d.cts +22 -0
  76. package/dist/discovery.d.ts +22 -0
  77. package/dist/discovery.js +377 -0
  78. package/dist/discovery.js.map +1 -0
  79. package/dist/execution-bridge-fpCnH6z5.d.cts +52 -0
  80. package/dist/execution-bridge-rk0uVy_n.d.ts +52 -0
  81. package/dist/execution.cjs +18 -0
  82. package/dist/execution.cjs.map +1 -0
  83. package/dist/execution.d.cts +42 -0
  84. package/dist/execution.d.ts +42 -0
  85. package/dist/execution.js +18 -0
  86. package/dist/execution.js.map +1 -0
  87. package/dist/index.cjs +74 -0
  88. package/dist/index.cjs.map +1 -0
  89. package/dist/index.d.cts +54 -0
  90. package/dist/index.d.ts +54 -0
  91. package/dist/index.js +74 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/module-CYfE7YhM.d.ts +11 -0
  94. package/dist/module-Ca9L3qCn.d.cts +11 -0
  95. package/dist/planning-CHrOqQ6b.d.cts +879 -0
  96. package/dist/planning-CHrOqQ6b.d.ts +879 -0
  97. package/dist/ports-B0McVg7B.d.ts +143 -0
  98. package/dist/ports-BeGO1VRI.d.cts +49 -0
  99. package/dist/ports-GQy0x-RI.d.cts +143 -0
  100. package/dist/ports-ykGiNUSx.d.ts +49 -0
  101. package/dist/runtime/index.cjs +205 -0
  102. package/dist/runtime/index.cjs.map +1 -0
  103. package/dist/runtime/index.d.cts +75 -0
  104. package/dist/runtime/index.d.ts +75 -0
  105. package/dist/runtime/index.js +205 -0
  106. package/dist/runtime/index.js.map +1 -0
  107. package/dist/runtime/integration.cjs +423 -0
  108. package/dist/runtime/integration.cjs.map +1 -0
  109. package/dist/runtime/integration.d.cts +658 -0
  110. package/dist/runtime/integration.d.ts +658 -0
  111. package/dist/runtime/integration.js +423 -0
  112. package/dist/runtime/integration.js.map +1 -0
  113. package/dist/storage.cjs +21 -0
  114. package/dist/storage.cjs.map +1 -0
  115. package/dist/storage.d.cts +71 -0
  116. package/dist/storage.d.ts +71 -0
  117. package/dist/storage.js +21 -0
  118. package/dist/storage.js.map +1 -0
  119. package/dist/store-DPwHzGiz.d.cts +28 -0
  120. package/dist/store-DgdrPCV9.d.ts +28 -0
  121. package/dist/task-projection-CSorlMws.d.ts +11 -0
  122. package/dist/task-projection-DKKB_ueb.d.cts +11 -0
  123. package/examples/standalone.ts +22 -0
  124. package/package.json +143 -4
@@ -0,0 +1,443 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } async function _asyncOptionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = await fn(value); } else if (op === 'call' || op === 'optionalCall') { value = await fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+ var _chunkEYTVC2H5cjs = require('./chunk-EYTVC2H5.cjs');
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+ var _chunkRBOER6EWcjs = require('./chunk-RBOER6EW.cjs');
23
+
24
+
25
+ var _chunk7KDOTBRXcjs = require('./chunk-7KDOTBRX.cjs');
26
+
27
+
28
+
29
+ var _chunkRSH6QSECcjs = require('./chunk-RSH6QSEC.cjs');
30
+
31
+
32
+
33
+ var _chunkAOHQCX7Kcjs = require('./chunk-AOHQCX7K.cjs');
34
+
35
+ // src/references.ts
36
+ async function resolveReference(reference, store, read) {
37
+ _chunkRBOER6EWcjs.requireValue.call(void 0, typeof reference === "string" && reference.length <= 100, "INVALID_REFERENCE");
38
+ const match = /^KB-([1-9]\d*)(?:#([1-9]\d*)|\/(L|D)-([1-9]\d*))?$/i.exec(reference.trim());
39
+ _chunkRBOER6EWcjs.requireValue.call(void 0, match && [match[1], match[2], match[4]].every((value) => value === void 0 || Number.isSafeInteger(Number(value))), "INVALID_REFERENCE");
40
+ const id = await _optionalChain([store, 'access', _ => _.boardId, 'optionalCall', _2 => _2(Number(match[1]))]);
41
+ _chunkRBOER6EWcjs.requireValue.call(void 0, id, "REFERENCE_NOT_FOUND");
42
+ const view = await read(id), board = view.state.board;
43
+ _chunkRBOER6EWcjs.requireValue.call(void 0, board.number === Number(match[1]), "INVALID_NUMBERING");
44
+ const target = { boardId: id, scope: board.scope, reference: _chunk7KDOTBRXcjs.kanbanReference.call(void 0, board) };
45
+ if (match[2]) {
46
+ const item = [...view.items, ..._nullishCoalesce(view.state.archiveIndex, () => ( []))].find((item2) => item2.number === Number(match[2]));
47
+ _chunkRBOER6EWcjs.requireValue.call(void 0, item, "REFERENCE_NOT_FOUND");
48
+ target.itemId = item.id;
49
+ target.reference = _chunk7KDOTBRXcjs.kanbanReference.call(void 0, board, item);
50
+ } else if (match[3]) {
51
+ const record = view.records.find((record2) => record2.kind === (match[3].toUpperCase() === "L" ? "log" : "decision") && record2.number === Number(match[4]));
52
+ _chunkRBOER6EWcjs.requireValue.call(void 0, record, "REFERENCE_NOT_FOUND");
53
+ target.recordId = record.id;
54
+ target.reference = _chunk7KDOTBRXcjs.kanbanReference.call(void 0, board, record);
55
+ }
56
+ return target;
57
+ }
58
+
59
+ // src/suggestions.ts
60
+ function validateSuggestionQuery(query) {
61
+ _chunkRBOER6EWcjs.requireValue.call(void 0, query && ["item", "parent", "dependency", "area", "milestone", "decision", "session"].includes(query.kind), "INVALID_SUGGESTION_KIND");
62
+ _chunkRBOER6EWcjs.requireValue.call(void 0, query.search === void 0 || typeof query.search === "string" && query.search.length <= 256, "INVALID_FILTER");
63
+ const limit = _nullishCoalesce(query.limit, () => ( 20));
64
+ _chunkRBOER6EWcjs.requireValue.call(void 0, Number.isSafeInteger(limit) && limit > 0 && limit <= 50, "INVALID_LIMIT");
65
+ if (query.excludeItemId !== void 0) _chunkRBOER6EWcjs.identity.call(void 0, query.excludeItemId);
66
+ if (query.excludeIds !== void 0) {
67
+ _chunkRBOER6EWcjs.requireValue.call(void 0, Array.isArray(query.excludeIds) && query.excludeIds.length <= 100, "INVALID_FILTER");
68
+ query.excludeIds.forEach(_chunkRBOER6EWcjs.identity);
69
+ }
70
+ if (query.childKind !== void 0) _chunkRBOER6EWcjs.identity.call(void 0, query.childKind);
71
+ return limit;
72
+ }
73
+ function suggestionPage(revision, rows, query) {
74
+ const limit = validateSuggestionQuery(query), search = _nullishCoalesce(_optionalChain([query, 'access', _3 => _3.search, 'optionalAccess', _4 => _4.trim, 'call', _5 => _5(), 'access', _6 => _6.toLocaleLowerCase, 'call', _7 => _7()]), () => ( ""));
75
+ const matched = rows.filter((row) => !_optionalChain([query, 'access', _8 => _8.excludeIds, 'optionalAccess', _9 => _9.includes, 'call', _10 => _10(row.id)])).filter((row) => `${row.id} ${row.label} ${_nullishCoalesce(row.detail, () => ( ""))}`.toLocaleLowerCase().includes(search)).sort((a, b) => score(a.label, search) - score(b.label, search) || a.label.localeCompare(b.label) || a.id.localeCompare(b.id));
76
+ return { revision, items: matched.slice(0, limit).map((row) => ({ id: row.id, label: row.label.slice(0, 256), ...row.detail ? { detail: row.detail.slice(0, 512) } : {}, ...row.status ? { status: row.status } : {}, ...row.archived !== void 0 ? { archived: row.archived } : {} })), hasMore: matched.length > limit };
77
+ }
78
+ var score = (label, query) => label.toLocaleLowerCase() === query ? 0 : label.toLocaleLowerCase().startsWith(query) ? 1 : 2;
79
+ function suggestFromState(state, query, items = _chunkEYTVC2H5cjs.catalog.call(void 0, state), records = (_nullishCoalesce(state.records, () => ( []))).map(_chunkEYTVC2H5cjs.recordSummary)) {
80
+ validateSuggestionQuery(query);
81
+ if (query.kind === "area" || query.kind === "milestone") {
82
+ const names = [...new Set(items.map((item) => item.planning[query.kind]).filter((name) => !!name))];
83
+ return suggestionPage(state.board.revision, names.map((name) => ({ id: name, label: name })), query);
84
+ }
85
+ if (query.kind === "decision") return suggestionPage(state.board.revision, records.filter((record) => record.kind === "decision" && record.status === "confirmed").map((record) => ({ id: record.id, label: record.title, detail: _chunk7KDOTBRXcjs.kanbanReference.call(void 0, state.board, record) })), query);
86
+ if (query.kind === "session") {
87
+ const ids = /* @__PURE__ */ new Set([
88
+ ...state.board.participants.map((p) => p.sessionId),
89
+ ...items.flatMap((item) => item.sessionIds),
90
+ ...records.flatMap((record) => record.sessionIds)
91
+ ]);
92
+ return suggestionPage(state.board.revision, [...ids].map((id) => ({ id, label: id })), query);
93
+ }
94
+ const blocked = query.kind === "dependency" && query.excludeItemId ? dependents(items, query.excludeItemId) : /* @__PURE__ */ new Set();
95
+ const rank = query.kind === "parent" ? _chunkRBOER6EWcjs.workType.call(void 0, state.board, _nullishCoalesce(_nullishCoalesce(query.childKind, () => ( state.board.defaultWorkType)), () => ( "task"))).rank : void 0;
96
+ const rows = items.filter((item) => item.id !== query.excludeItemId && !blocked.has(item.id) && (query.kind !== "dependency" || item.status !== "cancelled") && (rank === void 0 || item.archivedAt === void 0 && !_chunkEYTVC2H5cjs.terminal.call(void 0, item) && _chunkRBOER6EWcjs.workType.call(void 0, state.board, item.planning.kind).rank < rank)).map((item) => ({
97
+ id: item.id,
98
+ label: item.title,
99
+ status: item.status,
100
+ archived: item.archivedAt !== void 0,
101
+ detail: [_chunk7KDOTBRXcjs.kanbanReference.call(void 0, state.board, item), item.planning.area, item.planning.milestone].filter(Boolean).join(" \xB7 ")
102
+ }));
103
+ return suggestionPage(state.board.revision, rows, query);
104
+ }
105
+ function dependents(items, id) {
106
+ const reverse = /* @__PURE__ */ new Map();
107
+ for (const item of items) for (const dependency of item.planning.dependencies) reverse.set(dependency, [..._nullishCoalesce(reverse.get(dependency), () => ( [])), item.id]);
108
+ const result = /* @__PURE__ */ new Set([id]), pending = [id];
109
+ while (pending.length) for (const next of _nullishCoalesce(reverse.get(pending.pop()), () => ( []))) if (!result.has(next)) {
110
+ result.add(next);
111
+ pending.push(next);
112
+ }
113
+ return result;
114
+ }
115
+
116
+ // src/queries.ts
117
+ function queryItems(state, query = {}, all = _chunkEYTVC2H5cjs.catalog.call(void 0, state), now = Date.now()) {
118
+ const { cursor, limit = 50, ...filters } = query;
119
+ _chunkRBOER6EWcjs.requireValue.call(void 0, Number.isSafeInteger(limit) && limit >= 1 && limit <= 200, "INVALID_PAGE_SIZE");
120
+ _chunkRBOER6EWcjs.requireValue.call(void 0, ["ready", "backlog", "in-progress", "review", "closed", "proposed", "archive", "all"].includes(_nullishCoalesce(query.view, () => ( "ready"))), "INVALID_VIEW");
121
+ for (const field of ["area", "milestone", "parentId", "kind", "sessionId", "search"]) if (filters[field] !== void 0) _chunkRBOER6EWcjs.requireValue.call(void 0, typeof filters[field] === "string" && filters[field].length <= 256, "INVALID_FILTER");
122
+ if (filters.reference) _chunkRBOER6EWcjs.requireValue.call(void 0, typeof filters.reference.system === "string" && typeof filters.reference.id === "string" && filters.reference.system.length <= 256 && filters.reference.id.length <= 256, "INVALID_REFERENCE_FILTER");
123
+ const fingerprint = JSON.stringify(Object.fromEntries(Object.entries({ ...filters, view: _nullishCoalesce(filters.view, () => ( "ready")) }).sort(([a], [b]) => a.localeCompare(b)))), revision = state.board.revision;
124
+ all = all.map((item) => ({ ...item, task: _chunkAOHQCX7Kcjs.projectKanbanTask.call(void 0, item, state.workflow, now, all) }));
125
+ const matches = all.filter((item) => matchesQuery(item, filters, state.board)).sort(filters.view === "archive" ? (a, b) => (_nullishCoalesce(b.archivedAt, () => ( 0))) - (_nullishCoalesce(a.archivedAt, () => ( 0))) || a.id.localeCompare(b.id) : order);
126
+ let start = 0;
127
+ if (cursor) {
128
+ _chunkRBOER6EWcjs.requireValue.call(void 0, typeof cursor === "string" && cursor.length <= 4e3, "INVALID_CURSOR");
129
+ let decoded;
130
+ try {
131
+ decoded = JSON.parse(cursor);
132
+ } catch (e) {
133
+ _chunkRBOER6EWcjs.requireValue.call(void 0, false, "INVALID_CURSOR");
134
+ }
135
+ _chunkRBOER6EWcjs.requireValue.call(void 0, decoded && decoded.filter === fingerprint, "CURSOR_SCOPE_MISMATCH");
136
+ _chunkRBOER6EWcjs.requireValue.call(void 0, decoded.revision === revision, "CURSOR_STALE");
137
+ start = matches.findIndex((i) => i.id === decoded.after) + 1;
138
+ _chunkRBOER6EWcjs.requireValue.call(void 0, start > 0, "INVALID_CURSOR");
139
+ }
140
+ const progress = progressByParent(state, all);
141
+ const items = matches.slice(start, start + limit).map((item) => ({ ...item, ...progress.has(item.id) ? { progress: progress.get(item.id) } : {} }));
142
+ return { revision, total: matches.length, items, ...start + items.length < matches.length ? {
143
+ nextCursor: JSON.stringify({ revision, after: items.at(-1).id, filter: fingerprint })
144
+ } : {} };
145
+ }
146
+ function order(a, b) {
147
+ return a.planning.priority - b.planning.priority || a.createdAt - b.createdAt || a.id.localeCompare(b.id);
148
+ }
149
+ function matchesQuery(item, query, board) {
150
+ if (item.deletedAt !== void 0 && !query.includeDeleted) return false;
151
+ if (query.phase && _optionalChain([item, 'access', _11 => _11.task, 'optionalAccess', _12 => _12.phase]) !== query.phase) return false;
152
+ if (!matchesView(item, _nullishCoalesce(query.view, () => ( "ready")))) return false;
153
+ for (const field of ["area", "milestone", "parentId", "kind"]) if (query[field] !== void 0 && item.planning[field] !== query[field]) return false;
154
+ if (query.reference && !_optionalChain([item, 'access', _13 => _13.externalRefs, 'optionalAccess', _14 => _14.some, 'call', _15 => _15((r) => r.system === query.reference.system && r.id === query.reference.id)])) return false;
155
+ if (query.sessionId && !item.sessionIds.includes(query.sessionId)) return false;
156
+ if (query.search && !`${_chunk7KDOTBRXcjs.kanbanReference.call(void 0, board, item)} ${item.id} ${item.title} ${_nullishCoalesce(item.planning.area, () => ( ""))}`.toLowerCase().includes(query.search.toLowerCase())) return false;
157
+ return true;
158
+ }
159
+ function matchesView(item, view) {
160
+ if (view === "archive") return item.archivedAt !== void 0;
161
+ if (view === "all") return true;
162
+ if (item.archivedAt !== void 0) return false;
163
+ if (view === "in-progress") return item.status === "claimed" || item.status === "review";
164
+ if (view === "closed") return _chunkEYTVC2H5cjs.terminal.call(void 0, item);
165
+ return item.status === { ready: "open", backlog: "backlog", review: "review", proposed: "proposed" }[view];
166
+ }
167
+ function overview(state, now, items = state.board.items.map(_chunkEYTVC2H5cjs.summary)) {
168
+ items = items.filter((item) => item.deletedAt === void 0);
169
+ const count = (status) => items.filter((i) => i.status === status).length;
170
+ const age = state.board.policy.backlogReviewMs;
171
+ return {
172
+ revision: state.board.revision,
173
+ visible: items.length,
174
+ active: items.filter((i) => !_chunkEYTVC2H5cjs.terminal.call(void 0, i)).length,
175
+ archived: _nullishCoalesce(_optionalChain([state, 'access', _16 => _16.archiveIndex, 'optionalAccess', _17 => _17.length]), () => ( 0)),
176
+ ready: count("open"),
177
+ inProgress: count("claimed") + count("review"),
178
+ backlog: count("backlog"),
179
+ proposed: count("proposed"),
180
+ closed: count("completed") + count("cancelled"),
181
+ dueForReview: age ? items.filter((i) => i.status === "backlog" && now - i.updatedAt >= age).length : 0,
182
+ limits: _chunkEYTVC2H5cjs.limits.call(void 0, state)
183
+ };
184
+ }
185
+ function progressByParent(state, all) {
186
+ const byId = new Map(all.map((i) => [i.id, i]));
187
+ const result = /* @__PURE__ */ new Map();
188
+ for (const item of all.filter((i) => _chunkRBOER6EWcjs.executable.call(void 0, state.board, i))) {
189
+ let parent = item.planning.parentId;
190
+ const seen = /* @__PURE__ */ new Set();
191
+ while (parent && !seen.has(parent)) {
192
+ seen.add(parent);
193
+ const progress = _nullishCoalesce(result.get(parent), () => ( { total: 0, completed: 0, cancelled: 0, inProgress: 0 }));
194
+ progress.total++;
195
+ if (item.status === "completed") progress.completed++;
196
+ if (item.status === "cancelled") progress.cancelled++;
197
+ if (item.status === "claimed" || item.status === "review") progress.inProgress++;
198
+ result.set(parent, progress);
199
+ parent = _optionalChain([byId, 'access', _18 => _18.get, 'call', _19 => _19(parent), 'optionalAccess', _20 => _20.planning, 'access', _21 => _21.parentId]);
200
+ }
201
+ }
202
+ return result;
203
+ }
204
+
205
+ // src/module.ts
206
+ function createKanbanModule(options) {
207
+ return new KanbanModule(options);
208
+ }
209
+ var KanbanModule = (_class = class {
210
+ constructor(options) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);_class.prototype.__init7.call(this);_class.prototype.__init8.call(this);_class.prototype.__init9.call(this);_class.prototype.__init10.call(this);_class.prototype.__init11.call(this);_class.prototype.__init12.call(this);_class.prototype.__init13.call(this);_class.prototype.__init14.call(this);_class.prototype.__init15.call(this);_class.prototype.__init16.call(this);_class.prototype.__init17.call(this);_class.prototype.__init18.call(this);_class.prototype.__init19.call(this);
211
+ this.options = options;
212
+ this.now = _nullishCoalesce(options.now, () => ( Date.now));
213
+ this.id = _nullishCoalesce(options.id, () => ( _chunkRSH6QSECcjs.randomUUID));
214
+ this.actor = _chunkRBOER6EWcjs.normalizeActor.call(void 0, options.actor);
215
+ }
216
+ __init() {this.stopping = false}
217
+ __init2() {this.pending = /* @__PURE__ */ new Set()}
218
+ async quiesce() {
219
+ this.stopping = true;
220
+ await Promise.allSettled([...this.pending]);
221
+ }
222
+ async dispose() {
223
+ await this.quiesce();
224
+ }
225
+
226
+
227
+
228
+ __init3() {this.workflow = (boardId) => this.guard(this.id(), boardId, "workflow", async () => structuredClone(await _asyncNullishCoalesce((await this.readView(boardId)).state.workflow, async () => ( { plans: {}, dispatches: {}, ready: [], completedPlans: [] }))))}
229
+ __init4() {this.resolve = (reference) => this.guard(this.id(), void 0, "resolve", () => resolveReference(reference, this.options.store, (id) => this.readView(id)))}
230
+ __init5() {this.suggest = (boardId, query) => this.guard(this.id(), boardId, "suggest", async () => {
231
+ const snapshot = structuredClone(query), limit = validateSuggestionQuery(snapshot), view = await this.readView(boardId), state = view.state;
232
+ const suggestions = () => suggestFromState(state, snapshot, [...view.items, ..._nullishCoalesce(state.archiveIndex, () => ( []))], view.records);
233
+ if (!this.options.suggestions) return suggestions();
234
+ const rows = await this.options.suggestions(structuredClone(state.board), snapshot.kind, _nullishCoalesce(_optionalChain([snapshot, 'access', _50 => _50.search, 'optionalAccess', _51 => _51.trim, 'call', _52 => _52()]), () => ( "")), limit + 1);
235
+ if (rows === void 0) return suggestions();
236
+ const known = suggestions().items.filter((row) => !rows.some((entry) => entry.id === row.id));
237
+ return suggestionPage(state.board.revision, [...rows, ...known], snapshot);
238
+ })}
239
+ __init6() {this.records = (boardId, query) => this.guard(this.id(), boardId, "records", async () => {
240
+ const view = await this.readView(boardId);
241
+ return structuredClone(_chunkEYTVC2H5cjs.queryRecords.call(void 0, view.state, query, view.records));
242
+ })}
243
+ __init7() {this.record = (boardId, recordId) => this.guard(this.id(), boardId, "record", async () => {
244
+ _chunkRBOER6EWcjs.identity.call(void 0, recordId);
245
+ const record = await _asyncOptionalChain([(await this.readState(boardId)), 'access', async _53 => _53.records, 'optionalAccess', async _54 => _54.find, 'call', async _55 => _55((record2) => record2.id === recordId)]);
246
+ _chunkRBOER6EWcjs.requireValue.call(void 0, record, "RECORD_NOT_FOUND");
247
+ return structuredClone(record);
248
+ })}
249
+ __init8() {this.currentPrincipal = async () => structuredClone(_chunkRBOER6EWcjs.principal.call(void 0, this.actor))}
250
+ __init9() {this.create = (input) => {
251
+ const snapshot = structuredClone(input);
252
+ return this.guard(this.id(), snapshot.id, "create", async () => {
253
+ const initial = _chunkEYTVC2H5cjs.createBoardState.call(void 0, snapshot, this.actor, this.now()), board = initial.board;
254
+ await this.authorize(board, "create");
255
+ const state = await this.options.store.update(board.id, `create:${board.id}`, JSON.stringify({ snapshot, actor: this.actor }), (previous) => {
256
+ _chunkRBOER6EWcjs.requireValue.call(void 0, !previous, "BOARD_EXISTS");
257
+ return initial;
258
+ });
259
+ return structuredClone(state.board);
260
+ });
261
+ }}
262
+ __init10() {this.read = (boardId) => this.guard(
263
+ this.id(),
264
+ boardId,
265
+ "read",
266
+ async () => structuredClone((await this.readState(boardId)).board)
267
+ )}
268
+ __init11() {this.catalog = () => this.guard(this.id(), void 0, "catalog", async () => {
269
+ const boards = [];
270
+ for (const id of await this.options.store.ids()) {
271
+ try {
272
+ const view = await this.readView(id);
273
+ if (view.state.board.deletedAt !== void 0) continue;
274
+ boards.push({ ...view.state.board, executionPhase: _chunkAOHQCX7Kcjs.boardControlPhase.call(void 0, { ...view.state.board, items: view.items }), items: view.items.filter((item) => item.deletedAt === void 0).map(({ id: id2, title, number }) => ({ id: id2, title, ...number === void 0 ? {} : { number } })) });
275
+ } catch (error) {
276
+ if (!(error instanceof _chunkRSH6QSECcjs.KanbanError) || error.code !== "FORBIDDEN") throw error;
277
+ }
278
+ }
279
+ return structuredClone(boards);
280
+ })}
281
+ __init12() {this.list = () => this.guard(this.id(), void 0, "list", async () => {
282
+ const boards = [];
283
+ for (const boardId of await this.options.store.ids()) {
284
+ try {
285
+ const board = (await this.readState(boardId)).board;
286
+ if (board.deletedAt === void 0) boards.push(structuredClone(board));
287
+ } catch (error) {
288
+ if (!(error instanceof _chunkRSH6QSECcjs.KanbanError) || error.code !== "FORBIDDEN") throw error;
289
+ }
290
+ }
291
+ return boards;
292
+ })}
293
+ __init13() {this.execute = (boardId, input) => this.guard(input.operationId, boardId, `execute:${input.command.type}`, async () => {
294
+ const request = structuredClone(input);
295
+ _chunkRBOER6EWcjs.identity.call(void 0, request.operationId);
296
+ const state = await this.readState(boardId);
297
+ await this.authorize(state.board, request.command.type, request.command);
298
+ _chunkRBOER6EWcjs.requireValue.call(void 0, state.board.deletedAt === void 0, "BOARD_DELETED");
299
+ if (request.command.type === "delete-board") await _optionalChain([this, 'access', _56 => _56.options, 'access', _57 => _57.verifyDeletion, 'optionalCall', _58 => _58(structuredClone(state.board))]);
300
+ await _optionalChain([this, 'access', _59 => _59.options, 'access', _60 => _60.verifyLifecycle, 'optionalCall', _61 => _61(structuredClone(state.board), request.command)]);
301
+ await this.verifyRelease(state.board, request.command);
302
+ const verifyStage = request.command.type === "observe-execution" && request.command.outcome === "succeeded" ? "observe" : request.command.type === "accept-execution" && request.command.accept ? "accept" : void 0;
303
+ if (verifyStage && "dispatchId" in request.command) {
304
+ const dispatch = _optionalChain([state, 'access', _62 => _62.workflow, 'optionalAccess', _63 => _63.dispatches, 'access', _64 => _64[request.command.dispatchId]]);
305
+ _chunkRBOER6EWcjs.requireValue.call(void 0, dispatch, "DISPATCH_NOT_FOUND");
306
+ const outputs = request.command.type === "observe-execution" ? request.command.outputs : dispatch.outputs;
307
+ if (_optionalChain([outputs, 'optionalAccess', _65 => _65.length])) _chunkRBOER6EWcjs.requireValue.call(void 0, await _optionalChain([this, 'access', _66 => _66.options, 'access', _67 => _67.verifyOutputs, 'optionalCall', _68 => _68({
308
+ stage: verifyStage,
309
+ board: state.board,
310
+ itemId: dispatch.itemId,
311
+ dispatchId: dispatch.id,
312
+ outputs: structuredClone(outputs)
313
+ })]), "OUTPUT_VERIFICATION_REQUIRED");
314
+ }
315
+ const restore = request.command.type === "restore-item" ? await this.options.store.archived(boardId, request.command.itemId) : void 0;
316
+ const result = await this.options.store.update(boardId, request.operationId, JSON.stringify({ actor: this.actor, request }), (current) => {
317
+ _chunkRBOER6EWcjs.requireValue.call(void 0, current && current.board.revision === state.board.revision, "REVISION_CONFLICT");
318
+ _chunkRBOER6EWcjs.requireValue.call(void 0, current.board.revision === request.expectedRevision, "REVISION_CONFLICT");
319
+ if (restore) current.restore = restore;
320
+ return _chunkEYTVC2H5cjs.reduce.call(void 0, current, request.command, this.actor, request.operationId, this.now(), this.id());
321
+ });
322
+ await this.publishNotifications(boardId, request.operationId);
323
+ return structuredClone(result.board);
324
+ })}
325
+ __init14() {this.events = (boardId, after = 0, limit = 100) => this.guard(this.id(), boardId, "events", async () => {
326
+ _chunkRBOER6EWcjs.requireValue.call(void 0, Number.isSafeInteger(after) && after >= 0 && Number.isSafeInteger(limit) && limit > 0 && limit <= 500, "INVALID_CURSOR");
327
+ const { state } = await this.readView(boardId);
328
+ return this.options.store.events(boardId, after, limit, state.sequence);
329
+ })}
330
+ __init15() {this.query = (boardId, query) => this.guard(this.id(), boardId, "query", async () => {
331
+ const view = await this.readView(boardId);
332
+ return structuredClone(queryItems(view.state, query, [...view.items, ..._nullishCoalesce(view.state.archiveIndex, () => ( []))], this.now()));
333
+ })}
334
+ __init16() {this.overview = (boardId) => this.guard(this.id(), boardId, "overview", async () => {
335
+ const view = await this.readView(boardId);
336
+ return overview(view.state, this.now(), view.items);
337
+ })}
338
+ __init17() {this.item = (boardId, itemId) => this.guard(this.id(), boardId, "item", async () => {
339
+ _chunkRBOER6EWcjs.identity.call(void 0, itemId);
340
+ const state = await this.readState(boardId), item = state.board.items.find((i) => i.id === itemId);
341
+ if (item) return { item: structuredClone(item), task: _chunkAOHQCX7Kcjs.projectKanbanTask.call(void 0, item, state.workflow, this.now(), [...state.board.items, ..._nullishCoalesce(state.archiveIndex, () => ( []))]) };
342
+ _chunkRBOER6EWcjs.requireValue.call(void 0, _optionalChain([state, 'access', _69 => _69.archiveIndex, 'optionalAccess', _70 => _70.some, 'call', _71 => _71((i) => i.id === itemId)]), "ITEM_NOT_FOUND");
343
+ const record = await this.options.store.archived(boardId, itemId);
344
+ _chunkRBOER6EWcjs.requireValue.call(void 0, record, "REVISION_CONFLICT");
345
+ return { item: structuredClone(record.item), task: _chunkAOHQCX7Kcjs.projectKanbanTask.call(void 0, record.item, state.workflow, this.now(), state.archiveIndex), archive: { archivedAt: record.archivedAt, reason: record.reason } };
346
+ })}
347
+ __init18() {this.related = (sessionId) => this.guard(this.id(), void 0, "related", async () => {
348
+ _chunkRBOER6EWcjs.identity.call(void 0, sessionId);
349
+ const result = [];
350
+ for (const board of await this.list()) {
351
+ const state = await this.readState(board.id);
352
+ if (_chunkEYTVC2H5cjs.related.call(void 0, board).includes(sessionId) || _optionalChain([state, 'access', _72 => _72.records, 'optionalAccess', _73 => _73.some, 'call', _74 => _74((record) => record.sources.some((source) => source.sessionId === sessionId))]) || _optionalChain([state, 'access', _75 => _75.archiveIndex, 'optionalAccess', _76 => _76.some, 'call', _77 => _77((i) => i.sessionIds.includes(sessionId))])) result.push(board);
353
+ }
354
+ return result;
355
+ })}
356
+ __init19() {this.scan = () => this.guard(this.id(), void 0, "scan", async () => {
357
+ const errors = [];
358
+ for (const id of await this.options.store.ids()) {
359
+ try {
360
+ const board = (await this.readState(id)).board;
361
+ if (board.deletedAt !== void 0) {
362
+ await this.publishNotifications(id, this.id());
363
+ continue;
364
+ }
365
+ await this.execute(board.id, { operationId: this.id(), expectedRevision: board.revision, command: { type: "scan" } });
366
+ } catch (error) {
367
+ if (!(error instanceof _chunkRSH6QSECcjs.KanbanError) || !["REVISION_CONFLICT", "FORBIDDEN"].includes(error.code)) errors.push(error);
368
+ }
369
+ }
370
+ if (errors.length) throw new AggregateError(errors, "KANBAN_SCAN_FAILED");
371
+ })}
372
+ async verifyRelease(board, command) {
373
+ if (command.type !== "release-item") return;
374
+ const item = board.items.find((i) => i.id === command.itemId);
375
+ _chunkRBOER6EWcjs.requireValue.call(void 0, _optionalChain([item, 'optionalAccess', _78 => _78.owner]) && item.owner.token === command.token, "OWNER_MISMATCH");
376
+ const own = _chunkRBOER6EWcjs.sameOwner.call(void 0, item.owner, this.actor), actor = _chunkRBOER6EWcjs.principal.call(void 0, this.actor);
377
+ const coordinator = _optionalChain([board, 'access', _79 => _79.members, 'optionalAccess', _80 => _80.some, 'call', _81 => _81((m) => _chunkRBOER6EWcjs.principalKey.call(void 0, m) === _chunkRBOER6EWcjs.principalKey.call(void 0, actor) && _optionalChain([m, 'access', _82 => _82.roles, 'optionalAccess', _83 => _83.includes, 'call', _84 => _84("coordinator")]))]);
378
+ _chunkRBOER6EWcjs.requireValue.call(void 0, own || coordinator, "OWNER_MISMATCH");
379
+ if (!(own && actor.kind === "human")) _chunkRBOER6EWcjs.requireValue.call(void 0, await _optionalChain([this, 'access', _85 => _85.options, 'access', _86 => _86.verifyRelease, 'optionalCall', _87 => _87({ actor: this.actor, board: structuredClone(board), item: structuredClone(item) })]), "EXECUTION_NOT_SETTLED");
380
+ }
381
+ async publishNotifications(boardId, operationId) {
382
+ if (!this.options.onCommitted) return;
383
+ const started = this.now();
384
+ try {
385
+ await this.options.onCommitted(boardId, this, this.now());
386
+ this.log(operationId, boardId, "notify", started, "success");
387
+ } catch (error) {
388
+ this.log(operationId, boardId, "notify", started, "failure", safeCode(error, "NOTIFICATION_PROJECTION_FAILED"));
389
+ }
390
+ }
391
+ async readView(boardId) {
392
+ _chunkRBOER6EWcjs.identity.call(void 0, boardId);
393
+ if (!this.options.store.view) return _chunkEYTVC2H5cjs.viewFromState.call(void 0, await this.readState(boardId));
394
+ const view = await this.options.store.view(boardId);
395
+ _chunkRBOER6EWcjs.requireValue.call(void 0, view, "BOARD_NOT_FOUND");
396
+ await this.authorize(view.state.board, "read");
397
+ return view;
398
+ }
399
+ async readState(boardId) {
400
+ _chunkRBOER6EWcjs.identity.call(void 0, boardId);
401
+ const state = await this.options.store.read(boardId);
402
+ _chunkRBOER6EWcjs.requireValue.call(void 0, state, "BOARD_NOT_FOUND");
403
+ await this.authorize(state.board, "read");
404
+ return state;
405
+ }
406
+ authorize(board, action, command) {
407
+ return this.options.authorize({ actor: structuredClone(this.actor), board: structuredClone(board), action, command: structuredClone(command) });
408
+ }
409
+ async guard(operationId, boardId, stage, run) {
410
+ const start = this.now();
411
+ if (this.stopping) {
412
+ this.log(operationId, boardId, stage, start, "failure", "SERVICE_STOPPING");
413
+ throw new (0, _chunkRSH6QSECcjs.KanbanError)("SERVICE_STOPPING");
414
+ }
415
+ const operation = Promise.resolve().then(run);
416
+ this.pending.add(operation);
417
+ try {
418
+ const result = await operation;
419
+ this.log(operationId, boardId, stage, start, "success");
420
+ return result;
421
+ } catch (error) {
422
+ this.log(operationId, boardId, stage, start, "failure", safeCode(error, "PORT_FAILURE"));
423
+ throw error;
424
+ } finally {
425
+ this.pending.delete(operation);
426
+ }
427
+ }
428
+ log(operationId, boardId, stage, start, result, code) {
429
+ try {
430
+ this.options.log({ operationId, boardId, stage, result, code, durationMs: this.now() - start });
431
+ } catch (e2) {
432
+ }
433
+ }
434
+ }, _class);
435
+ function safeCode(error, fallback) {
436
+ const code = _optionalChain([error, 'optionalAccess', _88 => _88.code]);
437
+ return typeof code === "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(code) ? code : fallback;
438
+ }
439
+
440
+
441
+
442
+ exports.createKanbanModule = createKanbanModule;
443
+ //# sourceMappingURL=chunk-DINLR6E6.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/li/share/prj/x1/libraries/kanban/dist/chunk-DINLR6E6.cjs","../src/references.ts","../src/suggestions.ts","../src/queries.ts","../src/module.ts"],"names":["item","record"],"mappings":"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","file":"/home/li/share/prj/x1/libraries/kanban/dist/chunk-DINLR6E6.cjs","sourcesContent":[null,"import {kanbanReference, type KanbanReferenceTarget} from './contracts';\nimport type {KanbanReadView, KanbanStore} from './ports';\nimport {requireValue} from './rules';\n\nexport async function resolveReference(reference: string, store: KanbanStore, read: (id: string) => Promise<KanbanReadView>): Promise<KanbanReferenceTarget> {\n requireValue(typeof reference === 'string' && reference.length <= 100, 'INVALID_REFERENCE');\n const match = /^KB-([1-9]\\d*)(?:#([1-9]\\d*)|\\/(L|D)-([1-9]\\d*))?$/i.exec(reference.trim());\n requireValue(match && [match[1], match[2], match[4]].every(value => value === undefined || Number.isSafeInteger(Number(value))), 'INVALID_REFERENCE');\n const id = await store.boardId?.(Number(match[1])); requireValue(id, 'REFERENCE_NOT_FOUND');\n const view = await read(id), board = view.state.board;\n requireValue(board.number === Number(match[1]), 'INVALID_NUMBERING');\n const target: KanbanReferenceTarget = {boardId: id, scope: board.scope, reference: kanbanReference(board)};\n if (match[2]) {\n const item = [...view.items, ...(view.state.archiveIndex ?? [])].find(item => item.number === Number(match[2]));\n requireValue(item, 'REFERENCE_NOT_FOUND'); target.itemId = item.id; target.reference = kanbanReference(board, item);\n } else if (match[3]) {\n const record = view.records.find(record => record.kind === (match[3].toUpperCase() === 'L' ? 'log' : 'decision') && record.number === Number(match[4]));\n requireValue(record, 'REFERENCE_NOT_FOUND'); target.recordId = record.id; target.reference = kanbanReference(board, record);\n }\n return target;\n}\n","import {kanbanReference} from './contracts';\nimport type {KanbanSuggestion, KanbanSuggestionPage, KanbanSuggestionQuery} from './contracts';\nimport type {KanbanState} from './ports';\nimport {recordSummary} from './project-records';\nimport {catalog, terminal} from './planning';\nimport {workType} from './configuration';\nimport {identity, requireValue} from './rules';\n\nexport function validateSuggestionQuery(query: KanbanSuggestionQuery): number {\n requireValue(query && ['item', 'parent', 'dependency', 'area', 'milestone', 'decision', 'session'].includes(query.kind), 'INVALID_SUGGESTION_KIND');\n requireValue(query.search === undefined || typeof query.search === 'string' && query.search.length <= 256, 'INVALID_FILTER');\n const limit = query.limit ?? 20; requireValue(Number.isSafeInteger(limit) && limit > 0 && limit <= 50, 'INVALID_LIMIT');\n if (query.excludeItemId !== undefined) identity(query.excludeItemId);\n if (query.excludeIds !== undefined) {requireValue(Array.isArray(query.excludeIds) && query.excludeIds.length <= 100, 'INVALID_FILTER'); query.excludeIds.forEach(identity);}\n if (query.childKind !== undefined) identity(query.childKind);\n return limit;\n}\nexport function suggestionPage(revision: number, rows: KanbanSuggestion[], query: KanbanSuggestionQuery): KanbanSuggestionPage {\n const limit = validateSuggestionQuery(query), search = query.search?.trim().toLocaleLowerCase() ?? '';\n const matched = rows.filter(row => !query.excludeIds?.includes(row.id)).filter(row => `${row.id} ${row.label} ${row.detail ?? ''}`.toLocaleLowerCase().includes(search))\n .sort((a, b) => score(a.label, search) - score(b.label, search) || a.label.localeCompare(b.label) || a.id.localeCompare(b.id));\n return {revision, items: matched.slice(0, limit).map(row => ({id: row.id, label: row.label.slice(0, 256), ...(row.detail ? {detail: row.detail.slice(0, 512)} : {}), ...(row.status ? {status: row.status} : {}), ...(row.archived !== undefined ? {archived: row.archived} : {})})), hasMore: matched.length > limit};\n}\nconst score = (label: string, query: string) => label.toLocaleLowerCase() === query ? 0 : label.toLocaleLowerCase().startsWith(query) ? 1 : 2;\nexport function suggestFromState(state: KanbanState, query: KanbanSuggestionQuery, items = catalog(state), records = (state.records ?? []).map(recordSummary)): KanbanSuggestionPage {\n validateSuggestionQuery(query);\n if (query.kind === 'area' || query.kind === 'milestone') {\n const names = [...new Set(items.map(item => item.planning[query.kind as 'area' | 'milestone']).filter((name): name is string => !!name))];\n return suggestionPage(state.board.revision, names.map(name => ({id: name, label: name})), query);\n }\n if (query.kind === 'decision') return suggestionPage(state.board.revision, records\n .filter(record => record.kind === 'decision' && record.status === 'confirmed').map(record => ({id: record.id, label: record.title, detail: kanbanReference(state.board, record)})), query);\n if (query.kind === 'session') {\n const ids = new Set([...state.board.participants.map(p => p.sessionId), ...items.flatMap(item => item.sessionIds),\n ...records.flatMap(record => record.sessionIds)]);\n return suggestionPage(state.board.revision, [...ids].map(id => ({id, label: id})), query);\n }\n const blocked = query.kind === 'dependency' && query.excludeItemId ? dependents(items, query.excludeItemId) : new Set<string>();\n const rank = query.kind === 'parent' ? workType(state.board, query.childKind ?? state.board.defaultWorkType ?? 'task').rank : undefined;\n const rows = items.filter(item => item.id !== query.excludeItemId && !blocked.has(item.id)\n && (query.kind !== 'dependency' || item.status !== 'cancelled')\n && (rank === undefined || item.archivedAt === undefined && !terminal(item) && workType(state.board, item.planning.kind).rank < rank))\n .map(item => ({id: item.id, label: item.title, status: item.status, archived: item.archivedAt !== undefined,\n detail: [kanbanReference(state.board, item), item.planning.area, item.planning.milestone].filter(Boolean).join(' · ')}));\n return suggestionPage(state.board.revision, rows, query);\n}\nfunction dependents(items: import('./contracts').KanbanItemSummary[], id: string): Set<string> {\n const reverse = new Map<string, string[]>();\n for (const item of items) for (const dependency of item.planning.dependencies) reverse.set(dependency, [...(reverse.get(dependency) ?? []), item.id]);\n const result = new Set([id]), pending = [id];\n while (pending.length) for (const next of reverse.get(pending.pop()!) ?? []) if (!result.has(next)) {result.add(next); pending.push(next);}\n return result;\n}\n","import {projectKanbanTask} from './contracts/task-projection';\nimport {kanbanReference} from './contracts';\nimport type {KanbanItemPage, KanbanItemQuery, KanbanItemSummary, KanbanOverview} from './contracts';\nimport type {KanbanState} from './ports';\nimport {requireValue} from './rules';\nimport {executable} from './configuration';\nimport {catalog, limits, summary, terminal} from './planning';\n\nexport function queryItems(state: KanbanState, query: KanbanItemQuery = {}, all = catalog(state), now = Date.now()): KanbanItemPage {\n const {cursor, limit = 50, ...filters} = query;\n requireValue(Number.isSafeInteger(limit) && limit >= 1 && limit <= 200, 'INVALID_PAGE_SIZE');\n requireValue(['ready', 'backlog', 'in-progress', 'review', 'closed', 'proposed', 'archive', 'all'].includes(query.view ?? 'ready'), 'INVALID_VIEW');\n for (const field of ['area', 'milestone', 'parentId', 'kind', 'sessionId', 'search'] as const) if (filters[field] !== undefined) requireValue(typeof filters[field] === 'string' && filters[field]!.length <= 256, 'INVALID_FILTER');\n if (filters.reference) requireValue(typeof filters.reference.system === 'string' && typeof filters.reference.id === 'string' && filters.reference.system.length <= 256 && filters.reference.id.length <= 256, 'INVALID_REFERENCE_FILTER');\n const fingerprint = JSON.stringify(Object.fromEntries(Object.entries({...filters, view: filters.view ?? 'ready'}).sort(([a], [b]) => a.localeCompare(b)))), revision = state.board.revision;\n all = all.map(item => ({...item, task: projectKanbanTask(item, state.workflow, now, all)}));\n const matches = all.filter(item => matchesQuery(item, filters, state.board)).sort(filters.view === 'archive'\n ? (a, b) => (b.archivedAt ?? 0) - (a.archivedAt ?? 0) || a.id.localeCompare(b.id) : order);\n let start = 0;\n if (cursor) {\n requireValue(typeof cursor === 'string' && cursor.length <= 4000, 'INVALID_CURSOR');\n let decoded: {revision: number; after: string; filter: string};\n try {decoded = JSON.parse(cursor);} catch {requireValue(false, 'INVALID_CURSOR');}\n requireValue(decoded && decoded.filter === fingerprint, 'CURSOR_SCOPE_MISMATCH');\n requireValue(decoded.revision === revision, 'CURSOR_STALE');\n start = matches.findIndex(i => i.id === decoded.after) + 1; requireValue(start > 0, 'INVALID_CURSOR');\n }\n const progress = progressByParent(state, all);\n const items = matches.slice(start, start + limit).map(item => ({...item, ...(progress.has(item.id) ? {progress: progress.get(item.id)} : {})}));\n return {revision, total: matches.length, items, ...(start + items.length < matches.length ? {\n nextCursor: JSON.stringify({revision, after: items.at(-1)!.id, filter: fingerprint}),\n } : {})};\n}\nfunction order(a: KanbanItemSummary, b: KanbanItemSummary): number {\n return a.planning.priority - b.planning.priority || a.createdAt - b.createdAt || a.id.localeCompare(b.id);\n}\nfunction matchesQuery(item: KanbanItemSummary, query: Omit<KanbanItemQuery, 'cursor' | 'limit'>, board: KanbanState['board']): boolean {\n if (item.deletedAt !== undefined && !query.includeDeleted) return false;\n if (query.phase && item.task?.phase !== query.phase) return false;\n if (!matchesView(item, query.view ?? 'ready')) return false;\n for (const field of ['area', 'milestone', 'parentId', 'kind'] as const) if (query[field] !== undefined && item.planning[field] !== query[field]) return false;\n if (query.reference && !item.externalRefs?.some(r => r.system === query.reference!.system && r.id === query.reference!.id)) return false;\n if (query.sessionId && !item.sessionIds.includes(query.sessionId)) return false;\n if (query.search && !`${kanbanReference(board, item)} ${item.id} ${item.title} ${item.planning.area ?? ''}`.toLowerCase().includes(query.search.toLowerCase())) return false;\n return true;\n}\nfunction matchesView(item: KanbanItemSummary, view: KanbanItemQuery['view']): boolean {\n if (view === 'archive') return item.archivedAt !== undefined;\n if (view === 'all') return true;\n if (item.archivedAt !== undefined) return false;\n if (view === 'in-progress') return item.status === 'claimed' || item.status === 'review';\n if (view === 'closed') return terminal(item);\n return item.status === ({ready: 'open', backlog: 'backlog', review: 'review', proposed: 'proposed'} as Record<string, string>)[view!];\n}\nexport function overview(state: KanbanState, now: number, items = state.board.items.map(summary)): KanbanOverview {\n items = items.filter(item => item.deletedAt === undefined);\n const count = (status: string) => items.filter(i => i.status === status).length;\n const age = state.board.policy.backlogReviewMs;\n return {revision: state.board.revision, visible: items.length, active: items.filter(i => !terminal(i)).length, archived: state.archiveIndex?.length ?? 0, ready: count('open'),\n inProgress: count('claimed') + count('review'), backlog: count('backlog'), proposed: count('proposed'),\n closed: count('completed') + count('cancelled'), dueForReview: age ? items.filter(i => i.status === 'backlog' && now - i.updatedAt >= age).length : 0,\n limits: limits(state)};\n}\n\nfunction progressByParent(state: KanbanState, all: KanbanItemSummary[]): Map<string, NonNullable<KanbanItemSummary['progress']>> {\n const byId = new Map(all.map(i => [i.id, i]));\n const result = new Map<string, NonNullable<KanbanItemSummary['progress']>>();\n for (const item of all.filter(i => executable(state.board, i))) {\n let parent = item.planning.parentId; const seen = new Set<string>();\n while (parent && !seen.has(parent)) {\n seen.add(parent); const progress = result.get(parent) ?? {total: 0, completed: 0, cancelled: 0, inProgress: 0};\n progress.total++; if (item.status === 'completed') progress.completed++; if (item.status === 'cancelled') progress.cancelled++;\n if (item.status === 'claimed' || item.status === 'review') progress.inProgress++;\n result.set(parent, progress); parent = byId.get(parent)?.planning.parentId;\n }\n }\n return result;\n}\n","import {boardControlPhase} from './contracts/board-control';\nimport {projectKanbanTask} from './contracts/task-projection';\nimport {resolveReference} from './references';\nimport {suggestFromState, suggestionPage, validateSuggestionQuery} from './suggestions';\nimport {queryRecords} from './project-records';\nimport { randomUUID } from './utils';\nimport type { KanbanBoard, KanbanRequest, KanbanService, KanbanItemQuery, KanbanBoardInput } from './contracts';\nimport { KanbanError, type KanbanModuleOptions, type KanbanState } from './ports';\nimport { identity, requireValue } from './rules';\nimport {createBoardState} from './board';\nimport {normalizeActor, principal, principalKey, sameOwner} from './identity';\nimport {queryItems, overview} from './queries';\nimport {viewFromState} from './storage-codec';\nimport { reduce, related } from './reducer';\n\n/** Optional host-owned service; no timer or model calls are started implicitly. */\nexport function createKanbanModule(options: KanbanModuleOptions): KanbanModuleHandle { return new KanbanModule(options); }\nexport interface KanbanModuleHandle extends KanbanService {quiesce(): Promise<void>; dispose(): Promise<void>}\nclass KanbanModule implements KanbanService {\n private stopping = false;\n private readonly pending = new Set<Promise<unknown>>();\n async quiesce(): Promise<void> {this.stopping = true; await Promise.allSettled([...this.pending]);}\n async dispose(): Promise<void> {await this.quiesce();}\n private readonly now;\n private readonly id;\n private readonly actor;\n constructor(private readonly options: KanbanModuleOptions) {\n this.now = options.now ?? Date.now; this.id = options.id ?? randomUUID;\n this.actor = normalizeActor(options.actor);\n }\n workflow = (boardId: string) => this.guard(this.id(), boardId, 'workflow', async () => structuredClone((await this.readView(boardId)).state.workflow ?? {plans:{},dispatches:{},ready:[],completedPlans:[]}));\n resolve = (reference: string) => this.guard(this.id(), undefined, 'resolve', () => resolveReference(reference, this.options.store, id => this.readView(id)));\n suggest = (boardId: string, query: import('./contracts').KanbanSuggestionQuery) => this.guard(this.id(), boardId, 'suggest', async () => {\n const snapshot = structuredClone(query), limit = validateSuggestionQuery(snapshot), view = await this.readView(boardId), state = view.state;\n const suggestions = () => suggestFromState(state, snapshot, [...view.items, ...(state.archiveIndex ?? [])], view.records);\n if (!this.options.suggestions) return suggestions();\n const rows = await this.options.suggestions(structuredClone(state.board), snapshot.kind, snapshot.search?.trim() ?? '', limit + 1);\n if (rows === undefined) return suggestions();\n const known = suggestions().items.filter(row => !rows.some(entry => entry.id === row.id));\n return suggestionPage(state.board.revision, [...rows, ...known], snapshot);\n });\n records = (boardId: string, query?: import('./contracts').KanbanRecordQuery) => this.guard(this.id(), boardId, 'records', async () => {const view = await this.readView(boardId); return structuredClone(queryRecords(view.state, query, view.records));});\n record = (boardId: string, recordId: string) => this.guard(this.id(), boardId, 'record', async () => {\n identity(recordId); const record = (await this.readState(boardId)).records?.find(record => record.id === recordId);\n requireValue(record, 'RECORD_NOT_FOUND'); return structuredClone(record);\n });\n currentPrincipal = async () => structuredClone(principal(this.actor));\n create = (input: KanbanBoardInput): Promise<KanbanBoard> => {\n const snapshot = structuredClone(input);\n return this.guard(this.id(), snapshot.id, 'create', async () => {\n const initial = createBoardState(snapshot, this.actor, this.now()), board = initial.board;\n await this.authorize(board, 'create');\n const state = await this.options.store.update(board.id, `create:${board.id}`, JSON.stringify({ snapshot, actor: this.actor }), previous => {\n requireValue(!previous, 'BOARD_EXISTS'); return initial;\n });\n return structuredClone(state.board);\n });\n };\n read = (boardId: string): Promise<KanbanBoard> => this.guard(this.id(), boardId, 'read',\n async () => structuredClone((await this.readState(boardId)).board));\n catalog = () => this.guard(this.id(), undefined, 'catalog', async () => {\n const boards: import('./contracts').KanbanBoardSummary[] = [];\n for (const id of await this.options.store.ids()) {\n try {const view = await this.readView(id); if (view.state.board.deletedAt !== undefined) continue; boards.push({...view.state.board, executionPhase: boardControlPhase({...view.state.board, items: view.items}), items: view.items.filter(item => item.deletedAt === undefined).map(({id, title, number}) => ({id, title, ...(number === undefined ? {} : {number})}))});}\n catch (error) {if (!(error instanceof KanbanError) || error.code !== 'FORBIDDEN') throw error;}\n }\n return structuredClone(boards);\n });\n list = (): Promise<KanbanBoard[]> => this.guard(this.id(), undefined, 'list', async () => {\n const boards: KanbanBoard[] = [];\n for (const boardId of await this.options.store.ids()) {\n try { const board = (await this.readState(boardId)).board; if (board.deletedAt === undefined) boards.push(structuredClone(board)); }\n catch (error) { if (!(error instanceof KanbanError) || error.code !== 'FORBIDDEN') throw error; }\n }\n return boards;\n });\n execute = (boardId: string, input: KanbanRequest): Promise<KanbanBoard> => this.guard(input.operationId, boardId, `execute:${input.command.type}`, async () => {\n const request = structuredClone(input); identity(request.operationId);\n const state = await this.readState(boardId);\n await this.authorize(state.board, request.command.type, request.command);\n requireValue(state.board.deletedAt === undefined, 'BOARD_DELETED');\n if (request.command.type === 'delete-board') await this.options.verifyDeletion?.(structuredClone(state.board));\n await this.options.verifyLifecycle?.(structuredClone(state.board), request.command);\n await this.verifyRelease(state.board, request.command);\n const verifyStage = request.command.type === 'observe-execution' && request.command.outcome === 'succeeded' ? 'observe'\n : request.command.type === 'accept-execution' && request.command.accept ? 'accept' : undefined;\n if (verifyStage && 'dispatchId' in request.command) {\n const dispatch = state.workflow?.dispatches[request.command.dispatchId]; requireValue(dispatch, 'DISPATCH_NOT_FOUND');\n const outputs = request.command.type === 'observe-execution' ? request.command.outputs : dispatch.outputs;\n if (outputs?.length) requireValue(await this.options.verifyOutputs?.({stage: verifyStage, board: state.board, itemId: dispatch.itemId,\n dispatchId: dispatch.id, outputs: structuredClone(outputs)}), 'OUTPUT_VERIFICATION_REQUIRED');\n }\n const restore = request.command.type === 'restore-item' ? await this.options.store.archived(boardId, request.command.itemId) : undefined;\n const result = await this.options.store.update(boardId, request.operationId, JSON.stringify({ actor: this.actor, request }), current => {\n requireValue(current && current.board.revision === state.board.revision, 'REVISION_CONFLICT');\n requireValue(current.board.revision === request.expectedRevision, 'REVISION_CONFLICT');\n if (restore) current.restore = restore;\n return reduce(current, request.command, this.actor, request.operationId, this.now(), this.id());\n });\n await this.publishNotifications(boardId, request.operationId);\n return structuredClone(result.board);\n });\n events = (boardId: string, after = 0, limit = 100) => this.guard(this.id(), boardId, 'events', async () => {\n requireValue(Number.isSafeInteger(after) && after >= 0 && Number.isSafeInteger(limit) && limit > 0 && limit <= 500, 'INVALID_CURSOR');\n const {state} = await this.readView(boardId);\n return this.options.store.events(boardId, after, limit, state.sequence);\n });\n query = (boardId: string, query?: KanbanItemQuery) => this.guard(this.id(), boardId, 'query', async () => {const view = await this.readView(boardId); return structuredClone(queryItems(view.state, query, [...view.items, ...(view.state.archiveIndex ?? [])], this.now()));});\n overview = (boardId: string) => this.guard(this.id(), boardId, 'overview', async () => {const view = await this.readView(boardId); return overview(view.state, this.now(), view.items);});\n item = (boardId: string, itemId: string) => this.guard(this.id(), boardId, 'item', async () => {\n identity(itemId); const state = await this.readState(boardId), item = state.board.items.find(i => i.id === itemId);\n if (item) return {item: structuredClone(item),task:projectKanbanTask(item,state.workflow,this.now(),[...state.board.items,...state.archiveIndex??[]])};\n requireValue(state.archiveIndex?.some(i => i.id === itemId), 'ITEM_NOT_FOUND');\n const record = await this.options.store.archived(boardId, itemId); requireValue(record, 'REVISION_CONFLICT');\n return {item: structuredClone(record.item), task:projectKanbanTask(record.item,state.workflow,this.now(),state.archiveIndex), archive: {archivedAt: record.archivedAt, reason: record.reason}};\n });\n related = (sessionId: string): Promise<KanbanBoard[]> => this.guard(this.id(), undefined, 'related', async () => {\n identity(sessionId); const result: KanbanBoard[] = [];\n for (const board of await this.list()) {\n const state = await this.readState(board.id);\n if (related(board).includes(sessionId) || state.records?.some(record => record.sources.some(source => source.sessionId === sessionId)) || state.archiveIndex?.some(i => i.sessionIds.includes(sessionId))) result.push(board);\n }\n return result;\n });\n scan = (): Promise<void> => this.guard(this.id(), undefined, 'scan', async () => {\n const errors: unknown[] = [];\n for (const id of await this.options.store.ids()) {\n try {\n const board = (await this.readState(id)).board;\n if (board.deletedAt !== undefined) {await this.publishNotifications(id,this.id()); continue;}\n await this.execute(board.id, { operationId: this.id(), expectedRevision: board.revision, command: { type: 'scan' } });\n }\n catch (error) {\n if (!(error instanceof KanbanError) || !['REVISION_CONFLICT','FORBIDDEN'].includes(error.code)) errors.push(error);\n }\n }\n if (errors.length) throw new AggregateError(errors, 'KANBAN_SCAN_FAILED');\n });\n private async verifyRelease(board: KanbanBoard, command: KanbanRequest['command']): Promise<void> {\n if (command.type !== 'release-item') return;\n const item = board.items.find(i => i.id === command.itemId); requireValue(item?.owner && item.owner.token === command.token, 'OWNER_MISMATCH');\n const own = sameOwner(item.owner, this.actor), actor = principal(this.actor);\n const coordinator = board.members?.some(m => principalKey(m) === principalKey(actor) && m.roles?.includes('coordinator'));\n requireValue(own || coordinator, 'OWNER_MISMATCH');\n if (!(own && actor.kind === 'human')) requireValue(await this.options.verifyRelease?.({actor: this.actor, board: structuredClone(board), item: structuredClone(item)}), 'EXECUTION_NOT_SETTLED');\n }\n private async publishNotifications(boardId: string, operationId: string): Promise<void> {\n if (!this.options.onCommitted) return;\n const started = this.now();\n try {await this.options.onCommitted(boardId, this, this.now()); this.log(operationId, boardId, 'notify', started, 'success');}\n catch (error) {this.log(operationId, boardId, 'notify', started, 'failure', safeCode(error, 'NOTIFICATION_PROJECTION_FAILED'));}\n }\n private async readView(boardId: string) {\n identity(boardId);\n if (!this.options.store.view) return viewFromState(await this.readState(boardId));\n const view = await this.options.store.view(boardId); requireValue(view, 'BOARD_NOT_FOUND');\n await this.authorize(view.state.board, 'read'); return view;\n }\n private async readState(boardId: string): Promise<KanbanState> {\n identity(boardId); const state = await this.options.store.read(boardId); requireValue(state, 'BOARD_NOT_FOUND');\n await this.authorize(state.board, 'read'); return state;\n }\n private authorize(board: KanbanBoard, action: Parameters<KanbanModuleOptions['authorize']>[0]['action'], command?: KanbanRequest['command']) {\n return this.options.authorize({ actor: structuredClone(this.actor), board: structuredClone(board), action, command: structuredClone(command) });\n }\n private async guard<T>(operationId: string, boardId: string | undefined, stage: string, run: () => Promise<T>): Promise<T> {\n const start = this.now();\n if (this.stopping) {this.log(operationId,boardId,stage,start,'failure','SERVICE_STOPPING'); throw new KanbanError('SERVICE_STOPPING');}\n const operation = Promise.resolve().then(run); this.pending.add(operation);\n try { const result = await operation; this.log(operationId, boardId, stage, start, 'success'); return result; }\n catch (error) {\n this.log(operationId, boardId, stage, start, 'failure', safeCode(error, 'PORT_FAILURE')); throw error;\n } finally {this.pending.delete(operation);}\n }\n private log(operationId: string, boardId: string | undefined, stage: string, start: number, result: 'success' | 'failure', code?: string): void {\n try { this.options.log({ operationId, boardId, stage, result, code, durationMs: this.now() - start }); }\n catch { /* Diagnostics cannot undo a commit. */ }\n }\n}\n\nfunction safeCode(error: unknown, fallback: string): string {\n const code = (error as {code?: unknown})?.code;\n return typeof code === 'string' && /^[A-Z][A-Z0-9_]{0,79}$/.test(code) ? code : fallback;\n}\n"]}