@straiffi/archon 1.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.
Files changed (124) hide show
  1. package/README.md +224 -0
  2. package/dist/cli.js +216 -0
  3. package/dist/client/assets/index-8_-boBBA.css +2 -0
  4. package/dist/client/assets/index-s_jjeqha.js +176 -0
  5. package/dist/client/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  6. package/dist/client/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  7. package/dist/client/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  8. package/dist/client/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  9. package/dist/client/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  10. package/dist/client/favicon.svg +62 -0
  11. package/dist/client/icons.svg +24 -0
  12. package/dist/client/index.html +14 -0
  13. package/dist/server/db.js +764 -0
  14. package/dist/server/db.js.map +1 -0
  15. package/dist/server/index.js +5134 -0
  16. package/dist/server/index.js.map +1 -0
  17. package/dist/server/lib/agent.js +1302 -0
  18. package/dist/server/lib/agent.js.map +1 -0
  19. package/dist/server/lib/buildChains.js +2 -0
  20. package/dist/server/lib/buildChains.js.map +1 -0
  21. package/dist/server/lib/buildFlow.js +59 -0
  22. package/dist/server/lib/buildFlow.js.map +1 -0
  23. package/dist/server/lib/buildSequences.js +599 -0
  24. package/dist/server/lib/buildSequences.js.map +1 -0
  25. package/dist/server/lib/bundleActivity.js +95 -0
  26. package/dist/server/lib/bundleActivity.js.map +1 -0
  27. package/dist/server/lib/bundlePullRequests.js +126 -0
  28. package/dist/server/lib/bundlePullRequests.js.map +1 -0
  29. package/dist/server/lib/chatMessages.js +60 -0
  30. package/dist/server/lib/chatMessages.js.map +1 -0
  31. package/dist/server/lib/chatTargets.js +123 -0
  32. package/dist/server/lib/chatTargets.js.map +1 -0
  33. package/dist/server/lib/chatTicketProposals.js +180 -0
  34. package/dist/server/lib/chatTicketProposals.js.map +1 -0
  35. package/dist/server/lib/chats.js +279 -0
  36. package/dist/server/lib/chats.js.map +1 -0
  37. package/dist/server/lib/config.js +3 -0
  38. package/dist/server/lib/config.js.map +1 -0
  39. package/dist/server/lib/cors.js +30 -0
  40. package/dist/server/lib/cors.js.map +1 -0
  41. package/dist/server/lib/directoryPicker.js +174 -0
  42. package/dist/server/lib/directoryPicker.js.map +1 -0
  43. package/dist/server/lib/git.js +1284 -0
  44. package/dist/server/lib/git.js.map +1 -0
  45. package/dist/server/lib/integrations/github.js +511 -0
  46. package/dist/server/lib/integrations/github.js.map +1 -0
  47. package/dist/server/lib/integrations/index.js +162 -0
  48. package/dist/server/lib/integrations/index.js.map +1 -0
  49. package/dist/server/lib/integrations/jira.js +283 -0
  50. package/dist/server/lib/integrations/jira.js.map +1 -0
  51. package/dist/server/lib/integrations/planning.js +27 -0
  52. package/dist/server/lib/integrations/planning.js.map +1 -0
  53. package/dist/server/lib/integrations/types.js +2 -0
  54. package/dist/server/lib/integrations/types.js.map +1 -0
  55. package/dist/server/lib/lightweightPrompt.js +88 -0
  56. package/dist/server/lib/lightweightPrompt.js.map +1 -0
  57. package/dist/server/lib/models.js +219 -0
  58. package/dist/server/lib/models.js.map +1 -0
  59. package/dist/server/lib/preview.js +377 -0
  60. package/dist/server/lib/preview.js.map +1 -0
  61. package/dist/server/lib/previewProxy.js +659 -0
  62. package/dist/server/lib/previewProxy.js.map +1 -0
  63. package/dist/server/lib/projectAutoConfig.js +682 -0
  64. package/dist/server/lib/projectAutoConfig.js.map +1 -0
  65. package/dist/server/lib/projectFileSuggestions.js +133 -0
  66. package/dist/server/lib/projectFileSuggestions.js.map +1 -0
  67. package/dist/server/lib/projectMemory.js +1519 -0
  68. package/dist/server/lib/projectMemory.js.map +1 -0
  69. package/dist/server/lib/projectMemoryPrompt.js +390 -0
  70. package/dist/server/lib/projectMemoryPrompt.js.map +1 -0
  71. package/dist/server/lib/projectMemoryScan.js +681 -0
  72. package/dist/server/lib/projectMemoryScan.js.map +1 -0
  73. package/dist/server/lib/projectMemorySuggestions.js +166 -0
  74. package/dist/server/lib/projectMemorySuggestions.js.map +1 -0
  75. package/dist/server/lib/projectMemoryTransfer.js +958 -0
  76. package/dist/server/lib/projectMemoryTransfer.js.map +1 -0
  77. package/dist/server/lib/projects.js +569 -0
  78. package/dist/server/lib/projects.js.map +1 -0
  79. package/dist/server/lib/promptSkills.js +28 -0
  80. package/dist/server/lib/promptSkills.js.map +1 -0
  81. package/dist/server/lib/queue.js +15 -0
  82. package/dist/server/lib/queue.js.map +1 -0
  83. package/dist/server/lib/reviewFindings.js +390 -0
  84. package/dist/server/lib/reviewFindings.js.map +1 -0
  85. package/dist/server/lib/run.js +416 -0
  86. package/dist/server/lib/run.js.map +1 -0
  87. package/dist/server/lib/runtimePaths.js +93 -0
  88. package/dist/server/lib/runtimePaths.js.map +1 -0
  89. package/dist/server/lib/shell.js +27 -0
  90. package/dist/server/lib/shell.js.map +1 -0
  91. package/dist/server/lib/skills.js +124 -0
  92. package/dist/server/lib/skills.js.map +1 -0
  93. package/dist/server/lib/startDev.js +18 -0
  94. package/dist/server/lib/startDev.js.map +1 -0
  95. package/dist/server/lib/staticClient.js +80 -0
  96. package/dist/server/lib/staticClient.js.map +1 -0
  97. package/dist/server/lib/terminal.js +366 -0
  98. package/dist/server/lib/terminal.js.map +1 -0
  99. package/dist/server/lib/ticketDependencies.js +174 -0
  100. package/dist/server/lib/ticketDependencies.js.map +1 -0
  101. package/dist/server/lib/ticketMessages.js +65 -0
  102. package/dist/server/lib/ticketMessages.js.map +1 -0
  103. package/dist/server/lib/ticketOpenQuestions.js +128 -0
  104. package/dist/server/lib/ticketOpenQuestions.js.map +1 -0
  105. package/dist/server/lib/ticketUndo.js +549 -0
  106. package/dist/server/lib/ticketUndo.js.map +1 -0
  107. package/dist/server/lib/tickets.js +981 -0
  108. package/dist/server/lib/tickets.js.map +1 -0
  109. package/dist/server/lib/types.js +2 -0
  110. package/dist/server/lib/types.js.map +1 -0
  111. package/dist/server/package.json +3 -0
  112. package/dist/server/workers/build.js +229 -0
  113. package/dist/server/workers/build.js.map +1 -0
  114. package/dist/server/workers/chat.js +190 -0
  115. package/dist/server/workers/chat.js.map +1 -0
  116. package/dist/server/workers/followUp.js +204 -0
  117. package/dist/server/workers/followUp.js.map +1 -0
  118. package/dist/server/workers/plan.js +1130 -0
  119. package/dist/server/workers/plan.js.map +1 -0
  120. package/dist/server/workers/planFollowUp.js +360 -0
  121. package/dist/server/workers/planFollowUp.js.map +1 -0
  122. package/dist/server/workers/review.js +167 -0
  123. package/dist/server/workers/review.js.map +1 -0
  124. package/package.json +40 -0
