sqlew 2.1.3 → 3.0.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 (84) hide show
  1. package/CHANGELOG.md +891 -535
  2. package/README.md +302 -613
  3. package/assets/kanban-style.png +0 -0
  4. package/assets/schema.sql +531 -402
  5. package/dist/database.d.ts +9 -0
  6. package/dist/database.d.ts.map +1 -1
  7. package/dist/database.js +33 -34
  8. package/dist/database.js.map +1 -1
  9. package/dist/index.js +1024 -21
  10. package/dist/index.js.map +1 -1
  11. package/dist/migrations/add-task-tables.d.ts +47 -0
  12. package/dist/migrations/add-task-tables.d.ts.map +1 -0
  13. package/dist/migrations/add-task-tables.js +285 -0
  14. package/dist/migrations/add-task-tables.js.map +1 -0
  15. package/dist/migrations/index.d.ts +96 -0
  16. package/dist/migrations/index.d.ts.map +1 -0
  17. package/dist/migrations/index.js +239 -0
  18. package/dist/migrations/index.js.map +1 -0
  19. package/dist/migrations/migrate-decisions-to-tasks.d.ts +61 -0
  20. package/dist/migrations/migrate-decisions-to-tasks.d.ts.map +1 -0
  21. package/dist/migrations/migrate-decisions-to-tasks.js +442 -0
  22. package/dist/migrations/migrate-decisions-to-tasks.js.map +1 -0
  23. package/dist/schema.d.ts.map +1 -1
  24. package/dist/schema.js +14 -3
  25. package/dist/schema.js.map +1 -1
  26. package/dist/tools/constraints.d.ts +4 -0
  27. package/dist/tools/constraints.d.ts.map +1 -1
  28. package/dist/tools/constraints.js +6 -27
  29. package/dist/tools/constraints.js.map +1 -1
  30. package/dist/tools/context.d.ts +17 -1
  31. package/dist/tools/context.d.ts.map +1 -1
  32. package/dist/tools/context.js +195 -190
  33. package/dist/tools/context.js.map +1 -1
  34. package/dist/tools/files.d.ts.map +1 -1
  35. package/dist/tools/files.js +113 -166
  36. package/dist/tools/files.js.map +1 -1
  37. package/dist/tools/messaging.d.ts +2 -9
  38. package/dist/tools/messaging.d.ts.map +1 -1
  39. package/dist/tools/messaging.js +67 -126
  40. package/dist/tools/messaging.js.map +1 -1
  41. package/dist/tools/tasks.d.ts +90 -0
  42. package/dist/tools/tasks.d.ts.map +1 -0
  43. package/dist/tools/tasks.js +732 -0
  44. package/dist/tools/tasks.js.map +1 -0
  45. package/dist/tools/utils.d.ts +8 -1
  46. package/dist/tools/utils.d.ts.map +1 -1
  47. package/dist/tools/utils.js +50 -21
  48. package/dist/tools/utils.js.map +1 -1
  49. package/dist/types.d.ts +14 -0
  50. package/dist/types.d.ts.map +1 -1
  51. package/dist/utils/batch.d.ts +69 -0
  52. package/dist/utils/batch.d.ts.map +1 -0
  53. package/dist/utils/batch.js +148 -0
  54. package/dist/utils/batch.js.map +1 -0
  55. package/dist/utils/query-builder.d.ts +68 -0
  56. package/dist/utils/query-builder.d.ts.map +1 -0
  57. package/dist/utils/query-builder.js +116 -0
  58. package/dist/utils/query-builder.js.map +1 -0
  59. package/dist/utils/task-stale-detection.d.ts +28 -0
  60. package/dist/utils/task-stale-detection.d.ts.map +1 -0
  61. package/dist/utils/task-stale-detection.js +92 -0
  62. package/dist/utils/task-stale-detection.js.map +1 -0
  63. package/dist/utils/validators.d.ts +57 -0
  64. package/dist/utils/validators.d.ts.map +1 -0
  65. package/dist/utils/validators.js +117 -0
  66. package/dist/utils/validators.js.map +1 -0
  67. package/docs/AI_AGENT_GUIDE.md +1471 -0
  68. package/{ARCHITECTURE.md → docs/ARCHITECTURE.md} +636 -636
  69. package/docs/BEST_PRACTICES.md +481 -0
  70. package/docs/DECISION_TO_TASK_MIGRATION_GUIDE.md +457 -0
  71. package/docs/MIGRATION_CHAIN.md +280 -0
  72. package/{MIGRATION_v2.md → docs/MIGRATION_v2.md} +538 -538
  73. package/docs/SHARED_CONCEPTS.md +339 -0
  74. package/docs/TASK_ACTIONS.md +854 -0
  75. package/docs/TASK_LINKING.md +729 -0
  76. package/docs/TASK_MIGRATION.md +701 -0
  77. package/docs/TASK_OVERVIEW.md +363 -0
  78. package/docs/TASK_SYSTEM.md +1244 -0
  79. package/docs/TOOL_REFERENCE.md +471 -0
  80. package/docs/TOOL_SELECTION.md +279 -0
  81. package/docs/WORKFLOWS.md +602 -0
  82. package/docs/refactoring-summary-2025-10-17.md +365 -0
  83. package/docs/requirement-2025-10-17.md +508 -0
  84. package/package.json +64 -63
