sqlew 3.2.5 → 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 +288 -1011
- package/README.md +80 -263
- package/assets/config.example.toml +97 -0
- package/assets/schema.sql +6 -1
- 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 +46 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +155 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/types.d.ts +86 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +28 -0
- package/dist/config/types.js.map +1 -0
- package/dist/constants.d.ts +9 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +10 -0
- package/dist/constants.js.map +1 -1
- package/dist/database.d.ts +44 -122
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +145 -349
- package/dist/database.js.map +1 -1
- package/dist/index.js +223 -175
- 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/add-v3.5.0-pruned-files.d.ts +26 -0
- package/dist/migrations/add-v3.5.0-pruned-files.d.ts.map +1 -0
- package/dist/migrations/add-v3.5.0-pruned-files.js +107 -0
- package/dist/migrations/add-v3.5.0-pruned-files.js.map +1 -0
- package/dist/migrations/index.d.ts +26 -12
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +162 -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.d.ts +6 -0
- package/dist/tests/git-aware-completion.test.d.ts.map +1 -0
- package/dist/tests/git-aware-completion.test.js +160 -0
- package/dist/tests/git-aware-completion.test.js.map +1 -0
- 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.d.ts +6 -0
- package/dist/tests/tasks.auto-pruning-decision-link.test.d.ts.map +1 -0
- package/dist/tests/tasks.auto-pruning-decision-link.test.js +264 -0
- package/dist/tests/tasks.auto-pruning-decision-link.test.js.map +1 -0
- package/dist/tests/tasks.auto-pruning-partial.test.d.ts +6 -0
- package/dist/tests/tasks.auto-pruning-partial.test.d.ts.map +1 -0
- package/dist/tests/tasks.auto-pruning-partial.test.js +285 -0
- package/dist/tests/tasks.auto-pruning-partial.test.js.map +1 -0
- package/dist/tests/tasks.auto-pruning-persistence.test.d.ts +6 -0
- package/dist/tests/tasks.auto-pruning-persistence.test.d.ts.map +1 -0
- package/dist/tests/tasks.auto-pruning-persistence.test.js +250 -0
- package/dist/tests/tasks.auto-pruning-persistence.test.js.map +1 -0
- package/dist/tests/tasks.auto-pruning-safety.test.d.ts +12 -0
- package/dist/tests/tasks.auto-pruning-safety.test.d.ts.map +1 -0
- package/dist/tests/tasks.auto-pruning-safety.test.js +217 -0
- package/dist/tests/tasks.auto-pruning-safety.test.js.map +1 -0
- 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.d.ts +6 -0
- package/dist/tests/tasks.link-file-backward-compat.test.d.ts.map +1 -0
- package/dist/tests/tasks.link-file-backward-compat.test.js +247 -0
- package/dist/tests/tasks.link-file-backward-compat.test.js.map +1 -0
- package/dist/tests/tasks.watch-files-action.test.d.ts +6 -0
- package/dist/tests/tasks.watch-files-action.test.d.ts.map +1 -0
- package/dist/tests/tasks.watch-files-action.test.js +372 -0
- package/dist/tests/tasks.watch-files-action.test.js.map +1 -0
- package/dist/tests/tasks.watch-files-parameter.test.d.ts +6 -0
- package/dist/tests/tasks.watch-files-parameter.test.d.ts.map +1 -0
- package/dist/tests/tasks.watch-files-parameter.test.js +260 -0
- package/dist/tests/tasks.watch-files-parameter.test.js.map +1 -0
- package/dist/tests/two-step-git-completion.test.d.ts +6 -0
- package/dist/tests/two-step-git-completion.test.d.ts.map +1 -0
- package/dist/tests/two-step-git-completion.test.js +326 -0
- package/dist/tests/two-step-git-completion.test.js.map +1 -0
- package/dist/tests/vcs-staging.test.d.ts +6 -0
- package/dist/tests/vcs-staging.test.d.ts.map +1 -0
- package/dist/tests/vcs-staging.test.js +137 -0
- package/dist/tests/vcs-staging.test.js.map +1 -0
- package/dist/tools/config.d.ts +9 -4
- package/dist/tools/config.d.ts.map +1 -1
- package/dist/tools/config.js +16 -12
- package/dist/tools/config.js.map +1 -1
- package/dist/tools/constraints.d.ts +9 -3
- package/dist/tools/constraints.d.ts.map +1 -1
- package/dist/tools/constraints.js +66 -45
- package/dist/tools/constraints.js.map +1 -1
- package/dist/tools/context.d.ts +35 -16
- package/dist/tools/context.d.ts.map +1 -1
- package/dist/tools/context.js +374 -314
- package/dist/tools/context.js.map +1 -1
- package/dist/tools/files.d.ts +11 -4
- package/dist/tools/files.d.ts.map +1 -1
- package/dist/tools/files.js +173 -91
- 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 +13 -6
- package/dist/tools/messaging.d.ts.map +1 -1
- package/dist/tools/messaging.js +217 -129
- package/dist/tools/messaging.js.map +1 -1
- package/dist/tools/tasks.d.ts +42 -12
- package/dist/tools/tasks.d.ts.map +1 -1
- package/dist/tools/tasks.js +809 -347
- package/dist/tools/tasks.js.map +1 -1
- package/dist/tools/utils.d.ts +13 -5
- package/dist/tools/utils.d.ts.map +1 -1
- package/dist/tools/utils.js +92 -115
- package/dist/tools/utils.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.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/file-pruning.d.ts +69 -0
- package/dist/utils/file-pruning.d.ts.map +1 -0
- package/dist/utils/file-pruning.js +185 -0
- package/dist/utils/file-pruning.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/quality-checks.d.ts +60 -0
- package/dist/utils/quality-checks.d.ts.map +1 -0
- package/dist/utils/quality-checks.js +228 -0
- package/dist/utils/quality-checks.js.map +1 -0
- package/dist/utils/retention.d.ts +13 -5
- package/dist/utils/retention.d.ts.map +1 -1
- package/dist/utils/retention.js +20 -8
- package/dist/utils/retention.js.map +1 -1
- package/dist/utils/task-stale-detection.d.ts +77 -7
- package/dist/utils/task-stale-detection.d.ts.map +1 -1
- package/dist/utils/task-stale-detection.js +309 -34
- 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/vcs-adapter.d.ts +68 -0
- package/dist/utils/vcs-adapter.d.ts.map +1 -0
- package/dist/utils/vcs-adapter.js +187 -0
- package/dist/utils/vcs-adapter.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 +54 -4
- package/dist/watcher/file-watcher.d.ts.map +1 -1
- package/dist/watcher/file-watcher.js +329 -33
- package/dist/watcher/file-watcher.js.map +1 -1
- package/dist/watcher/gitignore-parser.d.ts +70 -0
- package/dist/watcher/gitignore-parser.d.ts.map +1 -0
- package/dist/watcher/gitignore-parser.js +191 -0
- package/dist/watcher/gitignore-parser.js.map +1 -0
- package/dist/watcher/index.d.ts +1 -0
- package/dist/watcher/index.d.ts.map +1 -1
- package/dist/watcher/index.js +1 -0
- package/dist/watcher/index.js.map +1 -1
- package/docs/AI_AGENT_GUIDE.md +1 -1
- package/docs/ARCHITECTURE.md +12 -0
- package/docs/AUTO_FILE_TRACKING.md +486 -82
- package/docs/BEST_PRACTICES.md +56 -448
- package/docs/CONFIGURATION.md +908 -0
- package/docs/GIT_AWARE_AUTO_COMPLETE.md +645 -0
- package/docs/MIGRATION_v3.3.md +602 -0
- package/docs/MIGRATION_v3.6.0.md +170 -0
- package/docs/SHARED_CONCEPTS.md +65 -209
- package/docs/TASK_ACTIONS.md +12 -0
- package/docs/TASK_OVERVIEW.md +125 -24
- package/docs/TASK_PRUNING.md +589 -0
- package/docs/TASK_SYSTEM.md +83 -13
- package/docs/TOOL_REFERENCE.md +94 -6
- package/docs/TOOL_SELECTION.md +41 -248
- package/package.json +21 -7
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,14 +133,15 @@ function setDecisionInternal(params, db) {
|
|
|
108
133
|
* Supports tags, layers, scopes, and version tracking
|
|
109
134
|
*
|
|
110
135
|
* @param params - Decision parameters
|
|
136
|
+
* @param adapter - Optional database adapter (for testing)
|
|
111
137
|
* @returns Response with success status and metadata
|
|
112
138
|
*/
|
|
113
|
-
export function setDecision(params) {
|
|
114
|
-
const
|
|
139
|
+
export async function setDecision(params, adapter) {
|
|
140
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
115
141
|
try {
|
|
116
142
|
// Use transaction for atomicity
|
|
117
|
-
return transaction(
|
|
118
|
-
return setDecisionInternal(params,
|
|
143
|
+
return await actualAdapter.transaction(async (trx) => {
|
|
144
|
+
return await setDecisionInternal(params, actualAdapter, trx);
|
|
119
145
|
});
|
|
120
146
|
}
|
|
121
147
|
catch (error) {
|
|
@@ -129,32 +155,30 @@ export function setDecision(params) {
|
|
|
129
155
|
* Supports filtering by status, layer, tags, and scope
|
|
130
156
|
*
|
|
131
157
|
* @param params - Filter parameters
|
|
158
|
+
* @param adapter - Optional database adapter (for testing)
|
|
132
159
|
* @returns Array of t_decisions with metadata
|
|
133
160
|
*/
|
|
134
|
-
export function getContext(params = {}) {
|
|
135
|
-
const
|
|
161
|
+
export async function getContext(params = {}, adapter) {
|
|
162
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
163
|
+
const knex = actualAdapter.getKnex();
|
|
136
164
|
try {
|
|
137
165
|
// Build query dynamically based on filters
|
|
138
|
-
let query = '
|
|
139
|
-
const queryParams = [];
|
|
166
|
+
let query = knex('v_tagged_decisions');
|
|
140
167
|
// Filter by status
|
|
141
168
|
if (params.status) {
|
|
142
169
|
if (!STRING_TO_STATUS[params.status]) {
|
|
143
170
|
throw new Error(`Invalid status: ${params.status}`);
|
|
144
171
|
}
|
|
145
|
-
query
|
|
146
|
-
queryParams.push(params.status);
|
|
172
|
+
query = query.where('status', params.status);
|
|
147
173
|
}
|
|
148
174
|
// Filter by layer
|
|
149
175
|
if (params.layer) {
|
|
150
|
-
query
|
|
151
|
-
queryParams.push(params.layer);
|
|
176
|
+
query = query.where('layer', params.layer);
|
|
152
177
|
}
|
|
153
178
|
// Filter by scope
|
|
154
179
|
if (params.scope) {
|
|
155
180
|
// Use LIKE for comma-separated scopes
|
|
156
|
-
query
|
|
157
|
-
queryParams.push(`%${params.scope}%`);
|
|
181
|
+
query = query.where('scopes', 'like', `%${params.scope}%`);
|
|
158
182
|
}
|
|
159
183
|
// Filter by tags
|
|
160
184
|
if (params.tags && params.tags.length > 0) {
|
|
@@ -162,24 +186,22 @@ export function getContext(params = {}) {
|
|
|
162
186
|
if (tagMatch === 'AND') {
|
|
163
187
|
// All tags must be present
|
|
164
188
|
for (const tag of params.tags) {
|
|
165
|
-
query
|
|
166
|
-
queryParams.push(`%${tag}%`);
|
|
189
|
+
query = query.where('tags', 'like', `%${tag}%`);
|
|
167
190
|
}
|
|
168
191
|
}
|
|
169
192
|
else {
|
|
170
193
|
// Any tag must be present (OR)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
194
|
+
query = query.where((builder) => {
|
|
195
|
+
for (const tag of params.tags) {
|
|
196
|
+
builder.orWhere('tags', 'like', `%${tag}%`);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
176
199
|
}
|
|
177
200
|
}
|
|
178
201
|
// Order by most recent
|
|
179
|
-
query
|
|
202
|
+
query = query.orderBy('updated', 'desc');
|
|
180
203
|
// Execute query
|
|
181
|
-
const
|
|
182
|
-
const rows = stmt.all(...queryParams);
|
|
204
|
+
const rows = await query.select('*');
|
|
183
205
|
return {
|
|
184
206
|
decisions: rows,
|
|
185
207
|
count: rows.length
|
|
@@ -196,10 +218,12 @@ export function getContext(params = {}) {
|
|
|
196
218
|
* Optionally includes decision context (v3.2.2)
|
|
197
219
|
*
|
|
198
220
|
* @param params - Decision key and optional include_context flag
|
|
221
|
+
* @param adapter - Optional database adapter (for testing)
|
|
199
222
|
* @returns Decision details or not found
|
|
200
223
|
*/
|
|
201
|
-
export function getDecision(params) {
|
|
202
|
-
const
|
|
224
|
+
export async function getDecision(params, adapter) {
|
|
225
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
226
|
+
const knex = actualAdapter.getKnex();
|
|
203
227
|
// Validate parameter
|
|
204
228
|
if (!params.key || params.key.trim() === '') {
|
|
205
229
|
throw new Error('Parameter "key" is required and cannot be empty');
|
|
@@ -207,7 +231,7 @@ export function getDecision(params) {
|
|
|
207
231
|
try {
|
|
208
232
|
// If include_context is true, use the context-aware function
|
|
209
233
|
if (params.include_context) {
|
|
210
|
-
const result = dbGetDecisionWithContext(
|
|
234
|
+
const result = await dbGetDecisionWithContext(actualAdapter, params.key);
|
|
211
235
|
if (!result) {
|
|
212
236
|
return {
|
|
213
237
|
found: false
|
|
@@ -235,8 +259,9 @@ export function getDecision(params) {
|
|
|
235
259
|
};
|
|
236
260
|
}
|
|
237
261
|
// Standard query without context (backward compatible)
|
|
238
|
-
const
|
|
239
|
-
|
|
262
|
+
const row = await knex('v_tagged_decisions')
|
|
263
|
+
.where({ key: params.key })
|
|
264
|
+
.first();
|
|
240
265
|
if (!row) {
|
|
241
266
|
return {
|
|
242
267
|
found: false
|
|
@@ -257,33 +282,33 @@ export function getDecision(params) {
|
|
|
257
282
|
* Provides flexible tag-based filtering with status and layer support
|
|
258
283
|
*
|
|
259
284
|
* @param params - Search parameters (tags, match_mode, status, layer)
|
|
285
|
+
* @param adapter - Optional database adapter (for testing)
|
|
260
286
|
* @returns Array of t_decisions matching tag criteria
|
|
261
287
|
*/
|
|
262
|
-
export function searchByTags(params) {
|
|
263
|
-
const
|
|
288
|
+
export async function searchByTags(params, adapter) {
|
|
289
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
290
|
+
const knex = actualAdapter.getKnex();
|
|
264
291
|
// Validate required parameters
|
|
265
292
|
if (!params.tags || params.tags.length === 0) {
|
|
266
293
|
throw new Error('Parameter "tags" is required and must contain at least one tag');
|
|
267
294
|
}
|
|
268
295
|
try {
|
|
269
296
|
const matchMode = params.match_mode || 'OR';
|
|
270
|
-
let query = '
|
|
271
|
-
const queryParams = [];
|
|
297
|
+
let query = knex('v_tagged_decisions');
|
|
272
298
|
// Apply tag filtering based on match mode
|
|
273
299
|
if (matchMode === 'AND') {
|
|
274
300
|
// All tags must be present
|
|
275
301
|
for (const tag of params.tags) {
|
|
276
|
-
query
|
|
277
|
-
queryParams.push(`%${tag}%`);
|
|
302
|
+
query = query.where('tags', 'like', `%${tag}%`);
|
|
278
303
|
}
|
|
279
304
|
}
|
|
280
305
|
else if (matchMode === 'OR') {
|
|
281
306
|
// Any tag must be present
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
307
|
+
query = query.where((builder) => {
|
|
308
|
+
for (const tag of params.tags) {
|
|
309
|
+
builder.orWhere('tags', 'like', `%${tag}%`);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
287
312
|
}
|
|
288
313
|
else {
|
|
289
314
|
throw new Error(`Invalid match_mode: ${matchMode}. Must be 'AND' or 'OR'`);
|
|
@@ -293,24 +318,21 @@ export function searchByTags(params) {
|
|
|
293
318
|
if (!STRING_TO_STATUS[params.status]) {
|
|
294
319
|
throw new Error(`Invalid status: ${params.status}. Must be 'active', 'deprecated', or 'draft'`);
|
|
295
320
|
}
|
|
296
|
-
query
|
|
297
|
-
queryParams.push(params.status);
|
|
321
|
+
query = query.where('status', params.status);
|
|
298
322
|
}
|
|
299
323
|
// Optional layer filter
|
|
300
324
|
if (params.layer) {
|
|
301
325
|
// Validate layer exists
|
|
302
|
-
const layerId = getLayerId(
|
|
326
|
+
const layerId = await getLayerId(actualAdapter, params.layer);
|
|
303
327
|
if (layerId === null) {
|
|
304
328
|
throw new Error(`Invalid layer: ${params.layer}. Must be one of: presentation, business, data, infrastructure, cross-cutting`);
|
|
305
329
|
}
|
|
306
|
-
query
|
|
307
|
-
queryParams.push(params.layer);
|
|
330
|
+
query = query.where('layer', params.layer);
|
|
308
331
|
}
|
|
309
332
|
// Order by most recent
|
|
310
|
-
query
|
|
333
|
+
query = query.orderBy('updated', 'desc');
|
|
311
334
|
// Execute query
|
|
312
|
-
const
|
|
313
|
-
const rows = stmt.all(...queryParams);
|
|
335
|
+
const rows = await query.select('*');
|
|
314
336
|
return {
|
|
315
337
|
decisions: rows,
|
|
316
338
|
count: rows.length
|
|
@@ -326,17 +348,21 @@ export function searchByTags(params) {
|
|
|
326
348
|
* Returns all historical versions ordered by timestamp (newest first)
|
|
327
349
|
*
|
|
328
350
|
* @param params - Decision key to get history for
|
|
351
|
+
* @param adapter - Optional database adapter (for testing)
|
|
329
352
|
* @returns Array of historical versions with metadata
|
|
330
353
|
*/
|
|
331
|
-
export function getVersions(params) {
|
|
332
|
-
const
|
|
354
|
+
export async function getVersions(params, adapter) {
|
|
355
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
356
|
+
const knex = actualAdapter.getKnex();
|
|
333
357
|
// Validate required parameter
|
|
334
358
|
if (!params.key || params.key.trim() === '') {
|
|
335
359
|
throw new Error('Parameter "key" is required and cannot be empty');
|
|
336
360
|
}
|
|
337
361
|
try {
|
|
338
362
|
// Get key_id for the decision
|
|
339
|
-
const keyResult =
|
|
363
|
+
const keyResult = await knex('m_context_keys')
|
|
364
|
+
.where({ key: params.key })
|
|
365
|
+
.first('id');
|
|
340
366
|
if (!keyResult) {
|
|
341
367
|
// Key doesn't exist, return empty history
|
|
342
368
|
return {
|
|
@@ -347,18 +373,11 @@ export function getVersions(params) {
|
|
|
347
373
|
}
|
|
348
374
|
const keyId = keyResult.id;
|
|
349
375
|
// Query t_decision_history with agent join
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
datetime(dh.ts, 'unixepoch') as timestamp
|
|
356
|
-
FROM t_decision_history dh
|
|
357
|
-
LEFT JOIN m_agents a ON dh.agent_id = a.id
|
|
358
|
-
WHERE dh.key_id = ?
|
|
359
|
-
ORDER BY dh.ts DESC
|
|
360
|
-
`);
|
|
361
|
-
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');
|
|
362
381
|
// Transform to response format
|
|
363
382
|
const history = rows.map(row => ({
|
|
364
383
|
version: row.version,
|
|
@@ -382,17 +401,19 @@ export function getVersions(params) {
|
|
|
382
401
|
* Supports status filtering and optional tag inclusion
|
|
383
402
|
*
|
|
384
403
|
* @param params - Layer name, optional status and include_tags
|
|
404
|
+
* @param adapter - Optional database adapter (for testing)
|
|
385
405
|
* @returns Array of t_decisions in the specified layer
|
|
386
406
|
*/
|
|
387
|
-
export function searchByLayer(params) {
|
|
388
|
-
const
|
|
407
|
+
export async function searchByLayer(params, adapter) {
|
|
408
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
409
|
+
const knex = actualAdapter.getKnex();
|
|
389
410
|
// Validate required parameter
|
|
390
411
|
if (!params.layer || params.layer.trim() === '') {
|
|
391
412
|
throw new Error('Parameter "layer" is required and cannot be empty');
|
|
392
413
|
}
|
|
393
414
|
try {
|
|
394
415
|
// Validate layer exists
|
|
395
|
-
const layerId = getLayerId(
|
|
416
|
+
const layerId = await getLayerId(actualAdapter, params.layer);
|
|
396
417
|
if (layerId === null) {
|
|
397
418
|
throw new Error(`Invalid layer: ${params.layer}. Must be one of: presentation, business, data, infrastructure, cross-cutting`);
|
|
398
419
|
}
|
|
@@ -403,69 +424,34 @@ export function searchByLayer(params) {
|
|
|
403
424
|
if (!STRING_TO_STATUS[statusValue]) {
|
|
404
425
|
throw new Error(`Invalid status: ${statusValue}. Must be 'active', 'deprecated', or 'draft'`);
|
|
405
426
|
}
|
|
406
|
-
let
|
|
407
|
-
const queryParams = [params.layer, statusValue];
|
|
427
|
+
let rows;
|
|
408
428
|
if (includeTagsValue) {
|
|
409
429
|
// Use v_tagged_decisions view for full metadata
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
`;
|
|
430
|
+
rows = await knex('v_tagged_decisions')
|
|
431
|
+
.where({ layer: params.layer, status: statusValue })
|
|
432
|
+
.orderBy('updated', 'desc')
|
|
433
|
+
.select('*');
|
|
415
434
|
}
|
|
416
435
|
else {
|
|
417
436
|
// Use base t_decisions table with minimal joins
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
LEFT JOIN m_layers l ON d.layer_id = l.id
|
|
436
|
-
LEFT JOIN m_agents a ON d.agent_id = a.id
|
|
437
|
-
WHERE l.name = ? AND d.status = ?
|
|
438
|
-
|
|
439
|
-
UNION ALL
|
|
440
|
-
|
|
441
|
-
SELECT
|
|
442
|
-
ck.key,
|
|
443
|
-
CAST(dn.value AS TEXT) as value,
|
|
444
|
-
dn.version,
|
|
445
|
-
CASE dn.status
|
|
446
|
-
WHEN 1 THEN 'active'
|
|
447
|
-
WHEN 2 THEN 'deprecated'
|
|
448
|
-
WHEN 3 THEN 'draft'
|
|
449
|
-
END as status,
|
|
450
|
-
l.name as layer,
|
|
451
|
-
NULL as tags,
|
|
452
|
-
NULL as scopes,
|
|
453
|
-
a.name as decided_by,
|
|
454
|
-
datetime(dn.ts, 'unixepoch') as updated
|
|
455
|
-
FROM t_decisions_numeric dn
|
|
456
|
-
INNER JOIN m_context_keys ck ON dn.key_id = ck.id
|
|
457
|
-
LEFT JOIN m_layers l ON dn.layer_id = l.id
|
|
458
|
-
LEFT JOIN m_agents a ON dn.agent_id = a.id
|
|
459
|
-
WHERE l.name = ? AND dn.status = ?
|
|
460
|
-
|
|
461
|
-
ORDER BY updated DESC
|
|
462
|
-
`;
|
|
463
|
-
// Add params for the numeric table part of UNION
|
|
464
|
-
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');
|
|
465
454
|
}
|
|
466
|
-
// Execute query
|
|
467
|
-
const stmt = db.prepare(query);
|
|
468
|
-
const rows = stmt.all(...queryParams);
|
|
469
455
|
return {
|
|
470
456
|
layer: params.layer,
|
|
471
457
|
decisions: rows,
|
|
@@ -502,9 +488,10 @@ export function searchByLayer(params) {
|
|
|
502
488
|
* All inferred fields can be overridden via optional parameters.
|
|
503
489
|
*
|
|
504
490
|
* @param params - Quick set parameters (key and value required)
|
|
491
|
+
* @param adapter - Optional database adapter (for testing)
|
|
505
492
|
* @returns Response with success status and inferred metadata
|
|
506
493
|
*/
|
|
507
|
-
export function quickSetDecision(params) {
|
|
494
|
+
export async function quickSetDecision(params, adapter) {
|
|
508
495
|
// Validate required parameters
|
|
509
496
|
if (!params.key || params.key.trim() === '') {
|
|
510
497
|
throw new Error('Parameter "key" is required and cannot be empty');
|
|
@@ -568,8 +555,8 @@ export function quickSetDecision(params) {
|
|
|
568
555
|
tags: inferredTags,
|
|
569
556
|
scopes: inferredScopes
|
|
570
557
|
};
|
|
571
|
-
// Call setDecision with full params
|
|
572
|
-
const result = setDecision(fullParams);
|
|
558
|
+
// Call setDecision with full params (pass adapter if provided)
|
|
559
|
+
const result = await setDecision(fullParams, adapter);
|
|
573
560
|
// Return response with inferred metadata
|
|
574
561
|
return {
|
|
575
562
|
success: result.success,
|
|
@@ -596,10 +583,12 @@ export function quickSetDecision(params) {
|
|
|
596
583
|
* - search_text: Full-text search in value field
|
|
597
584
|
*
|
|
598
585
|
* @param params - Advanced search parameters with filtering, sorting, pagination
|
|
586
|
+
* @param adapter - Optional database adapter (for testing)
|
|
599
587
|
* @returns Filtered decisions with total count for pagination
|
|
600
588
|
*/
|
|
601
|
-
export function searchAdvanced(params = {}) {
|
|
602
|
-
const
|
|
589
|
+
export async function searchAdvanced(params = {}, adapter) {
|
|
590
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
591
|
+
const knex = actualAdapter.getKnex();
|
|
603
592
|
try {
|
|
604
593
|
// Parse relative time to Unix timestamp
|
|
605
594
|
const parseRelativeTime = (relativeTime) => {
|
|
@@ -623,60 +612,64 @@ export function searchAdvanced(params = {}) {
|
|
|
623
612
|
}
|
|
624
613
|
};
|
|
625
614
|
// Build base query using v_tagged_decisions view
|
|
626
|
-
let query = '
|
|
627
|
-
const queryParams = [];
|
|
615
|
+
let query = knex('v_tagged_decisions');
|
|
628
616
|
// Filter by layers (OR relationship)
|
|
629
617
|
if (params.layers && params.layers.length > 0) {
|
|
630
|
-
|
|
631
|
-
query += ` AND (${layerConditions})`;
|
|
632
|
-
queryParams.push(...params.layers);
|
|
618
|
+
query = query.whereIn('layer', params.layers);
|
|
633
619
|
}
|
|
634
620
|
// Filter by tags_all (AND relationship - must have ALL tags)
|
|
635
621
|
if (params.tags_all && params.tags_all.length > 0) {
|
|
636
622
|
for (const tag of params.tags_all) {
|
|
637
|
-
query
|
|
638
|
-
|
|
623
|
+
query = query.where((builder) => {
|
|
624
|
+
builder.where('tags', 'like', `%${tag}%`).orWhere('tags', tag);
|
|
625
|
+
});
|
|
639
626
|
}
|
|
640
627
|
}
|
|
641
628
|
// Filter by tags_any (OR relationship - must have ANY tag)
|
|
642
629
|
if (params.tags_any && params.tags_any.length > 0) {
|
|
643
|
-
const
|
|
644
|
-
query
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
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
|
+
});
|
|
648
636
|
}
|
|
649
637
|
// Exclude tags
|
|
650
638
|
if (params.exclude_tags && params.exclude_tags.length > 0) {
|
|
651
639
|
for (const tag of params.exclude_tags) {
|
|
652
|
-
query
|
|
653
|
-
|
|
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
|
+
});
|
|
654
647
|
}
|
|
655
648
|
}
|
|
656
649
|
// Filter by scopes with wildcard support
|
|
657
650
|
if (params.scopes && params.scopes.length > 0) {
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
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
|
+
}
|
|
670
665
|
}
|
|
671
|
-
}
|
|
672
|
-
query += ` AND (${scopeConditions.join(' OR ')})`;
|
|
666
|
+
});
|
|
673
667
|
}
|
|
674
668
|
// Temporal filtering - updated_after
|
|
675
669
|
if (params.updated_after) {
|
|
676
670
|
const timestamp = parseRelativeTime(params.updated_after);
|
|
677
671
|
if (timestamp !== null) {
|
|
678
|
-
query
|
|
679
|
-
queryParams.push(timestamp);
|
|
672
|
+
query = query.whereRaw('unixepoch(updated) >= ?', [timestamp]);
|
|
680
673
|
}
|
|
681
674
|
else {
|
|
682
675
|
throw new Error(`Invalid updated_after format: ${params.updated_after}. Use ISO timestamp or relative time like "7d", "2h", "30m"`);
|
|
@@ -686,8 +679,7 @@ export function searchAdvanced(params = {}) {
|
|
|
686
679
|
if (params.updated_before) {
|
|
687
680
|
const timestamp = parseRelativeTime(params.updated_before);
|
|
688
681
|
if (timestamp !== null) {
|
|
689
|
-
query
|
|
690
|
-
queryParams.push(timestamp);
|
|
682
|
+
query = query.whereRaw('unixepoch(updated) <= ?', [timestamp]);
|
|
691
683
|
}
|
|
692
684
|
else {
|
|
693
685
|
throw new Error(`Invalid updated_before format: ${params.updated_before}. Use ISO timestamp or relative time like "7d", "2h", "30m"`);
|
|
@@ -695,25 +687,19 @@ export function searchAdvanced(params = {}) {
|
|
|
695
687
|
}
|
|
696
688
|
// Filter by decided_by (OR relationship)
|
|
697
689
|
if (params.decided_by && params.decided_by.length > 0) {
|
|
698
|
-
|
|
699
|
-
query += ` AND (${agentConditions})`;
|
|
700
|
-
queryParams.push(...params.decided_by);
|
|
690
|
+
query = query.whereIn('decided_by', params.decided_by);
|
|
701
691
|
}
|
|
702
692
|
// Filter by statuses (OR relationship)
|
|
703
693
|
if (params.statuses && params.statuses.length > 0) {
|
|
704
|
-
|
|
705
|
-
query += ` AND (${statusConditions})`;
|
|
706
|
-
queryParams.push(...params.statuses);
|
|
694
|
+
query = query.whereIn('status', params.statuses);
|
|
707
695
|
}
|
|
708
696
|
// Full-text search in value field
|
|
709
697
|
if (params.search_text) {
|
|
710
|
-
query
|
|
711
|
-
queryParams.push(`%${params.search_text}%`);
|
|
698
|
+
query = query.where('value', 'like', `%${params.search_text}%`);
|
|
712
699
|
}
|
|
713
700
|
// Count total matching records (before pagination)
|
|
714
|
-
const countQuery = query.
|
|
715
|
-
const
|
|
716
|
-
const countResult = countStmt.get(...queryParams);
|
|
701
|
+
const countQuery = query.clone().count('* as total');
|
|
702
|
+
const countResult = await countQuery.first();
|
|
717
703
|
const totalCount = countResult.total;
|
|
718
704
|
// Sorting
|
|
719
705
|
const sortBy = params.sort_by || 'updated';
|
|
@@ -725,7 +711,7 @@ export function searchAdvanced(params = {}) {
|
|
|
725
711
|
if (!['asc', 'desc'].includes(sortOrder)) {
|
|
726
712
|
throw new Error(`Invalid sort_order: ${sortOrder}. Must be 'asc' or 'desc'`);
|
|
727
713
|
}
|
|
728
|
-
query
|
|
714
|
+
query = query.orderBy(sortBy, sortOrder);
|
|
729
715
|
// Pagination
|
|
730
716
|
const limit = params.limit !== undefined ? params.limit : 20;
|
|
731
717
|
const offset = params.offset || 0;
|
|
@@ -736,11 +722,9 @@ export function searchAdvanced(params = {}) {
|
|
|
736
722
|
if (offset < 0) {
|
|
737
723
|
throw new Error('Parameter "offset" must be non-negative');
|
|
738
724
|
}
|
|
739
|
-
query
|
|
740
|
-
queryParams.push(limit, offset);
|
|
725
|
+
query = query.limit(limit).offset(offset);
|
|
741
726
|
// Execute query
|
|
742
|
-
const
|
|
743
|
-
const rows = stmt.all(...queryParams);
|
|
727
|
+
const rows = await query.select('*');
|
|
744
728
|
return {
|
|
745
729
|
decisions: rows,
|
|
746
730
|
count: rows.length,
|
|
@@ -758,37 +742,100 @@ export function searchAdvanced(params = {}) {
|
|
|
758
742
|
* Limit: 50 items per batch (constraint #3)
|
|
759
743
|
*
|
|
760
744
|
* @param params - Batch parameters with array of decisions and atomic flag
|
|
745
|
+
* @param adapter - Optional database adapter (for testing)
|
|
761
746
|
* @returns Response with success status and detailed results for each item
|
|
762
747
|
*/
|
|
763
|
-
export function setDecisionBatch(params) {
|
|
764
|
-
const
|
|
748
|
+
export async function setDecisionBatch(params, adapter) {
|
|
749
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
765
750
|
// Validate required parameters
|
|
766
751
|
if (!params.decisions || !Array.isArray(params.decisions)) {
|
|
767
752
|
throw new Error('Parameter "decisions" is required and must be an array');
|
|
768
753
|
}
|
|
769
|
-
|
|
770
|
-
// Use processBatch utility
|
|
771
|
-
const batchResult = processBatch(db, params.decisions, (decision, db) => {
|
|
772
|
-
const result = setDecisionInternal(decision, db);
|
|
754
|
+
if (params.decisions.length === 0) {
|
|
773
755
|
return {
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
756
|
+
success: true,
|
|
757
|
+
inserted: 0,
|
|
758
|
+
failed: 0,
|
|
759
|
+
results: []
|
|
777
760
|
};
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
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
|
+
}
|
|
792
839
|
}
|
|
793
840
|
/**
|
|
794
841
|
* Check for updates since a given timestamp (FR-003 Phase A)
|
|
@@ -796,10 +843,12 @@ export function setDecisionBatch(params) {
|
|
|
796
843
|
* Token cost: ~5-10 tokens per check
|
|
797
844
|
*
|
|
798
845
|
* @param params - Agent name and since_timestamp (ISO 8601)
|
|
846
|
+
* @param adapter - Optional database adapter (for testing)
|
|
799
847
|
* @returns Boolean flag and counts for decisions, messages, files
|
|
800
848
|
*/
|
|
801
|
-
export function hasUpdates(params) {
|
|
802
|
-
const
|
|
849
|
+
export async function hasUpdates(params, adapter) {
|
|
850
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
851
|
+
const knex = actualAdapter.getKnex();
|
|
803
852
|
// Validate required parameters
|
|
804
853
|
if (!params.agent_name || params.agent_name.trim() === '') {
|
|
805
854
|
throw new Error('Parameter "agent_name" is required and cannot be empty');
|
|
@@ -815,38 +864,43 @@ export function hasUpdates(params) {
|
|
|
815
864
|
}
|
|
816
865
|
const sinceTs = Math.floor(sinceDate.getTime() / 1000);
|
|
817
866
|
// Count decisions updated since timestamp (both string and numeric tables)
|
|
818
|
-
const
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
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);
|
|
827
876
|
// Get agent_id for the requesting agent
|
|
828
|
-
const agentResult =
|
|
877
|
+
const agentResult = await knex('m_agents')
|
|
878
|
+
.where({ name: params.agent_name })
|
|
879
|
+
.first('id');
|
|
829
880
|
// Count messages for the agent (received messages - to_agent_id matches OR broadcast messages)
|
|
830
881
|
let messagesCount = 0;
|
|
831
882
|
if (agentResult) {
|
|
832
883
|
const agentId = agentResult.id;
|
|
833
|
-
const
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
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;
|
|
839
893
|
}
|
|
840
894
|
// Count file changes since timestamp
|
|
841
|
-
const
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
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;
|
|
846
900
|
// Determine if there are any updates
|
|
847
|
-
const
|
|
901
|
+
const hasUpdatesFlag = decisionsCount > 0 || messagesCount > 0 || filesCount > 0;
|
|
848
902
|
return {
|
|
849
|
-
has_updates:
|
|
903
|
+
has_updates: hasUpdatesFlag,
|
|
850
904
|
counts: {
|
|
851
905
|
decisions: decisionsCount,
|
|
852
906
|
messages: messagesCount,
|
|
@@ -865,10 +919,12 @@ export function hasUpdates(params) {
|
|
|
865
919
|
* Validates required fields if template specifies any
|
|
866
920
|
*
|
|
867
921
|
* @param params - Template name, key, value, and optional overrides
|
|
922
|
+
* @param adapter - Optional database adapter (for testing)
|
|
868
923
|
* @returns Response with success status and applied defaults metadata
|
|
869
924
|
*/
|
|
870
|
-
export function setFromTemplate(params) {
|
|
871
|
-
const
|
|
925
|
+
export async function setFromTemplate(params, adapter) {
|
|
926
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
927
|
+
const knex = actualAdapter.getKnex();
|
|
872
928
|
// Validate required parameters
|
|
873
929
|
if (!params.template || params.template.trim() === '') {
|
|
874
930
|
throw new Error('Parameter "template" is required and cannot be empty');
|
|
@@ -881,7 +937,9 @@ export function setFromTemplate(params) {
|
|
|
881
937
|
}
|
|
882
938
|
try {
|
|
883
939
|
// Get template
|
|
884
|
-
const templateRow =
|
|
940
|
+
const templateRow = await knex('t_decision_templates')
|
|
941
|
+
.where({ name: params.template })
|
|
942
|
+
.first();
|
|
885
943
|
if (!templateRow) {
|
|
886
944
|
throw new Error(`Template not found: ${params.template}`);
|
|
887
945
|
}
|
|
@@ -919,8 +977,8 @@ export function setFromTemplate(params) {
|
|
|
919
977
|
if (!params.status && defaults.status) {
|
|
920
978
|
appliedDefaults.status = defaults.status;
|
|
921
979
|
}
|
|
922
|
-
// Call setDecision with merged params
|
|
923
|
-
const result = setDecision(decisionParams);
|
|
980
|
+
// Call setDecision with merged params (pass adapter if provided)
|
|
981
|
+
const result = await setDecision(decisionParams, actualAdapter);
|
|
924
982
|
return {
|
|
925
983
|
success: result.success,
|
|
926
984
|
key: result.key,
|
|
@@ -941,10 +999,12 @@ export function setFromTemplate(params) {
|
|
|
941
999
|
* Defines reusable defaults and required fields for decisions
|
|
942
1000
|
*
|
|
943
1001
|
* @param params - Template name, defaults, required fields, and creator
|
|
1002
|
+
* @param adapter - Optional database adapter (for testing)
|
|
944
1003
|
* @returns Response with success status and template ID
|
|
945
1004
|
*/
|
|
946
|
-
export function createTemplate(params) {
|
|
947
|
-
const
|
|
1005
|
+
export async function createTemplate(params, adapter) {
|
|
1006
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
1007
|
+
const knex = actualAdapter.getKnex();
|
|
948
1008
|
// Validate required parameters
|
|
949
1009
|
if (!params.name || params.name.trim() === '') {
|
|
950
1010
|
throw new Error('Parameter "name" is required and cannot be empty');
|
|
@@ -953,10 +1013,10 @@ export function createTemplate(params) {
|
|
|
953
1013
|
throw new Error('Parameter "defaults" is required and must be an object');
|
|
954
1014
|
}
|
|
955
1015
|
try {
|
|
956
|
-
return transaction(
|
|
1016
|
+
return await actualAdapter.transaction(async (trx) => {
|
|
957
1017
|
// Validate layer if provided in defaults
|
|
958
1018
|
if (params.defaults.layer) {
|
|
959
|
-
const layerId = getLayerId(
|
|
1019
|
+
const layerId = await getLayerId(actualAdapter, params.defaults.layer, trx);
|
|
960
1020
|
if (layerId === null) {
|
|
961
1021
|
throw new Error(`Invalid layer in defaults: ${params.defaults.layer}. Must be one of: presentation, business, data, infrastructure, cross-cutting`);
|
|
962
1022
|
}
|
|
@@ -968,20 +1028,21 @@ export function createTemplate(params) {
|
|
|
968
1028
|
// Get or create agent if creator specified
|
|
969
1029
|
let createdById = null;
|
|
970
1030
|
if (params.created_by) {
|
|
971
|
-
createdById = getOrCreateAgent(
|
|
1031
|
+
createdById = await getOrCreateAgent(actualAdapter, params.created_by, trx);
|
|
972
1032
|
}
|
|
973
1033
|
// Serialize defaults and required fields
|
|
974
1034
|
const defaultsJson = JSON.stringify(params.defaults);
|
|
975
1035
|
const requiredFieldsJson = params.required_fields ? JSON.stringify(params.required_fields) : null;
|
|
976
1036
|
// Insert template
|
|
977
|
-
const
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
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
|
+
});
|
|
982
1043
|
return {
|
|
983
1044
|
success: true,
|
|
984
|
-
template_id:
|
|
1045
|
+
template_id: id,
|
|
985
1046
|
template_name: params.name,
|
|
986
1047
|
message: `Template "${params.name}" created successfully`
|
|
987
1048
|
};
|
|
@@ -997,24 +1058,17 @@ export function createTemplate(params) {
|
|
|
997
1058
|
* Returns all templates with their defaults and metadata
|
|
998
1059
|
*
|
|
999
1060
|
* @param params - No parameters required
|
|
1061
|
+
* @param adapter - Optional database adapter (for testing)
|
|
1000
1062
|
* @returns Array of all templates with parsed JSON fields
|
|
1001
1063
|
*/
|
|
1002
|
-
export function listTemplates(params = {}) {
|
|
1003
|
-
const
|
|
1064
|
+
export async function listTemplates(params = {}, adapter) {
|
|
1065
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
1066
|
+
const knex = actualAdapter.getKnex();
|
|
1004
1067
|
try {
|
|
1005
|
-
const
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
t.defaults,
|
|
1010
|
-
t.required_fields,
|
|
1011
|
-
a.name as created_by,
|
|
1012
|
-
datetime(t.ts, 'unixepoch') as created_at
|
|
1013
|
-
FROM t_decision_templates t
|
|
1014
|
-
LEFT JOIN m_agents a ON t.created_by = a.id
|
|
1015
|
-
ORDER BY t.name ASC
|
|
1016
|
-
`);
|
|
1017
|
-
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');
|
|
1018
1072
|
// Parse JSON fields
|
|
1019
1073
|
const templates = rows.map(row => ({
|
|
1020
1074
|
id: row.id,
|
|
@@ -1047,18 +1101,22 @@ export function listTemplates(params = {}) {
|
|
|
1047
1101
|
* (tags, scopes) will also be deleted due to CASCADE constraints.
|
|
1048
1102
|
*
|
|
1049
1103
|
* @param params - Decision key to delete
|
|
1104
|
+
* @param adapter - Optional database adapter (for testing)
|
|
1050
1105
|
* @returns Response with success status
|
|
1051
1106
|
*/
|
|
1052
|
-
export function hardDeleteDecision(params) {
|
|
1053
|
-
const
|
|
1107
|
+
export async function hardDeleteDecision(params, adapter) {
|
|
1108
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
1109
|
+
const knex = actualAdapter.getKnex();
|
|
1054
1110
|
// Validate parameter
|
|
1055
1111
|
if (!params.key || params.key.trim() === '') {
|
|
1056
1112
|
throw new Error('Parameter "key" is required and cannot be empty');
|
|
1057
1113
|
}
|
|
1058
1114
|
try {
|
|
1059
|
-
return transaction(
|
|
1115
|
+
return await actualAdapter.transaction(async (trx) => {
|
|
1060
1116
|
// Get key_id
|
|
1061
|
-
const keyResult =
|
|
1117
|
+
const keyResult = await trx('m_context_keys')
|
|
1118
|
+
.where({ key: params.key })
|
|
1119
|
+
.first('id');
|
|
1062
1120
|
if (!keyResult) {
|
|
1063
1121
|
// Key doesn't exist - still return success (idempotent)
|
|
1064
1122
|
return {
|
|
@@ -1069,17 +1127,17 @@ export function hardDeleteDecision(params) {
|
|
|
1069
1127
|
}
|
|
1070
1128
|
const keyId = keyResult.id;
|
|
1071
1129
|
// Delete from t_decisions (if exists)
|
|
1072
|
-
const deletedString =
|
|
1130
|
+
const deletedString = await trx('t_decisions').where({ key_id: keyId }).delete();
|
|
1073
1131
|
// Delete from t_decisions_numeric (if exists)
|
|
1074
|
-
const deletedNumeric =
|
|
1132
|
+
const deletedNumeric = await trx('t_decisions_numeric').where({ key_id: keyId }).delete();
|
|
1075
1133
|
// Delete from t_decision_history (CASCADE should handle this, but explicit for clarity)
|
|
1076
|
-
const deletedHistory =
|
|
1134
|
+
const deletedHistory = await trx('t_decision_history').where({ key_id: keyId }).delete();
|
|
1077
1135
|
// Delete from t_decision_tags (CASCADE should handle this)
|
|
1078
|
-
const deletedTags =
|
|
1136
|
+
const deletedTags = await trx('t_decision_tags').where({ decision_key_id: keyId }).delete();
|
|
1079
1137
|
// Delete from t_decision_scopes (CASCADE should handle this)
|
|
1080
|
-
const deletedScopes =
|
|
1138
|
+
const deletedScopes = await trx('t_decision_scopes').where({ decision_key_id: keyId }).delete();
|
|
1081
1139
|
// Calculate total deleted records
|
|
1082
|
-
const totalDeleted = deletedString
|
|
1140
|
+
const totalDeleted = deletedString + deletedNumeric + deletedHistory + deletedTags + deletedScopes;
|
|
1083
1141
|
if (totalDeleted === 0) {
|
|
1084
1142
|
return {
|
|
1085
1143
|
success: true,
|
|
@@ -1107,10 +1165,11 @@ export function hardDeleteDecision(params) {
|
|
|
1107
1165
|
* Adds rich context (rationale, alternatives, tradeoffs) to a decision
|
|
1108
1166
|
*
|
|
1109
1167
|
* @param params - Context parameters
|
|
1168
|
+
* @param adapter - Optional database adapter (for testing)
|
|
1110
1169
|
* @returns Response with success status
|
|
1111
1170
|
*/
|
|
1112
|
-
export function addDecisionContextAction(params) {
|
|
1113
|
-
const
|
|
1171
|
+
export async function addDecisionContextAction(params, adapter) {
|
|
1172
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
1114
1173
|
// Validate required parameters
|
|
1115
1174
|
if (!params.key || params.key.trim() === '') {
|
|
1116
1175
|
throw new Error('Parameter "key" is required and cannot be empty');
|
|
@@ -1129,7 +1188,7 @@ export function addDecisionContextAction(params) {
|
|
|
1129
1188
|
if (tradeoffs && typeof tradeoffs === 'object') {
|
|
1130
1189
|
tradeoffs = JSON.stringify(tradeoffs);
|
|
1131
1190
|
}
|
|
1132
|
-
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);
|
|
1133
1192
|
return {
|
|
1134
1193
|
success: true,
|
|
1135
1194
|
context_id: contextId,
|
|
@@ -1147,12 +1206,13 @@ export function addDecisionContextAction(params) {
|
|
|
1147
1206
|
* Query decision contexts with optional filters
|
|
1148
1207
|
*
|
|
1149
1208
|
* @param params - Filter parameters
|
|
1209
|
+
* @param adapter - Optional database adapter (for testing)
|
|
1150
1210
|
* @returns Array of decision contexts
|
|
1151
1211
|
*/
|
|
1152
|
-
export function listDecisionContextsAction(params) {
|
|
1153
|
-
const
|
|
1212
|
+
export async function listDecisionContextsAction(params, adapter) {
|
|
1213
|
+
const actualAdapter = adapter ?? getAdapter();
|
|
1154
1214
|
try {
|
|
1155
|
-
const contexts = dbListDecisionContexts(
|
|
1215
|
+
const contexts = await dbListDecisionContexts(actualAdapter, {
|
|
1156
1216
|
decisionKey: params.decision_key,
|
|
1157
1217
|
relatedTaskId: params.related_task_id,
|
|
1158
1218
|
relatedConstraintId: params.related_constraint_id,
|