sqlew 3.1.2 → 3.2.3
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 +118 -0
- package/README.md +58 -3
- package/assets/schema.sql +28 -1
- package/dist/database.d.ts +65 -0
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +190 -0
- package/dist/database.js.map +1 -1
- package/dist/index.js +47 -1005
- package/dist/index.js.map +1 -1
- package/dist/migrations/add-decision-context.d.ts +28 -0
- package/dist/migrations/add-decision-context.d.ts.map +1 -0
- package/dist/migrations/add-decision-context.js +125 -0
- package/dist/migrations/add-decision-context.js.map +1 -0
- package/dist/migrations/add-task-dependencies.d.ts +26 -0
- package/dist/migrations/add-task-dependencies.d.ts.map +1 -0
- package/dist/migrations/add-task-dependencies.js +94 -0
- package/dist/migrations/add-task-dependencies.js.map +1 -0
- package/dist/migrations/index.d.ts +3 -1
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +32 -2
- package/dist/migrations/index.js.map +1 -1
- package/dist/schema.js +2 -2
- package/dist/schema.js.map +1 -1
- package/dist/tests/migrations/test-v3.2-migration.d.ts +6 -0
- package/dist/tests/migrations/test-v3.2-migration.d.ts.map +1 -0
- package/dist/tests/migrations/test-v3.2-migration.js +191 -0
- package/dist/tests/migrations/test-v3.2-migration.js.map +1 -0
- package/dist/tests/tasks.dependencies.test.d.ts +7 -0
- package/dist/tests/tasks.dependencies.test.d.ts.map +1 -0
- package/dist/tests/tasks.dependencies.test.js +613 -0
- package/dist/tests/tasks.dependencies.test.js.map +1 -0
- package/dist/tools/config.d.ts +10 -0
- package/dist/tools/config.d.ts.map +1 -1
- package/dist/tools/config.js +105 -0
- package/dist/tools/config.js.map +1 -1
- package/dist/tools/constraints.d.ts +10 -0
- package/dist/tools/constraints.d.ts.map +1 -1
- package/dist/tools/constraints.js +167 -0
- package/dist/tools/constraints.js.map +1 -1
- package/dist/tools/context.d.ts +29 -2
- package/dist/tools/context.d.ts.map +1 -1
- package/dist/tools/context.js +442 -106
- package/dist/tools/context.js.map +1 -1
- package/dist/tools/files.d.ts +8 -0
- package/dist/tools/files.d.ts.map +1 -1
- package/dist/tools/files.js +125 -0
- package/dist/tools/files.js.map +1 -1
- package/dist/tools/messaging.d.ts +8 -0
- package/dist/tools/messaging.d.ts.map +1 -1
- package/dist/tools/messaging.js +134 -0
- package/dist/tools/messaging.js.map +1 -1
- package/dist/tools/tasks.d.ts +32 -0
- package/dist/tools/tasks.d.ts.map +1 -1
- package/dist/tools/tasks.js +651 -8
- package/dist/tools/tasks.js.map +1 -1
- package/dist/tools/utils.d.ts +10 -0
- package/dist/tools/utils.d.ts.map +1 -1
- package/dist/tools/utils.js +179 -21
- package/dist/tools/utils.js.map +1 -1
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/AI_AGENT_GUIDE.md +25 -3
- package/docs/DECISION_CONTEXT.md +474 -0
- package/docs/HELP_PREVIEW_COMPARISON.md +259 -0
- package/docs/TASK_ACTIONS.md +311 -10
- package/docs/TASK_DEPENDENCIES.md +748 -0
- package/docs/TASK_LINKING.md +188 -8
- package/docs/TOOL_REFERENCE.md +158 -1
- package/docs/WORKFLOWS.md +25 -3
- package/package.json +4 -2
package/dist/tools/context.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Context management tools for MCP Shared Context Server
|
|
3
3
|
* Implements set_decision, get_context, and get_decision tools
|
|
4
4
|
*/
|
|
5
|
-
import { getDatabase, getOrCreateAgent, getOrCreateContextKey, getOrCreateTag, getOrCreateScope, getLayerId, transaction } from '../database.js';
|
|
5
|
+
import { getDatabase, getOrCreateAgent, getOrCreateContextKey, getOrCreateTag, getOrCreateScope, getLayerId, transaction, addDecisionContext as dbAddDecisionContext, getDecisionWithContext as dbGetDecisionWithContext, listDecisionContexts as dbListDecisionContexts } from '../database.js';
|
|
6
6
|
import { STRING_TO_STATUS, DEFAULT_VERSION, DEFAULT_STATUS } from '../constants.js';
|
|
7
7
|
import { processBatch } from '../utils/batch.js';
|
|
8
8
|
import { validateRequired, validateStatus, validateLayer } from '../utils/validators.js';
|
|
@@ -44,31 +44,31 @@ function setDecisionInternal(params, db) {
|
|
|
44
44
|
// Insert or update decision based on value type
|
|
45
45
|
if (isNumeric) {
|
|
46
46
|
// Numeric decision
|
|
47
|
-
const stmt = db.prepare(`
|
|
48
|
-
INSERT INTO t_decisions_numeric (key_id, value, agent_id, layer_id, version, status, ts)
|
|
49
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
50
|
-
ON CONFLICT(key_id) DO UPDATE SET
|
|
51
|
-
value = excluded.value,
|
|
52
|
-
agent_id = excluded.agent_id,
|
|
53
|
-
layer_id = excluded.layer_id,
|
|
54
|
-
version = excluded.version,
|
|
55
|
-
status = excluded.status,
|
|
56
|
-
ts = excluded.ts
|
|
47
|
+
const stmt = db.prepare(`
|
|
48
|
+
INSERT INTO t_decisions_numeric (key_id, value, agent_id, layer_id, version, status, ts)
|
|
49
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
50
|
+
ON CONFLICT(key_id) DO UPDATE SET
|
|
51
|
+
value = excluded.value,
|
|
52
|
+
agent_id = excluded.agent_id,
|
|
53
|
+
layer_id = excluded.layer_id,
|
|
54
|
+
version = excluded.version,
|
|
55
|
+
status = excluded.status,
|
|
56
|
+
ts = excluded.ts
|
|
57
57
|
`);
|
|
58
58
|
stmt.run(keyId, value, agentId, layerId, version, status, ts);
|
|
59
59
|
}
|
|
60
60
|
else {
|
|
61
61
|
// String decision
|
|
62
|
-
const stmt = db.prepare(`
|
|
63
|
-
INSERT INTO t_decisions (key_id, value, agent_id, layer_id, version, status, ts)
|
|
64
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
65
|
-
ON CONFLICT(key_id) DO UPDATE SET
|
|
66
|
-
value = excluded.value,
|
|
67
|
-
agent_id = excluded.agent_id,
|
|
68
|
-
layer_id = excluded.layer_id,
|
|
69
|
-
version = excluded.version,
|
|
70
|
-
status = excluded.status,
|
|
71
|
-
ts = excluded.ts
|
|
62
|
+
const stmt = db.prepare(`
|
|
63
|
+
INSERT INTO t_decisions (key_id, value, agent_id, layer_id, version, status, ts)
|
|
64
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
65
|
+
ON CONFLICT(key_id) DO UPDATE SET
|
|
66
|
+
value = excluded.value,
|
|
67
|
+
agent_id = excluded.agent_id,
|
|
68
|
+
layer_id = excluded.layer_id,
|
|
69
|
+
version = excluded.version,
|
|
70
|
+
status = excluded.status,
|
|
71
|
+
ts = excluded.ts
|
|
72
72
|
`);
|
|
73
73
|
stmt.run(keyId, String(value), agentId, layerId, version, status, ts);
|
|
74
74
|
}
|
|
@@ -193,8 +193,9 @@ export function getContext(params = {}) {
|
|
|
193
193
|
/**
|
|
194
194
|
* Get a specific decision by key
|
|
195
195
|
* Returns full metadata including tags, layer, scopes, version
|
|
196
|
+
* Optionally includes decision context (v3.2.2)
|
|
196
197
|
*
|
|
197
|
-
* @param params - Decision key
|
|
198
|
+
* @param params - Decision key and optional include_context flag
|
|
198
199
|
* @returns Decision details or not found
|
|
199
200
|
*/
|
|
200
201
|
export function getDecision(params) {
|
|
@@ -204,7 +205,36 @@ export function getDecision(params) {
|
|
|
204
205
|
throw new Error('Parameter "key" is required and cannot be empty');
|
|
205
206
|
}
|
|
206
207
|
try {
|
|
207
|
-
//
|
|
208
|
+
// If include_context is true, use the context-aware function
|
|
209
|
+
if (params.include_context) {
|
|
210
|
+
const result = dbGetDecisionWithContext(db, params.key);
|
|
211
|
+
if (!result) {
|
|
212
|
+
return {
|
|
213
|
+
found: false
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
found: true,
|
|
218
|
+
decision: {
|
|
219
|
+
key: result.key,
|
|
220
|
+
value: result.value,
|
|
221
|
+
version: result.version,
|
|
222
|
+
status: result.status,
|
|
223
|
+
layer: result.layer,
|
|
224
|
+
decided_by: result.decided_by,
|
|
225
|
+
updated: result.updated,
|
|
226
|
+
tags: null, // Not included in getDecisionWithContext
|
|
227
|
+
scopes: null // Not included in getDecisionWithContext
|
|
228
|
+
},
|
|
229
|
+
context: result.context.map(ctx => ({
|
|
230
|
+
...ctx,
|
|
231
|
+
// Parse JSON fields
|
|
232
|
+
alternatives_considered: ctx.alternatives_considered ? JSON.parse(ctx.alternatives_considered) : null,
|
|
233
|
+
tradeoffs: ctx.tradeoffs ? JSON.parse(ctx.tradeoffs) : null
|
|
234
|
+
}))
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
// Standard query without context (backward compatible)
|
|
208
238
|
const stmt = db.prepare('SELECT * FROM v_tagged_decisions WHERE key = ?');
|
|
209
239
|
const row = stmt.get(params.key);
|
|
210
240
|
if (!row) {
|
|
@@ -317,16 +347,16 @@ export function getVersions(params) {
|
|
|
317
347
|
}
|
|
318
348
|
const keyId = keyResult.id;
|
|
319
349
|
// Query t_decision_history with agent join
|
|
320
|
-
const stmt = db.prepare(`
|
|
321
|
-
SELECT
|
|
322
|
-
dh.version,
|
|
323
|
-
dh.value,
|
|
324
|
-
a.name as agent_name,
|
|
325
|
-
datetime(dh.ts, 'unixepoch') as timestamp
|
|
326
|
-
FROM t_decision_history dh
|
|
327
|
-
LEFT JOIN m_agents a ON dh.agent_id = a.id
|
|
328
|
-
WHERE dh.key_id = ?
|
|
329
|
-
ORDER BY dh.ts DESC
|
|
350
|
+
const stmt = db.prepare(`
|
|
351
|
+
SELECT
|
|
352
|
+
dh.version,
|
|
353
|
+
dh.value,
|
|
354
|
+
a.name as agent_name,
|
|
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
|
|
330
360
|
`);
|
|
331
361
|
const rows = stmt.all(keyId);
|
|
332
362
|
// Transform to response format
|
|
@@ -377,58 +407,58 @@ export function searchByLayer(params) {
|
|
|
377
407
|
const queryParams = [params.layer, statusValue];
|
|
378
408
|
if (includeTagsValue) {
|
|
379
409
|
// Use v_tagged_decisions view for full metadata
|
|
380
|
-
query = `
|
|
381
|
-
SELECT * FROM v_tagged_decisions
|
|
382
|
-
WHERE layer = ? AND status = ?
|
|
383
|
-
ORDER BY updated DESC
|
|
410
|
+
query = `
|
|
411
|
+
SELECT * FROM v_tagged_decisions
|
|
412
|
+
WHERE layer = ? AND status = ?
|
|
413
|
+
ORDER BY updated DESC
|
|
384
414
|
`;
|
|
385
415
|
}
|
|
386
416
|
else {
|
|
387
417
|
// Use base t_decisions table with minimal joins
|
|
388
|
-
query = `
|
|
389
|
-
SELECT
|
|
390
|
-
ck.key,
|
|
391
|
-
d.value,
|
|
392
|
-
d.version,
|
|
393
|
-
CASE d.status
|
|
394
|
-
WHEN 1 THEN 'active'
|
|
395
|
-
WHEN 2 THEN 'deprecated'
|
|
396
|
-
WHEN 3 THEN 'draft'
|
|
397
|
-
END as status,
|
|
398
|
-
l.name as layer,
|
|
399
|
-
NULL as tags,
|
|
400
|
-
NULL as scopes,
|
|
401
|
-
a.name as decided_by,
|
|
402
|
-
datetime(d.ts, 'unixepoch') as updated
|
|
403
|
-
FROM t_decisions d
|
|
404
|
-
INNER JOIN m_context_keys ck ON d.key_id = ck.id
|
|
405
|
-
LEFT JOIN m_layers l ON d.layer_id = l.id
|
|
406
|
-
LEFT JOIN m_agents a ON d.agent_id = a.id
|
|
407
|
-
WHERE l.name = ? AND d.status = ?
|
|
408
|
-
|
|
409
|
-
UNION ALL
|
|
410
|
-
|
|
411
|
-
SELECT
|
|
412
|
-
ck.key,
|
|
413
|
-
CAST(dn.value AS TEXT) as value,
|
|
414
|
-
dn.version,
|
|
415
|
-
CASE dn.status
|
|
416
|
-
WHEN 1 THEN 'active'
|
|
417
|
-
WHEN 2 THEN 'deprecated'
|
|
418
|
-
WHEN 3 THEN 'draft'
|
|
419
|
-
END as status,
|
|
420
|
-
l.name as layer,
|
|
421
|
-
NULL as tags,
|
|
422
|
-
NULL as scopes,
|
|
423
|
-
a.name as decided_by,
|
|
424
|
-
datetime(dn.ts, 'unixepoch') as updated
|
|
425
|
-
FROM t_decisions_numeric dn
|
|
426
|
-
INNER JOIN m_context_keys ck ON dn.key_id = ck.id
|
|
427
|
-
LEFT JOIN m_layers l ON dn.layer_id = l.id
|
|
428
|
-
LEFT JOIN m_agents a ON dn.agent_id = a.id
|
|
429
|
-
WHERE l.name = ? AND dn.status = ?
|
|
430
|
-
|
|
431
|
-
ORDER BY updated DESC
|
|
418
|
+
query = `
|
|
419
|
+
SELECT
|
|
420
|
+
ck.key,
|
|
421
|
+
d.value,
|
|
422
|
+
d.version,
|
|
423
|
+
CASE d.status
|
|
424
|
+
WHEN 1 THEN 'active'
|
|
425
|
+
WHEN 2 THEN 'deprecated'
|
|
426
|
+
WHEN 3 THEN 'draft'
|
|
427
|
+
END as status,
|
|
428
|
+
l.name as layer,
|
|
429
|
+
NULL as tags,
|
|
430
|
+
NULL as scopes,
|
|
431
|
+
a.name as decided_by,
|
|
432
|
+
datetime(d.ts, 'unixepoch') as updated
|
|
433
|
+
FROM t_decisions d
|
|
434
|
+
INNER JOIN m_context_keys ck ON d.key_id = ck.id
|
|
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
|
|
432
462
|
`;
|
|
433
463
|
// Add params for the numeric table part of UNION
|
|
434
464
|
queryParams.push(params.layer, statusValue);
|
|
@@ -785,12 +815,12 @@ export function hasUpdates(params) {
|
|
|
785
815
|
}
|
|
786
816
|
const sinceTs = Math.floor(sinceDate.getTime() / 1000);
|
|
787
817
|
// Count decisions updated since timestamp (both string and numeric tables)
|
|
788
|
-
const decisionCountStmt = db.prepare(`
|
|
789
|
-
SELECT COUNT(*) as count FROM (
|
|
790
|
-
SELECT ts FROM t_decisions WHERE ts > ?
|
|
791
|
-
UNION ALL
|
|
792
|
-
SELECT ts FROM t_decisions_numeric WHERE ts > ?
|
|
793
|
-
)
|
|
818
|
+
const decisionCountStmt = db.prepare(`
|
|
819
|
+
SELECT COUNT(*) as count FROM (
|
|
820
|
+
SELECT ts FROM t_decisions WHERE ts > ?
|
|
821
|
+
UNION ALL
|
|
822
|
+
SELECT ts FROM t_decisions_numeric WHERE ts > ?
|
|
823
|
+
)
|
|
794
824
|
`);
|
|
795
825
|
const decisionResult = decisionCountStmt.get(sinceTs, sinceTs);
|
|
796
826
|
const decisionsCount = decisionResult.count;
|
|
@@ -800,16 +830,16 @@ export function hasUpdates(params) {
|
|
|
800
830
|
let messagesCount = 0;
|
|
801
831
|
if (agentResult) {
|
|
802
832
|
const agentId = agentResult.id;
|
|
803
|
-
const messageCountStmt = db.prepare(`
|
|
804
|
-
SELECT COUNT(*) as count FROM t_agent_messages
|
|
805
|
-
WHERE ts > ? AND (to_agent_id = ? OR to_agent_id IS NULL)
|
|
833
|
+
const messageCountStmt = db.prepare(`
|
|
834
|
+
SELECT COUNT(*) as count FROM t_agent_messages
|
|
835
|
+
WHERE ts > ? AND (to_agent_id = ? OR to_agent_id IS NULL)
|
|
806
836
|
`);
|
|
807
837
|
const messageResult = messageCountStmt.get(sinceTs, agentId);
|
|
808
838
|
messagesCount = messageResult.count;
|
|
809
839
|
}
|
|
810
840
|
// Count file changes since timestamp
|
|
811
|
-
const fileCountStmt = db.prepare(`
|
|
812
|
-
SELECT COUNT(*) as count FROM t_file_changes WHERE ts > ?
|
|
841
|
+
const fileCountStmt = db.prepare(`
|
|
842
|
+
SELECT COUNT(*) as count FROM t_file_changes WHERE ts > ?
|
|
813
843
|
`);
|
|
814
844
|
const fileResult = fileCountStmt.get(sinceTs);
|
|
815
845
|
const filesCount = fileResult.count;
|
|
@@ -944,9 +974,9 @@ export function createTemplate(params) {
|
|
|
944
974
|
const defaultsJson = JSON.stringify(params.defaults);
|
|
945
975
|
const requiredFieldsJson = params.required_fields ? JSON.stringify(params.required_fields) : null;
|
|
946
976
|
// Insert template
|
|
947
|
-
const stmt = db.prepare(`
|
|
948
|
-
INSERT INTO t_decision_templates (name, defaults, required_fields, created_by)
|
|
949
|
-
VALUES (?, ?, ?, ?)
|
|
977
|
+
const stmt = db.prepare(`
|
|
978
|
+
INSERT INTO t_decision_templates (name, defaults, required_fields, created_by)
|
|
979
|
+
VALUES (?, ?, ?, ?)
|
|
950
980
|
`);
|
|
951
981
|
const info = stmt.run(params.name, defaultsJson, requiredFieldsJson, createdById);
|
|
952
982
|
return {
|
|
@@ -972,17 +1002,17 @@ export function createTemplate(params) {
|
|
|
972
1002
|
export function listTemplates(params = {}) {
|
|
973
1003
|
const db = getDatabase();
|
|
974
1004
|
try {
|
|
975
|
-
const stmt = db.prepare(`
|
|
976
|
-
SELECT
|
|
977
|
-
t.id,
|
|
978
|
-
t.name,
|
|
979
|
-
t.defaults,
|
|
980
|
-
t.required_fields,
|
|
981
|
-
a.name as created_by,
|
|
982
|
-
datetime(t.ts, 'unixepoch') as created_at
|
|
983
|
-
FROM t_decision_templates t
|
|
984
|
-
LEFT JOIN m_agents a ON t.created_by = a.id
|
|
985
|
-
ORDER BY t.name ASC
|
|
1005
|
+
const stmt = db.prepare(`
|
|
1006
|
+
SELECT
|
|
1007
|
+
t.id,
|
|
1008
|
+
t.name,
|
|
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
|
|
986
1016
|
`);
|
|
987
1017
|
const rows = stmt.all();
|
|
988
1018
|
// Parse JSON fields
|
|
@@ -1069,4 +1099,310 @@ export function hardDeleteDecision(params) {
|
|
|
1069
1099
|
throw new Error(`Failed to hard delete decision: ${message}`);
|
|
1070
1100
|
}
|
|
1071
1101
|
}
|
|
1102
|
+
// ============================================================================
|
|
1103
|
+
// Decision Context Actions (v3.2.2)
|
|
1104
|
+
// ============================================================================
|
|
1105
|
+
/**
|
|
1106
|
+
* Add decision context action
|
|
1107
|
+
* Adds rich context (rationale, alternatives, tradeoffs) to a decision
|
|
1108
|
+
*
|
|
1109
|
+
* @param params - Context parameters
|
|
1110
|
+
* @returns Response with success status
|
|
1111
|
+
*/
|
|
1112
|
+
export function addDecisionContextAction(params) {
|
|
1113
|
+
const db = getDatabase();
|
|
1114
|
+
// Validate required parameters
|
|
1115
|
+
if (!params.key || params.key.trim() === '') {
|
|
1116
|
+
throw new Error('Parameter "key" is required and cannot be empty');
|
|
1117
|
+
}
|
|
1118
|
+
if (!params.rationale || params.rationale.trim() === '') {
|
|
1119
|
+
throw new Error('Parameter "rationale" is required and cannot be empty');
|
|
1120
|
+
}
|
|
1121
|
+
try {
|
|
1122
|
+
// Parse JSON if provided as strings
|
|
1123
|
+
let alternatives = params.alternatives_considered || null;
|
|
1124
|
+
let tradeoffs = params.tradeoffs || null;
|
|
1125
|
+
// If already objects, stringify them
|
|
1126
|
+
if (alternatives && typeof alternatives === 'object') {
|
|
1127
|
+
alternatives = JSON.stringify(alternatives);
|
|
1128
|
+
}
|
|
1129
|
+
if (tradeoffs && typeof tradeoffs === 'object') {
|
|
1130
|
+
tradeoffs = JSON.stringify(tradeoffs);
|
|
1131
|
+
}
|
|
1132
|
+
const contextId = dbAddDecisionContext(db, params.key, params.rationale, alternatives, tradeoffs, params.decided_by || null, params.related_task_id || null, params.related_constraint_id || null);
|
|
1133
|
+
return {
|
|
1134
|
+
success: true,
|
|
1135
|
+
context_id: contextId,
|
|
1136
|
+
decision_key: params.key,
|
|
1137
|
+
message: `Decision context added successfully to "${params.key}"`
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
catch (error) {
|
|
1141
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1142
|
+
throw new Error(`Failed to add decision context: ${message}`);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* List decision contexts action
|
|
1147
|
+
* Query decision contexts with optional filters
|
|
1148
|
+
*
|
|
1149
|
+
* @param params - Filter parameters
|
|
1150
|
+
* @returns Array of decision contexts
|
|
1151
|
+
*/
|
|
1152
|
+
export function listDecisionContextsAction(params) {
|
|
1153
|
+
const db = getDatabase();
|
|
1154
|
+
try {
|
|
1155
|
+
const contexts = dbListDecisionContexts(db, {
|
|
1156
|
+
decisionKey: params.decision_key,
|
|
1157
|
+
relatedTaskId: params.related_task_id,
|
|
1158
|
+
relatedConstraintId: params.related_constraint_id,
|
|
1159
|
+
decidedBy: params.decided_by,
|
|
1160
|
+
limit: params.limit || 50,
|
|
1161
|
+
offset: params.offset || 0
|
|
1162
|
+
});
|
|
1163
|
+
return {
|
|
1164
|
+
success: true,
|
|
1165
|
+
contexts: contexts.map(ctx => ({
|
|
1166
|
+
...ctx,
|
|
1167
|
+
// Parse JSON fields for display
|
|
1168
|
+
alternatives_considered: ctx.alternatives_considered ? JSON.parse(ctx.alternatives_considered) : null,
|
|
1169
|
+
tradeoffs: ctx.tradeoffs ? JSON.parse(ctx.tradeoffs) : null
|
|
1170
|
+
})),
|
|
1171
|
+
count: contexts.length
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
catch (error) {
|
|
1175
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1176
|
+
throw new Error(`Failed to list decision contexts: ${message}`);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Help action for decision tool
|
|
1181
|
+
*/
|
|
1182
|
+
export function decisionHelp() {
|
|
1183
|
+
return {
|
|
1184
|
+
tool: 'decision',
|
|
1185
|
+
description: 'Manage decisions with metadata (tags, layers, versions, scopes)',
|
|
1186
|
+
note: '💡 TIP: Use action: "example" to see comprehensive usage scenarios and real-world examples for all decision actions.',
|
|
1187
|
+
purpose: {
|
|
1188
|
+
title: '⚠️ CRITICAL: Store WHY and REASON, Not WHAT',
|
|
1189
|
+
principle: 'Decisions table is for ARCHITECTURAL CONTEXT and REASONING, NOT implementation logs or task completion status',
|
|
1190
|
+
what_to_store: {
|
|
1191
|
+
correct: [
|
|
1192
|
+
'WHY a design choice was made (e.g., "Chose JWT over sessions because stateless auth scales better for our microservice architecture")',
|
|
1193
|
+
'REASONING behind architecture decisions (e.g., "Moved oscillator_type to MonophonicSynthConfig to separate synthesis methods - FM operators use different config")',
|
|
1194
|
+
'PROBLEM ANALYSIS and solution rationale (e.g., "Nested transaction bug: setDecision wraps in transaction, batch also wraps → solution: extract internal helper without transaction wrapper")',
|
|
1195
|
+
'DESIGN TRADE-OFFS and alternatives considered (e.g., "Query builder limited to simple filters, kept domain-specific logic inline for maintainability")',
|
|
1196
|
+
'CONSTRAINTS and requirements reasoning (e.g., "API response must be <100ms because mobile clients timeout at 200ms")',
|
|
1197
|
+
'BREAKING CHANGES with migration rationale (e.g., "Removed /v1/users endpoint - clients must use /v2/users with pagination for scalability")'
|
|
1198
|
+
],
|
|
1199
|
+
incorrect: [
|
|
1200
|
+
'❌ Task completion logs (e.g., "Task 5 completed", "Refactoring done", "Tests passing") → Use tasks tool instead',
|
|
1201
|
+
'❌ Implementation status (e.g., "Added validators.ts", "Fixed bug in batch_create", "Updated README") → These are WHAT, not WHY',
|
|
1202
|
+
'❌ Test results (e.g., "All tests passing", "Integration tests complete", "v3.0.2 testing verified") → Temporary status, not architectural context',
|
|
1203
|
+
'❌ Git commit summaries (e.g., "Released v3.0.2", "Created git commit 2bf55a0") → Belongs in git history',
|
|
1204
|
+
'❌ Documentation updates (e.g., "README reorganized", "Help actions enhanced") → Implementation logs, not decisions',
|
|
1205
|
+
'❌ Build status (e.g., "Build succeeded", "TypeScript compiled with zero errors") → Temporary status'
|
|
1206
|
+
]
|
|
1207
|
+
},
|
|
1208
|
+
analogy: {
|
|
1209
|
+
git_history: 'WHAT changed (files, lines, commits)',
|
|
1210
|
+
code_comments: 'HOW it works (implementation details, algorithms)',
|
|
1211
|
+
sqlew_decisions: 'WHY it was changed (reasoning, trade-offs, context)',
|
|
1212
|
+
sqlew_tasks: 'WHAT needs to be done (work items, status, completion)'
|
|
1213
|
+
},
|
|
1214
|
+
examples: [
|
|
1215
|
+
{
|
|
1216
|
+
key: 'api/auth/jwt-choice',
|
|
1217
|
+
value: 'Chose JWT over session-based auth because: (1) Stateless design scales horizontally, (2) Mobile clients can cache tokens, (3) Microservice architecture requires distributed auth. Trade-off: Revocation requires token blacklist, but acceptable for 15-min token lifetime.',
|
|
1218
|
+
explanation: 'Explains WHY JWT was chosen, considers trade-offs, provides architectural context'
|
|
1219
|
+
},
|
|
1220
|
+
{
|
|
1221
|
+
key: 'database/postgresql-choice',
|
|
1222
|
+
value: 'Selected PostgreSQL over MongoDB because: (1) Complex relational queries required for reporting, (2) ACID compliance critical for financial data, (3) Team has strong SQL expertise. Trade-off: Less flexible schema, but data integrity more important than schema flexibility for our use case.',
|
|
1223
|
+
explanation: 'Documents database choice with reasoning, alternatives considered, and trade-offs'
|
|
1224
|
+
},
|
|
1225
|
+
{
|
|
1226
|
+
key: 'security/encryption-at-rest',
|
|
1227
|
+
value: 'Implementing AES-256 encryption for all PII in database because: (1) GDPR compliance requires encryption at rest, (2) Recent security audit identified unencrypted sensitive data, (3) Performance impact <5ms per query acceptable. Alternative considered: Database-level encryption rejected due to backup/restore complexity.',
|
|
1228
|
+
explanation: 'Explains security decision with compliance reasoning and performance considerations'
|
|
1229
|
+
}
|
|
1230
|
+
],
|
|
1231
|
+
cleanup_rule: 'Delete decisions that start with "COMPLETED:", contain task status, test results, or implementation logs. Keep only architectural reasoning and design rationale.'
|
|
1232
|
+
},
|
|
1233
|
+
actions: {
|
|
1234
|
+
set: 'Set/update a decision. Params: key (required), value (required), agent, layer, version, status, tags, scopes',
|
|
1235
|
+
get: 'Get specific decision by key. Params: key (required), include_context (optional, boolean, default: false). When include_context=true, returns decision with attached context (rationale, alternatives, tradeoffs). Backward compatible - omitting flag returns standard decision format.',
|
|
1236
|
+
list: 'List/filter decisions. Params: status, layer, tags, scope, tag_match',
|
|
1237
|
+
search_tags: 'Search decisions by tags. Params: tags (required), match_mode, status, layer',
|
|
1238
|
+
search_layer: 'Search decisions by layer. Params: layer (required), status, include_tags',
|
|
1239
|
+
versions: 'Get version history for a decision. Params: key (required)',
|
|
1240
|
+
quick_set: 'Quick set with smart defaults (FR-002). Params: key (required), value (required), agent, layer, version, status, tags, scopes. Auto-infers layer from key prefix (api/*→presentation, db/*→data, service/*→business, config/*→infrastructure), tags from key hierarchy, scope from parent path. Defaults: status=active, version=1.0.0. All inferred fields can be overridden.',
|
|
1241
|
+
search_advanced: 'Advanced query with complex filtering (FR-004). Params: layers (OR), tags_all (AND), tags_any (OR), exclude_tags, scopes (wildcards), updated_after/before (ISO or relative like "7d"), decided_by, statuses, search_text, sort_by (updated/key/version), sort_order (asc/desc), limit (default:20, max:1000), offset (default:0). Returns decisions with total_count for pagination. All filters use parameterized queries (SQL injection protection).',
|
|
1242
|
+
set_batch: 'Batch set decisions (FR-005). Params: decisions (required, array of SetDecisionParams, max: 50), atomic (optional, boolean, default: true). Returns: {success, inserted, failed, results}. ATOMIC MODE BEHAVIOR (atomic: true): All decisions succeed or all fail as a single transaction. If ANY decision fails, entire batch is rolled back and error is thrown. Use for critical operations requiring consistency. NON-ATOMIC MODE (atomic: false): Each decision is processed independently. If some fail, others still succeed. Returns partial results with per-item success/error status. Use for best-effort batch operations or when individual failures are acceptable. RECOMMENDATION FOR AI AGENTS: Use atomic:false by default to avoid transaction failures from validation errors or malformed data. Only use atomic:true when all-or-nothing guarantee is required. 52% token reduction vs individual calls.',
|
|
1243
|
+
has_updates: 'Check for updates since timestamp (FR-003 Phase A - Lightweight Polling). Params: agent_name (required), since_timestamp (required, ISO 8601 format like "2025-10-14T08:00:00Z"). Returns: {has_updates: boolean, counts: {decisions: N, messages: N, files: N}}. Token cost: ~5-10 tokens per check. Uses COUNT queries on t_decisions, t_agent_messages, t_file_changes with timestamp filtering. Enables efficient polling without full data retrieval.',
|
|
1244
|
+
set_from_template: 'Set decision using template (FR-006). Params: template (required, template name), key (required), value (required), agent, layer (override), version, status (override), tags (override), scopes, plus any template-required fields. Applies template defaults (layer, status, tags) while allowing overrides. Validates required fields if specified by template. Returns: {success, key, key_id, version, template_used, applied_defaults, message}. Built-in templates: breaking_change, security_vulnerability, performance_optimization, deprecation, architecture_decision.',
|
|
1245
|
+
create_template: 'Create new decision template (FR-006). Params: name (required, unique), defaults (required, object with layer/status/tags/priority), required_fields (optional, array of field names), created_by (optional, agent name). Returns: {success, template_id, template_name, message}. Example defaults: {"layer":"business","status":"active","tags":["breaking"]}. Validates layer/status values.',
|
|
1246
|
+
list_templates: 'List all decision templates (FR-006). No params required. Returns: {templates: [{id, name, defaults, required_fields, created_by, created_at}], count}. Shows both built-in and custom templates.',
|
|
1247
|
+
hard_delete: 'Permanently delete a decision (hard delete). Params: key (required). WARNING: IRREVERSIBLE - removes all records including version history, tags, scopes. Use cases: manual cleanup after decision-to-task migration, remove test/debug decisions, purge sensitive data. Unlike soft delete (status=deprecated), this completely removes from database. Idempotent - safe to call even if already deleted. Returns: {success, key, message}.',
|
|
1248
|
+
add_decision_context: 'Add rich context to a decision (v3.2.2). Params: key (required), rationale (required), alternatives_considered (optional, JSON array), tradeoffs (optional, JSON object with pros/cons), decided_by (optional), related_task_id (optional), related_constraint_id (optional). Use to document WHY decisions were made, what alternatives were considered, and trade-offs. Multiple contexts can be attached to the same decision over time. Returns: {success, context_id, decision_key, message}.',
|
|
1249
|
+
list_decision_contexts: 'List decision contexts with filters (v3.2.2). Params: decision_key (optional), related_task_id (optional), related_constraint_id (optional), decided_by (optional), limit (default: 50), offset (default: 0). Returns: {success, contexts: [{id, decision_key, rationale, alternatives_considered, tradeoffs, decided_by, decision_date, related_task_id, related_constraint_id}], count}. JSON fields (alternatives, tradeoffs) are automatically parsed.'
|
|
1250
|
+
},
|
|
1251
|
+
examples: {
|
|
1252
|
+
set: '{ action: "set", key: "auth_method", value: "jwt", tags: ["security"] }',
|
|
1253
|
+
get: '{ action: "get", key: "auth_method" }',
|
|
1254
|
+
list: '{ action: "list", status: "active", layer: "infrastructure" }',
|
|
1255
|
+
search_tags: '{ action: "search_tags", tags: ["security", "api"] }',
|
|
1256
|
+
quick_set: '{ action: "quick_set", key: "api/instruments/oscillator-refactor", value: "Moved oscillator_type to MonophonicSynthConfig" }',
|
|
1257
|
+
search_advanced: '{ action: "search_advanced", layers: ["business", "data"], tags_all: ["breaking", "v0.3.3"], tags_any: ["api", "synthesis"], exclude_tags: ["deprecated"], scopes: ["api/instruments/*"], updated_after: "2025-10-01", statuses: ["active", "draft"], search_text: "oscillator", sort_by: "updated", sort_order: "desc", limit: 20, offset: 0 }',
|
|
1258
|
+
set_batch: '{ action: "set_batch", decisions: [{"key": "feat-1", "value": "...", "layer": "business"}, {"key": "feat-2", "value": "...", "layer": "data"}], atomic: true }',
|
|
1259
|
+
has_updates: '{ action: "has_updates", agent_name: "my-agent", since_timestamp: "2025-10-14T08:00:00Z" }',
|
|
1260
|
+
set_from_template: '{ action: "set_from_template", template: "breaking_change", key: "oscillator-type-moved", value: "oscillator_type moved to MonophonicSynthConfig" }',
|
|
1261
|
+
create_template: '{ action: "create_template", name: "bug_fix", defaults: {"layer":"business","tags":["bug","fix"],"status":"active"}, created_by: "my-agent" }',
|
|
1262
|
+
list_templates: '{ action: "list_templates" }',
|
|
1263
|
+
hard_delete: '{ action: "hard_delete", key: "task_old_authentication_refactor" }'
|
|
1264
|
+
},
|
|
1265
|
+
documentation: {
|
|
1266
|
+
tool_selection: 'docs/TOOL_SELECTION.md - Decision tree, tool comparison, when to use each tool (236 lines, ~12k tokens)',
|
|
1267
|
+
tool_reference: 'docs/TOOL_REFERENCE.md - Parameter requirements, batch operations, templates (471 lines, ~24k tokens)',
|
|
1268
|
+
workflows: 'docs/WORKFLOWS.md - Multi-step workflow examples, multi-agent coordination (602 lines, ~30k tokens)',
|
|
1269
|
+
best_practices: 'docs/BEST_PRACTICES.md - Common errors, best practices, troubleshooting (345 lines, ~17k tokens)',
|
|
1270
|
+
shared_concepts: 'docs/SHARED_CONCEPTS.md - Layer definitions, enum values (status/layer/priority), atomic mode (339 lines, ~17k tokens)'
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1275
|
+
* Example action for decision tool
|
|
1276
|
+
*/
|
|
1277
|
+
export function decisionExample() {
|
|
1278
|
+
return {
|
|
1279
|
+
tool: 'decision',
|
|
1280
|
+
description: 'Comprehensive decision tool examples without needing WebFetch access',
|
|
1281
|
+
scenarios: {
|
|
1282
|
+
basic_usage: {
|
|
1283
|
+
title: 'Basic Decision Management',
|
|
1284
|
+
examples: [
|
|
1285
|
+
{
|
|
1286
|
+
scenario: 'Record API design decision',
|
|
1287
|
+
request: '{ action: "set", key: "api_auth_method", value: "JWT with refresh tokens", layer: "business", tags: ["api", "security", "authentication"] }',
|
|
1288
|
+
explanation: 'Documents the choice of authentication method for the API'
|
|
1289
|
+
},
|
|
1290
|
+
{
|
|
1291
|
+
scenario: 'Retrieve a decision',
|
|
1292
|
+
request: '{ action: "get", key: "api_auth_method" }',
|
|
1293
|
+
response_structure: '{ key, value, layer, status, version, tags, scopes, decided_by, updated_at }'
|
|
1294
|
+
},
|
|
1295
|
+
{
|
|
1296
|
+
scenario: 'List all active decisions',
|
|
1297
|
+
request: '{ action: "list", status: "active", limit: 20 }',
|
|
1298
|
+
explanation: 'Returns active decisions with metadata for browsing'
|
|
1299
|
+
}
|
|
1300
|
+
]
|
|
1301
|
+
},
|
|
1302
|
+
advanced_filtering: {
|
|
1303
|
+
title: 'Advanced Search and Filtering',
|
|
1304
|
+
examples: [
|
|
1305
|
+
{
|
|
1306
|
+
scenario: 'Find all security-related decisions in business layer',
|
|
1307
|
+
request: '{ action: "search_advanced", layers: ["business"], tags_any: ["security", "authentication"], status: ["active"], sort_by: "updated", sort_order: "desc" }',
|
|
1308
|
+
explanation: 'Combines layer filtering, tag matching, and sorting'
|
|
1309
|
+
},
|
|
1310
|
+
{
|
|
1311
|
+
scenario: 'Search within API scope with multiple tags',
|
|
1312
|
+
request: '{ action: "search_advanced", scopes: ["api/*"], tags_all: ["breaking", "v2.0"], updated_after: "2025-01-01" }',
|
|
1313
|
+
explanation: 'Uses scope wildcards and timestamp filtering for recent breaking changes'
|
|
1314
|
+
}
|
|
1315
|
+
]
|
|
1316
|
+
},
|
|
1317
|
+
versioning_workflow: {
|
|
1318
|
+
title: 'Version Management',
|
|
1319
|
+
steps: [
|
|
1320
|
+
{
|
|
1321
|
+
step: 1,
|
|
1322
|
+
action: 'Create initial decision',
|
|
1323
|
+
request: '{ action: "set", key: "database_choice", value: "PostgreSQL", layer: "data", version: "1.0.0", tags: ["database"] }'
|
|
1324
|
+
},
|
|
1325
|
+
{
|
|
1326
|
+
step: 2,
|
|
1327
|
+
action: 'Update decision (creates new version)',
|
|
1328
|
+
request: '{ action: "set", key: "database_choice", value: "PostgreSQL with read replicas", layer: "data", version: "1.1.0", tags: ["database", "scaling"] }'
|
|
1329
|
+
},
|
|
1330
|
+
{
|
|
1331
|
+
step: 3,
|
|
1332
|
+
action: 'View version history',
|
|
1333
|
+
request: '{ action: "versions", key: "database_choice" }',
|
|
1334
|
+
result: 'Returns all versions with timestamps and changes'
|
|
1335
|
+
}
|
|
1336
|
+
]
|
|
1337
|
+
},
|
|
1338
|
+
batch_operations: {
|
|
1339
|
+
title: 'Batch Decision Management',
|
|
1340
|
+
examples: [
|
|
1341
|
+
{
|
|
1342
|
+
scenario: 'Record multiple related decisions atomically',
|
|
1343
|
+
request: '{ action: "set_batch", decisions: [{"key": "cache_layer", "value": "Redis", "layer": "infrastructure"}, {"key": "cache_ttl", "value": "3600", "layer": "infrastructure"}], atomic: true }',
|
|
1344
|
+
explanation: 'All decisions succeed or all fail together (atomic mode)'
|
|
1345
|
+
},
|
|
1346
|
+
{
|
|
1347
|
+
scenario: 'Best-effort batch insert',
|
|
1348
|
+
request: '{ action: "set_batch", decisions: [{...}, {...}, {...}], atomic: false }',
|
|
1349
|
+
explanation: 'Each decision processed independently - partial success allowed'
|
|
1350
|
+
}
|
|
1351
|
+
]
|
|
1352
|
+
},
|
|
1353
|
+
templates: {
|
|
1354
|
+
title: 'Using Decision Templates',
|
|
1355
|
+
examples: [
|
|
1356
|
+
{
|
|
1357
|
+
scenario: 'Use built-in breaking_change template',
|
|
1358
|
+
request: '{ action: "set_from_template", template: "breaking_change", key: "api_remove_legacy_endpoint", value: "Removed /v1/users endpoint - migrate to /v2/users" }',
|
|
1359
|
+
explanation: 'Automatically applies layer=business, tags=["breaking"], status=active'
|
|
1360
|
+
},
|
|
1361
|
+
{
|
|
1362
|
+
scenario: 'Create custom template',
|
|
1363
|
+
request: '{ action: "create_template", name: "feature_flag", defaults: {"layer": "presentation", "tags": ["feature-flag"], "status": "draft"}, created_by: "backend-team" }',
|
|
1364
|
+
explanation: 'Define reusable templates for common decision patterns'
|
|
1365
|
+
}
|
|
1366
|
+
]
|
|
1367
|
+
},
|
|
1368
|
+
quick_set_inference: {
|
|
1369
|
+
title: 'Quick Set with Smart Defaults',
|
|
1370
|
+
examples: [
|
|
1371
|
+
{
|
|
1372
|
+
scenario: 'Auto-infer layer from key prefix',
|
|
1373
|
+
request: '{ action: "quick_set", key: "api/instruments/oscillator-refactor", value: "Moved oscillator_type to MonophonicSynthConfig" }',
|
|
1374
|
+
inferred: 'layer=presentation (from api/*), tags=["instruments", "oscillator-refactor"], scope=api/instruments'
|
|
1375
|
+
},
|
|
1376
|
+
{
|
|
1377
|
+
scenario: 'Database decision with auto-inference',
|
|
1378
|
+
request: '{ action: "quick_set", key: "db/users/add-email-index", value: "Added index on email column" }',
|
|
1379
|
+
inferred: 'layer=data (from db/*), tags=["users", "add-email-index"]'
|
|
1380
|
+
}
|
|
1381
|
+
]
|
|
1382
|
+
}
|
|
1383
|
+
},
|
|
1384
|
+
best_practices: {
|
|
1385
|
+
key_naming: [
|
|
1386
|
+
'Use hierarchical keys: "api/users/authentication"',
|
|
1387
|
+
'Prefix with layer hint: api/* → presentation, db/* → data, service/* → business',
|
|
1388
|
+
'Use descriptive names that explain the decision context'
|
|
1389
|
+
],
|
|
1390
|
+
tagging: [
|
|
1391
|
+
'Tag with relevant categories: security, performance, breaking, etc.',
|
|
1392
|
+
'Include version tags for release-specific decisions',
|
|
1393
|
+
'Use consistent tag naming conventions across team'
|
|
1394
|
+
],
|
|
1395
|
+
versioning: [
|
|
1396
|
+
'Use semantic versioning: 1.0.0, 1.1.0, 2.0.0',
|
|
1397
|
+
'Increment major version for breaking changes',
|
|
1398
|
+
'Document rationale in decision value text'
|
|
1399
|
+
],
|
|
1400
|
+
cleanup: [
|
|
1401
|
+
'Mark deprecated decisions with status="deprecated"',
|
|
1402
|
+
'Use hard_delete only for sensitive data or migration cleanup',
|
|
1403
|
+
'Link related decisions using scopes'
|
|
1404
|
+
]
|
|
1405
|
+
}
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1072
1408
|
//# sourceMappingURL=context.js.map
|