@@ -2,7 +2,7 @@
2
2
  * Context management tools for MCP Shared Context Server
3
3
  * Implements set_decision, get_context, and get_decision tools
4
4
  */
5
- import type { SetDecisionParams, GetContextParams, GetDecisionParams, SetDecisionResponse, GetContextResponse, GetDecisionResponse, SearchByTagsParams, SearchByTagsResponse, GetVersionsParams, GetVersionsResponse, SearchByLayerParams, SearchByLayerResponse, QuickSetDecisionParams, QuickSetDecisionResponse, SearchAdvancedParams, SearchAdvancedResponse, SetDecisionBatchParams, SetDecisionBatchResponse, SetFromTemplateParams, SetFromTemplateResponse, CreateTemplateParams, CreateTemplateResponse, ListTemplatesParams, ListTemplatesResponse, HasUpdatesParams, HasUpdatesResponse } from '../types.js';
5
+ import type { SetDecisionParams, GetContextParams, GetDecisionParams, SetDecisionResponse, GetContextResponse, GetDecisionResponse, SearchByTagsParams, SearchByTagsResponse, GetVersionsParams, GetVersionsResponse, SearchByLayerParams, SearchByLayerResponse, QuickSetDecisionParams, QuickSetDecisionResponse, SearchAdvancedParams, SearchAdvancedResponse, SetDecisionBatchParams, SetDecisionBatchResponse, SetFromTemplateParams, SetFromTemplateResponse, CreateTemplateParams, CreateTemplateResponse, ListTemplatesParams, ListTemplatesResponse, HasUpdatesParams, HasUpdatesResponse, HardDeleteDecisionParams, HardDeleteDecisionResponse } from '../types.js';
6
6
  /**
7
7
  * Set or update a decision in the context
8
8
  * Auto-detects numeric vs string values and routes to appropriate table
@@ -143,4 +143,20 @@ export declare function createTemplate(params: CreateTemplateParams): CreateTemp
143
143
  * @returns Array of all templates with parsed JSON fields
144
144
  */
145
145
  export declare function listTemplates(params?: ListTemplatesParams): ListTemplatesResponse;