@@ -0,0 +1,958 @@
1
+ import { randomUUID } from 'crypto';
2
+ import db from '../db.js';
3
+ const PROJECT_MEMORY_ARCHIVE_EXPORT_VERSION = 3;
4
+ const PROJECT_MEMORY_ARCHIVE_SUPPORTED_VERSIONS = [1, 2, 3];
5
+ const PROJECT_MEMORY_STAGES = ['plan', 'build', 'review', 'follow_up', 'plan_follow_up', 'chat'];
6
+ const PROJECT_CONVENTION_PRIORITIES = ['critical', 'normal', 'low'];
7
+ const PROJECT_CONVENTION_INJECTION_MODES = ['always', 'relevant'];
8
+ const PROJECT_CONVENTION_STATUSES = ['active', 'archived'];
9
+ const PROJECT_DECISION_STATUSES = ['accepted', 'archived', 'superseded'];
10
+ const PROJECT_CONTEXT_SCAN_STATUSES = ['pending', 'running', 'done', 'error'];
11
+ const PROJECT_CONTEXT_ARTIFACT_KINDS = ['architecture', 'key_areas', 'conventions', 'risks'];
12
+ const TOOL_NAMES = ['claude', 'opencode'];
13
+ const isRecord = (value) => {
14
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
15
+ };
16
+ const normalizeString = (value) => {
17
+ return String(value ?? '')
18
+ .trim()
19
+ .toLowerCase()
20
+ .replace(/[^a-z0-9]+/g, ' ')
21
+ .replace(/\s+/g, ' ')
22
+ .trim();
23
+ };
24
+ const normalizeOptionalString = (value, fieldName) => {
25
+ if (value == null) {
26
+ return { value: null };
27
+ }
28
+ if (typeof value !== 'string') {
29
+ return { error: `${fieldName} must be a string or null` };
30
+ }
31
+ const trimmed = value.trim();
32
+ return { value: trimmed === '' ? null : trimmed };
33
+ };
34
+ const validateRequiredString = (value, fieldName) => {
35
+ if (typeof value !== 'string' || value.trim() === '') {
36
+ return { error: `${fieldName} must be a non-empty string` };
37
+ }
38
+ return { value: value.trim() };
39
+ };
40
+ const normalizeStringArray = (value, fieldName) => {
41
+ if (!Array.isArray(value)) {
42
+ return { error: `${fieldName} must be an array of strings` };
43
+ }
44
+ const normalized = [];
45
+ for (const entry of value) {
46
+ if (typeof entry !== 'string' || entry.trim() === '') {
47
+ return { error: `${fieldName} must be an array of strings` };
48
+ }
49
+ const trimmed = entry.trim();
50
+ if (!normalized.includes(trimmed)) {
51
+ normalized.push(trimmed);
52
+ }
53
+ }
54
+ return { value: normalized };
55
+ };
56
+ const validateStages = (value) => {
57
+ if (!Array.isArray(value)) {
58
+ return { error: 'memory.conventions[].stages must be an array of valid project memory stages' };
59
+ }
60
+ const normalized = [];
61
+ for (const stage of value) {
62
+ if (!PROJECT_MEMORY_STAGES.includes(stage)) {
63
+ return { error: 'memory.conventions[].stages must be an array of valid project memory stages' };
64
+ }
65
+ if (!normalized.includes(stage)) {
66
+ normalized.push(stage);
67
+ }
68
+ }
69
+ return { value: normalized };
70
+ };
71
+ const validateEnum = (value, values, fieldName) => {
72
+ if (!values.includes(value)) {
73
+ return { error: `${fieldName} must be one of ${values.join(', ')}` };
74
+ }
75
+ return { value: value };
76
+ };
77
+ const validateNullableEnum = (value, values, fieldName) => {
78
+ if (value == null) {
79
+ return { value: null };
80
+ }
81
+ return validateEnum(value, values, fieldName);
82
+ };
83
+ const parseJsonColumn = (value) => {
84
+ if (!value) {
85
+ return null;
86
+ }
87
+ try {
88
+ return JSON.parse(value);
89
+ }
90
+ catch {
91
+ return null;
92
+ }
93
+ };
94
+ const parseStringArrayColumn = (value) => {
95
+ const parsed = parseJsonColumn(value);
96
+ if (!Array.isArray(parsed)) {
97
+ return [];
98
+ }
99
+ return parsed
100
+ .filter((entry) => typeof entry === 'string')
101
+ .map(entry => entry.trim())
102
+ .filter(Boolean);
103
+ };
104
+ const serializeConvention = (row) => {
105
+ return {
106
+ title: row.title,
107
+ scope: row.scope,
108
+ instruction: row.instruction,
109
+ rationale: row.rationale,
110
+ stages: parseStringArrayColumn(row.stages_json),
111
+ priority: row.priority,
112
+ injection_mode: PROJECT_CONVENTION_INJECTION_MODES.includes(row.injection_mode)
113
+ ? row.injection_mode
114
+ : 'relevant',
115
+ status: row.status,
116
+ created_at: row.created_at,
117
+ updated_at: row.updated_at,
118
+ };
119
+ };
120
+ const serializeDecision = (row) => {
121
+ return {
122
+ title: row.title,
123
+ scope: row.scope,
124
+ decision: row.decision,
125
+ rationale: row.rationale,
126
+ implications: parseStringArrayColumn(row.implications_json),
127
+ status: row.status,
128
+ created_at: row.created_at,
129
+ updated_at: row.updated_at,
130
+ };
131
+ };
132
+ const serializeMemoryItemFromConvention = (row) => {
133
+ return {
134
+ kind: 'convention',
135
+ state: row.status,
136
+ source: 'manual',
137
+ title: row.title,
138
+ scope: row.scope,
139
+ content: row.instruction,
140
+ rationale: row.rationale,
141
+ implications: [],
142
+ stages: parseStringArrayColumn(row.stages_json),
143
+ priority: row.priority,
144
+ injection_mode: PROJECT_CONVENTION_INJECTION_MODES.includes(row.injection_mode)
145
+ ? row.injection_mode
146
+ : 'relevant',
147
+ created_at: row.created_at,
148
+ updated_at: row.updated_at,
149
+ };
150
+ };
151
+ const serializeMemoryItemFromDecision = (row) => {
152
+ return {
153
+ kind: 'decision',
154
+ state: row.status === 'accepted' ? 'active' : row.status,
155
+ source: 'manual',
156
+ title: row.title,
157
+ scope: row.scope,
158
+ content: row.decision,
159
+ rationale: row.rationale,
160
+ implications: parseStringArrayColumn(row.implications_json),
161
+ stages: [],
162
+ priority: null,
163
+ injection_mode: null,
164
+ created_at: row.created_at,
165
+ updated_at: row.updated_at,
166
+ };
167
+ };
168
+ const serializeArtifact = (row) => {
169
+ return {
170
+ kind: row.kind,
171
+ title: row.title,
172
+ content_json: parseJsonColumn(row.content_json),
173
+ content_markdown: row.content_markdown,
174
+ created_at: row.created_at,
175
+ };
176
+ };
177
+ const fingerprintConvention = (item) => {
178
+ return JSON.stringify([
179
+ normalizeString(item.title),
180
+ normalizeString(item.scope),
181
+ normalizeString(item.instruction),
182
+ normalizeString(item.rationale),
183
+ [...item.stages].sort(),
184
+ item.priority,
185
+ item.injection_mode,
186
+ item.status,
187
+ ]);
188
+ };
189
+ const fingerprintDecision = (item) => {
190
+ return JSON.stringify([
191
+ normalizeString(item.title),
192
+ normalizeString(item.scope),
193
+ normalizeString(item.decision),
194
+ normalizeString(item.rationale),
195
+ item.implications.map(entry => normalizeString(entry)),
196
+ item.status,
197
+ ]);
198
+ };
199
+ const fingerprintScan = (item) => {
200
+ return JSON.stringify([
201
+ item.status,
202
+ normalizeString(item.repo_head),
203
+ normalizeString(item.repo_branch),
204
+ item.scanner_tool,
205
+ normalizeString(item.scanner_model),
206
+ normalizeString(item.scanner_variant),
207
+ normalizeString(item.summary_markdown),
208
+ normalizeString(item.error),
209
+ ]);
210
+ };
211
+ const fingerprintArtifact = (item) => {
212
+ return JSON.stringify([
213
+ item.kind,
214
+ normalizeString(item.title),
215
+ item.content_json ?? null,
216
+ normalizeString(item.content_markdown),
217
+ ]);
218
+ };
219
+ const sanitizeJsonValue = (value) => {
220
+ if (value === undefined) {
221
+ return null;
222
+ }
223
+ return value;
224
+ };
225
+ const validateArchiveConvention = (value) => {
226
+ if (!isRecord(value)) {
227
+ return { error: 'memory.conventions[] must be an object' };
228
+ }
229
+ const title = validateRequiredString(value.title, 'memory.conventions[].title');
230
+ if ('error' in title) {
231
+ return title;
232
+ }
233
+ const scope = normalizeOptionalString(value.scope, 'memory.conventions[].scope');
234
+ if ('error' in scope) {
235
+ return scope;
236
+ }
237
+ const instruction = validateRequiredString(value.instruction, 'memory.conventions[].instruction');
238
+ if ('error' in instruction) {
239
+ return instruction;
240
+ }
241
+ const rationale = normalizeOptionalString(value.rationale, 'memory.conventions[].rationale');
242
+ if ('error' in rationale) {
243
+ return rationale;
244
+ }
245
+ const stages = validateStages(value.stages);
246
+ if ('error' in stages) {
247
+ return stages;
248
+ }
249
+ const priority = validateEnum(value.priority, PROJECT_CONVENTION_PRIORITIES, 'memory.conventions[].priority');
250
+ if ('error' in priority) {
251
+ return priority;
252
+ }
253
+ const injectionMode = value.injection_mode == null
254
+ ? { value: 'relevant' }
255
+ : validateEnum(value.injection_mode, PROJECT_CONVENTION_INJECTION_MODES, 'memory.conventions[].injection_mode');
256
+ if ('error' in injectionMode) {
257
+ return injectionMode;
258
+ }
259
+ const status = validateEnum(value.status, PROJECT_CONVENTION_STATUSES, 'memory.conventions[].status');
260
+ if ('error' in status) {
261
+ return status;
262
+ }
263
+ const createdAt = validateRequiredString(value.created_at, 'memory.conventions[].created_at');
264
+ if ('error' in createdAt) {
265
+ return createdAt;
266
+ }
267
+ const updatedAt = validateRequiredString(value.updated_at, 'memory.conventions[].updated_at');
268
+ if ('error' in updatedAt) {
269
+ return updatedAt;
270
+ }
271
+ return {
272
+ value: {
273
+ title: title.value,
274
+ scope: scope.value,
275
+ instruction: instruction.value,
276
+ rationale: rationale.value,
277
+ stages: stages.value,
278
+ priority: priority.value,
279
+ injection_mode: injectionMode.value,
280
+ status: status.value,
281
+ created_at: createdAt.value,
282
+ updated_at: updatedAt.value,
283
+ },
284
+ };
285
+ };
286
+ const validateArchiveDecision = (value) => {
287
+ if (!isRecord(value)) {
288
+ return { error: 'memory.decisions[] must be an object' };
289
+ }
290
+ const title = validateRequiredString(value.title, 'memory.decisions[].title');
291
+ if ('error' in title) {
292
+ return title;
293
+ }
294
+ const scope = normalizeOptionalString(value.scope, 'memory.decisions[].scope');
295
+ if ('error' in scope) {
296
+ return scope;
297
+ }
298
+ const decision = validateRequiredString(value.decision, 'memory.decisions[].decision');
299
+ if ('error' in decision) {
300
+ return decision;
301
+ }
302
+ const rationale = normalizeOptionalString(value.rationale, 'memory.decisions[].rationale');
303
+ if ('error' in rationale) {
304
+ return rationale;
305
+ }
306
+ const implications = normalizeStringArray(value.implications, 'memory.decisions[].implications');
307
+ if ('error' in implications) {
308
+ return implications;
309
+ }
310
+ const status = validateEnum(value.status, PROJECT_DECISION_STATUSES, 'memory.decisions[].status');
311
+ if ('error' in status) {
312
+ return status;
313
+ }
314
+ const createdAt = validateRequiredString(value.created_at, 'memory.decisions[].created_at');
315
+ if ('error' in createdAt) {
316
+ return createdAt;
317
+ }
318
+ const updatedAt = validateRequiredString(value.updated_at, 'memory.decisions[].updated_at');
319
+ if ('error' in updatedAt) {
320
+ return updatedAt;
321
+ }
322
+ return {
323
+ value: {
324
+ title: title.value,
325
+ scope: scope.value,
326
+ decision: decision.value,
327
+ rationale: rationale.value,
328
+ implications: implications.value,
329
+ status: status.value,
330
+ created_at: createdAt.value,
331
+ updated_at: updatedAt.value,
332
+ },
333
+ };
334
+ };
335
+ const validateArchiveItem = (value) => {
336
+ if (!isRecord(value)) {
337
+ return { error: 'memory.items[] must be an object' };
338
+ }
339
+ const kind = validateEnum(value.kind, ['decision', 'convention'], 'memory.items[].kind');
340
+ if ('error' in kind) {
341
+ return kind;
342
+ }
343
+ const state = validateEnum(value.state, ['active', 'archived', 'superseded'], 'memory.items[].state');
344
+ if ('error' in state) {
345
+ return state;
346
+ }
347
+ const source = validateEnum(value.source, ['manual'], 'memory.items[].source');
348
+ if ('error' in source) {
349
+ return source;
350
+ }
351
+ if (kind.value === 'convention' && state.value === 'superseded') {
352
+ return { error: 'memory.items[] conventions cannot use the superseded state' };
353
+ }
354
+ const title = validateRequiredString(value.title, 'memory.items[].title');
355
+ if ('error' in title) {
356
+ return title;
357
+ }
358
+ const scope = normalizeOptionalString(value.scope, 'memory.items[].scope');
359
+ if ('error' in scope) {
360
+ return scope;
361
+ }
362
+ const content = validateRequiredString(value.content, 'memory.items[].content');
363
+ if ('error' in content) {
364
+ return content;
365
+ }
366
+ const rationale = normalizeOptionalString(value.rationale, 'memory.items[].rationale');
367
+ if ('error' in rationale) {
368
+ return rationale;
369
+ }
370
+ const implications = normalizeStringArray(value.implications ?? [], 'memory.items[].implications');
371
+ if ('error' in implications) {
372
+ return implications;
373
+ }
374
+ const stages = validateStages(value.stages ?? []);
375
+ if ('error' in stages) {
376
+ return { error: 'memory.items[].stages must be an array of valid project memory stages' };
377
+ }
378
+ const priority = value.priority == null
379
+ ? { value: null }
380
+ : validateEnum(value.priority, PROJECT_CONVENTION_PRIORITIES, 'memory.items[].priority');
381
+ if ('error' in priority) {
382
+ return priority;
383
+ }
384
+ const injectionMode = value.injection_mode == null
385
+ ? { value: null }
386
+ : validateEnum(value.injection_mode, PROJECT_CONVENTION_INJECTION_MODES, 'memory.items[].injection_mode');
387
+ if ('error' in injectionMode) {
388
+ return injectionMode;
389
+ }
390
+ if (kind.value === 'decision' && stages.value.length > 0) {
391
+ return { error: 'memory.items[] decisions cannot include stages' };
392
+ }
393
+ if (kind.value === 'decision' && priority.value !== null) {
394
+ return { error: 'memory.items[] decisions cannot include priority' };
395
+ }
396
+ if (kind.value === 'decision' && injectionMode.value !== null) {
397
+ return { error: 'memory.items[] decisions cannot include injection_mode' };
398
+ }
399
+ if (kind.value === 'convention' && implications.value.length > 0) {
400
+ return { error: 'memory.items[] conventions cannot include implications' };
401
+ }
402
+ if (kind.value === 'convention' && injectionMode.value === null) {
403
+ return { error: 'memory.items[] conventions must include injection_mode' };
404
+ }
405
+ const createdAt = validateRequiredString(value.created_at, 'memory.items[].created_at');
406
+ if ('error' in createdAt) {
407
+ return createdAt;
408
+ }
409
+ const updatedAt = validateRequiredString(value.updated_at, 'memory.items[].updated_at');
410
+ if ('error' in updatedAt) {
411
+ return updatedAt;
412
+ }
413
+ return {
414
+ value: {
415
+ kind: kind.value,
416
+ state: state.value,
417
+ source: source.value,
418
+ title: title.value,
419
+ scope: scope.value,
420
+ content: content.value,
421
+ rationale: rationale.value,
422
+ implications: implications.value,
423
+ stages: stages.value,
424
+ priority: priority.value,
425
+ injection_mode: injectionMode.value,
426
+ created_at: createdAt.value,
427
+ updated_at: updatedAt.value,
428
+ },
429
+ };
430
+ };
431
+ const validateArchiveArtifact = (value) => {
432
+ if (!isRecord(value)) {
433
+ return { error: 'memory.scans[].artifacts[] must be an object' };
434
+ }
435
+ const kind = validateEnum(value.kind, PROJECT_CONTEXT_ARTIFACT_KINDS, 'memory.scans[].artifacts[].kind');
436
+ if ('error' in kind) {
437
+ return kind;
438
+ }
439
+ const title = validateRequiredString(value.title, 'memory.scans[].artifacts[].title');
440
+ if ('error' in title) {
441
+ return title;
442
+ }
443
+ const markdown = normalizeOptionalString(value.content_markdown, 'memory.scans[].artifacts[].content_markdown');
444
+ if ('error' in markdown) {
445
+ return markdown;
446
+ }
447
+ const createdAt = validateRequiredString(value.created_at, 'memory.scans[].artifacts[].created_at');
448
+ if ('error' in createdAt) {
449
+ return createdAt;
450
+ }
451
+ return {
452
+ value: {
453
+ kind: kind.value,
454
+ title: title.value,
455
+ content_json: sanitizeJsonValue(value.content_json),
456
+ content_markdown: markdown.value,
457
+ created_at: createdAt.value,
458
+ },
459
+ };
460
+ };
461
+ const validateArchiveScan = (value) => {
462
+ if (!isRecord(value)) {
463
+ return { error: 'memory.scans[] must be an object' };
464
+ }
465
+ const status = validateEnum(value.status, PROJECT_CONTEXT_SCAN_STATUSES, 'memory.scans[].status');
466
+ if ('error' in status) {
467
+ return status;
468
+ }
469
+ const repoHead = normalizeOptionalString(value.repo_head, 'memory.scans[].repo_head');
470
+ if ('error' in repoHead) {
471
+ return repoHead;
472
+ }
473
+ const repoBranch = normalizeOptionalString(value.repo_branch, 'memory.scans[].repo_branch');
474
+ if ('error' in repoBranch) {
475
+ return repoBranch;
476
+ }
477
+ const scannerTool = validateNullableEnum(value.scanner_tool, TOOL_NAMES, 'memory.scans[].scanner_tool');
478
+ if ('error' in scannerTool) {
479
+ return scannerTool;
480
+ }
481
+ const scannerModel = normalizeOptionalString(value.scanner_model, 'memory.scans[].scanner_model');
482
+ if ('error' in scannerModel) {
483
+ return scannerModel;
484
+ }
485
+ const scannerVariant = normalizeOptionalString(value.scanner_variant, 'memory.scans[].scanner_variant');
486
+ if ('error' in scannerVariant) {
487
+ return scannerVariant;
488
+ }
489
+ const summaryMarkdown = normalizeOptionalString(value.summary_markdown, 'memory.scans[].summary_markdown');
490
+ if ('error' in summaryMarkdown) {
491
+ return summaryMarkdown;
492
+ }
493
+ const errorValue = normalizeOptionalString(value.error, 'memory.scans[].error');
494
+ if ('error' in errorValue) {
495
+ return errorValue;
496
+ }
497
+ const createdAt = validateRequiredString(value.created_at, 'memory.scans[].created_at');
498
+ if ('error' in createdAt) {
499
+ return createdAt;
500
+ }
501
+ const updatedAt = validateRequiredString(value.updated_at, 'memory.scans[].updated_at');
502
+ if ('error' in updatedAt) {
503
+ return updatedAt;
504
+ }
505
+ if (!Array.isArray(value.artifacts)) {
506
+ return { error: 'memory.scans[].artifacts must be an array' };
507
+ }
508
+ const artifacts = [];
509
+ for (const artifactValue of value.artifacts) {
510
+ const artifact = validateArchiveArtifact(artifactValue);
511
+ if ('error' in artifact) {
512
+ return artifact;
513
+ }
514
+ artifacts.push(artifact.value);
515
+ }
516
+ return {
517
+ value: {
518
+ status: status.value,
519
+ repo_head: repoHead.value,
520
+ repo_branch: repoBranch.value,
521
+ scanner_tool: scannerTool.value,
522
+ scanner_model: scannerModel.value,
523
+ scanner_variant: scannerVariant.value,
524
+ summary_markdown: summaryMarkdown.value,
525
+ error: errorValue.value,
526
+ created_at: createdAt.value,
527
+ updated_at: updatedAt.value,
528
+ artifacts,
529
+ },
530
+ };
531
+ };
532
+ export const validateProjectMemoryArchive = (value) => {
533
+ if (!isRecord(value)) {
534
+ return { error: 'Project memory archive must be an object' };
535
+ }
536
+ if (!PROJECT_MEMORY_ARCHIVE_SUPPORTED_VERSIONS.includes(value.schema_version)) {
537
+ return { error: `Unsupported project memory archive schema version: ${String(value.schema_version ?? 'missing')}` };
538
+ }
539
+ const exportedAt = validateRequiredString(value.exported_at, 'exported_at');
540
+ if ('error' in exportedAt) {
541
+ return exportedAt;
542
+ }
543
+ if (!isRecord(value.project)) {
544
+ return { error: 'project must be an object' };
545
+ }
546
+ const sourceProjectId = validateRequiredString(value.project.source_project_id, 'project.source_project_id');
547
+ if ('error' in sourceProjectId) {
548
+ return sourceProjectId;
549
+ }
550
+ const projectName = validateRequiredString(value.project.name, 'project.name');
551
+ if ('error' in projectName) {
552
+ return projectName;
553
+ }
554
+ if (typeof value.project.memory_enabled !== 'boolean') {
555
+ return { error: 'project.memory_enabled must be a boolean' };
556
+ }
557
+ if (!isRecord(value.memory)) {
558
+ return { error: 'memory must be an object' };
559
+ }
560
+ if (!Array.isArray(value.memory.scans)) {
561
+ return { error: 'memory.scans must be an array' };
562
+ }
563
+ const scans = [];
564
+ for (const scanValue of value.memory.scans) {
565
+ const scan = validateArchiveScan(scanValue);
566
+ if ('error' in scan) {
567
+ return scan;
568
+ }
569
+ scans.push(scan.value);
570
+ }
571
+ if (value.schema_version === 3) {
572
+ if (!Array.isArray(value.memory.items)) {
573
+ return { error: 'memory.items must be an array' };
574
+ }
575
+ const items = [];
576
+ for (const itemValue of value.memory.items) {
577
+ const item = validateArchiveItem(itemValue);
578
+ if ('error' in item) {
579
+ return item;
580
+ }
581
+ items.push(item.value);
582
+ }
583
+ return {
584
+ value: {
585
+ schema_version: 3,
586
+ exported_at: exportedAt.value,
587
+ project: {
588
+ source_project_id: sourceProjectId.value,
589
+ name: projectName.value,
590
+ memory_enabled: value.project.memory_enabled,
591
+ },
592
+ memory: {
593
+ items,
594
+ scans,
595
+ },
596
+ },
597
+ };
598
+ }
599
+ if (!Array.isArray(value.memory.conventions)) {
600
+ return { error: 'memory.conventions must be an array' };
601
+ }
602
+ if (!Array.isArray(value.memory.decisions)) {
603
+ return { error: 'memory.decisions must be an array' };
604
+ }
605
+ const conventions = [];
606
+ for (const conventionValue of value.memory.conventions) {
607
+ const convention = validateArchiveConvention(conventionValue);
608
+ if ('error' in convention) {
609
+ return convention;
610
+ }
611
+ conventions.push(convention.value);
612
+ }
613
+ const decisions = [];
614
+ for (const decisionValue of value.memory.decisions) {
615
+ const decision = validateArchiveDecision(decisionValue);
616
+ if ('error' in decision) {
617
+ return decision;
618
+ }
619
+ decisions.push(decision.value);
620
+ }
621
+ return {
622
+ value: {
623
+ schema_version: value.schema_version,
624
+ exported_at: exportedAt.value,
625
+ project: {
626
+ source_project_id: sourceProjectId.value,
627
+ name: projectName.value,
628
+ memory_enabled: value.project.memory_enabled,
629
+ },
630
+ memory: {
631
+ conventions,
632
+ decisions,
633
+ scans,
634
+ },
635
+ },
636
+ };
637
+ };
638
+ export const exportProjectMemoryArchive = (project) => {
639
+ const conventionRows = db.prepare(`
640
+ SELECT *
641
+ FROM project_conventions
642
+ WHERE project_id = ?
643
+ ORDER BY created_at ASC, id ASC
644
+ `).all(project.id);
645
+ const decisionRows = db.prepare(`
646
+ SELECT *
647
+ FROM project_decisions
648
+ WHERE project_id = ?
649
+ ORDER BY created_at ASC, id ASC
650
+ `).all(project.id);
651
+ const scanRows = db.prepare(`
652
+ SELECT *
653
+ FROM project_context_scans
654
+ WHERE project_id = ?
655
+ ORDER BY created_at ASC, id ASC
656
+ `).all(project.id);
657
+ const artifactRows = db.prepare(`
658
+ SELECT *
659
+ FROM project_context_artifacts
660
+ WHERE project_id = ?
661
+ ORDER BY created_at ASC, id ASC
662
+ `).all(project.id);
663
+ const artifactsByScanId = new Map();
664
+ for (const artifactRow of artifactRows) {
665
+ const current = artifactsByScanId.get(artifactRow.scan_id) ?? [];
666
+ current.push(serializeArtifact(artifactRow));
667
+ artifactsByScanId.set(artifactRow.scan_id, current);
668
+ }
669
+ return {
670
+ schema_version: PROJECT_MEMORY_ARCHIVE_EXPORT_VERSION,
671
+ exported_at: new Date().toISOString(),
672
+ project: {
673
+ source_project_id: project.id,
674
+ name: project.name,
675
+ memory_enabled: Boolean(project.memory_enabled),
676
+ },
677
+ memory: {
678
+ items: [...conventionRows.map(serializeMemoryItemFromConvention), ...decisionRows.map(serializeMemoryItemFromDecision)]
679
+ .sort((left, right) => left.created_at.localeCompare(right.created_at) || left.updated_at.localeCompare(right.updated_at) || left.title.localeCompare(right.title)),
680
+ scans: scanRows.map(scanRow => ({
681
+ status: scanRow.status,
682
+ repo_head: scanRow.repo_head,
683
+ repo_branch: scanRow.repo_branch,
684
+ scanner_tool: scanRow.scanner_tool,
685
+ scanner_model: scanRow.scanner_model,
686
+ scanner_variant: scanRow.scanner_variant,
687
+ summary_markdown: scanRow.summary_markdown,
688
+ error: scanRow.error,
689
+ created_at: scanRow.created_at,
690
+ updated_at: scanRow.updated_at,
691
+ artifacts: artifactsByScanId.get(scanRow.id) ?? [],
692
+ })),
693
+ },
694
+ };
695
+ };
696
+ const buildExistingConventionTitleMap = (items) => {
697
+ const map = new Map();
698
+ for (const item of items) {
699
+ const title = normalizeString(item.title);
700
+ if (!title) {
701
+ continue;
702
+ }
703
+ const values = map.get(title) ?? new Set();
704
+ values.add(fingerprintConvention(item));
705
+ map.set(title, values);
706
+ }
707
+ return map;
708
+ };
709
+ const buildExistingDecisionTitleMap = (items) => {
710
+ const map = new Map();
711
+ for (const item of items) {
712
+ const title = normalizeString(item.title);
713
+ if (!title) {
714
+ continue;
715
+ }
716
+ const values = map.get(title) ?? new Set();
717
+ values.add(fingerprintDecision(item));
718
+ map.set(title, values);
719
+ }
720
+ return map;
721
+ };
722
+ const createConflict = (kind, title) => {
723
+ return {
724
+ kind,
725
+ title,
726
+ reason: 'title_conflict',
727
+ };
728
+ };
729
+ export const importProjectMemoryArchive = (projectId, archiveValue) => {
730
+ const archive = validateProjectMemoryArchive(archiveValue);
731
+ if ('error' in archive) {
732
+ return archive;
733
+ }
734
+ const result = {
735
+ summary: {
736
+ conventions_imported: 0,
737
+ conventions_skipped: 0,
738
+ decisions_imported: 0,
739
+ decisions_skipped: 0,
740
+ scans_imported: 0,
741
+ scans_skipped: 0,
742
+ artifacts_imported: 0,
743
+ artifacts_skipped: 0,
744
+ },
745
+ conflicts: [],
746
+ };
747
+ const transaction = db.transaction((validatedArchive) => {
748
+ const archiveConventions = validatedArchive.schema_version === 3
749
+ ? validatedArchive.memory.items
750
+ .filter((item) => item.kind === 'convention')
751
+ .map(item => ({
752
+ title: item.title,
753
+ scope: item.scope,
754
+ instruction: item.content,
755
+ rationale: item.rationale,
756
+ stages: item.stages,
757
+ priority: item.priority ?? 'normal',
758
+ injection_mode: item.injection_mode ?? 'relevant',
759
+ status: item.state === 'active' ? 'active' : 'archived',
760
+ created_at: item.created_at,
761
+ updated_at: item.updated_at,
762
+ }))
763
+ : validatedArchive.memory.conventions;
764
+ const archiveDecisions = validatedArchive.schema_version === 3
765
+ ? validatedArchive.memory.items
766
+ .filter((item) => item.kind === 'decision')
767
+ .map(item => ({
768
+ title: item.title,
769
+ scope: item.scope,
770
+ decision: item.content,
771
+ rationale: item.rationale,
772
+ implications: item.implications,
773
+ status: item.state === 'active' ? 'accepted' : item.state === 'archived' ? 'archived' : 'superseded',
774
+ created_at: item.created_at,
775
+ updated_at: item.updated_at,
776
+ }))
777
+ : validatedArchive.memory.decisions;
778
+ const existingConventionRows = db.prepare(`
779
+ SELECT *
780
+ FROM project_conventions
781
+ WHERE project_id = ?
782
+ ORDER BY created_at ASC, id ASC
783
+ `).all(projectId);
784
+ const existingDecisionRows = db.prepare(`
785
+ SELECT *
786
+ FROM project_decisions
787
+ WHERE project_id = ?
788
+ ORDER BY created_at ASC, id ASC
789
+ `).all(projectId);
790
+ const existingScanRows = db.prepare(`
791
+ SELECT *
792
+ FROM project_context_scans
793
+ WHERE project_id = ?
794
+ ORDER BY created_at ASC, id ASC
795
+ `).all(projectId);
796
+ const existingArtifactRows = db.prepare(`
797
+ SELECT *
798
+ FROM project_context_artifacts
799
+ WHERE project_id = ?
800
+ ORDER BY created_at ASC, id ASC
801
+ `).all(projectId);
802
+ const existingConventions = existingConventionRows.map(serializeConvention);
803
+ const existingDecisions = existingDecisionRows.map(serializeDecision);
804
+ const conventionFingerprints = new Set(existingConventions.map(fingerprintConvention));
805
+ const decisionFingerprints = new Set(existingDecisions.map(fingerprintDecision));
806
+ const conventionTitles = buildExistingConventionTitleMap(existingConventions);
807
+ const decisionTitles = buildExistingDecisionTitleMap(existingDecisions);
808
+ const insertConvention = db.prepare(`
809
+ INSERT INTO project_conventions (
810
+ id,
811
+ project_id,
812
+ title,
813
+ scope,
814
+ instruction,
815
+ rationale,
816
+ stages_json,
817
+ priority,
818
+ injection_mode,
819
+ status,
820
+ created_at,
821
+ updated_at
822
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
823
+ `);
824
+ const insertDecision = db.prepare(`
825
+ INSERT INTO project_decisions (
826
+ id,
827
+ project_id,
828
+ title,
829
+ scope,
830
+ decision,
831
+ rationale,
832
+ implications_json,
833
+ status,
834
+ created_at,
835
+ updated_at
836
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
837
+ `);
838
+ const insertScan = db.prepare(`
839
+ INSERT INTO project_context_scans (
840
+ id,
841
+ project_id,
842
+ status,
843
+ repo_head,
844
+ repo_branch,
845
+ scanner_tool,
846
+ scanner_model,
847
+ scanner_variant,
848
+ summary_markdown,
849
+ error,
850
+ created_at,
851
+ updated_at
852
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
853
+ `);
854
+ const insertArtifact = db.prepare(`
855
+ INSERT INTO project_context_artifacts (
856
+ id,
857
+ scan_id,
858
+ project_id,
859
+ kind,
860
+ title,
861
+ content_json,
862
+ content_markdown,
863
+ created_at
864
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
865
+ `);
866
+ for (const convention of archiveConventions) {
867
+ const fingerprint = fingerprintConvention(convention);
868
+ const normalizedTitle = normalizeString(convention.title);
869
+ if (conventionFingerprints.has(fingerprint)) {
870
+ result.summary.conventions_skipped += 1;
871
+ continue;
872
+ }
873
+ const titleFingerprints = conventionTitles.get(normalizedTitle);
874
+ if (titleFingerprints && !titleFingerprints.has(fingerprint)) {
875
+ result.summary.conventions_skipped += 1;
876
+ result.conflicts.push(createConflict('convention', convention.title));
877
+ continue;
878
+ }
879
+ insertConvention.run(randomUUID(), projectId, convention.title, convention.scope, convention.instruction, convention.rationale, convention.stages.length > 0 ? JSON.stringify(convention.stages) : null, convention.priority, convention.injection_mode, convention.status, convention.created_at, convention.updated_at);
880
+ conventionFingerprints.add(fingerprint);
881
+ const nextTitleFingerprints = titleFingerprints ?? new Set();
882
+ nextTitleFingerprints.add(fingerprint);
883
+ conventionTitles.set(normalizedTitle, nextTitleFingerprints);
884
+ result.summary.conventions_imported += 1;
885
+ }
886
+ for (const decision of archiveDecisions) {
887
+ const fingerprint = fingerprintDecision(decision);
888
+ const normalizedTitle = normalizeString(decision.title);
889
+ if (decisionFingerprints.has(fingerprint)) {
890
+ result.summary.decisions_skipped += 1;
891
+ continue;
892
+ }
893
+ const titleFingerprints = decisionTitles.get(normalizedTitle);
894
+ if (titleFingerprints && !titleFingerprints.has(fingerprint)) {
895
+ result.summary.decisions_skipped += 1;
896
+ result.conflicts.push(createConflict('decision', decision.title));
897
+ continue;
898
+ }
899
+ insertDecision.run(randomUUID(), projectId, decision.title, decision.scope, decision.decision, decision.rationale, decision.implications.length > 0 ? JSON.stringify(decision.implications) : null, decision.status, decision.created_at, decision.updated_at);
900
+ decisionFingerprints.add(fingerprint);
901
+ const nextTitleFingerprints = titleFingerprints ?? new Set();
902
+ nextTitleFingerprints.add(fingerprint);
903
+ decisionTitles.set(normalizedTitle, nextTitleFingerprints);
904
+ result.summary.decisions_imported += 1;
905
+ }
906
+ const scanFingerprintToId = new Map();
907
+ for (const scanRow of existingScanRows) {
908
+ scanFingerprintToId.set(fingerprintScan({
909
+ status: scanRow.status,
910
+ repo_head: scanRow.repo_head,
911
+ repo_branch: scanRow.repo_branch,
912
+ scanner_tool: scanRow.scanner_tool,
913
+ scanner_model: scanRow.scanner_model,
914
+ scanner_variant: scanRow.scanner_variant,
915
+ summary_markdown: scanRow.summary_markdown,
916
+ error: scanRow.error,
917
+ created_at: scanRow.created_at,
918
+ updated_at: scanRow.updated_at,
919
+ artifacts: [],
920
+ }), scanRow.id);
921
+ }
922
+ const artifactFingerprintsByScanId = new Map();
923
+ for (const artifactRow of existingArtifactRows) {
924
+ const current = artifactFingerprintsByScanId.get(artifactRow.scan_id) ?? new Set();
925
+ current.add(fingerprintArtifact(serializeArtifact(artifactRow)));
926
+ artifactFingerprintsByScanId.set(artifactRow.scan_id, current);
927
+ }
928
+ for (const scan of validatedArchive.memory.scans) {
929
+ const scanFingerprint = fingerprintScan(scan);
930
+ let targetScanId = scanFingerprintToId.get(scanFingerprint);
931
+ if (targetScanId) {
932
+ result.summary.scans_skipped += 1;
933
+ }
934
+ else {
935
+ targetScanId = randomUUID();
936
+ insertScan.run(targetScanId, projectId, scan.status, scan.repo_head, scan.repo_branch, scan.scanner_tool, scan.scanner_model, scan.scanner_variant, scan.summary_markdown, scan.error, scan.created_at, scan.updated_at);
937
+ scanFingerprintToId.set(scanFingerprint, targetScanId);
938
+ artifactFingerprintsByScanId.set(targetScanId, new Set());
939
+ result.summary.scans_imported += 1;
940
+ }
941
+ const artifactFingerprints = artifactFingerprintsByScanId.get(targetScanId) ?? new Set();
942
+ artifactFingerprintsByScanId.set(targetScanId, artifactFingerprints);
943
+ for (const artifact of scan.artifacts) {
944
+ const artifactFingerprint = fingerprintArtifact(artifact);
945
+ if (artifactFingerprints.has(artifactFingerprint)) {
946
+ result.summary.artifacts_skipped += 1;
947
+ continue;
948
+ }
949
+ insertArtifact.run(randomUUID(), targetScanId, projectId, artifact.kind, artifact.title, artifact.content_json == null ? null : JSON.stringify(artifact.content_json), artifact.content_markdown, artifact.created_at);
950
+ artifactFingerprints.add(artifactFingerprint);
951
+ result.summary.artifacts_imported += 1;
952
+ }
953
+ }
954
+ });
955
+ transaction(archive.value);
956
+ return { value: result };
957
+ };
958
+ //# sourceMappingURL=projectMemoryTransfer.js.map