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/tasks.js
CHANGED
|
@@ -317,6 +317,58 @@ export function updateTask(params) {
|
|
|
317
317
|
throw new Error(`Failed to update task: ${message}`);
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
|
+
/**
|
|
321
|
+
* Internal helper: Query task dependencies (used by getTask and getDependencies)
|
|
322
|
+
*/
|
|
323
|
+
function queryTaskDependencies(db, taskId, includeDetails = false) {
|
|
324
|
+
// Build query based on include_details flag
|
|
325
|
+
let selectFields;
|
|
326
|
+
if (includeDetails) {
|
|
327
|
+
// Include description from t_task_details
|
|
328
|
+
selectFields = `
|
|
329
|
+
t.id,
|
|
330
|
+
t.title,
|
|
331
|
+
s.name as status,
|
|
332
|
+
t.priority,
|
|
333
|
+
aa.name as assigned_to,
|
|
334
|
+
t.created_ts,
|
|
335
|
+
t.updated_ts,
|
|
336
|
+
td.description
|
|
337
|
+
`;
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
// Metadata only (token-efficient)
|
|
341
|
+
selectFields = `
|
|
342
|
+
t.id,
|
|
343
|
+
t.title,
|
|
344
|
+
s.name as status,
|
|
345
|
+
t.priority
|
|
346
|
+
`;
|
|
347
|
+
}
|
|
348
|
+
// Get blockers (tasks that block this task)
|
|
349
|
+
const blockersQuery = `
|
|
350
|
+
SELECT ${selectFields}
|
|
351
|
+
FROM t_tasks t
|
|
352
|
+
JOIN t_task_dependencies d ON t.id = d.blocker_task_id
|
|
353
|
+
LEFT JOIN m_task_statuses s ON t.status_id = s.id
|
|
354
|
+
LEFT JOIN m_agents aa ON t.assigned_agent_id = aa.id
|
|
355
|
+
${includeDetails ? 'LEFT JOIN t_task_details td ON t.id = td.task_id' : ''}
|
|
356
|
+
WHERE d.blocked_task_id = ?
|
|
357
|
+
`;
|
|
358
|
+
const blockers = db.prepare(blockersQuery).all(taskId);
|
|
359
|
+
// Get blocking (tasks this task blocks)
|
|
360
|
+
const blockingQuery = `
|
|
361
|
+
SELECT ${selectFields}
|
|
362
|
+
FROM t_tasks t
|
|
363
|
+
JOIN t_task_dependencies d ON t.id = d.blocked_task_id
|
|
364
|
+
LEFT JOIN m_task_statuses s ON t.status_id = s.id
|
|
365
|
+
LEFT JOIN m_agents aa ON t.assigned_agent_id = aa.id
|
|
366
|
+
${includeDetails ? 'LEFT JOIN t_task_details td ON t.id = td.task_id' : ''}
|
|
367
|
+
WHERE d.blocker_task_id = ?
|
|
368
|
+
`;
|
|
369
|
+
const blocking = db.prepare(blockingQuery).all(taskId);
|
|
370
|
+
return { blockers, blocking };
|
|
371
|
+
}
|
|
320
372
|
/**
|
|
321
373
|
* Get full task details
|
|
322
374
|
*/
|
|
@@ -389,7 +441,8 @@ export function getTask(params) {
|
|
|
389
441
|
WHERE tfl.task_id = ?
|
|
390
442
|
`);
|
|
391
443
|
const files = filesStmt.all(params.task_id).map((row) => row.path);
|
|
392
|
-
|
|
444
|
+
// Build result
|
|
445
|
+
const result = {
|
|
393
446
|
found: true,
|
|
394
447
|
task: {
|
|
395
448
|
...task,
|
|
@@ -399,6 +452,15 @@ export function getTask(params) {
|
|
|
399
452
|
linked_files: files
|
|
400
453
|
}
|
|
401
454
|
};
|
|
455
|
+
// Include dependencies if requested (token-efficient, metadata-only)
|
|
456
|
+
if (params.include_dependencies) {
|
|
457
|
+
const deps = queryTaskDependencies(db, params.task_id, false);
|
|
458
|
+
result.task.dependencies = {
|
|
459
|
+
blockers: deps.blockers,
|
|
460
|
+
blocking: deps.blocking
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
return result;
|
|
402
464
|
}
|
|
403
465
|
catch (error) {
|
|
404
466
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -413,36 +475,61 @@ export function listTasks(params = {}) {
|
|
|
413
475
|
try {
|
|
414
476
|
// Run auto-stale detection before listing
|
|
415
477
|
const transitionCount = detectAndTransitionStaleTasks(db);
|
|
416
|
-
// Build query
|
|
417
|
-
let query
|
|
478
|
+
// Build query with optional dependency counts
|
|
479
|
+
let query;
|
|
480
|
+
if (params.include_dependency_counts) {
|
|
481
|
+
// Include dependency counts with LEFT JOINs
|
|
482
|
+
query = `
|
|
483
|
+
SELECT
|
|
484
|
+
vt.*,
|
|
485
|
+
COALESCE(blockers.blocked_by_count, 0) as blocked_by_count,
|
|
486
|
+
COALESCE(blocking.blocking_count, 0) as blocking_count
|
|
487
|
+
FROM v_task_board vt
|
|
488
|
+
LEFT JOIN (
|
|
489
|
+
SELECT blocked_task_id, COUNT(*) as blocked_by_count
|
|
490
|
+
FROM t_task_dependencies
|
|
491
|
+
GROUP BY blocked_task_id
|
|
492
|
+
) blockers ON vt.id = blockers.blocked_task_id
|
|
493
|
+
LEFT JOIN (
|
|
494
|
+
SELECT blocker_task_id, COUNT(*) as blocking_count
|
|
495
|
+
FROM t_task_dependencies
|
|
496
|
+
GROUP BY blocker_task_id
|
|
497
|
+
) blocking ON vt.id = blocking.blocker_task_id
|
|
498
|
+
WHERE 1=1
|
|
499
|
+
`;
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
// Standard query without dependency counts
|
|
503
|
+
query = 'SELECT * FROM v_task_board WHERE 1=1';
|
|
504
|
+
}
|
|
418
505
|
const queryParams = [];
|
|
419
506
|
// Filter by status
|
|
420
507
|
if (params.status) {
|
|
421
508
|
if (!STATUS_TO_ID[params.status]) {
|
|
422
509
|
throw new Error(`Invalid status: ${params.status}. Must be one of: todo, in_progress, waiting_review, blocked, done, archived`);
|
|
423
510
|
}
|
|
424
|
-
query += ' AND status = ?';
|
|
511
|
+
query += params.include_dependency_counts ? ' AND vt.status = ?' : ' AND status = ?';
|
|
425
512
|
queryParams.push(params.status);
|
|
426
513
|
}
|
|
427
514
|
// Filter by assigned agent
|
|
428
515
|
if (params.assigned_agent) {
|
|
429
|
-
query += ' AND assigned_to = ?';
|
|
516
|
+
query += params.include_dependency_counts ? ' AND vt.assigned_to = ?' : ' AND assigned_to = ?';
|
|
430
517
|
queryParams.push(params.assigned_agent);
|
|
431
518
|
}
|
|
432
519
|
// Filter by layer
|
|
433
520
|
if (params.layer) {
|
|
434
|
-
query += ' AND layer = ?';
|
|
521
|
+
query += params.include_dependency_counts ? ' AND vt.layer = ?' : ' AND layer = ?';
|
|
435
522
|
queryParams.push(params.layer);
|
|
436
523
|
}
|
|
437
524
|
// Filter by tags
|
|
438
525
|
if (params.tags && params.tags.length > 0) {
|
|
439
526
|
for (const tag of params.tags) {
|
|
440
|
-
query += ' AND tags LIKE ?';
|
|
527
|
+
query += params.include_dependency_counts ? ' AND vt.tags LIKE ?' : ' AND tags LIKE ?';
|
|
441
528
|
queryParams.push(`%${tag}%`);
|
|
442
529
|
}
|
|
443
530
|
}
|
|
444
531
|
// Order by updated timestamp (most recent first)
|
|
445
|
-
query += ' ORDER BY updated_ts DESC';
|
|
532
|
+
query += params.include_dependency_counts ? ' ORDER BY vt.updated_ts DESC' : ' ORDER BY updated_ts DESC';
|
|
446
533
|
// Pagination
|
|
447
534
|
const limit = params.limit !== undefined ? params.limit : 50;
|
|
448
535
|
const offset = params.offset || 0;
|
|
@@ -670,6 +757,167 @@ export function archiveTask(params) {
|
|
|
670
757
|
throw new Error(`Failed to archive task: ${message}`);
|
|
671
758
|
}
|
|
672
759
|
}
|
|
760
|
+
/**
|
|
761
|
+
* Add dependency (blocking relationship) between tasks
|
|
762
|
+
*/
|
|
763
|
+
export function addDependency(params) {
|
|
764
|
+
const db = getDatabase();
|
|
765
|
+
if (!params.blocker_task_id) {
|
|
766
|
+
throw new Error('Parameter "blocker_task_id" is required');
|
|
767
|
+
}
|
|
768
|
+
if (!params.blocked_task_id) {
|
|
769
|
+
throw new Error('Parameter "blocked_task_id" is required');
|
|
770
|
+
}
|
|
771
|
+
try {
|
|
772
|
+
return transaction(db, () => {
|
|
773
|
+
// Validation 1: No self-dependencies
|
|
774
|
+
if (params.blocker_task_id === params.blocked_task_id) {
|
|
775
|
+
throw new Error('Self-dependency not allowed');
|
|
776
|
+
}
|
|
777
|
+
// Validation 2: Both tasks must exist and check if archived
|
|
778
|
+
const blockerTask = db.prepare('SELECT id, status_id FROM t_tasks WHERE id = ?').get(params.blocker_task_id);
|
|
779
|
+
const blockedTask = db.prepare('SELECT id, status_id FROM t_tasks WHERE id = ?').get(params.blocked_task_id);
|
|
780
|
+
if (!blockerTask) {
|
|
781
|
+
throw new Error(`Blocker task #${params.blocker_task_id} not found`);
|
|
782
|
+
}
|
|
783
|
+
if (!blockedTask) {
|
|
784
|
+
throw new Error(`Blocked task #${params.blocked_task_id} not found`);
|
|
785
|
+
}
|
|
786
|
+
// Validation 3: Neither task is archived
|
|
787
|
+
if (blockerTask.status_id === TASK_STATUS.ARCHIVED) {
|
|
788
|
+
throw new Error(`Cannot add dependency: Task #${params.blocker_task_id} is archived`);
|
|
789
|
+
}
|
|
790
|
+
if (blockedTask.status_id === TASK_STATUS.ARCHIVED) {
|
|
791
|
+
throw new Error(`Cannot add dependency: Task #${params.blocked_task_id} is archived`);
|
|
792
|
+
}
|
|
793
|
+
// Validation 4: No direct circular (reverse relationship)
|
|
794
|
+
const reverseExists = db.prepare(`
|
|
795
|
+
SELECT 1 FROM t_task_dependencies
|
|
796
|
+
WHERE blocker_task_id = ? AND blocked_task_id = ?
|
|
797
|
+
`).get(params.blocked_task_id, params.blocker_task_id);
|
|
798
|
+
if (reverseExists) {
|
|
799
|
+
throw new Error(`Circular dependency detected: Task #${params.blocked_task_id} already blocks Task #${params.blocker_task_id}`);
|
|
800
|
+
}
|
|
801
|
+
// Validation 5: No transitive circular (check if adding this would create a cycle)
|
|
802
|
+
const cycleCheck = db.prepare(`
|
|
803
|
+
WITH RECURSIVE dependency_chain AS (
|
|
804
|
+
-- Start from the task that would be blocked
|
|
805
|
+
SELECT blocked_task_id as task_id, 1 as depth
|
|
806
|
+
FROM t_task_dependencies
|
|
807
|
+
WHERE blocker_task_id = ?
|
|
808
|
+
|
|
809
|
+
UNION ALL
|
|
810
|
+
|
|
811
|
+
-- Follow the chain of dependencies
|
|
812
|
+
SELECT d.blocked_task_id, dc.depth + 1
|
|
813
|
+
FROM t_task_dependencies d
|
|
814
|
+
JOIN dependency_chain dc ON d.blocker_task_id = dc.task_id
|
|
815
|
+
WHERE dc.depth < 100
|
|
816
|
+
)
|
|
817
|
+
SELECT task_id FROM dependency_chain WHERE task_id = ?
|
|
818
|
+
`).get(params.blocked_task_id, params.blocker_task_id);
|
|
819
|
+
if (cycleCheck) {
|
|
820
|
+
// Build cycle path for error message
|
|
821
|
+
const cyclePathResult = db.prepare(`
|
|
822
|
+
WITH RECURSIVE dependency_chain AS (
|
|
823
|
+
SELECT blocked_task_id as task_id, 1 as depth,
|
|
824
|
+
CAST(blocked_task_id AS TEXT) as path
|
|
825
|
+
FROM t_task_dependencies
|
|
826
|
+
WHERE blocker_task_id = ?
|
|
827
|
+
|
|
828
|
+
UNION ALL
|
|
829
|
+
|
|
830
|
+
SELECT d.blocked_task_id, dc.depth + 1,
|
|
831
|
+
dc.path || ' → ' || d.blocked_task_id
|
|
832
|
+
FROM t_task_dependencies d
|
|
833
|
+
JOIN dependency_chain dc ON d.blocker_task_id = dc.task_id
|
|
834
|
+
WHERE dc.depth < 100
|
|
835
|
+
)
|
|
836
|
+
SELECT path FROM dependency_chain WHERE task_id = ? ORDER BY depth DESC LIMIT 1
|
|
837
|
+
`).get(params.blocked_task_id, params.blocker_task_id);
|
|
838
|
+
const cyclePath = cyclePathResult?.path || `#${params.blocked_task_id} → ... → #${params.blocker_task_id}`;
|
|
839
|
+
throw new Error(`Circular dependency detected: Task #${params.blocker_task_id} → #${cyclePath} → #${params.blocker_task_id}`);
|
|
840
|
+
}
|
|
841
|
+
// All validations passed - insert dependency
|
|
842
|
+
const insertStmt = db.prepare(`
|
|
843
|
+
INSERT INTO t_task_dependencies (blocker_task_id, blocked_task_id)
|
|
844
|
+
VALUES (?, ?)
|
|
845
|
+
`);
|
|
846
|
+
insertStmt.run(params.blocker_task_id, params.blocked_task_id);
|
|
847
|
+
return {
|
|
848
|
+
success: true,
|
|
849
|
+
message: `Dependency added: Task #${params.blocker_task_id} blocks Task #${params.blocked_task_id}`
|
|
850
|
+
};
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
catch (error) {
|
|
854
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
855
|
+
// Don't wrap error messages that are already descriptive
|
|
856
|
+
if (message.includes('not found') || message.includes('not allowed') || message.includes('Circular dependency') || message.includes('Cannot add dependency')) {
|
|
857
|
+
throw new Error(message);
|
|
858
|
+
}
|
|
859
|
+
throw new Error(`Failed to add dependency: ${message}`);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Remove dependency between tasks
|
|
864
|
+
*/
|
|
865
|
+
export function removeDependency(params) {
|
|
866
|
+
const db = getDatabase();
|
|
867
|
+
if (!params.blocker_task_id) {
|
|
868
|
+
throw new Error('Parameter "blocker_task_id" is required');
|
|
869
|
+
}
|
|
870
|
+
if (!params.blocked_task_id) {
|
|
871
|
+
throw new Error('Parameter "blocked_task_id" is required');
|
|
872
|
+
}
|
|
873
|
+
try {
|
|
874
|
+
const deleteStmt = db.prepare(`
|
|
875
|
+
DELETE FROM t_task_dependencies
|
|
876
|
+
WHERE blocker_task_id = ? AND blocked_task_id = ?
|
|
877
|
+
`);
|
|
878
|
+
deleteStmt.run(params.blocker_task_id, params.blocked_task_id);
|
|
879
|
+
return {
|
|
880
|
+
success: true,
|
|
881
|
+
message: `Dependency removed: Task #${params.blocker_task_id} no longer blocks Task #${params.blocked_task_id}`
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
catch (error) {
|
|
885
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
886
|
+
throw new Error(`Failed to remove dependency: ${message}`);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Get dependencies for a task (bidirectional: what blocks this task, what this task blocks)
|
|
891
|
+
*/
|
|
892
|
+
export function getDependencies(params) {
|
|
893
|
+
const db = getDatabase();
|
|
894
|
+
if (!params.task_id) {
|
|
895
|
+
throw new Error('Parameter "task_id" is required');
|
|
896
|
+
}
|
|
897
|
+
const includeDetails = params.include_details || false;
|
|
898
|
+
try {
|
|
899
|
+
// Check if task exists
|
|
900
|
+
const taskExists = db.prepare('SELECT id FROM t_tasks WHERE id = ?').get(params.task_id);
|
|
901
|
+
if (!taskExists) {
|
|
902
|
+
throw new Error(`Task with id ${params.task_id} not found`);
|
|
903
|
+
}
|
|
904
|
+
// Use the shared helper function
|
|
905
|
+
const deps = queryTaskDependencies(db, params.task_id, includeDetails);
|
|
906
|
+
return {
|
|
907
|
+
task_id: params.task_id,
|
|
908
|
+
blockers: deps.blockers,
|
|
909
|
+
blocking: deps.blocking
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
catch (error) {
|
|
913
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
914
|
+
// Don't wrap error messages that are already descriptive
|
|
915
|
+
if (message.includes('not found')) {
|
|
916
|
+
throw new Error(message);
|
|
917
|
+
}
|
|
918
|
+
throw new Error(`Failed to get dependencies: ${message}`);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
673
921
|
/**
|
|
674
922
|
* Create multiple tasks atomically
|
|
675
923
|
*/
|
|
@@ -708,6 +956,7 @@ export function taskHelp() {
|
|
|
708
956
|
tool: 'task',
|
|
709
957
|
description: 'Kanban Task Watcher for managing tasks with AI-optimized lifecycle states',
|
|
710
958
|
note: '💡 TIP: Use action: "example" to see comprehensive usage scenarios and real-world examples for all task actions.',
|
|
959
|
+
important: '🚨 AUTOMATIC FILE WATCHING: Linking files to tasks activates automatic file change monitoring and acceptance criteria validation. You can save 300 tokens per file compared to registering watchers manually. See auto_file_tracking section below.',
|
|
711
960
|
actions: {
|
|
712
961
|
create: {
|
|
713
962
|
description: 'Create a new task',
|
|
@@ -776,6 +1025,7 @@ export function taskHelp() {
|
|
|
776
1025
|
required_params: ['task_id', 'link_type', 'target_id'],
|
|
777
1026
|
optional_params: ['link_relation'],
|
|
778
1027
|
link_types: ['decision', 'constraint', 'file'],
|
|
1028
|
+
file_linking_behavior: '⚠️ IMPORTANT: When link_type="file", this action ACTIVATES AUTOMATIC FILE WATCHING. The file watcher monitors linked files for changes and validates acceptance criteria when files are saved. You can save 300 tokens per file compared to registering watchers manually.',
|
|
779
1029
|
example: {
|
|
780
1030
|
action: 'link',
|
|
781
1031
|
task_id: 5,
|
|
@@ -808,6 +1058,69 @@ export function taskHelp() {
|
|
|
808
1058
|
atomic: true
|
|
809
1059
|
}
|
|
810
1060
|
},
|
|
1061
|
+
add_dependency: {
|
|
1062
|
+
description: 'Add blocking relationship between tasks',
|
|
1063
|
+
required_params: ['blocker_task_id', 'blocked_task_id'],
|
|
1064
|
+
validations: [
|
|
1065
|
+
'No self-dependencies',
|
|
1066
|
+
'No circular dependencies (direct or transitive)',
|
|
1067
|
+
'Both tasks must exist',
|
|
1068
|
+
'Neither task can be archived'
|
|
1069
|
+
],
|
|
1070
|
+
example: {
|
|
1071
|
+
action: 'add_dependency',
|
|
1072
|
+
blocker_task_id: 1,
|
|
1073
|
+
blocked_task_id: 2
|
|
1074
|
+
},
|
|
1075
|
+
note: 'Task #1 must be completed before Task #2 can start'
|
|
1076
|
+
},
|
|
1077
|
+
remove_dependency: {
|
|
1078
|
+
description: 'Remove blocking relationship between tasks',
|
|
1079
|
+
required_params: ['blocker_task_id', 'blocked_task_id'],
|
|
1080
|
+
example: {
|
|
1081
|
+
action: 'remove_dependency',
|
|
1082
|
+
blocker_task_id: 1,
|
|
1083
|
+
blocked_task_id: 2
|
|
1084
|
+
},
|
|
1085
|
+
note: 'Silently succeeds even if dependency does not exist'
|
|
1086
|
+
},
|
|
1087
|
+
get_dependencies: {
|
|
1088
|
+
description: 'Query task dependencies (bidirectional)',
|
|
1089
|
+
required_params: ['task_id'],
|
|
1090
|
+
optional_params: ['include_details'],
|
|
1091
|
+
returns: {
|
|
1092
|
+
blockers: 'Array of tasks that block this task',
|
|
1093
|
+
blocking: 'Array of tasks this task blocks'
|
|
1094
|
+
},
|
|
1095
|
+
example: {
|
|
1096
|
+
action: 'get_dependencies',
|
|
1097
|
+
task_id: 2,
|
|
1098
|
+
include_details: true
|
|
1099
|
+
},
|
|
1100
|
+
note: 'Defaults to metadata-only (token-efficient). Set include_details=true for full task details.'
|
|
1101
|
+
},
|
|
1102
|
+
watcher: {
|
|
1103
|
+
description: 'Query file watcher status and monitored files/tasks',
|
|
1104
|
+
required_params: [],
|
|
1105
|
+
optional_params: ['subaction'],
|
|
1106
|
+
subactions: ['status', 'list_files', 'list_tasks', 'help'],
|
|
1107
|
+
default_subaction: 'status',
|
|
1108
|
+
examples: {
|
|
1109
|
+
status: {
|
|
1110
|
+
action: 'watcher',
|
|
1111
|
+
subaction: 'status'
|
|
1112
|
+
},
|
|
1113
|
+
list_files: {
|
|
1114
|
+
action: 'watcher',
|
|
1115
|
+
subaction: 'list_files'
|
|
1116
|
+
},
|
|
1117
|
+
list_tasks: {
|
|
1118
|
+
action: 'watcher',
|
|
1119
|
+
subaction: 'list_tasks'
|
|
1120
|
+
}
|
|
1121
|
+
},
|
|
1122
|
+
note: 'Use to monitor which files/tasks are being watched. File watching activates automatically when you link files to tasks.'
|
|
1123
|
+
},
|
|
811
1124
|
help: {
|
|
812
1125
|
description: 'Return this help documentation',
|
|
813
1126
|
example: { action: 'help' }
|
|
@@ -831,6 +1144,24 @@ export function taskHelp() {
|
|
|
831
1144
|
3: 'high',
|
|
832
1145
|
4: 'critical'
|
|
833
1146
|
},
|
|
1147
|
+
auto_file_tracking: {
|
|
1148
|
+
description: 'Automatic file watching and acceptance criteria validation - save 300 tokens per file vs manual registration',
|
|
1149
|
+
recommendation: '⭐ BEST PRACTICE: Except in exceptional cases, it is recommended to set up file watchers for all tasks that involve code changes. This provides automatic status tracking with zero token overhead.',
|
|
1150
|
+
how_it_works: [
|
|
1151
|
+
'1. Link files to tasks using the link action with link_type="file"',
|
|
1152
|
+
'2. File watcher automatically activates and monitors linked files',
|
|
1153
|
+
'3. When files are saved, watcher detects changes',
|
|
1154
|
+
'4. If task has acceptance_criteria, watcher validates criteria against changes',
|
|
1155
|
+
'5. Results appear in terminal output with pass/fail status'
|
|
1156
|
+
],
|
|
1157
|
+
requirements: [
|
|
1158
|
+
'Task must have files linked via link action',
|
|
1159
|
+
'File paths must be relative to project root (e.g., "src/api/auth.ts")',
|
|
1160
|
+
'Watcher only monitors files explicitly linked to tasks'
|
|
1161
|
+
],
|
|
1162
|
+
token_efficiency: 'File watching happens in background. No MCP tokens consumed until you query status. Manual file tracking would cost ~500-1000 tokens per file check.',
|
|
1163
|
+
documentation_reference: 'docs/AUTO_FILE_TRACKING.md - Complete guide with examples'
|
|
1164
|
+
},
|
|
834
1165
|
documentation: {
|
|
835
1166
|
task_overview: 'docs/TASK_OVERVIEW.md - Lifecycle, status transitions, auto-stale detection (363 lines, ~10k tokens)',
|
|
836
1167
|
task_actions: 'docs/TASK_ACTIONS.md - All action references with examples (854 lines, ~21k tokens)',
|
|
@@ -842,4 +1173,316 @@ export function taskHelp() {
|
|
|
842
1173
|
}
|
|
843
1174
|
};
|
|
844
1175
|
}
|
|
1176
|
+
/**
|
|
1177
|
+
* Query file watcher status and monitored files/tasks
|
|
1178
|
+
*/
|
|
1179
|
+
export function watcherStatus(args) {
|
|
1180
|
+
const subaction = args.subaction || 'status';
|
|
1181
|
+
const watcher = FileWatcher.getInstance();
|
|
1182
|
+
if (subaction === 'help') {
|
|
1183
|
+
return {
|
|
1184
|
+
action: 'watcher',
|
|
1185
|
+
description: 'Query file watcher status and monitored files/tasks',
|
|
1186
|
+
subactions: {
|
|
1187
|
+
status: {
|
|
1188
|
+
description: 'Get overall watcher status (running, files watched, tasks monitored)',
|
|
1189
|
+
example: { action: 'watcher', subaction: 'status' }
|
|
1190
|
+
},
|
|
1191
|
+
list_files: {
|
|
1192
|
+
description: 'List all files being watched with their associated tasks',
|
|
1193
|
+
example: { action: 'watcher', subaction: 'list_files' }
|
|
1194
|
+
},
|
|
1195
|
+
list_tasks: {
|
|
1196
|
+
description: 'List all tasks that have active file watchers',
|
|
1197
|
+
example: { action: 'watcher', subaction: 'list_tasks' }
|
|
1198
|
+
},
|
|
1199
|
+
help: {
|
|
1200
|
+
description: 'Show this help documentation',
|
|
1201
|
+
example: { action: 'watcher', subaction: 'help' }
|
|
1202
|
+
}
|
|
1203
|
+
},
|
|
1204
|
+
note: 'File watching activates automatically when you link files to tasks using the link action with link_type="file". The watcher monitors linked files for changes and validates acceptance criteria.'
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
if (subaction === 'status') {
|
|
1208
|
+
const status = watcher.getStatus();
|
|
1209
|
+
return {
|
|
1210
|
+
success: true,
|
|
1211
|
+
watcher_status: {
|
|
1212
|
+
running: status.running,
|
|
1213
|
+
files_watched: status.filesWatched,
|
|
1214
|
+
tasks_monitored: status.tasksWatched
|
|
1215
|
+
},
|
|
1216
|
+
message: status.running
|
|
1217
|
+
? `File watcher is running. Monitoring ${status.filesWatched} file(s) across ${status.tasksWatched} task(s).`
|
|
1218
|
+
: 'File watcher is not running. Link files to tasks to activate automatic file watching.'
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
if (subaction === 'list_files') {
|
|
1222
|
+
const db = getDatabase();
|
|
1223
|
+
const fileLinks = db.prepare(`
|
|
1224
|
+
SELECT DISTINCT tfl.file_path, t.id, t.title, ts.status_name
|
|
1225
|
+
FROM t_task_file_links tfl
|
|
1226
|
+
JOIN t_tasks t ON tfl.task_id = t.id
|
|
1227
|
+
JOIN m_task_statuses ts ON t.status_id = ts.id
|
|
1228
|
+
WHERE t.status_id != 6 -- Exclude archived tasks
|
|
1229
|
+
ORDER BY tfl.file_path, t.id
|
|
1230
|
+
`).all();
|
|
1231
|
+
// Group by file
|
|
1232
|
+
const fileMap = new Map();
|
|
1233
|
+
for (const link of fileLinks) {
|
|
1234
|
+
if (!fileMap.has(link.file_path)) {
|
|
1235
|
+
fileMap.set(link.file_path, []);
|
|
1236
|
+
}
|
|
1237
|
+
fileMap.get(link.file_path).push({
|
|
1238
|
+
task_id: link.id,
|
|
1239
|
+
task_title: link.title,
|
|
1240
|
+
status: link.status_name
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
const files = Array.from(fileMap.entries()).map(([path, tasks]) => ({
|
|
1244
|
+
file_path: path,
|
|
1245
|
+
tasks: tasks
|
|
1246
|
+
}));
|
|
1247
|
+
return {
|
|
1248
|
+
success: true,
|
|
1249
|
+
files_watched: files.length,
|
|
1250
|
+
files: files,
|
|
1251
|
+
message: files.length > 0
|
|
1252
|
+
? `Watching ${files.length} file(s) linked to tasks.`
|
|
1253
|
+
: 'No files currently linked to tasks. Use link action with link_type="file" to activate file watching.'
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
if (subaction === 'list_tasks') {
|
|
1257
|
+
const db = getDatabase();
|
|
1258
|
+
const taskLinks = db.prepare(`
|
|
1259
|
+
SELECT t.id, t.title, ts.status_name, COUNT(DISTINCT tfl.file_path) as file_count,
|
|
1260
|
+
GROUP_CONCAT(DISTINCT tfl.file_path, ', ') as files
|
|
1261
|
+
FROM t_tasks t
|
|
1262
|
+
JOIN m_task_statuses ts ON t.status_id = ts.id
|
|
1263
|
+
JOIN t_task_file_links tfl ON t.id = tfl.task_id
|
|
1264
|
+
WHERE t.status_id != 6 -- Exclude archived tasks
|
|
1265
|
+
GROUP BY t.id, t.title, ts.status_name
|
|
1266
|
+
ORDER BY t.id
|
|
1267
|
+
`).all();
|
|
1268
|
+
const tasks = taskLinks.map(task => ({
|
|
1269
|
+
task_id: task.id,
|
|
1270
|
+
task_title: task.title,
|
|
1271
|
+
status: task.status_name,
|
|
1272
|
+
files_count: task.file_count,
|
|
1273
|
+
files: task.files.split(', ')
|
|
1274
|
+
}));
|
|
1275
|
+
return {
|
|
1276
|
+
success: true,
|
|
1277
|
+
tasks_monitored: tasks.length,
|
|
1278
|
+
tasks: tasks,
|
|
1279
|
+
message: tasks.length > 0
|
|
1280
|
+
? `Monitoring ${tasks.length} task(s) with linked files.`
|
|
1281
|
+
: 'No tasks currently have linked files. Use link action with link_type="file" to activate file watching.'
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
return {
|
|
1285
|
+
error: `Invalid subaction: ${subaction}. Valid subactions: status, list_files, list_tasks, help`
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* Get comprehensive examples for task tool
|
|
1290
|
+
* @returns Examples documentation object
|
|
1291
|
+
*/
|
|
1292
|
+
export function taskExample() {
|
|
1293
|
+
return {
|
|
1294
|
+
tool: 'task',
|
|
1295
|
+
description: 'Comprehensive task management examples for Kanban-style workflow',
|
|
1296
|
+
scenarios: {
|
|
1297
|
+
basic_task_management: {
|
|
1298
|
+
title: 'Creating and Managing Tasks',
|
|
1299
|
+
examples: [
|
|
1300
|
+
{
|
|
1301
|
+
scenario: 'Create a new task',
|
|
1302
|
+
request: '{ action: "create", title: "Implement user authentication", description: "Add JWT-based auth to API", priority: 3, assigned_agent: "backend-agent", layer: "business", tags: ["authentication", "security"] }',
|
|
1303
|
+
explanation: 'Creates task in todo status with high priority'
|
|
1304
|
+
},
|
|
1305
|
+
{
|
|
1306
|
+
scenario: 'Get task details',
|
|
1307
|
+
request: '{ action: "get", task_id: 5 }',
|
|
1308
|
+
response: 'Full task details including metadata, links, and timestamps'
|
|
1309
|
+
},
|
|
1310
|
+
{
|
|
1311
|
+
scenario: 'List tasks by status',
|
|
1312
|
+
request: '{ action: "list", status: "in_progress", limit: 20 }',
|
|
1313
|
+
explanation: 'View all in-progress tasks'
|
|
1314
|
+
}
|
|
1315
|
+
]
|
|
1316
|
+
},
|
|
1317
|
+
status_workflow: {
|
|
1318
|
+
title: 'Task Lifecycle (Status Transitions)',
|
|
1319
|
+
workflow: [
|
|
1320
|
+
{
|
|
1321
|
+
step: 1,
|
|
1322
|
+
status: 'todo',
|
|
1323
|
+
action: '{ action: "create", title: "...", status: "todo" }',
|
|
1324
|
+
description: 'Task created and waiting to be started'
|
|
1325
|
+
},
|
|
1326
|
+
{
|
|
1327
|
+
step: 2,
|
|
1328
|
+
status: 'in_progress',
|
|
1329
|
+
action: '{ action: "move", task_id: 1, new_status: "in_progress" }',
|
|
1330
|
+
description: 'Agent starts working on task'
|
|
1331
|
+
},
|
|
1332
|
+
{
|
|
1333
|
+
step: 3,
|
|
1334
|
+
status: 'waiting_review',
|
|
1335
|
+
action: '{ action: "move", task_id: 1, new_status: "waiting_review" }',
|
|
1336
|
+
description: 'Work complete, awaiting review/approval'
|
|
1337
|
+
},
|
|
1338
|
+
{
|
|
1339
|
+
step: 4,
|
|
1340
|
+
status: 'done',
|
|
1341
|
+
action: '{ action: "move", task_id: 1, new_status: "done" }',
|
|
1342
|
+
description: 'Task reviewed and completed'
|
|
1343
|
+
},
|
|
1344
|
+
{
|
|
1345
|
+
step: 5,
|
|
1346
|
+
status: 'archived',
|
|
1347
|
+
action: '{ action: "archive", task_id: 1 }',
|
|
1348
|
+
description: 'Task archived for historical record'
|
|
1349
|
+
}
|
|
1350
|
+
],
|
|
1351
|
+
blocked_status: {
|
|
1352
|
+
description: 'Use "blocked" when task cannot proceed due to dependencies',
|
|
1353
|
+
example: '{ action: "move", task_id: 1, new_status: "blocked" }'
|
|
1354
|
+
}
|
|
1355
|
+
},
|
|
1356
|
+
auto_stale_detection: {
|
|
1357
|
+
title: 'Automatic Stale Task Management',
|
|
1358
|
+
behavior: [
|
|
1359
|
+
{
|
|
1360
|
+
rule: 'in_progress > 2 hours → waiting_review',
|
|
1361
|
+
explanation: 'Tasks stuck in progress auto-move to waiting_review',
|
|
1362
|
+
rationale: 'Prevents tasks from being forgotten while in progress'
|
|
1363
|
+
},
|
|
1364
|
+
{
|
|
1365
|
+
rule: 'waiting_review > 24 hours → todo',
|
|
1366
|
+
explanation: 'Unreviewed tasks return to todo queue',
|
|
1367
|
+
rationale: 'Ensures waiting tasks dont accumulate indefinitely'
|
|
1368
|
+
}
|
|
1369
|
+
],
|
|
1370
|
+
configuration: {
|
|
1371
|
+
keys: ['task_stale_hours_in_progress', 'task_stale_hours_waiting_review', 'task_auto_stale_enabled'],
|
|
1372
|
+
note: 'Configure via config table in database'
|
|
1373
|
+
}
|
|
1374
|
+
},
|
|
1375
|
+
task_linking: {
|
|
1376
|
+
title: 'Linking Tasks to Context',
|
|
1377
|
+
examples: [
|
|
1378
|
+
{
|
|
1379
|
+
scenario: 'Link task to decision',
|
|
1380
|
+
request: '{ action: "link", task_id: 5, link_type: "decision", target_id: "api_auth_method", link_relation: "implements" }',
|
|
1381
|
+
explanation: 'Track which tasks implement specific decisions'
|
|
1382
|
+
},
|
|
1383
|
+
{
|
|
1384
|
+
scenario: 'Link task to constraint',
|
|
1385
|
+
request: '{ action: "link", task_id: 5, link_type: "constraint", target_id: 3, link_relation: "addresses" }',
|
|
1386
|
+
explanation: 'Show task addresses a performance/architecture/security constraint'
|
|
1387
|
+
},
|
|
1388
|
+
{
|
|
1389
|
+
scenario: 'Link task to file',
|
|
1390
|
+
request: '{ action: "link", task_id: 5, link_type: "file", target_id: "src/api/auth.ts", link_relation: "modifies" }',
|
|
1391
|
+
explanation: 'Activates automatic file watching for the task - saves 300 tokens per file vs manual registration',
|
|
1392
|
+
behavior: 'File watcher monitors linked files and validates acceptance criteria when files change'
|
|
1393
|
+
}
|
|
1394
|
+
]
|
|
1395
|
+
},
|
|
1396
|
+
batch_operations: {
|
|
1397
|
+
title: 'Batch Task Creation',
|
|
1398
|
+
examples: [
|
|
1399
|
+
{
|
|
1400
|
+
scenario: 'Create multiple related tasks',
|
|
1401
|
+
request: '{ action: "batch_create", tasks: [{"title": "Design API", "priority": 3}, {"title": "Implement API", "priority": 3}, {"title": "Write tests", "priority": 2}], atomic: false }',
|
|
1402
|
+
explanation: 'Create task breakdown - use atomic:false for best-effort'
|
|
1403
|
+
}
|
|
1404
|
+
]
|
|
1405
|
+
},
|
|
1406
|
+
filtering_queries: {
|
|
1407
|
+
title: 'Advanced Task Queries',
|
|
1408
|
+
examples: [
|
|
1409
|
+
{
|
|
1410
|
+
scenario: 'Find high-priority tasks for agent',
|
|
1411
|
+
request: '{ action: "list", assigned_agent: "backend-agent", priority: 3, status: "todo" }',
|
|
1412
|
+
note: 'Priority is numeric: 1=low, 2=medium, 3=high, 4=critical'
|
|
1413
|
+
},
|
|
1414
|
+
{
|
|
1415
|
+
scenario: 'Get all security-related tasks',
|
|
1416
|
+
request: '{ action: "list", tags: ["security"], limit: 50 }',
|
|
1417
|
+
explanation: 'Filter by tags for topic-based views'
|
|
1418
|
+
},
|
|
1419
|
+
{
|
|
1420
|
+
scenario: 'View infrastructure layer tasks',
|
|
1421
|
+
request: '{ action: "list", layer: "infrastructure" }',
|
|
1422
|
+
explanation: 'See all DevOps/config related tasks'
|
|
1423
|
+
}
|
|
1424
|
+
]
|
|
1425
|
+
},
|
|
1426
|
+
file_watcher_status: {
|
|
1427
|
+
title: 'File Watcher Status Queries',
|
|
1428
|
+
examples: [
|
|
1429
|
+
{
|
|
1430
|
+
scenario: 'Check if file watcher is running',
|
|
1431
|
+
request: '{ action: "watcher", subaction: "status" }',
|
|
1432
|
+
explanation: 'Returns running status, files watched count, tasks monitored count',
|
|
1433
|
+
response: '{ running: true, files_watched: 5, tasks_monitored: 3 }'
|
|
1434
|
+
},
|
|
1435
|
+
{
|
|
1436
|
+
scenario: 'List all files being watched',
|
|
1437
|
+
request: '{ action: "watcher", subaction: "list_files" }',
|
|
1438
|
+
explanation: 'Shows file paths and which tasks are watching them',
|
|
1439
|
+
response: '{ files: [{ file_path: "src/api/auth.ts", tasks: [{ task_id: 5, title: "...", status: "in_progress" }] }] }'
|
|
1440
|
+
},
|
|
1441
|
+
{
|
|
1442
|
+
scenario: 'List tasks with active file watchers',
|
|
1443
|
+
request: '{ action: "watcher", subaction: "list_tasks" }',
|
|
1444
|
+
explanation: 'Shows tasks and which files they are watching',
|
|
1445
|
+
response: '{ tasks: [{ task_id: 5, title: "...", files: ["src/api/auth.ts", "src/api/middleware.ts"] }] }'
|
|
1446
|
+
}
|
|
1447
|
+
]
|
|
1448
|
+
}
|
|
1449
|
+
},
|
|
1450
|
+
valid_transitions: {
|
|
1451
|
+
from_todo: ['in_progress', 'blocked', 'done', 'archived'],
|
|
1452
|
+
from_in_progress: ['waiting_review', 'blocked', 'todo'],
|
|
1453
|
+
from_waiting_review: ['done', 'in_progress', 'todo'],
|
|
1454
|
+
from_blocked: ['todo', 'in_progress'],
|
|
1455
|
+
from_done: ['archived', 'todo'],
|
|
1456
|
+
from_archived: []
|
|
1457
|
+
},
|
|
1458
|
+
best_practices: {
|
|
1459
|
+
task_creation: [
|
|
1460
|
+
'Use descriptive titles (200 char max)',
|
|
1461
|
+
'Set appropriate priority: 1=low, 2=medium (default), 3=high, 4=critical',
|
|
1462
|
+
'Assign to layer where work will be done',
|
|
1463
|
+
'Tag comprehensively for easy filtering',
|
|
1464
|
+
'Include acceptance_criteria for complex tasks'
|
|
1465
|
+
],
|
|
1466
|
+
status_management: [
|
|
1467
|
+
'Move to in_progress when starting work',
|
|
1468
|
+
'Use waiting_review for completed but unverified work',
|
|
1469
|
+
'Set to blocked with notes explaining dependency',
|
|
1470
|
+
'Archive done tasks periodically for cleaner views'
|
|
1471
|
+
],
|
|
1472
|
+
linking: [
|
|
1473
|
+
'⭐ RECOMMENDED: Set up file watchers for all tasks involving code changes (except exceptional cases)',
|
|
1474
|
+
'Link tasks to decisions they implement',
|
|
1475
|
+
'Link to constraints they address',
|
|
1476
|
+
'Link files to activate automatic file watching (save 300 tokens per file vs manual registration)',
|
|
1477
|
+
'Use descriptive link_relation values'
|
|
1478
|
+
],
|
|
1479
|
+
coordination: [
|
|
1480
|
+
'Use assigned_agent for clear ownership',
|
|
1481
|
+
'Filter by status for Kanban board views',
|
|
1482
|
+
'Monitor auto-stale transitions for stuck work',
|
|
1483
|
+
'Use tags for cross-cutting concerns (security, performance, etc.)'
|
|
1484
|
+
]
|
|
1485
|
+
}
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
845
1488
|
//# sourceMappingURL=tasks.js.map
|