146
+ /**
147
+ * Permanently delete a decision and all related data (hard delete)
148
+ * Unlike soft delete (status=deprecated), this removes all records from database
149
+ *
150
+ * Use cases:
151
+ * - Manual cleanup after decision-to-task migration
152
+ * - Remove test/debug decisions that are no longer needed
153
+ * - Purge sensitive data that should not be retained
154
+ *
155
+ * WARNING: This operation is irreversible. Version history and all relationships
156
+ * (tags, scopes) will also be deleted due to CASCADE constraints.
157
+ *
158
+ * @param params - Decision key to delete
159
+ * @returns Response with success status
160
+ */
161
+ export declare function hardDeleteDecision(params: HardDeleteDecisionParams): HardDeleteDecisionResponse;
146
162
  //# sourceMappingURL=context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/tools/context.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EAInB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,sBAAsB,EACtB,wBAAwB,EACxB,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,EACtB,wBAAwB,EACxB,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,sBAAsB,EACtB,mBAAmB,EACnB,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,EACnB,MAAM,aAAa,CAAC;AAoHrB;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,mBAAmB,CAY1E;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,MAAM,GAAE,gBAAqB,GAAG,kBAAkB,CAiE5E;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,mBAAmB,CA2B1E;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,oBAAoB,CAkE7E;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,mBAAmB,CA4D1E;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,qBAAqB,CAkGhF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,wBAAwB,CAoFzF;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAC,MAAM,GAAE,oBAAyB,GAAG,sBAAsB,CA4KxF;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,wBAAwB,CAkGzF;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,kBAAkB,CAoEvE;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,qBAAqB,GAAG,uBAAuB,CA8FtF;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,sBAAsB,CAwDnF;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,GAAE,mBAAwB,GAAG,qBAAqB,CA4CrF"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/tools/context.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EAInB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,sBAAsB,EACtB,wBAAwB,EACxB,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,EACtB,wBAAwB,EACxB,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,sBAAsB,EACtB,mBAAmB,EACnB,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,EAClB,wBAAwB,EACxB,0BAA0B,EAC3B,MAAM,aAAa,CAAC;AA+GrB;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,mBAAmB,CAY1E;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,MAAM,GAAE,gBAAqB,GAAG,kBAAkB,CAiE5E;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,mBAAmB,CA2B1E;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,oBAAoB,CAkE7E;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,mBAAmB,CA4D1E;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,qBAAqB,CAkGhF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,wBAAwB,CAoFzF;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAAC,MAAM,GAAE,oBAAyB,GAAG,sBAAsB,CA4KxF;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,wBAAwB,CAuCzF;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,kBAAkB,CAoEvE;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,qBAAqB,GAAG,uBAAuB,CA8FtF;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,sBAAsB,CAwDnF;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,GAAE,mBAAwB,GAAG,qBAAqB,CA4CrF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,wBAAwB,GAAG,0BAA0B,CA4D/F"}
@@ -4,6 +4,8 @@
4
4
  */
5
5
  import { getDatabase, getOrCreateAgent, getOrCreateContextKey, getOrCreateTag, getOrCreateScope, getLayerId, transaction } from '../database.js';
6
6
  import { STRING_TO_STATUS, DEFAULT_VERSION, DEFAULT_STATUS } from '../constants.js';
7
+ import { processBatch } from '../utils/batch.js';
8
+ import { validateRequired, validateStatus, validateLayer } from '../utils/validators.js';
7
9
  /**
8
10
  * Internal helper: Set decision without wrapping in transaction
9
11
  * Used by setDecision (with transaction) and setDecisionBatch (manages its own transaction)
@@ -14,9 +16,7 @@ import { STRING_TO_STATUS, DEFAULT_VERSION, DEFAULT_STATUS } from '../constants.
14
16
  */
