sqlew 3.5.3 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +247 -1772
- package/README.md +70 -304
- package/assets/config.example.toml +97 -0
- package/dist/adapters/index.d.ts +11 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +21 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/mysql-adapter.d.ts +31 -0
- package/dist/adapters/mysql-adapter.d.ts.map +1 -0
- package/dist/adapters/mysql-adapter.js +63 -0
- package/dist/adapters/mysql-adapter.js.map +1 -0
- package/dist/adapters/postgresql-adapter.d.ts +31 -0
- package/dist/adapters/postgresql-adapter.d.ts.map +1 -0
- package/dist/adapters/postgresql-adapter.js +63 -0
- package/dist/adapters/postgresql-adapter.js.map +1 -0
- package/dist/adapters/sqlite-adapter.d.ts +37 -0
- package/dist/adapters/sqlite-adapter.d.ts.map +1 -0
- package/dist/adapters/sqlite-adapter.js +129 -0
- package/dist/adapters/sqlite-adapter.js.map +1 -0
- package/dist/adapters/types.d.ts +33 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +2 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/cli.js +55 -54
- package/dist/cli.js.map +1 -1
- package/dist/config/example-generator.d.ts +11 -0
- package/dist/config/example-generator.d.ts.map +1 -0
- package/dist/config/example-generator.js +48 -0
- package/dist/config/example-generator.js.map +1 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +4 -0
- package/dist/config/loader.js.map +1 -1
- package/dist/config/types.d.ts +9 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js.map +1 -1
- package/dist/database.d.ts +44 -122
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +145 -416
- package/dist/database.js.map +1 -1
- package/dist/index.js +215 -185
- package/dist/index.js.map +1 -1
- package/dist/knexfile.d.ts +6 -0
- package/dist/knexfile.d.ts.map +1 -0
- package/dist/knexfile.js +85 -0
- package/dist/knexfile.js.map +1 -0
- package/dist/migrations/add-help-system-tables.d.ts +35 -0
- package/dist/migrations/add-help-system-tables.d.ts.map +1 -0
- package/dist/migrations/add-help-system-tables.js +206 -0
- package/dist/migrations/add-help-system-tables.js.map +1 -0
- package/dist/migrations/add-token-tracking.d.ts +28 -0
- package/dist/migrations/add-token-tracking.d.ts.map +1 -0
- package/dist/migrations/add-token-tracking.js +108 -0
- package/dist/migrations/add-token-tracking.js.map +1 -0
- package/dist/migrations/index.d.ts +25 -12
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +147 -20
- package/dist/migrations/index.js.map +1 -1
- package/dist/migrations/knex/20251025020452_create_master_tables.d.ts +4 -0
- package/dist/migrations/knex/20251025020452_create_master_tables.d.ts.map +1 -0
- package/dist/migrations/knex/20251025020452_create_master_tables.js +65 -0
- package/dist/migrations/knex/20251025020452_create_master_tables.js.map +1 -0
- package/dist/migrations/knex/20251025021152_create_transaction_tables.d.ts +4 -0
- package/dist/migrations/knex/20251025021152_create_transaction_tables.d.ts.map +1 -0
- package/dist/migrations/knex/20251025021152_create_transaction_tables.js +235 -0
- package/dist/migrations/knex/20251025021152_create_transaction_tables.js.map +1 -0
- package/dist/migrations/knex/20251025021351_create_indexes.d.ts +4 -0
- package/dist/migrations/knex/20251025021351_create_indexes.d.ts.map +1 -0
- package/dist/migrations/knex/20251025021351_create_indexes.js +62 -0
- package/dist/migrations/knex/20251025021351_create_indexes.js.map +1 -0
- package/dist/migrations/knex/20251025021416_seed_master_data.d.ts +4 -0
- package/dist/migrations/knex/20251025021416_seed_master_data.d.ts.map +1 -0
- package/dist/migrations/knex/20251025021416_seed_master_data.js +58 -0
- package/dist/migrations/knex/20251025021416_seed_master_data.js.map +1 -0
- package/dist/migrations/knex/20251025070349_create_views.d.ts +4 -0
- package/dist/migrations/knex/20251025070349_create_views.d.ts.map +1 -0
- package/dist/migrations/knex/20251025070349_create_views.js +143 -0
- package/dist/migrations/knex/20251025070349_create_views.js.map +1 -0
- package/dist/migrations/knex/20251025081221_add_link_type_to_task_decision_links.d.ts +4 -0
- package/dist/migrations/knex/20251025081221_add_link_type_to_task_decision_links.d.ts.map +1 -0
- package/dist/migrations/knex/20251025081221_add_link_type_to_task_decision_links.js +15 -0
- package/dist/migrations/knex/20251025081221_add_link_type_to_task_decision_links.js.map +1 -0
- package/dist/migrations/knex/20251025082220_fix_task_dependencies_columns.d.ts +8 -0
- package/dist/migrations/knex/20251025082220_fix_task_dependencies_columns.d.ts.map +1 -0
- package/dist/migrations/knex/20251025082220_fix_task_dependencies_columns.js +12 -0
- package/dist/migrations/knex/20251025082220_fix_task_dependencies_columns.js.map +1 -0
- package/dist/migrations/knex/20251025090000_create_help_system_tables.d.ts +19 -0
- package/dist/migrations/knex/20251025090000_create_help_system_tables.d.ts.map +1 -0
- package/dist/migrations/knex/20251025090000_create_help_system_tables.js +115 -0
- package/dist/migrations/knex/20251025090000_create_help_system_tables.js.map +1 -0
- package/dist/migrations/knex/20251025090100_seed_help_categories_and_use_cases.d.ts +13 -0
- package/dist/migrations/knex/20251025090100_seed_help_categories_and_use_cases.d.ts.map +1 -0
- package/dist/migrations/knex/20251025090100_seed_help_categories_and_use_cases.js +377 -0
- package/dist/migrations/knex/20251025090100_seed_help_categories_and_use_cases.js.map +1 -0
- package/dist/migrations/knex/20251025100000_seed_help_metadata.d.ts +15 -0
- package/dist/migrations/knex/20251025100000_seed_help_metadata.d.ts.map +1 -0
- package/dist/migrations/knex/20251025100000_seed_help_metadata.js +253 -0
- package/dist/migrations/knex/20251025100000_seed_help_metadata.js.map +1 -0
- package/dist/migrations/knex/20251025100100_seed_remaining_use_cases.d.ts +16 -0
- package/dist/migrations/knex/20251025100100_seed_remaining_use_cases.d.ts.map +1 -0
- package/dist/migrations/knex/20251025100100_seed_remaining_use_cases.js +276 -0
- package/dist/migrations/knex/20251025100100_seed_remaining_use_cases.js.map +1 -0
- package/dist/migrations/knex/20251025120000_add_cascade_to_task_dependencies.d.ts +8 -0
- package/dist/migrations/knex/20251025120000_add_cascade_to_task_dependencies.d.ts.map +1 -0
- package/dist/migrations/knex/20251025120000_add_cascade_to_task_dependencies.js +64 -0
- package/dist/migrations/knex/20251025120000_add_cascade_to_task_dependencies.js.map +1 -0
- package/dist/migrations/seed-help-data.d.ts +48 -0
- package/dist/migrations/seed-help-data.d.ts.map +1 -0
- package/dist/migrations/seed-help-data.js +1466 -0
- package/dist/migrations/seed-help-data.js.map +1 -0
- package/dist/migrations/seed-tool-metadata.d.ts +24 -0
- package/dist/migrations/seed-tool-metadata.d.ts.map +1 -0
- package/dist/migrations/seed-tool-metadata.js +392 -0
- package/dist/migrations/seed-tool-metadata.js.map +1 -0
- package/dist/migrations/v3.6.0-help-system-refactor.d.ts +46 -0
- package/dist/migrations/v3.6.0-help-system-refactor.d.ts.map +1 -0
- package/dist/migrations/v3.6.0-help-system-refactor.js +223 -0
- package/dist/migrations/v3.6.0-help-system-refactor.js.map +1 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +2 -0
- package/dist/schema.js.map +1 -1
- package/dist/tests/git-aware-completion.test.js +89 -70
- package/dist/tests/git-aware-completion.test.js.map +1 -1
- package/dist/tests/help-system.test.d.ts +23 -0
- package/dist/tests/help-system.test.d.ts.map +1 -0
- package/dist/tests/help-system.test.js +374 -0
- package/dist/tests/help-system.test.js.map +1 -0
- package/dist/tests/tasks.auto-pruning-decision-link.test.js +92 -78
- package/dist/tests/tasks.auto-pruning-decision-link.test.js.map +1 -1
- package/dist/tests/tasks.auto-pruning-partial.test.js +106 -95
- package/dist/tests/tasks.auto-pruning-partial.test.js.map +1 -1
- package/dist/tests/tasks.auto-pruning-persistence.test.js +115 -97
- package/dist/tests/tasks.auto-pruning-persistence.test.js.map +1 -1
- package/dist/tests/tasks.auto-pruning-safety.test.js +124 -103
- package/dist/tests/tasks.auto-pruning-safety.test.js.map +1 -1
- package/dist/tests/tasks.dependencies.test.js +338 -307
- package/dist/tests/tasks.dependencies.test.js.map +1 -1
- package/dist/tests/tasks.link-file-backward-compat.test.js +116 -104
- package/dist/tests/tasks.link-file-backward-compat.test.js.map +1 -1
- package/dist/tests/tasks.watch-files-action.test.js +122 -101
- package/dist/tests/tasks.watch-files-action.test.js.map +1 -1
- package/dist/tests/tasks.watch-files-parameter.test.js +105 -94
- package/dist/tests/tasks.watch-files-parameter.test.js.map +1 -1
- package/dist/tests/two-step-git-completion.test.js +176 -133
- package/dist/tests/two-step-git-completion.test.js.map +1 -1
- package/dist/tests/vcs-staging.test.js +1 -1
- package/dist/tests/vcs-staging.test.js.map +1 -1
- package/dist/tools/config.d.ts +9 -6
- package/dist/tools/config.d.ts.map +1 -1
- package/dist/tools/config.js +16 -14
- package/dist/tools/config.js.map +1 -1
- package/dist/tools/constraints.d.ts +10 -7
- package/dist/tools/constraints.d.ts.map +1 -1
- package/dist/tools/constraints.js +66 -48
- package/dist/tools/constraints.js.map +1 -1
- package/dist/tools/context.d.ts +36 -33
- package/dist/tools/context.d.ts.map +1 -1
- package/dist/tools/context.js +374 -330
- package/dist/tools/context.js.map +1 -1
- package/dist/tools/files.d.ts +12 -9
- package/dist/tools/files.d.ts.map +1 -1
- package/dist/tools/files.js +173 -95
- package/dist/tools/files.js.map +1 -1
- package/dist/tools/help-queries.d.ts +130 -0
- package/dist/tools/help-queries.d.ts.map +1 -0
- package/dist/tools/help-queries.js +393 -0
- package/dist/tools/help-queries.js.map +1 -0
- package/dist/tools/messaging.d.ts +14 -11
- package/dist/tools/messaging.d.ts.map +1 -1
- package/dist/tools/messaging.js +217 -133
- package/dist/tools/messaging.js.map +1 -1
- package/dist/tools/tasks.d.ts +18 -16
- package/dist/tools/tasks.d.ts.map +1 -1
- package/dist/tools/tasks.js +513 -439
- package/dist/tools/tasks.js.map +1 -1
- package/dist/tools/utils.d.ts +14 -11
- package/dist/tools/utils.d.ts.map +1 -1
- package/dist/tools/utils.js +86 -121
- package/dist/tools/utils.js.map +1 -1
- package/dist/utils/activity-logging.d.ts +114 -0
- package/dist/utils/activity-logging.d.ts.map +1 -0
- package/dist/utils/activity-logging.js +162 -0
- package/dist/utils/activity-logging.js.map +1 -0
- package/dist/utils/batch.d.ts +2 -2
- package/dist/utils/batch.d.ts.map +1 -1
- package/dist/utils/batch.js +8 -8
- package/dist/utils/batch.js.map +1 -1
- package/dist/utils/cleanup.d.ts +21 -13
- package/dist/utils/cleanup.d.ts.map +1 -1
- package/dist/utils/cleanup.js +31 -24
- package/dist/utils/cleanup.js.map +1 -1
- package/dist/utils/debug-logger.d.ts +44 -0
- package/dist/utils/debug-logger.d.ts.map +1 -0
- package/dist/utils/debug-logger.js +116 -0
- package/dist/utils/debug-logger.js.map +1 -0
- package/dist/utils/help-tracking.d.ts +55 -0
- package/dist/utils/help-tracking.d.ts.map +1 -0
- package/dist/utils/help-tracking.js +88 -0
- package/dist/utils/help-tracking.js.map +1 -0
- package/dist/utils/retention.d.ts +7 -7
- package/dist/utils/retention.d.ts.map +1 -1
- package/dist/utils/retention.js +12 -12
- package/dist/utils/retention.js.map +1 -1
- package/dist/utils/task-stale-detection.d.ts +15 -13
- package/dist/utils/task-stale-detection.d.ts.map +1 -1
- package/dist/utils/task-stale-detection.js +100 -302
- package/dist/utils/task-stale-detection.js.map +1 -1
- package/dist/utils/token-estimation.d.ts +72 -0
- package/dist/utils/token-estimation.d.ts.map +1 -0
- package/dist/utils/token-estimation.js +71 -0
- package/dist/utils/token-estimation.js.map +1 -0
- package/dist/utils/token-logging.d.ts +48 -0
- package/dist/utils/token-logging.d.ts.map +1 -0
- package/dist/utils/token-logging.js +112 -0
- package/dist/utils/token-logging.js.map +1 -0
- package/dist/utils/view-queries.d.ts +34 -0
- package/dist/utils/view-queries.d.ts.map +1 -0
- package/dist/utils/view-queries.js +192 -0
- package/dist/utils/view-queries.js.map +1 -0
- package/dist/watcher/file-watcher.d.ts.map +1 -1
- package/dist/watcher/file-watcher.js +25 -11
- package/dist/watcher/file-watcher.js.map +1 -1
- package/docs/BEST_PRACTICES.md +56 -448
- package/docs/MIGRATION_v3.6.0.md +170 -0
- package/docs/SHARED_CONCEPTS.md +63 -208
- package/docs/TASK_OVERVIEW.md +2 -2
- package/docs/TOOL_SELECTION.md +41 -248
- package/package.json +16 -4
package/dist/tools/context.js
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Context management tools for MCP Shared Context Server
|
|
3
3
|
* Implements set_decision, get_context, and get_decision tools
|
|
4
|
+
*
|
|
5
|
+
* CONVERTED: Using Knex.js with DatabaseAdapter (async/await)
|
|
4
6
|
*/
|
|
5
|
-
import {
|
|
7
|
+
import { getAdapter, getOrCreateAgent, getOrCreateContextKey, getOrCreateTag, getOrCreateScope, getLayerId, addDecisionContext as dbAddDecisionContext, getDecisionWithContext as dbGetDecisionWithContext, listDecisionContexts as dbListDecisionContexts } from '../database.js';
|
|
6
8
|
import { STRING_TO_STATUS, DEFAULT_VERSION, DEFAULT_STATUS } from '../constants.js';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
+
import { validateRequired, validateStatus } from '../utils/validators.js';
|
|
10
|
+
import { logDecisionSet, logDecisionUpdate, recordDecisionHistory } from '../utils/activity-logging.js';
|
|
9
11
|
/**
|
|
10
12
|
* Internal helper: Set decision without wrapping in transaction
|
|
11
13
|
* Used by setDecision (with transaction) and setDecisionBatch (manages its own transaction)
|
|
12
14
|
*
|
|
13
15
|
* @param params - Decision parameters
|
|
14
|
-
* @param
|
|
16
|
+
* @param adapter - Database adapter instance
|
|
17
|
+
* @param trx - Optional transaction
|
|
15
18
|
* @returns Response with success status and metadata
|
|
16
19
|
*/
|
|
17
|
-
function setDecisionInternal(params,
|
|
20
|
+
async function setDecisionInternal(params, adapter, trx) {
|
|
21
|
+
const knex = trx || adapter.getKnex();
|
|
18
22
|
// Validate required parameters
|
|
19
23
|
const trimmedKey = validateRequired(params.key, 'key');
|
|
20
24
|
if (params.value === undefined || params.value === null) {
|
|
@@ -34,64 +38,85 @@ function setDecisionInternal(params, db) {
|
|
|
34
38
|
// Validate layer if provided
|
|
35
39
|
let layerId = null;
|
|
36
40
|
if (params.layer) {
|
|
37
|
-
|
|
41
|
+
const validLayers = ['presentation', 'business', 'data', 'infrastructure', 'cross-cutting'];
|
|
42
|
+
if (!validLayers.includes(params.layer)) {
|
|
43
|
+
throw new Error(`Invalid layer. Must be one of: ${validLayers.join(', ')}`);
|
|
44
|
+
}
|
|
45
|
+
layerId = await getLayerId(adapter, params.layer, trx);
|
|
46
|
+
if (layerId === null) {
|
|
47
|
+
throw new Error(`Layer not found in database: ${params.layer}`);
|
|
48
|
+
}
|
|
38
49
|
}
|
|
39
50
|
// Get or create master records
|
|
40
|
-
const agentId = getOrCreateAgent(
|
|
41
|
-
const keyId = getOrCreateContextKey(
|
|
51
|
+
const agentId = await getOrCreateAgent(adapter, agentName, trx);
|
|
52
|
+
const keyId = await getOrCreateContextKey(adapter, params.key, trx);
|
|
42
53
|
// Current timestamp
|
|
43
54
|
const ts = Math.floor(Date.now() / 1000);
|
|
55
|
+
// Check if decision already exists for activity logging
|
|
56
|
+
const existingDecision = await knex(isNumeric ? 't_decisions_numeric' : 't_decisions')
|
|
57
|
+
.where({ key_id: keyId })
|
|
58
|
+
.first();
|
|
44
59
|
// Insert or update decision based on value type
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
const tableName = isNumeric ? 't_decisions_numeric' : 't_decisions';
|
|
61
|
+
const decisionData = {
|
|
62
|
+
key_id: keyId,
|
|
63
|
+
value: isNumeric ? value : String(value),
|
|
64
|
+
agent_id: agentId,
|
|
65
|
+
layer_id: layerId,
|
|
66
|
+
version: version,
|
|
67
|
+
status: status,
|
|
68
|
+
ts: ts
|
|
69
|
+
};
|
|
70
|
+
await adapter.upsert(tableName, decisionData, ['key_id']);
|
|
71
|
+
// Activity logging (replaces triggers)
|
|
72
|
+
if (existingDecision) {
|
|
73
|
+
// Update case - log update and record history
|
|
74
|
+
await logDecisionUpdate(knex, {
|
|
75
|
+
key: params.key,
|
|
76
|
+
old_value: String(existingDecision.value),
|
|
77
|
+
new_value: String(value),
|
|
78
|
+
old_version: existingDecision.version,
|
|
79
|
+
new_version: version,
|
|
80
|
+
agent_id: agentId,
|
|
81
|
+
layer_id: layerId || undefined
|
|
82
|
+
});
|
|
83
|
+
await recordDecisionHistory(knex, {
|
|
84
|
+
key_id: keyId,
|
|
85
|
+
version: existingDecision.version,
|
|
86
|
+
value: String(existingDecision.value),
|
|
87
|
+
agent_id: existingDecision.agent_id,
|
|
88
|
+
ts: existingDecision.ts
|
|
89
|
+
});
|
|
59
90
|
}
|
|
60
91
|
else {
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
status = excluded.status,
|
|
71
|
-
ts = excluded.ts
|
|
72
|
-
`);
|
|
73
|
-
stmt.run(keyId, String(value), agentId, layerId, version, status, ts);
|
|
92
|
+
// New decision case - log set
|
|
93
|
+
await logDecisionSet(knex, {
|
|
94
|
+
key: params.key,
|
|
95
|
+
value: String(value),
|
|
96
|
+
version: version,
|
|
97
|
+
status: status,
|
|
98
|
+
agent_id: agentId,
|
|
99
|
+
layer_id: layerId || undefined
|
|
100
|
+
});
|
|
74
101
|
}
|
|
75
102
|
// Handle m_tags (many-to-many)
|
|
76
103
|
if (params.tags && params.tags.length > 0) {
|
|
77
104
|
// Clear existing tags
|
|
78
|
-
|
|
105
|
+
await knex('t_decision_tags').where({ decision_key_id: keyId }).delete();
|
|
79
106
|
// Insert new tags
|
|
80
|
-
const tagStmt = db.prepare('INSERT INTO t_decision_tags (decision_key_id, tag_id) VALUES (?, ?)');
|
|
81
107
|
for (const tagName of params.tags) {
|
|
82
|
-
const tagId = getOrCreateTag(
|
|
83
|
-
|
|
108
|
+
const tagId = await getOrCreateTag(adapter, tagName, trx);
|
|
109
|
+
await knex('t_decision_tags').insert({ decision_key_id: keyId, tag_id: tagId });
|
|
84
110
|
}
|
|
85
111
|
}
|
|
86
112
|
// Handle m_scopes (many-to-many)
|
|
87
113
|
if (params.scopes && params.scopes.length > 0) {
|
|
88
114
|
// Clear existing scopes
|
|
89
|
-
|
|
115
|
+
await knex('t_decision_scopes').where({ decision_key_id: keyId }).delete();
|
|
90
116
|
// Insert new scopes
|
|
91
|
-
const scopeStmt = db.prepare('INSERT INTO t_decision_scopes (decision_key_id, scope_id) VALUES (?, ?)');
|
|
92
117
|
for (const scopeName of params.scopes) {
|
|
93
|
-
const scopeId = getOrCreateScope(
|
|
94
|
-
|
|
118
|
+
const scopeId = await getOrCreateScope(adapter, scopeName, trx);
|
|
119
|
+
await knex('t_decision_scopes').insert({ decision_key_id: keyId, scope_id: scopeId });
|
|
95
120
|
}
|
|
96
121
|
}
|
|
97
122
|
return {
|
|
@@ -108,15 +133,15 @@ function setDecisionInternal(params, db) {
|
|
|
108
133
|
* Supports tags, layers, scopes, and version tracking
|
|
109
134
|
*
|
|
110
135
|
* @param params - Decision parameters
|
|
111
|
-
* @param
|
|
136
|
+
* @param adapter - Optional database adapter (for testing)
|
|
112
137
|
* @returns Response with success status and metadata
|
|
113
138
|
*/
|
|
114
|
-
export function setDecision(params,
|
|
115
|
-
const
|
|
139
|
+
export async function setDecision(params, adapter) {
|
|
140
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
116
141
|
try {
|
|
117
142
|
// Use transaction for atomicity
|
|
118
|
-
return transaction(
|
|
119
|
-
return setDecisionInternal(params,
|
|
143
|
+
return await actualAdapter.transaction(async (trx) => {
|
|
144
|
+
return await setDecisionInternal(params, actualAdapter, trx);
|
|
120
145
|
});
|
|
121
146
|
}
|
|
122
147
|
catch (error) {
|
|
@@ -130,33 +155,30 @@ export function setDecision(params, db) {
|
|
|
130
155
|
* Supports filtering by status, layer, tags, and scope
|
|
131
156
|
*
|
|
132
157
|
* @param params - Filter parameters
|
|
133
|
-
* @param
|
|
158
|
+
* @param adapter - Optional database adapter (for testing)
|
|
134
159
|
* @returns Array of t_decisions with metadata
|
|
135
160
|
*/
|
|
136
|
-
export function getContext(params = {},
|
|
137
|
-
const
|
|
161
|
+
export async function getContext(params = {}, adapter) {
|
|
162
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
163
|
+
const knex = actualAdapter.getKnex();
|
|
138
164
|
try {
|
|
139
165
|
// Build query dynamically based on filters
|
|
140
|
-
let query = '
|
|
141
|
-
const queryParams = [];
|
|
166
|
+
let query = knex('v_tagged_decisions');
|
|
142
167
|
// Filter by status
|
|
143
168
|
if (params.status) {
|
|
144
169
|
if (!STRING_TO_STATUS[params.status]) {
|
|
145
170
|
throw new Error(`Invalid status: ${params.status}`);
|
|
146
171
|
}
|
|
147
|
-
query
|
|
148
|
-
queryParams.push(params.status);
|
|
172
|
+
query = query.where('status', params.status);
|
|
149
173
|
}
|
|
150
174
|
// Filter by layer
|
|
151
175
|
if (params.layer) {
|
|
152
|
-
query
|
|
153
|
-
queryParams.push(params.layer);
|
|
176
|
+
query = query.where('layer', params.layer);
|
|
154
177
|
}
|
|
155
178
|
// Filter by scope
|
|
156
179
|
if (params.scope) {
|
|
157
180
|
// Use LIKE for comma-separated scopes
|
|
158
|
-
query
|
|
159
|
-
queryParams.push(`%${params.scope}%`);
|
|
181
|
+
query = query.where('scopes', 'like', `%${params.scope}%`);
|
|
160
182
|
}
|
|
161
183
|
// Filter by tags
|
|
162
184
|
if (params.tags && params.tags.length > 0) {
|
|
@@ -164,24 +186,22 @@ export function getContext(params = {}, db) {
|
|
|
164
186
|
if (tagMatch === 'AND') {
|
|
165
187
|
// All tags must be present
|
|
166
188
|
for (const tag of params.tags) {
|
|
167
|
-
query
|
|
168
|
-
queryParams.push(`%${tag}%`);
|
|
189
|
+
query = query.where('tags', 'like', `%${tag}%`);
|
|
169
190
|
}
|
|
170
191
|
}
|
|
171
192
|
else {
|
|
172
193
|
// Any tag must be present (OR)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
194
|
+
query = query.where((builder) => {
|
|
195
|
+
for (const tag of params.tags) {
|
|
196
|
+
builder.orWhere('tags', 'like', `%${tag}%`);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
178
199
|
}
|
|
179
200
|
}
|
|
180
201
|
// Order by most recent
|
|
181
|
-
query
|
|
202
|
+
query = query.orderBy('updated', 'desc');
|
|
182
203
|
// Execute query
|
|
183
|
-
const
|
|
184
|
-
const rows = stmt.all(...queryParams);
|
|
204
|
+
const rows = await query.select('*');
|
|
185
205
|
return {
|
|
186
206
|
decisions: rows,
|
|
187
207
|
count: rows.length
|
|
@@ -198,11 +218,12 @@ export function getContext(params = {}, db) {
|
|
|
198
218
|
* Optionally includes decision context (v3.2.2)
|
|
199
219
|
*
|
|
200
220
|
* @param params - Decision key and optional include_context flag
|
|
201
|
-
* @param
|
|
221
|
+
* @param adapter - Optional database adapter (for testing)
|
|
202
222
|
* @returns Decision details or not found
|
|
203
223
|
*/
|
|
204
|
-
export function getDecision(params,
|
|
205
|
-
const
|
|
224
|
+
export async function getDecision(params, adapter) {
|
|
225
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
226
|
+
const knex = actualAdapter.getKnex();
|
|
206
227
|
// Validate parameter
|
|
207
228
|
if (!params.key || params.key.trim() === '') {
|
|
208
229
|
throw new Error('Parameter "key" is required and cannot be empty');
|
|
@@ -210,7 +231,7 @@ export function getDecision(params, db) {
|
|
|
210
231
|
try {
|
|
211
232
|
// If include_context is true, use the context-aware function
|
|
212
233
|
if (params.include_context) {
|
|
213
|
-
const result = dbGetDecisionWithContext(
|
|
234
|
+
const result = await dbGetDecisionWithContext(actualAdapter, params.key);
|
|
214
235
|
if (!result) {
|
|
215
236
|
return {
|
|
216
237
|
found: false
|
|
@@ -238,8 +259,9 @@ export function getDecision(params, db) {
|
|
|
238
259
|
};
|
|
239
260
|
}
|
|
240
261
|
// Standard query without context (backward compatible)
|
|
241
|
-
const
|
|
242
|
-
|
|
262
|
+
const row = await knex('v_tagged_decisions')
|
|
263
|
+
.where({ key: params.key })
|
|
264
|
+
.first();
|
|
243
265
|
if (!row) {
|
|
244
266
|
return {
|
|
245
267
|
found: false
|
|
@@ -260,34 +282,33 @@ export function getDecision(params, db) {
|
|
|
260
282
|
* Provides flexible tag-based filtering with status and layer support
|
|
261
283
|
*
|
|
262
284
|
* @param params - Search parameters (tags, match_mode, status, layer)
|
|
263
|
-
* @param
|
|
285
|
+
* @param adapter - Optional database adapter (for testing)
|
|
264
286
|
* @returns Array of t_decisions matching tag criteria
|
|
265
287
|
*/
|
|
266
|
-
export function searchByTags(params,
|
|
267
|
-
const
|
|
288
|
+
export async function searchByTags(params, adapter) {
|
|
289
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
290
|
+
const knex = actualAdapter.getKnex();
|
|
268
291
|
// Validate required parameters
|
|
269
292
|
if (!params.tags || params.tags.length === 0) {
|
|
270
293
|
throw new Error('Parameter "tags" is required and must contain at least one tag');
|
|
271
294
|
}
|
|
272
295
|
try {
|
|
273
296
|
const matchMode = params.match_mode || 'OR';
|
|
274
|
-
let query = '
|
|
275
|
-
const queryParams = [];
|
|
297
|
+
let query = knex('v_tagged_decisions');
|
|
276
298
|
// Apply tag filtering based on match mode
|
|
277
299
|
if (matchMode === 'AND') {
|
|
278
300
|
// All tags must be present
|
|
279
301
|
for (const tag of params.tags) {
|
|
280
|
-
query
|
|
281
|
-
queryParams.push(`%${tag}%`);
|
|
302
|
+
query = query.where('tags', 'like', `%${tag}%`);
|
|
282
303
|
}
|
|
283
304
|
}
|
|
284
305
|
else if (matchMode === 'OR') {
|
|
285
306
|
// Any tag must be present
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
307
|
+
query = query.where((builder) => {
|
|
308
|
+
for (const tag of params.tags) {
|
|
309
|
+
builder.orWhere('tags', 'like', `%${tag}%`);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
291
312
|
}
|
|
292
313
|
else {
|
|
293
314
|
throw new Error(`Invalid match_mode: ${matchMode}. Must be 'AND' or 'OR'`);
|
|
@@ -297,24 +318,21 @@ export function searchByTags(params, db) {
|
|
|
297
318
|
if (!STRING_TO_STATUS[params.status]) {
|
|
298
319
|
throw new Error(`Invalid status: ${params.status}. Must be 'active', 'deprecated', or 'draft'`);
|
|
299
320
|
}
|
|
300
|
-
query
|
|
301
|
-
queryParams.push(params.status);
|
|
321
|
+
query = query.where('status', params.status);
|
|
302
322
|
}
|
|
303
323
|
// Optional layer filter
|
|
304
324
|
if (params.layer) {
|
|
305
325
|
// Validate layer exists
|
|
306
|
-
const layerId = getLayerId(
|
|
326
|
+
const layerId = await getLayerId(actualAdapter, params.layer);
|
|
307
327
|
if (layerId === null) {
|
|
308
328
|
throw new Error(`Invalid layer: ${params.layer}. Must be one of: presentation, business, data, infrastructure, cross-cutting`);
|
|
309
329
|
}
|
|
310
|
-
query
|
|
311
|
-
queryParams.push(params.layer);
|
|
330
|
+
query = query.where('layer', params.layer);
|
|
312
331
|
}
|
|
313
332
|
// Order by most recent
|
|
314
|
-
query
|
|
333
|
+
query = query.orderBy('updated', 'desc');
|
|
315
334
|
// Execute query
|
|
316
|
-
const
|
|
317
|
-
const rows = stmt.all(...queryParams);
|
|
335
|
+
const rows = await query.select('*');
|
|
318
336
|
return {
|
|
319
337
|
decisions: rows,
|
|
320
338
|
count: rows.length
|
|
@@ -330,18 +348,21 @@ export function searchByTags(params, db) {
|
|
|
330
348
|
* Returns all historical versions ordered by timestamp (newest first)
|
|
331
349
|
*
|
|
332
350
|
* @param params - Decision key to get history for
|
|
333
|
-
* @param
|
|
351
|
+
* @param adapter - Optional database adapter (for testing)
|
|
334
352
|
* @returns Array of historical versions with metadata
|
|
335
353
|
*/
|
|
336
|
-
export function getVersions(params,
|
|
337
|
-
const
|
|
354
|
+
export async function getVersions(params, adapter) {
|
|
355
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
356
|
+
const knex = actualAdapter.getKnex();
|
|
338
357
|
// Validate required parameter
|
|
339
358
|
if (!params.key || params.key.trim() === '') {
|
|
340
359
|
throw new Error('Parameter "key" is required and cannot be empty');
|
|
341
360
|
}
|
|
342
361
|
try {
|
|
343
362
|
// Get key_id for the decision
|
|
344
|
-
const keyResult =
|
|
363
|
+
const keyResult = await knex('m_context_keys')
|
|
364
|
+
.where({ key: params.key })
|
|
365
|
+
.first('id');
|
|
345
366
|
if (!keyResult) {
|
|
346
367
|
// Key doesn't exist, return empty history
|
|
347
368
|
return {
|
|
@@ -352,18 +373,11 @@ export function getVersions(params, db) {
|
|
|
352
373
|
}
|
|
353
374
|
const keyId = keyResult.id;
|
|
354
375
|
// Query t_decision_history with agent join
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
datetime(dh.ts, 'unixepoch') as timestamp
|
|
361
|
-
FROM t_decision_history dh
|
|
362
|
-
LEFT JOIN m_agents a ON dh.agent_id = a.id
|
|
363
|
-
WHERE dh.key_id = ?
|
|
364
|
-
ORDER BY dh.ts DESC
|
|
365
|
-
`);
|
|
366
|
-
const rows = stmt.all(keyId);
|
|
376
|
+
const rows = await knex('t_decision_history as dh')
|
|
377
|
+
.leftJoin('m_agents as a', 'dh.agent_id', 'a.id')
|
|
378
|
+
.where('dh.key_id', keyId)
|
|
379
|
+
.select('dh.version', 'dh.value', 'a.name as agent_name', knex.raw(`datetime(dh.ts, 'unixepoch') as timestamp`))
|
|
380
|
+
.orderBy('dh.ts', 'desc');
|
|
367
381
|
// Transform to response format
|
|
368
382
|
const history = rows.map(row => ({
|
|
369
383
|
version: row.version,
|
|
@@ -387,18 +401,19 @@ export function getVersions(params, db) {
|
|
|
387
401
|
* Supports status filtering and optional tag inclusion
|
|
388
402
|
*
|
|
389
403
|
* @param params - Layer name, optional status and include_tags
|
|
390
|
-
* @param
|
|
404
|
+
* @param adapter - Optional database adapter (for testing)
|
|
391
405
|
* @returns Array of t_decisions in the specified layer
|
|
392
406
|
*/
|
|
393
|
-
export function searchByLayer(params,
|
|
394
|
-
const
|
|
407
|
+
export async function searchByLayer(params, adapter) {
|
|
408
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
409
|
+
const knex = actualAdapter.getKnex();
|
|
395
410
|
// Validate required parameter
|
|
396
411
|
if (!params.layer || params.layer.trim() === '') {
|
|
397
412
|
throw new Error('Parameter "layer" is required and cannot be empty');
|
|
398
413
|
}
|
|
399
414
|
try {
|
|
400
415
|
// Validate layer exists
|
|
401
|
-
const layerId = getLayerId(
|
|
416
|
+
const layerId = await getLayerId(actualAdapter, params.layer);
|
|
402
417
|
if (layerId === null) {
|
|
403
418
|
throw new Error(`Invalid layer: ${params.layer}. Must be one of: presentation, business, data, infrastructure, cross-cutting`);
|
|
404
419
|
}
|
|
@@ -409,69 +424,34 @@ export function searchByLayer(params, db) {
|
|
|
409
424
|
if (!STRING_TO_STATUS[statusValue]) {
|
|
410
425
|
throw new Error(`Invalid status: ${statusValue}. Must be 'active', 'deprecated', or 'draft'`);
|
|
411
426
|
}
|
|
412
|
-
let
|
|
413
|
-
const queryParams = [params.layer, statusValue];
|
|
427
|
+
let rows;
|
|
414
428
|
if (includeTagsValue) {
|
|
415
429
|
// Use v_tagged_decisions view for full metadata
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
`;
|
|
430
|
+
rows = await knex('v_tagged_decisions')
|
|
431
|
+
.where({ layer: params.layer, status: statusValue })
|
|
432
|
+
.orderBy('updated', 'desc')
|
|
433
|
+
.select('*');
|
|
421
434
|
}
|
|
422
435
|
else {
|
|
423
436
|
// Use base t_decisions table with minimal joins
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
LEFT JOIN m_layers l ON d.layer_id = l.id
|
|
442
|
-
LEFT JOIN m_agents a ON d.agent_id = a.id
|
|
443
|
-
WHERE l.name = ? AND d.status = ?
|
|
444
|
-
|
|
445
|
-
UNION ALL
|
|
446
|
-
|
|
447
|
-
SELECT
|
|
448
|
-
ck.key,
|
|
449
|
-
CAST(dn.value AS TEXT) as value,
|
|
450
|
-
dn.version,
|
|
451
|
-
CASE dn.status
|
|
452
|
-
WHEN 1 THEN 'active'
|
|
453
|
-
WHEN 2 THEN 'deprecated'
|
|
454
|
-
WHEN 3 THEN 'draft'
|
|
455
|
-
END as status,
|
|
456
|
-
l.name as layer,
|
|
457
|
-
NULL as tags,
|
|
458
|
-
NULL as scopes,
|
|
459
|
-
a.name as decided_by,
|
|
460
|
-
datetime(dn.ts, 'unixepoch') as updated
|
|
461
|
-
FROM t_decisions_numeric dn
|
|
462
|
-
INNER JOIN m_context_keys ck ON dn.key_id = ck.id
|
|
463
|
-
LEFT JOIN m_layers l ON dn.layer_id = l.id
|
|
464
|
-
LEFT JOIN m_agents a ON dn.agent_id = a.id
|
|
465
|
-
WHERE l.name = ? AND dn.status = ?
|
|
466
|
-
|
|
467
|
-
ORDER BY updated DESC
|
|
468
|
-
`;
|
|
469
|
-
// Add params for the numeric table part of UNION
|
|
470
|
-
queryParams.push(params.layer, statusValue);
|
|
437
|
+
const statusInt = STRING_TO_STATUS[statusValue];
|
|
438
|
+
const stringDecisions = knex('t_decisions as d')
|
|
439
|
+
.innerJoin('m_context_keys as ck', 'd.key_id', 'ck.id')
|
|
440
|
+
.leftJoin('m_layers as l', 'd.layer_id', 'l.id')
|
|
441
|
+
.leftJoin('m_agents as a', 'd.agent_id', 'a.id')
|
|
442
|
+
.where('l.name', params.layer)
|
|
443
|
+
.where('d.status', statusInt)
|
|
444
|
+
.select('ck.key', 'd.value', 'd.version', knex.raw(`CASE d.status WHEN 1 THEN 'active' WHEN 2 THEN 'deprecated' WHEN 3 THEN 'draft' END as status`), 'l.name as layer', knex.raw('NULL as tags'), knex.raw('NULL as scopes'), 'a.name as decided_by', knex.raw(`datetime(d.ts, 'unixepoch') as updated`));
|
|
445
|
+
const numericDecisions = knex('t_decisions_numeric as dn')
|
|
446
|
+
.innerJoin('m_context_keys as ck', 'dn.key_id', 'ck.id')
|
|
447
|
+
.leftJoin('m_layers as l', 'dn.layer_id', 'l.id')
|
|
448
|
+
.leftJoin('m_agents as a', 'dn.agent_id', 'a.id')
|
|
449
|
+
.where('l.name', params.layer)
|
|
450
|
+
.where('dn.status', statusInt)
|
|
451
|
+
.select('ck.key', knex.raw('CAST(dn.value AS TEXT) as value'), 'dn.version', knex.raw(`CASE dn.status WHEN 1 THEN 'active' WHEN 2 THEN 'deprecated' WHEN 3 THEN 'draft' END as status`), 'l.name as layer', knex.raw('NULL as tags'), knex.raw('NULL as scopes'), 'a.name as decided_by', knex.raw(`datetime(dn.ts, 'unixepoch') as updated`));
|
|
452
|
+
// Union both queries
|
|
453
|
+
rows = await stringDecisions.union([numericDecisions]).orderBy('updated', 'desc');
|
|
471
454
|
}
|
|
472
|
-
// Execute query
|
|
473
|
-
const stmt = actualDb.prepare(query);
|
|
474
|
-
const rows = stmt.all(...queryParams);
|
|
475
455
|
return {
|
|
476
456
|
layer: params.layer,
|
|
477
457
|
decisions: rows,
|
|
@@ -508,10 +488,10 @@ export function searchByLayer(params, db) {
|
|
|
508
488
|
* All inferred fields can be overridden via optional parameters.
|
|
509
489
|
*
|
|
510
490
|
* @param params - Quick set parameters (key and value required)
|
|
511
|
-
* @param
|
|
491
|
+
* @param adapter - Optional database adapter (for testing)
|
|
512
492
|
* @returns Response with success status and inferred metadata
|
|
513
493
|
*/
|
|
514
|
-
export function quickSetDecision(params,
|
|
494
|
+
export async function quickSetDecision(params, adapter) {
|
|
515
495
|
// Validate required parameters
|
|
516
496
|
if (!params.key || params.key.trim() === '') {
|
|
517
497
|
throw new Error('Parameter "key" is required and cannot be empty');
|
|
@@ -575,8 +555,8 @@ export function quickSetDecision(params, db) {
|
|
|
575
555
|
tags: inferredTags,
|
|
576
556
|
scopes: inferredScopes
|
|
577
557
|
};
|
|
578
|
-
// Call setDecision with full params (pass
|
|
579
|
-
const result = setDecision(fullParams,
|
|
558
|
+
// Call setDecision with full params (pass adapter if provided)
|
|
559
|
+
const result = await setDecision(fullParams, adapter);
|
|
580
560
|
// Return response with inferred metadata
|
|
581
561
|
return {
|
|
582
562
|
success: result.success,
|
|
@@ -603,11 +583,12 @@ export function quickSetDecision(params, db) {
|
|
|
603
583
|
* - search_text: Full-text search in value field
|
|
604
584
|
*
|
|
605
585
|
* @param params - Advanced search parameters with filtering, sorting, pagination
|
|
606
|
-
* @param
|
|
586
|
+
* @param adapter - Optional database adapter (for testing)
|
|
607
587
|
* @returns Filtered decisions with total count for pagination
|
|
608
588
|
*/
|
|
609
|
-
export function searchAdvanced(params = {},
|
|
610
|
-
const
|
|
589
|
+
export async function searchAdvanced(params = {}, adapter) {
|
|
590
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
591
|
+
const knex = actualAdapter.getKnex();
|
|
611
592
|
try {
|
|
612
593
|
// Parse relative time to Unix timestamp
|
|
613
594
|
const parseRelativeTime = (relativeTime) => {
|
|
@@ -631,60 +612,64 @@ export function searchAdvanced(params = {}, db) {
|
|
|
631
612
|
}
|
|
632
613
|
};
|
|
633
614
|
// Build base query using v_tagged_decisions view
|
|
634
|
-
let query = '
|
|
635
|
-
const queryParams = [];
|
|
615
|
+
let query = knex('v_tagged_decisions');
|
|
636
616
|
// Filter by layers (OR relationship)
|
|
637
617
|
if (params.layers && params.layers.length > 0) {
|
|
638
|
-
|
|
639
|
-
query += ` AND (${layerConditions})`;
|
|
640
|
-
queryParams.push(...params.layers);
|
|
618
|
+
query = query.whereIn('layer', params.layers);
|
|
641
619
|
}
|
|
642
620
|
// Filter by tags_all (AND relationship - must have ALL tags)
|
|
643
621
|
if (params.tags_all && params.tags_all.length > 0) {
|
|
644
622
|
for (const tag of params.tags_all) {
|
|
645
|
-
query
|
|
646
|
-
|
|
623
|
+
query = query.where((builder) => {
|
|
624
|
+
builder.where('tags', 'like', `%${tag}%`).orWhere('tags', tag);
|
|
625
|
+
});
|
|
647
626
|
}
|
|
648
627
|
}
|
|
649
628
|
// Filter by tags_any (OR relationship - must have ANY tag)
|
|
650
629
|
if (params.tags_any && params.tags_any.length > 0) {
|
|
651
|
-
const
|
|
652
|
-
query
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
630
|
+
const tagsAny = params.tags_any; // Capture for closure
|
|
631
|
+
query = query.where((builder) => {
|
|
632
|
+
for (const tag of tagsAny) {
|
|
633
|
+
builder.orWhere('tags', 'like', `%${tag}%`).orWhere('tags', tag);
|
|
634
|
+
}
|
|
635
|
+
});
|
|
656
636
|
}
|
|
657
637
|
// Exclude tags
|
|
658
638
|
if (params.exclude_tags && params.exclude_tags.length > 0) {
|
|
659
639
|
for (const tag of params.exclude_tags) {
|
|
660
|
-
query
|
|
661
|
-
|
|
640
|
+
query = query.where((builder) => {
|
|
641
|
+
builder.whereNull('tags')
|
|
642
|
+
.orWhere((subBuilder) => {
|
|
643
|
+
subBuilder.where('tags', 'not like', `%${tag}%`)
|
|
644
|
+
.where('tags', '!=', tag);
|
|
645
|
+
});
|
|
646
|
+
});
|
|
662
647
|
}
|
|
663
648
|
}
|
|
664
649
|
// Filter by scopes with wildcard support
|
|
665
650
|
if (params.scopes && params.scopes.length > 0) {
|
|
666
|
-
const
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
651
|
+
const scopes = params.scopes; // Capture for closure
|
|
652
|
+
query = query.where((builder) => {
|
|
653
|
+
for (const scope of scopes) {
|
|
654
|
+
if (scope.includes('*')) {
|
|
655
|
+
// Wildcard pattern - convert to LIKE pattern
|
|
656
|
+
const likePattern = scope.replace(/\*/g, '%');
|
|
657
|
+
builder.orWhere('scopes', 'like', `%${likePattern}%`)
|
|
658
|
+
.orWhere('scopes', likePattern);
|
|
659
|
+
}
|
|
660
|
+
else {
|
|
661
|
+
// Exact match
|
|
662
|
+
builder.orWhere('scopes', 'like', `%${scope}%`)
|
|
663
|
+
.orWhere('scopes', scope);
|
|
664
|
+
}
|
|
678
665
|
}
|
|
679
|
-
}
|
|
680
|
-
query += ` AND (${scopeConditions.join(' OR ')})`;
|
|
666
|
+
});
|
|
681
667
|
}
|
|
682
668
|
// Temporal filtering - updated_after
|
|
683
669
|
if (params.updated_after) {
|
|
684
670
|
const timestamp = parseRelativeTime(params.updated_after);
|
|
685
671
|
if (timestamp !== null) {
|
|
686
|
-
query
|
|
687
|
-
queryParams.push(timestamp);
|
|
672
|
+
query = query.whereRaw('unixepoch(updated) >= ?', [timestamp]);
|
|
688
673
|
}
|
|
689
674
|
else {
|
|
690
675
|
throw new Error(`Invalid updated_after format: ${params.updated_after}. Use ISO timestamp or relative time like "7d", "2h", "30m"`);
|
|
@@ -694,8 +679,7 @@ export function searchAdvanced(params = {}, db) {
|
|
|
694
679
|
if (params.updated_before) {
|
|
695
680
|
const timestamp = parseRelativeTime(params.updated_before);
|
|
696
681
|
if (timestamp !== null) {
|
|
697
|
-
query
|
|
698
|
-
queryParams.push(timestamp);
|
|
682
|
+
query = query.whereRaw('unixepoch(updated) <= ?', [timestamp]);
|
|
699
683
|
}
|
|
700
684
|
else {
|
|
701
685
|
throw new Error(`Invalid updated_before format: ${params.updated_before}. Use ISO timestamp or relative time like "7d", "2h", "30m"`);
|
|
@@ -703,25 +687,19 @@ export function searchAdvanced(params = {}, db) {
|
|
|
703
687
|
}
|
|
704
688
|
// Filter by decided_by (OR relationship)
|
|
705
689
|
if (params.decided_by && params.decided_by.length > 0) {
|
|
706
|
-
|
|
707
|
-
query += ` AND (${agentConditions})`;
|
|
708
|
-
queryParams.push(...params.decided_by);
|
|
690
|
+
query = query.whereIn('decided_by', params.decided_by);
|
|
709
691
|
}
|
|
710
692
|
// Filter by statuses (OR relationship)
|
|
711
693
|
if (params.statuses && params.statuses.length > 0) {
|
|
712
|
-
|
|
713
|
-
query += ` AND (${statusConditions})`;
|
|
714
|
-
queryParams.push(...params.statuses);
|
|
694
|
+
query = query.whereIn('status', params.statuses);
|
|
715
695
|
}
|
|
716
696
|
// Full-text search in value field
|
|
717
697
|
if (params.search_text) {
|
|
718
|
-
query
|
|
719
|
-
queryParams.push(`%${params.search_text}%`);
|
|
698
|
+
query = query.where('value', 'like', `%${params.search_text}%`);
|
|
720
699
|
}
|
|
721
700
|
// Count total matching records (before pagination)
|
|
722
|
-
const countQuery = query.
|
|
723
|
-
const
|
|
724
|
-
const countResult = countStmt.get(...queryParams);
|
|
701
|
+
const countQuery = query.clone().count('* as total');
|
|
702
|
+
const countResult = await countQuery.first();
|
|
725
703
|
const totalCount = countResult.total;
|
|
726
704
|
// Sorting
|
|
727
705
|
const sortBy = params.sort_by || 'updated';
|
|
@@ -733,7 +711,7 @@ export function searchAdvanced(params = {}, db) {
|
|
|
733
711
|
if (!['asc', 'desc'].includes(sortOrder)) {
|
|
734
712
|
throw new Error(`Invalid sort_order: ${sortOrder}. Must be 'asc' or 'desc'`);
|
|
735
713
|
}
|
|
736
|
-
query
|
|
714
|
+
query = query.orderBy(sortBy, sortOrder);
|
|
737
715
|
// Pagination
|
|
738
716
|
const limit = params.limit !== undefined ? params.limit : 20;
|
|
739
717
|
const offset = params.offset || 0;
|
|
@@ -744,11 +722,9 @@ export function searchAdvanced(params = {}, db) {
|
|
|
744
722
|
if (offset < 0) {
|
|
745
723
|
throw new Error('Parameter "offset" must be non-negative');
|
|
746
724
|
}
|
|
747
|
-
query
|
|
748
|
-
queryParams.push(limit, offset);
|
|
725
|
+
query = query.limit(limit).offset(offset);
|
|
749
726
|
// Execute query
|
|
750
|
-
const
|
|
751
|
-
const rows = stmt.all(...queryParams);
|
|
727
|
+
const rows = await query.select('*');
|
|
752
728
|
return {
|
|
753
729
|
decisions: rows,
|
|
754
730
|
count: rows.length,
|
|
@@ -766,38 +742,100 @@ export function searchAdvanced(params = {}, db) {
|
|
|
766
742
|
* Limit: 50 items per batch (constraint #3)
|
|
767
743
|
*
|
|
768
744
|
* @param params - Batch parameters with array of decisions and atomic flag
|
|
769
|
-
* @param
|
|
745
|
+
* @param adapter - Optional database adapter (for testing)
|
|
770
746
|
* @returns Response with success status and detailed results for each item
|
|
771
747
|
*/
|
|
772
|
-
export function setDecisionBatch(params,
|
|
773
|
-
const
|
|
748
|
+
export async function setDecisionBatch(params, adapter) {
|
|
749
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
774
750
|
// Validate required parameters
|
|
775
751
|
if (!params.decisions || !Array.isArray(params.decisions)) {
|
|
776
752
|
throw new Error('Parameter "decisions" is required and must be an array');
|
|
777
753
|
}
|
|
778
|
-
|
|
779
|
-
// Use processBatch utility
|
|
780
|
-
const batchResult = processBatch(actualDb, params.decisions, (decision, db) => {
|
|
781
|
-
const result = setDecisionInternal(decision, db);
|
|
754
|
+
if (params.decisions.length === 0) {
|
|
782
755
|
return {
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
756
|
+
success: true,
|
|
757
|
+
inserted: 0,
|
|
758
|
+
failed: 0,
|
|
759
|
+
results: []
|
|
786
760
|
};
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
761
|
+
}
|
|
762
|
+
if (params.decisions.length > 50) {
|
|
763
|
+
throw new Error('Parameter "decisions" must contain at most 50 items');
|
|
764
|
+
}
|
|
765
|
+
const atomic = params.atomic !== undefined ? params.atomic : true;
|
|
766
|
+
try {
|
|
767
|
+
if (atomic) {
|
|
768
|
+
// Atomic mode: All or nothing
|
|
769
|
+
const results = await actualAdapter.transaction(async (trx) => {
|
|
770
|
+
const processedResults = [];
|
|
771
|
+
for (const decision of params.decisions) {
|
|
772
|
+
try {
|
|
773
|
+
const result = await setDecisionInternal(decision, actualAdapter, trx);
|
|
774
|
+
processedResults.push({
|
|
775
|
+
key: decision.key,
|
|
776
|
+
key_id: result.key_id,
|
|
777
|
+
version: result.version,
|
|
778
|
+
success: true,
|
|
779
|
+
error: undefined
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
catch (error) {
|
|
783
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
784
|
+
throw new Error(`Batch failed at decision "${decision.key}": ${message}`);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
return processedResults;
|
|
788
|
+
});
|
|
789
|
+
return {
|
|
790
|
+
success: true,
|
|
791
|
+
inserted: results.length,
|
|
792
|
+
failed: 0,
|
|
793
|
+
results: results
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
// Non-atomic mode: Process each independently
|
|
798
|
+
const results = [];
|
|
799
|
+
let inserted = 0;
|
|
800
|
+
let failed = 0;
|
|
801
|
+
for (const decision of params.decisions) {
|
|
802
|
+
try {
|
|
803
|
+
const result = await actualAdapter.transaction(async (trx) => {
|
|
804
|
+
return await setDecisionInternal(decision, actualAdapter, trx);
|
|
805
|
+
});
|
|
806
|
+
results.push({
|
|
807
|
+
key: decision.key,
|
|
808
|
+
key_id: result.key_id,
|
|
809
|
+
version: result.version,
|
|
810
|
+
success: true,
|
|
811
|
+
error: undefined
|
|
812
|
+
});
|
|
813
|
+
inserted++;
|
|
814
|
+
}
|
|
815
|
+
catch (error) {
|
|
816
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
817
|
+
results.push({
|
|
818
|
+
key: decision.key,
|
|
819
|
+
key_id: undefined,
|
|
820
|
+
version: undefined,
|
|
821
|
+
success: false,
|
|
822
|
+
error: message
|
|
823
|
+
});
|
|
824
|
+
failed++;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
return {
|
|
828
|
+
success: failed === 0,
|
|
829
|
+
inserted: inserted,
|
|
830
|
+
failed: failed,
|
|
831
|
+
results: results
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
catch (error) {
|
|
836
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
837
|
+
throw new Error(`Failed to execute batch operation: ${message}`);
|
|
838
|
+
}
|
|
801
839
|
}
|
|
802
840
|
/**
|
|
803
841
|
* Check for updates since a given timestamp (FR-003 Phase A)
|
|
@@ -805,11 +843,12 @@ export function setDecisionBatch(params, db) {
|
|
|
805
843
|
* Token cost: ~5-10 tokens per check
|
|
806
844
|
*
|
|
807
845
|
* @param params - Agent name and since_timestamp (ISO 8601)
|
|
808
|
-
* @param
|
|
846
|
+
* @param adapter - Optional database adapter (for testing)
|
|
809
847
|
* @returns Boolean flag and counts for decisions, messages, files
|
|
810
848
|
*/
|
|
811
|
-
export function hasUpdates(params,
|
|
812
|
-
const
|
|
849
|
+
export async function hasUpdates(params, adapter) {
|
|
850
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
851
|
+
const knex = actualAdapter.getKnex();
|
|
813
852
|
// Validate required parameters
|
|
814
853
|
if (!params.agent_name || params.agent_name.trim() === '') {
|
|
815
854
|
throw new Error('Parameter "agent_name" is required and cannot be empty');
|
|
@@ -825,38 +864,43 @@ export function hasUpdates(params, db) {
|
|
|
825
864
|
}
|
|
826
865
|
const sinceTs = Math.floor(sinceDate.getTime() / 1000);
|
|
827
866
|
// Count decisions updated since timestamp (both string and numeric tables)
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
const decisionsCount =
|
|
867
|
+
const decisionCount1 = await knex('t_decisions')
|
|
868
|
+
.where('ts', '>', sinceTs)
|
|
869
|
+
.count('* as count')
|
|
870
|
+
.first();
|
|
871
|
+
const decisionCount2 = await knex('t_decisions_numeric')
|
|
872
|
+
.where('ts', '>', sinceTs)
|
|
873
|
+
.count('* as count')
|
|
874
|
+
.first();
|
|
875
|
+
const decisionsCount = (decisionCount1?.count || 0) + (decisionCount2?.count || 0);
|
|
837
876
|
// Get agent_id for the requesting agent
|
|
838
|
-
const agentResult =
|
|
877
|
+
const agentResult = await knex('m_agents')
|
|
878
|
+
.where({ name: params.agent_name })
|
|
879
|
+
.first('id');
|
|
839
880
|
// Count messages for the agent (received messages - to_agent_id matches OR broadcast messages)
|
|
840
881
|
let messagesCount = 0;
|
|
841
882
|
if (agentResult) {
|
|
842
883
|
const agentId = agentResult.id;
|
|
843
|
-
const
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
884
|
+
const messageResult = await knex('t_agent_messages')
|
|
885
|
+
.where('ts', '>', sinceTs)
|
|
886
|
+
.where((builder) => {
|
|
887
|
+
builder.where('to_agent_id', agentId)
|
|
888
|
+
.orWhereNull('to_agent_id');
|
|
889
|
+
})
|
|
890
|
+
.count('* as count')
|
|
891
|
+
.first();
|
|
892
|
+
messagesCount = messageResult?.count || 0;
|
|
849
893
|
}
|
|
850
894
|
// Count file changes since timestamp
|
|
851
|
-
const
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
const filesCount = fileResult
|
|
895
|
+
const fileResult = await knex('t_file_changes')
|
|
896
|
+
.where('ts', '>', sinceTs)
|
|
897
|
+
.count('* as count')
|
|
898
|
+
.first();
|
|
899
|
+
const filesCount = fileResult?.count || 0;
|
|
856
900
|
// Determine if there are any updates
|
|
857
|
-
const
|
|
901
|
+
const hasUpdatesFlag = decisionsCount > 0 || messagesCount > 0 || filesCount > 0;
|
|
858
902
|
return {
|
|
859
|
-
has_updates:
|
|
903
|
+
has_updates: hasUpdatesFlag,
|
|
860
904
|
counts: {
|
|
861
905
|
decisions: decisionsCount,
|
|
862
906
|
messages: messagesCount,
|
|
@@ -875,11 +919,12 @@ export function hasUpdates(params, db) {
|
|
|
875
919
|
* Validates required fields if template specifies any
|
|
876
920
|
*
|
|
877
921
|
* @param params - Template name, key, value, and optional overrides
|
|
878
|
-
* @param
|
|
922
|
+
* @param adapter - Optional database adapter (for testing)
|
|
879
923
|
* @returns Response with success status and applied defaults metadata
|
|
880
924
|
*/
|
|
881
|
-
export function setFromTemplate(params,
|
|
882
|
-
const
|
|
925
|
+
export async function setFromTemplate(params, adapter) {
|
|
926
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
927
|
+
const knex = actualAdapter.getKnex();
|
|
883
928
|
// Validate required parameters
|
|
884
929
|
if (!params.template || params.template.trim() === '') {
|
|
885
930
|
throw new Error('Parameter "template" is required and cannot be empty');
|
|
@@ -892,7 +937,9 @@ export function setFromTemplate(params, db) {
|
|
|
892
937
|
}
|
|
893
938
|
try {
|
|
894
939
|
// Get template
|
|
895
|
-
const templateRow =
|
|
940
|
+
const templateRow = await knex('t_decision_templates')
|
|
941
|
+
.where({ name: params.template })
|
|
942
|
+
.first();
|
|
896
943
|
if (!templateRow) {
|
|
897
944
|
throw new Error(`Template not found: ${params.template}`);
|
|
898
945
|
}
|
|
@@ -930,8 +977,8 @@ export function setFromTemplate(params, db) {
|
|
|
930
977
|
if (!params.status && defaults.status) {
|
|
931
978
|
appliedDefaults.status = defaults.status;
|
|
932
979
|
}
|
|
933
|
-
// Call setDecision with merged params (pass
|
|
934
|
-
const result = setDecision(decisionParams,
|
|
980
|
+
// Call setDecision with merged params (pass adapter if provided)
|
|
981
|
+
const result = await setDecision(decisionParams, actualAdapter);
|
|
935
982
|
return {
|
|
936
983
|
success: result.success,
|
|
937
984
|
key: result.key,
|
|
@@ -952,11 +999,12 @@ export function setFromTemplate(params, db) {
|
|
|
952
999
|
* Defines reusable defaults and required fields for decisions
|
|
953
1000
|
*
|
|
954
1001
|
* @param params - Template name, defaults, required fields, and creator
|
|
955
|
-
* @param
|
|
1002
|
+
* @param adapter - Optional database adapter (for testing)
|
|
956
1003
|
* @returns Response with success status and template ID
|
|
957
1004
|
*/
|
|
958
|
-
export function createTemplate(params,
|
|
959
|
-
const
|
|
1005
|
+
export async function createTemplate(params, adapter) {
|
|
1006
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
1007
|
+
const knex = actualAdapter.getKnex();
|
|
960
1008
|
// Validate required parameters
|
|
961
1009
|
if (!params.name || params.name.trim() === '') {
|
|
962
1010
|
throw new Error('Parameter "name" is required and cannot be empty');
|
|
@@ -965,10 +1013,10 @@ export function createTemplate(params, db) {
|
|
|
965
1013
|
throw new Error('Parameter "defaults" is required and must be an object');
|
|
966
1014
|
}
|
|
967
1015
|
try {
|
|
968
|
-
return transaction(
|
|
1016
|
+
return await actualAdapter.transaction(async (trx) => {
|
|
969
1017
|
// Validate layer if provided in defaults
|
|
970
1018
|
if (params.defaults.layer) {
|
|
971
|
-
const layerId = getLayerId(
|
|
1019
|
+
const layerId = await getLayerId(actualAdapter, params.defaults.layer, trx);
|
|
972
1020
|
if (layerId === null) {
|
|
973
1021
|
throw new Error(`Invalid layer in defaults: ${params.defaults.layer}. Must be one of: presentation, business, data, infrastructure, cross-cutting`);
|
|
974
1022
|
}
|
|
@@ -980,20 +1028,21 @@ export function createTemplate(params, db) {
|
|
|
980
1028
|
// Get or create agent if creator specified
|
|
981
1029
|
let createdById = null;
|
|
982
1030
|
if (params.created_by) {
|
|
983
|
-
createdById = getOrCreateAgent(
|
|
1031
|
+
createdById = await getOrCreateAgent(actualAdapter, params.created_by, trx);
|
|
984
1032
|
}
|
|
985
1033
|
// Serialize defaults and required fields
|
|
986
1034
|
const defaultsJson = JSON.stringify(params.defaults);
|
|
987
1035
|
const requiredFieldsJson = params.required_fields ? JSON.stringify(params.required_fields) : null;
|
|
988
1036
|
// Insert template
|
|
989
|
-
const
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1037
|
+
const [id] = await trx('t_decision_templates').insert({
|
|
1038
|
+
name: params.name,
|
|
1039
|
+
defaults: defaultsJson,
|
|
1040
|
+
required_fields: requiredFieldsJson,
|
|
1041
|
+
created_by: createdById
|
|
1042
|
+
});
|
|
994
1043
|
return {
|
|
995
1044
|
success: true,
|
|
996
|
-
template_id:
|
|
1045
|
+
template_id: id,
|
|
997
1046
|
template_name: params.name,
|
|
998
1047
|
message: `Template "${params.name}" created successfully`
|
|
999
1048
|
};
|
|
@@ -1009,25 +1058,17 @@ export function createTemplate(params, db) {
|
|
|
1009
1058
|
* Returns all templates with their defaults and metadata
|
|
1010
1059
|
*
|
|
1011
1060
|
* @param params - No parameters required
|
|
1012
|
-
* @param
|
|
1061
|
+
* @param adapter - Optional database adapter (for testing)
|
|
1013
1062
|
* @returns Array of all templates with parsed JSON fields
|
|
1014
1063
|
*/
|
|
1015
|
-
export function listTemplates(params = {},
|
|
1016
|
-
const
|
|
1064
|
+
export async function listTemplates(params = {}, adapter) {
|
|
1065
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
1066
|
+
const knex = actualAdapter.getKnex();
|
|
1017
1067
|
try {
|
|
1018
|
-
const
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
t.defaults,
|
|
1023
|
-
t.required_fields,
|
|
1024
|
-
a.name as created_by,
|
|
1025
|
-
datetime(t.ts, 'unixepoch') as created_at
|
|
1026
|
-
FROM t_decision_templates t
|
|
1027
|
-
LEFT JOIN m_agents a ON t.created_by = a.id
|
|
1028
|
-
ORDER BY t.name ASC
|
|
1029
|
-
`);
|
|
1030
|
-
const rows = stmt.all();
|
|
1068
|
+
const rows = await knex('t_decision_templates as t')
|
|
1069
|
+
.leftJoin('m_agents as a', 't.created_by', 'a.id')
|
|
1070
|
+
.select('t.id', 't.name', 't.defaults', 't.required_fields', 'a.name as created_by', knex.raw(`datetime(t.ts, 'unixepoch') as created_at`))
|
|
1071
|
+
.orderBy('t.name', 'asc');
|
|
1031
1072
|
// Parse JSON fields
|
|
1032
1073
|
const templates = rows.map(row => ({
|
|
1033
1074
|
id: row.id,
|
|
@@ -1060,19 +1101,22 @@ export function listTemplates(params = {}, db) {
|
|
|
1060
1101
|
* (tags, scopes) will also be deleted due to CASCADE constraints.
|
|
1061
1102
|
*
|
|
1062
1103
|
* @param params - Decision key to delete
|
|
1063
|
-
* @param
|
|
1104
|
+
* @param adapter - Optional database adapter (for testing)
|
|
1064
1105
|
* @returns Response with success status
|
|
1065
1106
|
*/
|
|
1066
|
-
export function hardDeleteDecision(params,
|
|
1067
|
-
const
|
|
1107
|
+
export async function hardDeleteDecision(params, adapter) {
|
|
1108
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
1109
|
+
const knex = actualAdapter.getKnex();
|
|
1068
1110
|
// Validate parameter
|
|
1069
1111
|
if (!params.key || params.key.trim() === '') {
|
|
1070
1112
|
throw new Error('Parameter "key" is required and cannot be empty');
|
|
1071
1113
|
}
|
|
1072
1114
|
try {
|
|
1073
|
-
return transaction(
|
|
1115
|
+
return await actualAdapter.transaction(async (trx) => {
|
|
1074
1116
|
// Get key_id
|
|
1075
|
-
const keyResult =
|
|
1117
|
+
const keyResult = await trx('m_context_keys')
|
|
1118
|
+
.where({ key: params.key })
|
|
1119
|
+
.first('id');
|
|
1076
1120
|
if (!keyResult) {
|
|
1077
1121
|
// Key doesn't exist - still return success (idempotent)
|
|
1078
1122
|
return {
|
|
@@ -1083,17 +1127,17 @@ export function hardDeleteDecision(params, db) {
|
|
|
1083
1127
|
}
|
|
1084
1128
|
const keyId = keyResult.id;
|
|
1085
1129
|
// Delete from t_decisions (if exists)
|
|
1086
|
-
const deletedString =
|
|
1130
|
+
const deletedString = await trx('t_decisions').where({ key_id: keyId }).delete();
|
|
1087
1131
|
// Delete from t_decisions_numeric (if exists)
|
|
1088
|
-
const deletedNumeric =
|
|
1132
|
+
const deletedNumeric = await trx('t_decisions_numeric').where({ key_id: keyId }).delete();
|
|
1089
1133
|
// Delete from t_decision_history (CASCADE should handle this, but explicit for clarity)
|
|
1090
|
-
const deletedHistory =
|
|
1134
|
+
const deletedHistory = await trx('t_decision_history').where({ key_id: keyId }).delete();
|
|
1091
1135
|
// Delete from t_decision_tags (CASCADE should handle this)
|
|
1092
|
-
const deletedTags =
|
|
1136
|
+
const deletedTags = await trx('t_decision_tags').where({ decision_key_id: keyId }).delete();
|
|
1093
1137
|
// Delete from t_decision_scopes (CASCADE should handle this)
|
|
1094
|
-
const deletedScopes =
|
|
1138
|
+
const deletedScopes = await trx('t_decision_scopes').where({ decision_key_id: keyId }).delete();
|
|
1095
1139
|
// Calculate total deleted records
|
|
1096
|
-
const totalDeleted = deletedString
|
|
1140
|
+
const totalDeleted = deletedString + deletedNumeric + deletedHistory + deletedTags + deletedScopes;
|
|
1097
1141
|
if (totalDeleted === 0) {
|
|
1098
1142
|
return {
|
|
1099
1143
|
success: true,
|
|
@@ -1121,11 +1165,11 @@ export function hardDeleteDecision(params, db) {
|
|
|
1121
1165
|
* Adds rich context (rationale, alternatives, tradeoffs) to a decision
|
|
1122
1166
|
*
|
|
1123
1167
|
* @param params - Context parameters
|
|
1124
|
-
* @param
|
|
1168
|
+
* @param adapter - Optional database adapter (for testing)
|
|
1125
1169
|
* @returns Response with success status
|
|
1126
1170
|
*/
|
|
1127
|
-
export function addDecisionContextAction(params,
|
|
1128
|
-
const
|
|
1171
|
+
export async function addDecisionContextAction(params, adapter) {
|
|
1172
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
1129
1173
|
// Validate required parameters
|
|
1130
1174
|
if (!params.key || params.key.trim() === '') {
|
|
1131
1175
|
throw new Error('Parameter "key" is required and cannot be empty');
|
|
@@ -1144,7 +1188,7 @@ export function addDecisionContextAction(params, db) {
|
|
|
1144
1188
|
if (tradeoffs && typeof tradeoffs === 'object') {
|
|
1145
1189
|
tradeoffs = JSON.stringify(tradeoffs);
|
|
1146
1190
|
}
|
|
1147
|
-
const contextId = dbAddDecisionContext(
|
|
1191
|
+
const contextId = await dbAddDecisionContext(actualAdapter, params.key, params.rationale, alternatives, tradeoffs, params.decided_by || null, params.related_task_id || null, params.related_constraint_id || null);
|
|
1148
1192
|
return {
|
|
1149
1193
|
success: true,
|
|
1150
1194
|
context_id: contextId,
|
|
@@ -1162,13 +1206,13 @@ export function addDecisionContextAction(params, db) {
|
|
|
1162
1206
|
* Query decision contexts with optional filters
|
|
1163
1207
|
*
|
|
1164
1208
|
* @param params - Filter parameters
|
|
1165
|
-
* @param
|
|
1209
|
+
* @param adapter - Optional database adapter (for testing)
|
|
1166
1210
|
* @returns Array of decision contexts
|
|
1167
1211
|
*/
|
|
1168
|
-
export function listDecisionContextsAction(params,
|
|
1169
|
-
const
|
|
1212
|
+
export async function listDecisionContextsAction(params, adapter) {
|
|
1213
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
1170
1214
|
try {
|
|
1171
|
-
const contexts = dbListDecisionContexts(
|
|
1215
|
+
const contexts = await dbListDecisionContexts(actualAdapter, {
|
|
1172
1216
|
decisionKey: params.decision_key,
|
|
1173
1217
|
relatedTaskId: params.related_task_id,
|
|
1174
1218
|
relatedConstraintId: params.related_constraint_id,
|