15
17
  function setDecisionInternal(params, db) {
16
18
  // Validate required parameters
17
- if (!params.key || params.key.trim() === '') {
18
- throw new Error('Parameter "key" is required and cannot be empty');
19
- }
19
+ const trimmedKey = validateRequired(params.key, 'key');
20
20
  if (params.value === undefined || params.value === null) {
21
21
  throw new Error('Parameter "value" is required');
22
22
  }
@@ -28,16 +28,13 @@ function setDecisionInternal(params, db) {
28
28
  const status = params.status ? STRING_TO_STATUS[params.status] : DEFAULT_STATUS;
29
29
  const agentName = params.agent || 'system';
30
30
  // Validate status
31
- if (params.status && !STRING_TO_STATUS[params.status]) {
32
- throw new Error(`Invalid status: ${params.status}. Must be 'active', 'deprecated', or 'draft'`);
31
+ if (params.status) {
32
+ validateStatus(params.status);
33
33
  }
34
34
  // Validate layer if provided
35
35
  let layerId = null;
36
36
  if (params.layer) {
37
- layerId = getLayerId(db, params.layer);
38
- if (layerId === null) {
39
- throw new Error(`Invalid layer: ${params.layer}. Must be one of: presentation, business, data, infrastructure, cross-cutting`);
40
- }
37
+ layerId = validateLayer(db, params.layer);
41
38
  }
42
39
  // Get or create master records
43
40
  const agentId = getOrCreateAgent(db, agentName);
@@ -47,31 +44,31 @@ function setDecisionInternal(params, db) {
47
44
  // Insert or update decision based on value type
48
45
  if (isNumeric) {
49
46
  // Numeric decision
50
- const stmt = db.prepare(`
51
- INSERT INTO t_decisions_numeric (key_id, value, agent_id, layer_id, version, status, ts)
52
- VALUES (?, ?, ?, ?, ?, ?, ?)
53
- ON CONFLICT(key_id) DO UPDATE SET
54
- value = excluded.value,
55
- agent_id = excluded.agent_id,
56
- layer_id = excluded.layer_id,
57
- version = excluded.version,
58
- status = excluded.status,
59
- ts = excluded.ts
47
+ const stmt = db.prepare(`
48
+ INSERT INTO t_decisions_numeric (key_id, value, agent_id, layer_id, version, status, ts)
49
+ VALUES (?, ?, ?, ?, ?, ?, ?)
50
+ ON CONFLICT(key_id) DO UPDATE SET
51
+ value = excluded.value,
52
+ agent_id = excluded.agent_id,
53
+ layer_id = excluded.layer_id,
54
+ version = excluded.version,
55
+ status = excluded.status,
56
+ ts = excluded.ts
60
57
  `);
61
58
  stmt.run(keyId, value, agentId, layerId, version, status, ts);
62
59
  }
63
60
  else {
64
61
  // String decision
65
- const stmt = db.prepare(`
66
- INSERT INTO t_decisions (key_id, value, agent_id, layer_id, version, status, ts)
67
- VALUES (?, ?, ?, ?, ?, ?, ?)
68
- ON CONFLICT(key_id) DO UPDATE SET
69
- value = excluded.value,
70
- agent_id = excluded.agent_id,
71
- layer_id = excluded.layer_id,
72
- version = excluded.version,
73
- status = excluded.status,
74
- ts = excluded.ts
62
+ const stmt = db.prepare(`
63
+ INSERT INTO t_decisions (key_id, value, agent_id, layer_id, version, status, ts)
64
+ VALUES (?, ?, ?, ?, ?, ?, ?)
65
+ ON CONFLICT(key_id) DO UPDATE SET
66
+ value = excluded.value,
67
+ agent_id = excluded.agent_id,
68
+ layer_id = excluded.layer_id,
69
+ version = excluded.version,
70
+ status = excluded.status,
71
+ ts = excluded.ts
75
72
  `);
76
73
  stmt.run(keyId, String(value), agentId, layerId, version, status, ts);
77
74
  }
@@ -320,16 +317,16 @@ export function getVersions(params) {
320
317
  }
321
318
  const keyId = keyResult.id;
322
319
  // Query t_decision_history with agent join
323
- const stmt = db.prepare(`
324
- SELECT
325
- dh.version,
326
- dh.value,
327
- a.name as agent_name,
328
- datetime(dh.ts, 'unixepoch') as timestamp
329
- FROM t_decision_history dh
330
- LEFT JOIN m_agents a ON dh.agent_id = a.id
331
- WHERE dh.key_id = ?
332
- ORDER BY dh.ts DESC
320
+ const stmt = db.prepare(`
321
+ SELECT
322
+ dh.version,
323
+ dh.value,
324
+ a.name as agent_name,
325
+ datetime(dh.ts, 'unixepoch') as timestamp
326
+ FROM t_decision_history dh
327
+ LEFT JOIN m_agents a ON dh.agent_id = a.id
328
+ WHERE dh.key_id = ?
329
+ ORDER BY dh.ts DESC
333
330
  `);
334
331
  const rows = stmt.all(keyId);
335
332
  // Transform to response format
@@ -380,58 +377,58 @@ export function searchByLayer(params) {
380
377
  const queryParams = [params.layer, statusValue];
381
378
  if (includeTagsValue) {
382
379
  // Use v_tagged_decisions view for full metadata
383
- query = `
384
- SELECT * FROM v_tagged_decisions
385
- WHERE layer = ? AND status = ?
386
- ORDER BY updated DESC
380
+ query = `
381
+ SELECT * FROM v_tagged_decisions
382
+ WHERE layer = ? AND status = ?
383
+ ORDER BY updated DESC
387
384
  `;
388
385
  }
389
386
  else {
390
387
  // Use base t_decisions table with minimal joins
391
- query = `
392
- SELECT
393
- ck.key,
394
- d.value,
395
- d.version,
396
- CASE d.status
397
- WHEN 1 THEN 'active'
398
- WHEN 2 THEN 'deprecated'
399
- WHEN 3 THEN 'draft'
400
- END as status,
401
- l.name as layer,
402
- NULL as tags,
403
- NULL as scopes,
404
- a.name as decided_by,
405
- datetime(d.ts, 'unixepoch') as updated
406
- FROM t_decisions d
407
- INNER JOIN m_context_keys ck ON d.key_id = ck.id
408
- LEFT JOIN m_layers l ON d.layer_id = l.id
409
- LEFT JOIN m_agents a ON d.agent_id = a.id
410
- WHERE l.name = ? AND d.status = ?
411
-
412
- UNION ALL
413
-
414
- SELECT
415
- ck.key,
416
- CAST(dn.value AS TEXT) as value,
417
- dn.version,
418
- CASE dn.status
419
- WHEN 1 THEN 'active'
420
- WHEN 2 THEN 'deprecated'
421
- WHEN 3 THEN 'draft'
422
- END as status,
423
- l.name as layer,
424
- NULL as tags,
425
- NULL as scopes,
426
- a.name as decided_by,
427
- datetime(dn.ts, 'unixepoch') as updated
428
- FROM t_decisions_numeric dn
429
- INNER JOIN m_context_keys ck ON dn.key_id = ck.id
430
- LEFT JOIN m_layers l ON dn.layer_id = l.id
431
- LEFT JOIN m_agents a ON dn.agent_id = a.id
432
- WHERE l.name = ? AND dn.status = ?
433
-
434
- ORDER BY updated DESC
388
+ query = `
389
+ SELECT
390
+ ck.key,
391
+ d.value,
392
+ d.version,
393
+ CASE d.status
394
+ WHEN 1 THEN 'active'
395
+ WHEN 2 THEN 'deprecated'
396
+ WHEN 3 THEN 'draft'
397
+ END as status,
398
+ l.name as layer,
399
+ NULL as tags,
400
+ NULL as scopes,
401
+ a.name as decided_by,
402
+ datetime(d.ts, 'unixepoch') as updated
403
+ FROM t_decisions d
404
+ INNER JOIN m_context_keys ck ON d.key_id = ck.id
405
+ LEFT JOIN m_layers l ON d.layer_id = l.id
406
+ LEFT JOIN m_agents a ON d.agent_id = a.id
407
+ WHERE l.name = ? AND d.status = ?
408
+
409
+ UNION ALL
410
+
411
+ SELECT
412
+ ck.key,
413
+ CAST(dn.value AS TEXT) as value,
414
+ dn.version,
415
+ CASE dn.status
416
+ WHEN 1 THEN 'active'
417
+ WHEN 2 THEN 'deprecated'
418
+ WHEN 3 THEN 'draft'
419
+ END as status,
420
+ l.name as layer,
421
+ NULL as tags,
422
+ NULL as scopes,
423
+ a.name as decided_by,
424
+ datetime(dn.ts, 'unixepoch') as updated
425
+ FROM t_decisions_numeric dn
426
+ INNER JOIN m_context_keys ck ON dn.key_id = ck.id
427
+ LEFT JOIN m_layers l ON dn.layer_id = l.id
428
+ LEFT JOIN m_agents a ON dn.agent_id = a.id
429
+ WHERE l.name = ? AND dn.status = ?
430
+
431
+ ORDER BY updated DESC
435
432
  `;
436
433
  // Add params for the numeric table part of UNION
437
434
  queryParams.push(params.layer, statusValue);
@@ -739,86 +736,29 @@ export function setDecisionBatch(params) {
739
736
  if (!params.decisions || !Array.isArray(params.decisions)) {
740
737
  throw new Error('Parameter "decisions" is required and must be an array');
741
738
  }
742
- // Enforce limit (constraint #3)
743
- if (params.decisions.length === 0) {
744
- throw new Error('Parameter "decisions" must contain at least one item');
745
- }
746
- if (params.decisions.length > 50) {
747
- throw new Error('Batch operations are limited to 50 items maximum (constraint #3)');
748
- }
749
739
  const atomic = params.atomic !== undefined ? params.atomic : true;
750
- const results = [];
751
- let inserted = 0;
752
- let failed = 0;
753
- // Helper function to process a single decision
754
- const processSingleDecision = (decision) => {
755
- try {
756
- const result = setDecisionInternal(decision, db);
757
- results.push({
758
- key: decision.key,
759
- key_id: result.key_id,
760
- version: result.version,
761
- success: true
762
- });
763
- inserted++;
764
- }
765
- catch (error) {
766
- const errorMessage = error instanceof Error ? error.message : String(error);
767
- results.push({
768
- key: decision.key,
769
- success: false,
770
- error: errorMessage
771
- });
772
- failed++;
773
- // In atomic mode, throw immediately to trigger rollback
774
- if (atomic) {
775
- throw error;
776
- }
777
- }
740
+ // Use processBatch utility
741
+ const batchResult = processBatch(db, params.decisions, (decision, db) => {
742
+ const result = setDecisionInternal(decision, db);
743
+ return {
744
+ key: decision.key,
745
+ key_id: result.key_id,
746
+ version: result.version
747
+ };
748
+ }, atomic, 50);
749
+ // Map batch results to SetDecisionBatchResponse format
750
+ return {
751
+ success: batchResult.success,
752
+ inserted: batchResult.processed,
753
+ failed: batchResult.failed,
754
+ results: batchResult.results.map(r => ({
755
+ key: r.data?.key || '',
756
+ key_id: r.data?.key_id,
757
+ version: r.data?.version,
758
+ success: r.success,
759
+ error: r.error
760
+ }))
778
761
  };
779
- try {
780
- if (atomic) {
781
- // Atomic mode: use transaction, all succeed or all fail
782
- return transaction(db, () => {
783
- for (const decision of params.decisions) {
784
- processSingleDecision(decision);
785
- }
786
- return {
787
- success: failed === 0,
788
- inserted,
789
- failed,
790
- results
791
- };
792
- });
793
- }
794
- else {
795
- // Non-atomic mode: process all, return individual results
796
- for (const decision of params.decisions) {
797
- processSingleDecision(decision);
798
- }
799
- return {
800
- success: failed === 0,
801
- inserted,
802
- failed,
803
- results
804
- };
805
- }
806
- }
807
- catch (error) {
808
- if (atomic) {
809
- // In atomic mode, if any error occurred, all failed
810
- throw new Error(`Batch operation failed (atomic mode): ${error instanceof Error ? error.message : String(error)}`);
811
- }
812
- else {
813
- // In non-atomic mode, return partial results
814
- return {
815
- success: false,
816
- inserted,
817
- failed,
818
- results
819
- };
820
- }
821
- }
822
762
  }
823
763
  /**
824
764
  * Check for updates since a given timestamp (FR-003 Phase A)
@@ -845,12 +785,12 @@ export function hasUpdates(params) {
845
785
  }
846
786
  const sinceTs = Math.floor(sinceDate.getTime() / 1000);
847
787
  // Count decisions updated since timestamp (both string and numeric tables)
848
- const decisionCountStmt = db.prepare(`
849
- SELECT COUNT(*) as count FROM (
850
- SELECT ts FROM t_decisions WHERE ts > ?
851
- UNION ALL
852
- SELECT ts FROM t_decisions_numeric WHERE ts > ?
853
- )
788
+ const decisionCountStmt = db.prepare(`
789
+ SELECT COUNT(*) as count FROM (
790
+ SELECT ts FROM t_decisions WHERE ts > ?
791
+ UNION ALL
792
+ SELECT ts FROM t_decisions_numeric WHERE ts > ?
793
+ )
854
794
  `);
855
795
  const decisionResult = decisionCountStmt.get(sinceTs, sinceTs);
856
796
  const decisionsCount = decisionResult.count;
@@ -860,16 +800,16 @@ export function hasUpdates(params) {
860
800
  let messagesCount = 0;
861
801
  if (agentResult) {
862
802
  const agentId = agentResult.id;
863
- const messageCountStmt = db.prepare(`
864
- SELECT COUNT(*) as count FROM t_agent_messages
865
- WHERE ts > ? AND (to_agent_id = ? OR to_agent_id IS NULL)
803
+ const messageCountStmt = db.prepare(`
804
+ SELECT COUNT(*) as count FROM t_agent_messages
805
+ WHERE ts > ? AND (to_agent_id = ? OR to_agent_id IS NULL)
866
806
  `);
867
807
  const messageResult = messageCountStmt.get(sinceTs, agentId);
868
808
  messagesCount = messageResult.count;
869
809
  }
870
810
  // Count file changes since timestamp
871
- const fileCountStmt = db.prepare(`
872
- SELECT COUNT(*) as count FROM t_file_changes WHERE ts > ?
811
+ const fileCountStmt = db.prepare(`
812
+ SELECT COUNT(*) as count FROM t_file_changes WHERE ts > ?
873
813
  `);
874
814
  const fileResult = fileCountStmt.get(sinceTs);
875
815
  const filesCount = fileResult.count;
@@ -1004,9 +944,9 @@ export function createTemplate(params) {
1004
944
  const defaultsJson = JSON.stringify(params.defaults);
1005
945
  const requiredFieldsJson = params.required_fields ? JSON.stringify(params.required_fields) : null;
1006
946
  // Insert template
1007
- const stmt = db.prepare(`
1008
- INSERT INTO t_decision_templates (name, defaults, required_fields, created_by)
1009
- VALUES (?, ?, ?, ?)
947
+ const stmt = db.prepare(`
948
+ INSERT INTO t_decision_templates (name, defaults, required_fields, created_by)
949
+ VALUES (?, ?, ?, ?)
1010
950
  `);
1011
951
  const info = stmt.run(params.name, defaultsJson, requiredFieldsJson, createdById);
1012
952
  return {
@@ -1032,17 +972,17 @@ export function createTemplate(params) {
1032
972
  export function listTemplates(params = {}) {
1033
973
  const db = getDatabase();
1034
974
  try {
1035
- const stmt = db.prepare(`
1036
- SELECT
1037
- t.id,
1038
- t.name,
1039
- t.defaults,
1040
- t.required_fields,
1041
- a.name as created_by,
1042
- datetime(t.ts, 'unixepoch') as created_at
1043
- FROM t_decision_templates t
1044
- LEFT JOIN m_agents a ON t.created_by = a.id
1045
- ORDER BY t.name ASC
975
+ const stmt = db.prepare(`
976
+ SELECT
977
+ t.id,
978
+ t.name,
979
+ t.defaults,
980
+ t.required_fields,
981
+ a.name as created_by,
982
+ datetime(t.ts, 'unixepoch') as created_at
983
+ FROM t_decision_templates t
984
+ LEFT JOIN m_agents a ON t.created_by = a.id
985
+ ORDER BY t.name ASC
1046
986
  `);
1047
987
  const rows = stmt.all();
1048
988
  // Parse JSON fields
@@ -1064,4 +1004,69 @@ export function listTemplates(params = {}) {
1064
1004
  throw new Error(`Failed to list templates: ${message}`);
1065
1005
  }
1066
1006
  }
1007
+ /**
1008
+ * Permanently delete a decision and all related data (hard delete)
1009
+ * Unlike soft delete (status=deprecated), this removes all records from database
1010
+ *
1011
+ * Use cases:
1012
+ * - Manual cleanup after decision-to-task migration
1013
+ * - Remove test/debug decisions that are no longer needed
1014
+ * - Purge sensitive data that should not be retained
1015
+ *
1016
+ * WARNING: This operation is irreversible. Version history and all relationships
1017
+ * (tags, scopes) will also be deleted due to CASCADE constraints.
1018
+ *
1019
+ * @param params - Decision key to delete
1020
+ * @returns Response with success status
1021
+ */
1022
+ export function hardDeleteDecision(params) {
1023
+ const db = getDatabase();
1024
+ // Validate parameter
1025
+ if (!params.key || params.key.trim() === '') {
1026
+ throw new Error('Parameter "key" is required and cannot be empty');
1027
+ }
1028
+ try {
1029
+ return transaction(db, () => {
1030
+ // Get key_id
1031
+ const keyResult = db.prepare('SELECT id FROM m_context_keys WHERE key = ?').get(params.key);
1032
+ if (!keyResult) {
1033
+ // Key doesn't exist - still return success (idempotent)
1034
+ return {
1035
+ success: true,
1036
+ key: params.key,
1037
+ message: `Decision "${params.key}" not found (already deleted or never existed)`
1038
+ };
1039
+ }
1040
+ const keyId = keyResult.id;
1041
+ // Delete from t_decisions (if exists)
1042
+ const deletedString = db.prepare('DELETE FROM t_decisions WHERE key_id = ?').run(keyId);
1043
+ // Delete from t_decisions_numeric (if exists)
1044
+ const deletedNumeric = db.prepare('DELETE FROM t_decisions_numeric WHERE key_id = ?').run(keyId);
1045
+ // Delete from t_decision_history (CASCADE should handle this, but explicit for clarity)
1046
+ const deletedHistory = db.prepare('DELETE FROM t_decision_history WHERE key_id = ?').run(keyId);
1047
+ // Delete from t_decision_tags (CASCADE should handle this)
1048
+ const deletedTags = db.prepare('DELETE FROM t_decision_tags WHERE decision_key_id = ?').run(keyId);
1049
+ // Delete from t_decision_scopes (CASCADE should handle this)
1050
+ const deletedScopes = db.prepare('DELETE FROM t_decision_scopes WHERE decision_key_id = ?').run(keyId);
1051
+ // Calculate total deleted records
1052
+ const totalDeleted = deletedString.changes + deletedNumeric.changes + deletedHistory.changes + deletedTags.changes + deletedScopes.changes;
1053
+ if (totalDeleted === 0) {
1054
+ return {
1055
+ success: true,
1056
+ key: params.key,
1057
+ message: `Decision "${params.key}" not found (already deleted or never existed)`
1058
+ };
1059
+ }
1060
+ return {
1061
+ success: true,
1062
+ key: params.key,
1063
+ message: `Decision "${params.key}" permanently deleted (${totalDeleted} record${totalDeleted === 1 ? '' : 's'})`
1064
+ };
1065
+ });
1066
+ }
1067
+ catch (error) {
1068
+ const message = error instanceof Error ? error.message : String(error);
1069
+ throw new Error(`Failed to hard delete decision: ${message}`);
1070
+ }
1071
+ }
1067
1072
  //# sourceMappingURL=context.js.map