@stackmemoryai/stackmemory 0.2.7 → 0.2.8
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/README.md +25 -8
- package/dist/scripts/cancel-duplicate-tasks.js +2 -1
- package/dist/scripts/cancel-duplicate-tasks.js.map +1 -1
- package/dist/scripts/list-linear-tasks.js +3 -4
- package/dist/scripts/list-linear-tasks.js.map +1 -1
- package/dist/scripts/merge-linear-duplicates-safe.js +4 -2
- package/dist/scripts/merge-linear-duplicates-safe.js.map +1 -1
- package/dist/scripts/show-linear-summary.js +4 -1
- package/dist/scripts/show-linear-summary.js.map +1 -1
- package/dist/scripts/status.js +6 -2
- package/dist/scripts/status.js.map +1 -1
- package/dist/src/cli/auto-detect.js.map +1 -1
- package/dist/src/cli/claude-sm.js.map +1 -1
- package/dist/src/cli/commands/config.d.ts +6 -0
- package/dist/src/cli/commands/config.d.ts.map +1 -0
- package/dist/src/cli/commands/config.js +224 -0
- package/dist/src/cli/commands/config.js.map +1 -0
- package/dist/src/cli/commands/linear.d.ts.map +1 -1
- package/dist/src/cli/commands/linear.js +123 -47
- package/dist/src/cli/commands/linear.js.map +1 -1
- package/dist/src/cli/index.d.ts.map +1 -1
- package/dist/src/cli/index.js +48 -1
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/core/config/config-manager.d.ts +95 -0
- package/dist/src/core/config/config-manager.d.ts.map +1 -0
- package/dist/src/core/config/config-manager.js +359 -0
- package/dist/src/core/config/config-manager.js.map +1 -0
- package/dist/src/core/config/types.d.ts +72 -0
- package/dist/src/core/config/types.d.ts.map +1 -0
- package/dist/src/core/config/types.js +127 -0
- package/dist/src/core/config/types.js.map +1 -0
- package/dist/src/core/context/compaction-handler.d.ts +119 -0
- package/dist/src/core/context/compaction-handler.d.ts.map +1 -0
- package/dist/src/core/context/compaction-handler.js +306 -0
- package/dist/src/core/context/compaction-handler.js.map +1 -0
- package/dist/src/core/context/model-aware-compaction.d.ts +101 -0
- package/dist/src/core/context/model-aware-compaction.d.ts.map +1 -0
- package/dist/src/core/context/model-aware-compaction.js +616 -0
- package/dist/src/core/context/model-aware-compaction.js.map +1 -0
- package/dist/src/core/query/query-parser.d.ts +104 -0
- package/dist/src/core/query/query-parser.d.ts.map +1 -0
- package/dist/src/core/query/query-parser.js +347 -0
- package/dist/src/core/query/query-parser.js.map +1 -0
- package/dist/src/core/retrieval/index.d.ts +8 -0
- package/dist/src/core/retrieval/index.d.ts.map +1 -0
- package/dist/src/core/retrieval/index.js +8 -0
- package/dist/src/core/retrieval/index.js.map +1 -0
- package/dist/src/core/retrieval/llm-context-retrieval.d.ts +71 -0
- package/dist/src/core/retrieval/llm-context-retrieval.d.ts.map +1 -0
- package/dist/src/core/retrieval/llm-context-retrieval.js +545 -0
- package/dist/src/core/retrieval/llm-context-retrieval.js.map +1 -0
- package/dist/src/core/retrieval/summary-generator.d.ts +63 -0
- package/dist/src/core/retrieval/summary-generator.d.ts.map +1 -0
- package/dist/src/core/retrieval/summary-generator.js +622 -0
- package/dist/src/core/retrieval/summary-generator.js.map +1 -0
- package/dist/src/core/retrieval/types.d.ts +257 -0
- package/dist/src/core/retrieval/types.d.ts.map +1 -0
- package/dist/src/core/retrieval/types.js +18 -0
- package/dist/src/core/retrieval/types.js.map +1 -0
- package/dist/src/core/trace/trace-detector.d.ts +108 -0
- package/dist/src/core/trace/trace-detector.d.ts.map +1 -0
- package/dist/src/core/trace/trace-detector.demo.d.ts +5 -0
- package/dist/src/core/trace/trace-detector.demo.d.ts.map +1 -0
- package/dist/src/core/trace/trace-detector.demo.js +145 -0
- package/dist/src/core/trace/trace-detector.demo.js.map +1 -0
- package/dist/src/core/trace/trace-detector.js +425 -0
- package/dist/src/core/trace/trace-detector.js.map +1 -0
- package/dist/src/core/trace/trace-store.d.ts +60 -0
- package/dist/src/core/trace/trace-store.d.ts.map +1 -0
- package/dist/src/core/trace/trace-store.js +323 -0
- package/dist/src/core/trace/trace-store.js.map +1 -0
- package/dist/src/core/trace/types.d.ts +81 -0
- package/dist/src/core/trace/types.d.ts.map +1 -0
- package/dist/src/core/trace/types.js +70 -0
- package/dist/src/core/trace/types.js.map +1 -0
- package/dist/src/integrations/linear/sync-manager.d.ts +76 -0
- package/dist/src/integrations/linear/sync-manager.d.ts.map +1 -0
- package/dist/src/integrations/linear/sync-manager.js +223 -0
- package/dist/src/integrations/linear/sync-manager.js.map +1 -0
- package/dist/src/integrations/mcp/server.d.ts +8 -0
- package/dist/src/integrations/mcp/server.d.ts.map +1 -1
- package/dist/src/integrations/mcp/server.js +368 -16
- package/dist/src/integrations/mcp/server.js.map +1 -1
- package/dist/src/integrations/mcp/trace-test.d.ts +5 -0
- package/dist/src/integrations/mcp/trace-test.d.ts.map +1 -0
- package/dist/src/integrations/mcp/trace-test.js +54 -0
- package/dist/src/integrations/mcp/trace-test.js.map +1 -0
- package/dist/src/services/config-service.d.ts +1 -1
- package/dist/src/services/config-service.d.ts.map +1 -1
- package/dist/src/services/config-service.js.map +1 -1
- package/dist/src/types/task.d.ts +11 -1
- package/dist/src/types/task.d.ts.map +1 -1
- package/dist/src/utils/logger.d.ts +4 -4
- package/dist/src/utils/logger.d.ts.map +1 -1
- package/dist/src/utils/logger.js.map +1 -1
- package/package.json +9 -8
- package/dist/attention-scoring/src/attention-tracker.d.ts +0 -79
- package/dist/attention-scoring/src/attention-tracker.d.ts.map +0 -1
- package/dist/attention-scoring/src/attention-tracker.js +0 -488
- package/dist/attention-scoring/src/attention-tracker.js.map +0 -1
- package/dist/attention-scoring/src/mcp-integration.d.ts +0 -56
- package/dist/attention-scoring/src/mcp-integration.d.ts.map +0 -1
- package/dist/attention-scoring/src/mcp-integration.js +0 -369
- package/dist/attention-scoring/src/mcp-integration.js.map +0 -1
- package/dist/index.js +0 -382
- package/dist/p2p-sync/src/p2p-sync.d.ts +0 -81
- package/dist/p2p-sync/src/p2p-sync.d.ts.map +0 -1
- package/dist/p2p-sync/src/p2p-sync.js +0 -457
- package/dist/p2p-sync/src/p2p-sync.js.map +0 -1
- package/dist/p2p-sync/src/team-context-sync.d.ts +0 -99
- package/dist/p2p-sync/src/team-context-sync.d.ts.map +0 -1
- package/dist/p2p-sync/src/team-context-sync.js +0 -491
- package/dist/p2p-sync/src/team-context-sync.js.map +0 -1
- package/dist/scripts/merge-linear-duplicates.d.ts +0 -7
- package/dist/scripts/merge-linear-duplicates.d.ts.map +0 -1
- package/dist/scripts/merge-linear-duplicates.js +0 -126
- package/dist/scripts/merge-linear-duplicates.js.map +0 -1
- package/dist/src/analytics/api/analytics-api.d.ts +0 -24
- package/dist/src/analytics/api/analytics-api.d.ts.map +0 -1
- package/dist/src/analytics/api/analytics-api.js +0 -279
- package/dist/src/analytics/api/analytics-api.js.map +0 -1
- package/dist/src/analytics/core/analytics-service.d.ts +0 -23
- package/dist/src/analytics/core/analytics-service.d.ts.map +0 -1
- package/dist/src/analytics/core/analytics-service.js +0 -160
- package/dist/src/analytics/core/analytics-service.js.map +0 -1
- package/dist/src/analytics/index.d.ts +0 -12
- package/dist/src/analytics/index.d.ts.map +0 -1
- package/dist/src/analytics/index.js +0 -11
- package/dist/src/analytics/index.js.map +0 -1
- package/dist/src/analytics/queries/metrics-queries.d.ts +0 -11
- package/dist/src/analytics/queries/metrics-queries.d.ts.map +0 -1
- package/dist/src/analytics/queries/metrics-queries.js +0 -179
- package/dist/src/analytics/queries/metrics-queries.js.map +0 -1
- package/dist/src/analytics/types/metrics.d.ts +0 -60
- package/dist/src/analytics/types/metrics.d.ts.map +0 -1
- package/dist/src/analytics/types/metrics.js +0 -2
- package/dist/src/analytics/types/metrics.js.map +0 -1
- package/dist/src/beads/beads-task-store.d.ts +0 -117
- package/dist/src/beads/beads-task-store.d.ts.map +0 -1
- package/dist/src/beads/beads-task-store.js +0 -318
- package/dist/src/beads/beads-task-store.js.map +0 -1
- package/dist/src/beads/task-aware-context.d.ts +0 -103
- package/dist/src/beads/task-aware-context.d.ts.map +0 -1
- package/dist/src/beads/task-aware-context.js +0 -395
- package/dist/src/beads/task-aware-context.js.map +0 -1
- package/dist/src/beads-task-store.d.ts +0 -117
- package/dist/src/beads-task-store.d.ts.map +0 -1
- package/dist/src/beads-task-store.js +0 -318
- package/dist/src/beads-task-store.js.map +0 -1
- package/dist/src/cli/__tests__/index.test.d.ts +0 -5
- package/dist/src/cli/__tests__/index.test.d.ts.map +0 -1
- package/dist/src/cli/__tests__/index.test.js +0 -726
- package/dist/src/cli/__tests__/index.test.js.map +0 -1
- package/dist/src/cli/analytics-viewer.d.ts +0 -3
- package/dist/src/cli/analytics-viewer.d.ts.map +0 -1
- package/dist/src/cli/analytics-viewer.js +0 -89
- package/dist/src/cli/analytics-viewer.js.map +0 -1
- package/dist/src/cli/cli.d.ts +0 -7
- package/dist/src/cli/cli.d.ts.map +0 -1
- package/dist/src/cli/cli.js +0 -704
- package/dist/src/cli/cli.js.map +0 -1
- package/dist/src/cli/project-commands.d.ts +0 -8
- package/dist/src/cli/project-commands.d.ts.map +0 -1
- package/dist/src/cli/project-commands.js +0 -212
- package/dist/src/cli/project-commands.js.map +0 -1
- package/dist/src/cli.d.ts +0 -7
- package/dist/src/cli.d.ts.map +0 -1
- package/dist/src/cli.js +0 -73
- package/dist/src/cli.js.map +0 -1
- package/dist/src/core/context/__tests__/frame-manager.test.d.ts +0 -5
- package/dist/src/core/context/__tests__/frame-manager.test.d.ts.map +0 -1
- package/dist/src/core/context/__tests__/frame-manager.test.js +0 -892
- package/dist/src/core/context/__tests__/frame-manager.test.js.map +0 -1
- package/dist/src/core/error-handler.d.ts +0 -46
- package/dist/src/core/error-handler.d.ts.map +0 -1
- package/dist/src/core/error-handler.js +0 -212
- package/dist/src/core/error-handler.js.map +0 -1
- package/dist/src/core/errors/__tests__/error-handling.test.d.ts +0 -5
- package/dist/src/core/errors/__tests__/error-handling.test.d.ts.map +0 -1
- package/dist/src/core/errors/__tests__/error-handling.test.js +0 -239
- package/dist/src/core/errors/__tests__/error-handling.test.js.map +0 -1
- package/dist/src/core/frame-manager.d.ts +0 -106
- package/dist/src/core/frame-manager.d.ts.map +0 -1
- package/dist/src/core/frame-manager.js +0 -387
- package/dist/src/core/frame-manager.js.map +0 -1
- package/dist/src/core/logger.d.ts +0 -24
- package/dist/src/core/logger.d.ts.map +0 -1
- package/dist/src/core/logger.js +0 -121
- package/dist/src/core/logger.js.map +0 -1
- package/dist/src/core/logger.test.d.ts +0 -2
- package/dist/src/core/logger.test.d.ts.map +0 -1
- package/dist/src/core/logger.test.js +0 -31
- package/dist/src/core/logger.test.js.map +0 -1
- package/dist/src/core/progress-tracker.d.ts +0 -95
- package/dist/src/core/progress-tracker.d.ts.map +0 -1
- package/dist/src/core/progress-tracker.js +0 -178
- package/dist/src/core/progress-tracker.js.map +0 -1
- package/dist/src/core/project-manager.d.ts +0 -130
- package/dist/src/core/project-manager.d.ts.map +0 -1
- package/dist/src/core/project-manager.js +0 -582
- package/dist/src/core/project-manager.js.map +0 -1
- package/dist/src/core/update-checker.d.ts +0 -38
- package/dist/src/core/update-checker.d.ts.map +0 -1
- package/dist/src/core/update-checker.js +0 -156
- package/dist/src/core/update-checker.js.map +0 -1
- package/dist/src/error-handler.d.ts +0 -42
- package/dist/src/error-handler.d.ts.map +0 -1
- package/dist/src/error-handler.js +0 -155
- package/dist/src/error-handler.js.map +0 -1
- package/dist/src/features/tasks/__tests__/pebbles-task-store.test.d.ts +0 -5
- package/dist/src/features/tasks/__tests__/pebbles-task-store.test.d.ts.map +0 -1
- package/dist/src/features/tasks/__tests__/pebbles-task-store.test.js +0 -712
- package/dist/src/features/tasks/__tests__/pebbles-task-store.test.js.map +0 -1
- package/dist/src/frame-manager.d.ts +0 -106
- package/dist/src/frame-manager.d.ts.map +0 -1
- package/dist/src/frame-manager.js +0 -361
- package/dist/src/frame-manager.js.map +0 -1
- package/dist/src/integrations/browser-mcp.d.ts +0 -94
- package/dist/src/integrations/browser-mcp.d.ts.map +0 -1
- package/dist/src/integrations/browser-mcp.js +0 -431
- package/dist/src/integrations/browser-mcp.js.map +0 -1
- package/dist/src/integrations/linear/__tests__/auth.test.d.ts +0 -5
- package/dist/src/integrations/linear/__tests__/auth.test.d.ts.map +0 -1
- package/dist/src/integrations/linear/__tests__/auth.test.js +0 -517
- package/dist/src/integrations/linear/__tests__/auth.test.js.map +0 -1
- package/dist/src/integrations/linear/__tests__/sync-service.test.d.ts +0 -5
- package/dist/src/integrations/linear/__tests__/sync-service.test.d.ts.map +0 -1
- package/dist/src/integrations/linear/__tests__/sync-service.test.js +0 -700
- package/dist/src/integrations/linear/__tests__/sync-service.test.js.map +0 -1
- package/dist/src/integrations/linear-auth.d.ts +0 -99
- package/dist/src/integrations/linear-auth.d.ts.map +0 -1
- package/dist/src/integrations/linear-auth.js +0 -319
- package/dist/src/integrations/linear-auth.js.map +0 -1
- package/dist/src/integrations/linear-auto-sync.d.ts +0 -77
- package/dist/src/integrations/linear-auto-sync.d.ts.map +0 -1
- package/dist/src/integrations/linear-auto-sync.js +0 -268
- package/dist/src/integrations/linear-auto-sync.js.map +0 -1
- package/dist/src/integrations/linear-client.d.ts +0 -86
- package/dist/src/integrations/linear-client.d.ts.map +0 -1
- package/dist/src/integrations/linear-client.js +0 -277
- package/dist/src/integrations/linear-client.js.map +0 -1
- package/dist/src/integrations/linear-config.d.ts +0 -51
- package/dist/src/integrations/linear-config.d.ts.map +0 -1
- package/dist/src/integrations/linear-config.js +0 -103
- package/dist/src/integrations/linear-config.js.map +0 -1
- package/dist/src/integrations/linear-sync.d.ts +0 -97
- package/dist/src/integrations/linear-sync.d.ts.map +0 -1
- package/dist/src/integrations/linear-sync.js +0 -391
- package/dist/src/integrations/linear-sync.js.map +0 -1
- package/dist/src/integrations/mcp/__tests__/server.test.d.ts +0 -5
- package/dist/src/integrations/mcp/__tests__/server.test.d.ts.map +0 -1
- package/dist/src/integrations/mcp/__tests__/server.test.js +0 -790
- package/dist/src/integrations/mcp/__tests__/server.test.js.map +0 -1
- package/dist/src/logger.d.ts +0 -24
- package/dist/src/logger.d.ts.map +0 -1
- package/dist/src/logger.js +0 -120
- package/dist/src/logger.js.map +0 -1
- package/dist/src/mcp/mcp-server.d.ts +0 -40
- package/dist/src/mcp/mcp-server.d.ts.map +0 -1
- package/dist/src/mcp/mcp-server.js +0 -828
- package/dist/src/mcp/mcp-server.js.map +0 -1
- package/dist/src/mcp-server.d.ts +0 -32
- package/dist/src/mcp-server.d.ts.map +0 -1
- package/dist/src/mcp-server.js +0 -441
- package/dist/src/mcp-server.js.map +0 -1
- package/dist/src/pebbles/pebbles-task-store.d.ts +0 -117
- package/dist/src/pebbles/pebbles-task-store.d.ts.map +0 -1
- package/dist/src/pebbles/pebbles-task-store.js +0 -335
- package/dist/src/pebbles/pebbles-task-store.js.map +0 -1
- package/dist/src/pebbles/task-aware-context.d.ts +0 -103
- package/dist/src/pebbles/task-aware-context.d.ts.map +0 -1
- package/dist/src/pebbles/task-aware-context.js +0 -412
- package/dist/src/pebbles/task-aware-context.js.map +0 -1
- package/dist/src/railway/index.d.ts +0 -7
- package/dist/src/railway/index.d.ts.map +0 -1
- package/dist/src/railway/index.js +0 -401
- package/dist/src/railway/index.js.map +0 -1
- package/dist/src/runway/auth/auth-middleware.d.ts +0 -66
- package/dist/src/runway/auth/auth-middleware.d.ts.map +0 -1
- package/dist/src/runway/auth/auth-middleware.js +0 -337
- package/dist/src/runway/auth/auth-middleware.js.map +0 -1
- package/dist/src/runway/server/runway-mcp-server.d.ts +0 -46
- package/dist/src/runway/server/runway-mcp-server.d.ts.map +0 -1
- package/dist/src/runway/server/runway-mcp-server.js +0 -601
- package/dist/src/runway/server/runway-mcp-server.js.map +0 -1
- package/dist/src/runway.bak/auth/auth-middleware.d.ts +0 -66
- package/dist/src/runway.bak/auth/auth-middleware.d.ts.map +0 -1
- package/dist/src/runway.bak/auth/auth-middleware.js +0 -337
- package/dist/src/runway.bak/auth/auth-middleware.js.map +0 -1
- package/dist/src/runway.bak/server/runway-mcp-server.d.ts +0 -46
- package/dist/src/runway.bak/server/runway-mcp-server.d.ts.map +0 -1
- package/dist/src/runway.bak/server/runway-mcp-server.js +0 -601
- package/dist/src/runway.bak/server/runway-mcp-server.js.map +0 -1
- package/dist/src/task-aware-context.d.ts +0 -103
- package/dist/src/task-aware-context.d.ts.map +0 -1
- package/dist/src/task-aware-context.js +0 -395
- package/dist/src/task-aware-context.js.map +0 -1
|
@@ -1,712 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for PebblesTaskStore - Git-native JSONL storage with SQLite cache
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
5
|
-
import Database from 'better-sqlite3';
|
|
6
|
-
import { PebblesTaskStore } from '../pebbles-task-store.js';
|
|
7
|
-
import { DatabaseError, TaskError } from '../../../core/errors/index.js';
|
|
8
|
-
import { join } from 'path';
|
|
9
|
-
import { mkdtempSync, rmSync, existsSync, readFileSync, writeFileSync } from 'fs';
|
|
10
|
-
import { tmpdir } from 'os';
|
|
11
|
-
describe('PebblesTaskStore', () => {
|
|
12
|
-
let db;
|
|
13
|
-
let taskStore;
|
|
14
|
-
let tempDir;
|
|
15
|
-
let projectRoot;
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
// Create a temporary directory for test
|
|
18
|
-
tempDir = mkdtempSync(join(tmpdir(), 'stackmemory-task-test-'));
|
|
19
|
-
projectRoot = tempDir;
|
|
20
|
-
// Initialize database
|
|
21
|
-
const dbPath = join(tempDir, '.stackmemory', 'context.db');
|
|
22
|
-
db = new Database(dbPath);
|
|
23
|
-
// Create task store
|
|
24
|
-
taskStore = new PebblesTaskStore(projectRoot, db);
|
|
25
|
-
});
|
|
26
|
-
afterEach(() => {
|
|
27
|
-
// Clean up
|
|
28
|
-
if (db) {
|
|
29
|
-
db.close();
|
|
30
|
-
}
|
|
31
|
-
if (tempDir) {
|
|
32
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
describe('Initialization', () => {
|
|
36
|
-
it('should create .stackmemory directory if it does not exist', () => {
|
|
37
|
-
const stackmemoryDir = join(projectRoot, '.stackmemory');
|
|
38
|
-
expect(existsSync(stackmemoryDir)).toBe(true);
|
|
39
|
-
});
|
|
40
|
-
it('should initialize cache database schema correctly', () => {
|
|
41
|
-
// Check if task_cache table exists
|
|
42
|
-
const tables = db.prepare(`
|
|
43
|
-
SELECT name FROM sqlite_master
|
|
44
|
-
WHERE type='table' AND name='task_cache'
|
|
45
|
-
`).all();
|
|
46
|
-
expect(tables).toHaveLength(1);
|
|
47
|
-
});
|
|
48
|
-
it('should create required indexes', () => {
|
|
49
|
-
const indexes = db.prepare(`
|
|
50
|
-
SELECT name FROM sqlite_master
|
|
51
|
-
WHERE type='index' AND name LIKE 'idx_task_%'
|
|
52
|
-
`).all();
|
|
53
|
-
const expectedIndexes = [
|
|
54
|
-
'idx_task_status',
|
|
55
|
-
'idx_task_priority',
|
|
56
|
-
'idx_task_frame',
|
|
57
|
-
'idx_task_timestamp',
|
|
58
|
-
'idx_task_parent'
|
|
59
|
-
];
|
|
60
|
-
expectedIndexes.forEach(expectedIndex => {
|
|
61
|
-
expect(indexes.some((idx) => idx.name === expectedIndex)).toBe(true);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
it('should load existing tasks from JSONL file', () => {
|
|
65
|
-
// Create pre-existing JSONL file
|
|
66
|
-
const tasksFile = join(projectRoot, '.stackmemory', 'tasks.jsonl');
|
|
67
|
-
const existingTask = {
|
|
68
|
-
id: 'test-123',
|
|
69
|
-
type: 'task_create',
|
|
70
|
-
timestamp: Math.floor(Date.now() / 1000),
|
|
71
|
-
frame_id: 'frame-123',
|
|
72
|
-
title: 'Existing Task',
|
|
73
|
-
description: 'Pre-existing task',
|
|
74
|
-
status: 'pending',
|
|
75
|
-
priority: 'medium',
|
|
76
|
-
created_at: Math.floor(Date.now() / 1000),
|
|
77
|
-
depends_on: [],
|
|
78
|
-
blocks: [],
|
|
79
|
-
tags: ['test'],
|
|
80
|
-
context_score: 0.5
|
|
81
|
-
};
|
|
82
|
-
writeFileSync(tasksFile, JSON.stringify(existingTask) + '\n');
|
|
83
|
-
// Create new task store instance
|
|
84
|
-
const newTaskStore = new PebblesTaskStore(projectRoot, db);
|
|
85
|
-
// Should load the existing task
|
|
86
|
-
const loadedTask = newTaskStore.getTask('test-123');
|
|
87
|
-
expect(loadedTask).toBeDefined();
|
|
88
|
-
expect(loadedTask.title).toBe('Existing Task');
|
|
89
|
-
});
|
|
90
|
-
it('should handle corrupted JSONL lines gracefully', () => {
|
|
91
|
-
const tasksFile = join(projectRoot, '.stackmemory', 'tasks.jsonl');
|
|
92
|
-
const content = `
|
|
93
|
-
{"valid": "task", "id": "valid-1", "type": "task_create", "timestamp": 1, "frame_id": "f1", "title": "Valid", "status": "pending", "priority": "medium", "created_at": 1, "depends_on": [], "blocks": [], "tags": []}
|
|
94
|
-
{invalid json line
|
|
95
|
-
{"id": "valid-2", "type": "task_create", "timestamp": 2, "frame_id": "f2", "title": "Valid 2", "status": "pending", "priority": "high", "created_at": 2, "depends_on": [], "blocks": [], "tags": []}
|
|
96
|
-
`.trim();
|
|
97
|
-
writeFileSync(tasksFile, content);
|
|
98
|
-
// Should load only valid tasks and skip corrupted lines
|
|
99
|
-
const newTaskStore = new PebblesTaskStore(projectRoot, db);
|
|
100
|
-
expect(newTaskStore.getTask('valid-1')).toBeDefined();
|
|
101
|
-
expect(newTaskStore.getTask('valid-2')).toBeDefined();
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
describe('Task Creation', () => {
|
|
105
|
-
it('should create a new task successfully', () => {
|
|
106
|
-
const taskId = taskStore.createTask({
|
|
107
|
-
title: 'Test Task',
|
|
108
|
-
description: 'A test task',
|
|
109
|
-
priority: 'high',
|
|
110
|
-
frameId: 'frame-123',
|
|
111
|
-
tags: ['test', 'urgent'],
|
|
112
|
-
estimatedEffort: 60,
|
|
113
|
-
assignee: 'developer'
|
|
114
|
-
});
|
|
115
|
-
expect(taskId).toBeDefined();
|
|
116
|
-
expect(taskId).toMatch(/^tsk-[a-f0-9]{8}$/);
|
|
117
|
-
const task = taskStore.getTask(taskId);
|
|
118
|
-
expect(task).toBeDefined();
|
|
119
|
-
expect(task.title).toBe('Test Task');
|
|
120
|
-
expect(task.description).toBe('A test task');
|
|
121
|
-
expect(task.priority).toBe('high');
|
|
122
|
-
expect(task.status).toBe('pending');
|
|
123
|
-
expect(task.frame_id).toBe('frame-123');
|
|
124
|
-
expect(task.tags).toEqual(['test', 'urgent']);
|
|
125
|
-
expect(task.estimated_effort).toBe(60);
|
|
126
|
-
expect(task.assignee).toBe('developer');
|
|
127
|
-
expect(task.depends_on).toEqual([]);
|
|
128
|
-
expect(task.blocks).toEqual([]);
|
|
129
|
-
});
|
|
130
|
-
it('should create task with minimal required fields', () => {
|
|
131
|
-
const taskId = taskStore.createTask({
|
|
132
|
-
title: 'Minimal Task',
|
|
133
|
-
frameId: 'frame-456'
|
|
134
|
-
});
|
|
135
|
-
const task = taskStore.getTask(taskId);
|
|
136
|
-
expect(task).toBeDefined();
|
|
137
|
-
expect(task.title).toBe('Minimal Task');
|
|
138
|
-
expect(task.frame_id).toBe('frame-456');
|
|
139
|
-
expect(task.priority).toBe('medium');
|
|
140
|
-
expect(task.status).toBe('pending');
|
|
141
|
-
expect(task.description).toBeUndefined();
|
|
142
|
-
expect(task.tags).toEqual([]);
|
|
143
|
-
});
|
|
144
|
-
it('should create task with parent relationship', () => {
|
|
145
|
-
const parentId = taskStore.createTask({
|
|
146
|
-
title: 'Parent Task',
|
|
147
|
-
frameId: 'frame-parent'
|
|
148
|
-
});
|
|
149
|
-
const childId = taskStore.createTask({
|
|
150
|
-
title: 'Child Task',
|
|
151
|
-
frameId: 'frame-child',
|
|
152
|
-
parentId
|
|
153
|
-
});
|
|
154
|
-
const childTask = taskStore.getTask(childId);
|
|
155
|
-
expect(childTask.parent_id).toBe(parentId);
|
|
156
|
-
});
|
|
157
|
-
it('should create task with dependencies', () => {
|
|
158
|
-
const dep1Id = taskStore.createTask({
|
|
159
|
-
title: 'Dependency 1',
|
|
160
|
-
frameId: 'frame-dep1'
|
|
161
|
-
});
|
|
162
|
-
const dep2Id = taskStore.createTask({
|
|
163
|
-
title: 'Dependency 2',
|
|
164
|
-
frameId: 'frame-dep2'
|
|
165
|
-
});
|
|
166
|
-
const taskId = taskStore.createTask({
|
|
167
|
-
title: 'Dependent Task',
|
|
168
|
-
frameId: 'frame-main',
|
|
169
|
-
dependsOn: [dep1Id, dep2Id]
|
|
170
|
-
});
|
|
171
|
-
const task = taskStore.getTask(taskId);
|
|
172
|
-
expect(task.depends_on).toEqual([dep1Id, dep2Id]);
|
|
173
|
-
});
|
|
174
|
-
it('should append task to JSONL file', () => {
|
|
175
|
-
const taskId = taskStore.createTask({
|
|
176
|
-
title: 'JSONL Test',
|
|
177
|
-
frameId: 'frame-jsonl'
|
|
178
|
-
});
|
|
179
|
-
const tasksFile = join(projectRoot, '.stackmemory', 'tasks.jsonl');
|
|
180
|
-
expect(existsSync(tasksFile)).toBe(true);
|
|
181
|
-
const content = readFileSync(tasksFile, 'utf-8');
|
|
182
|
-
expect(content).toContain(taskId);
|
|
183
|
-
expect(content).toContain('JSONL Test');
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
describe('Task Status Updates', () => {
|
|
187
|
-
let taskId;
|
|
188
|
-
beforeEach(() => {
|
|
189
|
-
taskId = taskStore.createTask({
|
|
190
|
-
title: 'Status Test Task',
|
|
191
|
-
frameId: 'frame-status'
|
|
192
|
-
});
|
|
193
|
-
});
|
|
194
|
-
it('should update task status from pending to in_progress', () => {
|
|
195
|
-
taskStore.updateTaskStatus(taskId, 'in_progress');
|
|
196
|
-
const task = taskStore.getTask(taskId);
|
|
197
|
-
expect(task.status).toBe('in_progress');
|
|
198
|
-
expect(task.started_at).toBeDefined();
|
|
199
|
-
});
|
|
200
|
-
it('should update task status from in_progress to completed', () => {
|
|
201
|
-
// First set to in_progress
|
|
202
|
-
taskStore.updateTaskStatus(taskId, 'in_progress');
|
|
203
|
-
// Then complete it
|
|
204
|
-
taskStore.updateTaskStatus(taskId, 'completed');
|
|
205
|
-
const task = taskStore.getTask(taskId);
|
|
206
|
-
expect(task.status).toBe('completed');
|
|
207
|
-
expect(task.completed_at).toBeDefined();
|
|
208
|
-
expect(task.actual_effort).toBeDefined();
|
|
209
|
-
});
|
|
210
|
-
it('should update task status to blocked', () => {
|
|
211
|
-
taskStore.updateTaskStatus(taskId, 'blocked', 'Waiting for external API');
|
|
212
|
-
const task = taskStore.getTask(taskId);
|
|
213
|
-
expect(task.status).toBe('blocked');
|
|
214
|
-
expect(task.type).toBe('task_block');
|
|
215
|
-
});
|
|
216
|
-
it('should update task status to cancelled', () => {
|
|
217
|
-
taskStore.updateTaskStatus(taskId, 'cancelled');
|
|
218
|
-
const task = taskStore.getTask(taskId);
|
|
219
|
-
expect(task.status).toBe('cancelled');
|
|
220
|
-
});
|
|
221
|
-
it('should throw error when updating non-existent task', () => {
|
|
222
|
-
expect(() => {
|
|
223
|
-
taskStore.updateTaskStatus('non-existent', 'completed');
|
|
224
|
-
}).toThrow(TaskError);
|
|
225
|
-
});
|
|
226
|
-
it('should throw error when changing completed task status', () => {
|
|
227
|
-
taskStore.updateTaskStatus(taskId, 'in_progress');
|
|
228
|
-
taskStore.updateTaskStatus(taskId, 'completed');
|
|
229
|
-
expect(() => {
|
|
230
|
-
taskStore.updateTaskStatus(taskId, 'in_progress');
|
|
231
|
-
}).toThrow(TaskError);
|
|
232
|
-
});
|
|
233
|
-
it('should allow changing completed task to cancelled', () => {
|
|
234
|
-
taskStore.updateTaskStatus(taskId, 'in_progress');
|
|
235
|
-
taskStore.updateTaskStatus(taskId, 'completed');
|
|
236
|
-
// Should allow completed -> cancelled
|
|
237
|
-
expect(() => {
|
|
238
|
-
taskStore.updateTaskStatus(taskId, 'cancelled');
|
|
239
|
-
}).not.toThrow();
|
|
240
|
-
const task = taskStore.getTask(taskId);
|
|
241
|
-
expect(task.status).toBe('cancelled');
|
|
242
|
-
});
|
|
243
|
-
it('should calculate actual effort correctly', () => {
|
|
244
|
-
const startTime = Date.now();
|
|
245
|
-
taskStore.updateTaskStatus(taskId, 'in_progress');
|
|
246
|
-
// Simulate some work time
|
|
247
|
-
vi.useFakeTimers();
|
|
248
|
-
vi.advanceTimersByTime(30 * 60 * 1000); // 30 minutes
|
|
249
|
-
taskStore.updateTaskStatus(taskId, 'completed');
|
|
250
|
-
const task = taskStore.getTask(taskId);
|
|
251
|
-
expect(task.actual_effort).toBe(30); // 30 minutes
|
|
252
|
-
vi.useRealTimers();
|
|
253
|
-
});
|
|
254
|
-
it('should update task type based on status change', () => {
|
|
255
|
-
const task1 = taskStore.getTask(taskId);
|
|
256
|
-
expect(task1.type).toBe('task_create');
|
|
257
|
-
taskStore.updateTaskStatus(taskId, 'in_progress');
|
|
258
|
-
const task2 = taskStore.getTask(taskId);
|
|
259
|
-
expect(task2.type).toBe('task_update');
|
|
260
|
-
taskStore.updateTaskStatus(taskId, 'completed');
|
|
261
|
-
const task3 = taskStore.getTask(taskId);
|
|
262
|
-
expect(task3.type).toBe('task_complete');
|
|
263
|
-
const blockedTaskId = taskStore.createTask({
|
|
264
|
-
title: 'Blocked Task',
|
|
265
|
-
frameId: 'frame-blocked'
|
|
266
|
-
});
|
|
267
|
-
taskStore.updateTaskStatus(blockedTaskId, 'blocked');
|
|
268
|
-
const task4 = taskStore.getTask(blockedTaskId);
|
|
269
|
-
expect(task4.type).toBe('task_block');
|
|
270
|
-
});
|
|
271
|
-
});
|
|
272
|
-
describe('Task Dependencies', () => {
|
|
273
|
-
let task1Id;
|
|
274
|
-
let task2Id;
|
|
275
|
-
beforeEach(() => {
|
|
276
|
-
task1Id = taskStore.createTask({
|
|
277
|
-
title: 'Task 1',
|
|
278
|
-
frameId: 'frame-1'
|
|
279
|
-
});
|
|
280
|
-
task2Id = taskStore.createTask({
|
|
281
|
-
title: 'Task 2',
|
|
282
|
-
frameId: 'frame-2'
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
it('should add dependency relationship', () => {
|
|
286
|
-
taskStore.addDependency(task2Id, task1Id);
|
|
287
|
-
const task2 = taskStore.getTask(task2Id);
|
|
288
|
-
const task1 = taskStore.getTask(task1Id);
|
|
289
|
-
expect(task2.depends_on).toContain(task1Id);
|
|
290
|
-
expect(task1.blocks).toContain(task2Id);
|
|
291
|
-
});
|
|
292
|
-
it('should prevent duplicate dependencies', () => {
|
|
293
|
-
taskStore.addDependency(task2Id, task1Id);
|
|
294
|
-
taskStore.addDependency(task2Id, task1Id); // Add again
|
|
295
|
-
const task2 = taskStore.getTask(task2Id);
|
|
296
|
-
expect(task2.depends_on.filter(id => id === task1Id)).toHaveLength(1);
|
|
297
|
-
});
|
|
298
|
-
it('should throw error for non-existent task', () => {
|
|
299
|
-
expect(() => {
|
|
300
|
-
taskStore.addDependency('non-existent', task1Id);
|
|
301
|
-
}).toThrow(TaskError);
|
|
302
|
-
expect(() => {
|
|
303
|
-
taskStore.addDependency(task2Id, 'non-existent');
|
|
304
|
-
}).toThrow(TaskError);
|
|
305
|
-
});
|
|
306
|
-
it('should detect circular dependencies', () => {
|
|
307
|
-
taskStore.addDependency(task2Id, task1Id); // task2 depends on task1
|
|
308
|
-
// Try to make task1 depend on task2 (circular)
|
|
309
|
-
expect(() => {
|
|
310
|
-
taskStore.addDependency(task1Id, task2Id);
|
|
311
|
-
}).toThrow(TaskError);
|
|
312
|
-
});
|
|
313
|
-
it('should detect complex circular dependencies', () => {
|
|
314
|
-
const task3Id = taskStore.createTask({
|
|
315
|
-
title: 'Task 3',
|
|
316
|
-
frameId: 'frame-3'
|
|
317
|
-
});
|
|
318
|
-
const task4Id = taskStore.createTask({
|
|
319
|
-
title: 'Task 4',
|
|
320
|
-
frameId: 'frame-4'
|
|
321
|
-
});
|
|
322
|
-
// Create chain: task1 -> task2 -> task3 -> task4
|
|
323
|
-
taskStore.addDependency(task2Id, task1Id);
|
|
324
|
-
taskStore.addDependency(task3Id, task2Id);
|
|
325
|
-
taskStore.addDependency(task4Id, task3Id);
|
|
326
|
-
// Try to make task1 depend on task4 (circular)
|
|
327
|
-
expect(() => {
|
|
328
|
-
taskStore.addDependency(task1Id, task4Id);
|
|
329
|
-
}).toThrow(TaskError);
|
|
330
|
-
});
|
|
331
|
-
it('should handle self-dependency prevention', () => {
|
|
332
|
-
expect(() => {
|
|
333
|
-
taskStore.addDependency(task1Id, task1Id);
|
|
334
|
-
}).toThrow(TaskError);
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
describe('Task Queries', () => {
|
|
338
|
-
beforeEach(() => {
|
|
339
|
-
// Create various tasks for testing
|
|
340
|
-
const tasks = [
|
|
341
|
-
{ title: 'Active Task 1', status: 'pending', priority: 'high', frameId: 'frame-1' },
|
|
342
|
-
{ title: 'Active Task 2', status: 'in_progress', priority: 'medium', frameId: 'frame-1' },
|
|
343
|
-
{ title: 'Completed Task', status: 'completed', priority: 'low', frameId: 'frame-2' },
|
|
344
|
-
{ title: 'Blocked Task', status: 'blocked', priority: 'urgent', frameId: 'frame-3' },
|
|
345
|
-
{ title: 'Cancelled Task', status: 'cancelled', priority: 'medium', frameId: 'frame-3' }
|
|
346
|
-
];
|
|
347
|
-
tasks.forEach(task => {
|
|
348
|
-
const taskId = taskStore.createTask(task);
|
|
349
|
-
if (task.status !== 'pending') {
|
|
350
|
-
taskStore.updateTaskStatus(taskId, task.status);
|
|
351
|
-
}
|
|
352
|
-
});
|
|
353
|
-
});
|
|
354
|
-
it('should get all active tasks', () => {
|
|
355
|
-
const activeTasks = taskStore.getActiveTasks();
|
|
356
|
-
expect(activeTasks).toHaveLength(3); // pending, in_progress, blocked
|
|
357
|
-
expect(activeTasks.every(t => ['pending', 'in_progress'].includes(t.status))).toBe(true);
|
|
358
|
-
});
|
|
359
|
-
it('should get active tasks filtered by frame', () => {
|
|
360
|
-
const frame1Tasks = taskStore.getActiveTasks('frame-1');
|
|
361
|
-
expect(frame1Tasks).toHaveLength(2);
|
|
362
|
-
expect(frame1Tasks.every(t => t.frame_id === 'frame-1')).toBe(true);
|
|
363
|
-
});
|
|
364
|
-
it('should order active tasks by priority and creation time', () => {
|
|
365
|
-
const activeTasks = taskStore.getActiveTasks();
|
|
366
|
-
// Should be ordered by priority desc, created_at asc
|
|
367
|
-
const priorities = activeTasks.map(t => t.priority);
|
|
368
|
-
expect(priorities[0]).toBe('high'); // Highest priority first
|
|
369
|
-
});
|
|
370
|
-
it('should get blocking tasks', () => {
|
|
371
|
-
// Create tasks with blocking relationships
|
|
372
|
-
const blockingTaskId = taskStore.createTask({
|
|
373
|
-
title: 'Blocking Task',
|
|
374
|
-
frameId: 'frame-blocker'
|
|
375
|
-
});
|
|
376
|
-
const dependentTaskId = taskStore.createTask({
|
|
377
|
-
title: 'Dependent Task',
|
|
378
|
-
frameId: 'frame-dependent'
|
|
379
|
-
});
|
|
380
|
-
taskStore.addDependency(dependentTaskId, blockingTaskId);
|
|
381
|
-
const blockingTasks = taskStore.getBlockingTasks();
|
|
382
|
-
expect(blockingTasks.length).toBeGreaterThan(0);
|
|
383
|
-
const foundBlocking = blockingTasks.find(t => t.id === blockingTaskId);
|
|
384
|
-
expect(foundBlocking).toBeDefined();
|
|
385
|
-
});
|
|
386
|
-
it('should return empty array when no active tasks exist', () => {
|
|
387
|
-
// Complete all tasks
|
|
388
|
-
const activeTasks = taskStore.getActiveTasks();
|
|
389
|
-
activeTasks.forEach(task => {
|
|
390
|
-
if (task.status === 'pending') {
|
|
391
|
-
taskStore.updateTaskStatus(task.id, 'in_progress');
|
|
392
|
-
}
|
|
393
|
-
if (task.status === 'in_progress') {
|
|
394
|
-
taskStore.updateTaskStatus(task.id, 'completed');
|
|
395
|
-
}
|
|
396
|
-
if (task.status === 'blocked') {
|
|
397
|
-
taskStore.updateTaskStatus(task.id, 'cancelled');
|
|
398
|
-
}
|
|
399
|
-
});
|
|
400
|
-
const remainingActive = taskStore.getActiveTasks();
|
|
401
|
-
expect(remainingActive).toHaveLength(0);
|
|
402
|
-
});
|
|
403
|
-
});
|
|
404
|
-
describe('Task Metrics', () => {
|
|
405
|
-
beforeEach(() => {
|
|
406
|
-
// Create tasks with various states for metrics testing
|
|
407
|
-
const tasks = [
|
|
408
|
-
{ title: 'Task 1', status: 'pending', priority: 'high' },
|
|
409
|
-
{ title: 'Task 2', status: 'pending', priority: 'medium' },
|
|
410
|
-
{ title: 'Task 3', status: 'completed', priority: 'low', effort: 30 },
|
|
411
|
-
{ title: 'Task 4', status: 'completed', priority: 'high', effort: 60 },
|
|
412
|
-
{ title: 'Task 5', status: 'blocked', priority: 'urgent' },
|
|
413
|
-
{ title: 'Task 6', status: 'cancelled', priority: 'medium' }
|
|
414
|
-
];
|
|
415
|
-
tasks.forEach(task => {
|
|
416
|
-
const taskId = taskStore.createTask({
|
|
417
|
-
title: task.title,
|
|
418
|
-
frameId: 'frame-metrics',
|
|
419
|
-
priority: task.priority,
|
|
420
|
-
estimatedEffort: task.effort
|
|
421
|
-
});
|
|
422
|
-
if (task.status !== 'pending') {
|
|
423
|
-
if (task.status === 'completed') {
|
|
424
|
-
taskStore.updateTaskStatus(taskId, 'in_progress');
|
|
425
|
-
}
|
|
426
|
-
taskStore.updateTaskStatus(taskId, task.status);
|
|
427
|
-
}
|
|
428
|
-
});
|
|
429
|
-
});
|
|
430
|
-
it('should calculate basic metrics correctly', () => {
|
|
431
|
-
const metrics = taskStore.getMetrics();
|
|
432
|
-
expect(metrics.total_tasks).toBe(6);
|
|
433
|
-
expect(metrics.by_status.pending).toBe(2);
|
|
434
|
-
expect(metrics.by_status.completed).toBe(2);
|
|
435
|
-
expect(metrics.by_status.blocked).toBe(1);
|
|
436
|
-
expect(metrics.by_status.cancelled).toBe(1);
|
|
437
|
-
expect(metrics.by_priority.high).toBe(2);
|
|
438
|
-
expect(metrics.by_priority.medium).toBe(2);
|
|
439
|
-
expect(metrics.by_priority.low).toBe(1);
|
|
440
|
-
expect(metrics.by_priority.urgent).toBe(1);
|
|
441
|
-
expect(metrics.completion_rate).toBe(2 / 6); // 2 completed out of 6 total
|
|
442
|
-
expect(metrics.blocked_tasks).toBe(1);
|
|
443
|
-
});
|
|
444
|
-
it('should handle metrics when no tasks exist', () => {
|
|
445
|
-
// Create empty task store
|
|
446
|
-
const emptyDb = new Database(':memory:');
|
|
447
|
-
const emptyStore = new PebblesTaskStore(projectRoot, emptyDb);
|
|
448
|
-
const metrics = emptyStore.getMetrics();
|
|
449
|
-
expect(metrics.total_tasks).toBe(0);
|
|
450
|
-
expect(metrics.completion_rate).toBe(0);
|
|
451
|
-
expect(metrics.blocked_tasks).toBe(0);
|
|
452
|
-
emptyDb.close();
|
|
453
|
-
});
|
|
454
|
-
it('should calculate effort accuracy', () => {
|
|
455
|
-
// Create task with estimated effort for accuracy calculation
|
|
456
|
-
const taskId = taskStore.createTask({
|
|
457
|
-
title: 'Effort Test',
|
|
458
|
-
frameId: 'frame-effort',
|
|
459
|
-
estimatedEffort: 60 // 1 hour estimate
|
|
460
|
-
});
|
|
461
|
-
taskStore.updateTaskStatus(taskId, 'in_progress');
|
|
462
|
-
// Mock actual effort to be close to estimate
|
|
463
|
-
const task = taskStore.getTask(taskId);
|
|
464
|
-
if (task) {
|
|
465
|
-
// Manually update actual effort for testing
|
|
466
|
-
const stmt = db.prepare(`
|
|
467
|
-
UPDATE task_cache
|
|
468
|
-
SET actual_effort = ?
|
|
469
|
-
WHERE id = ?
|
|
470
|
-
`);
|
|
471
|
-
stmt.run(50, taskId); // Actual: 50 min vs Estimate: 60 min
|
|
472
|
-
}
|
|
473
|
-
const metrics = taskStore.getMetrics();
|
|
474
|
-
expect(metrics.avg_effort_accuracy).toBeGreaterThan(0);
|
|
475
|
-
});
|
|
476
|
-
});
|
|
477
|
-
describe('Linear Integration Export', () => {
|
|
478
|
-
beforeEach(() => {
|
|
479
|
-
// Create tasks without Linear integration
|
|
480
|
-
taskStore.createTask({
|
|
481
|
-
title: 'Local Task 1',
|
|
482
|
-
frameId: 'frame-1',
|
|
483
|
-
description: 'Task for Linear export',
|
|
484
|
-
priority: 'high',
|
|
485
|
-
estimatedEffort: 120
|
|
486
|
-
});
|
|
487
|
-
taskStore.createTask({
|
|
488
|
-
title: 'Local Task 2',
|
|
489
|
-
frameId: 'frame-2',
|
|
490
|
-
priority: 'medium'
|
|
491
|
-
});
|
|
492
|
-
// Create task with existing Linear reference (should be excluded)
|
|
493
|
-
const taskWithLinear = taskStore.createTask({
|
|
494
|
-
title: 'Already Synced',
|
|
495
|
-
frameId: 'frame-3'
|
|
496
|
-
});
|
|
497
|
-
// Manually add Linear external reference
|
|
498
|
-
db.prepare(`
|
|
499
|
-
UPDATE task_cache
|
|
500
|
-
SET external_refs = ?
|
|
501
|
-
WHERE id = ?
|
|
502
|
-
`).run(JSON.stringify({ linear: { id: 'LIN-123' } }), taskWithLinear);
|
|
503
|
-
});
|
|
504
|
-
it('should export tasks for Linear integration', () => {
|
|
505
|
-
const exported = taskStore.exportForLinear();
|
|
506
|
-
expect(exported).toHaveLength(2); // Should exclude task with existing Linear ref
|
|
507
|
-
const task1 = exported.find(t => t.title === 'Local Task 1');
|
|
508
|
-
expect(task1).toBeDefined();
|
|
509
|
-
expect(task1.description).toBe('Task for Linear export');
|
|
510
|
-
expect(task1.priority).toBe(3); // high -> 3 in Linear
|
|
511
|
-
expect(task1.estimate).toBe(120);
|
|
512
|
-
const task2 = exported.find(t => t.title === 'Local Task 2');
|
|
513
|
-
expect(task2).toBeDefined();
|
|
514
|
-
expect(task2.priority).toBe(2); // medium -> 2 in Linear
|
|
515
|
-
});
|
|
516
|
-
it('should map priorities correctly for Linear', () => {
|
|
517
|
-
const priorities = {
|
|
518
|
-
low: 1,
|
|
519
|
-
medium: 2,
|
|
520
|
-
high: 3,
|
|
521
|
-
urgent: 4
|
|
522
|
-
};
|
|
523
|
-
Object.entries(priorities).forEach(([priority, expectedValue]) => {
|
|
524
|
-
const taskId = taskStore.createTask({
|
|
525
|
-
title: `Priority ${priority}`,
|
|
526
|
-
frameId: 'frame-priority',
|
|
527
|
-
priority: priority
|
|
528
|
-
});
|
|
529
|
-
const exported = taskStore.exportForLinear();
|
|
530
|
-
const task = exported.find(t => t.title === `Priority ${priority}`);
|
|
531
|
-
expect(task.priority).toBe(expectedValue);
|
|
532
|
-
});
|
|
533
|
-
});
|
|
534
|
-
it('should map statuses correctly for Linear', () => {
|
|
535
|
-
const statusMappings = {
|
|
536
|
-
pending: 'Backlog',
|
|
537
|
-
in_progress: 'In Progress',
|
|
538
|
-
completed: 'Done',
|
|
539
|
-
blocked: 'Blocked',
|
|
540
|
-
cancelled: 'Cancelled'
|
|
541
|
-
};
|
|
542
|
-
Object.entries(statusMappings).forEach(([status, expectedLinearStatus]) => {
|
|
543
|
-
const taskId = taskStore.createTask({
|
|
544
|
-
title: `Status ${status}`,
|
|
545
|
-
frameId: 'frame-status'
|
|
546
|
-
});
|
|
547
|
-
if (status !== 'pending') {
|
|
548
|
-
taskStore.updateTaskStatus(taskId, status);
|
|
549
|
-
}
|
|
550
|
-
const exported = taskStore.exportForLinear();
|
|
551
|
-
const task = exported.find(t => t.title === `Status ${status}`);
|
|
552
|
-
expect(task.status).toBe(expectedLinearStatus);
|
|
553
|
-
});
|
|
554
|
-
});
|
|
555
|
-
});
|
|
556
|
-
describe('Error Handling and Edge Cases', () => {
|
|
557
|
-
it('should handle database errors gracefully', () => {
|
|
558
|
-
const taskId = taskStore.createTask({
|
|
559
|
-
title: 'Error Test',
|
|
560
|
-
frameId: 'frame-error'
|
|
561
|
-
});
|
|
562
|
-
// Close database to simulate error
|
|
563
|
-
db.close();
|
|
564
|
-
expect(() => {
|
|
565
|
-
taskStore.getTask(taskId);
|
|
566
|
-
}).toThrow(DatabaseError);
|
|
567
|
-
expect(() => {
|
|
568
|
-
taskStore.updateTaskStatus(taskId, 'completed');
|
|
569
|
-
}).toThrow();
|
|
570
|
-
});
|
|
571
|
-
it('should handle JSONL file write errors gracefully', () => {
|
|
572
|
-
// Mock fs.appendFile to throw error
|
|
573
|
-
const originalAppendFile = require('fs').appendFile;
|
|
574
|
-
require('fs').appendFile = vi.fn((path, data, callback) => {
|
|
575
|
-
callback(new Error('Write failed'));
|
|
576
|
-
});
|
|
577
|
-
// Should not throw but should log error
|
|
578
|
-
expect(() => {
|
|
579
|
-
taskStore.createTask({
|
|
580
|
-
title: 'Write Error Test',
|
|
581
|
-
frameId: 'frame-write-error'
|
|
582
|
-
});
|
|
583
|
-
}).not.toThrow();
|
|
584
|
-
// Restore original
|
|
585
|
-
require('fs').appendFile = originalAppendFile;
|
|
586
|
-
});
|
|
587
|
-
it('should handle concurrent task operations', () => {
|
|
588
|
-
const tasks = [];
|
|
589
|
-
// Create multiple tasks quickly
|
|
590
|
-
for (let i = 0; i < 20; i++) {
|
|
591
|
-
const taskId = taskStore.createTask({
|
|
592
|
-
title: `Concurrent Task ${i}`,
|
|
593
|
-
frameId: `frame-${i}`,
|
|
594
|
-
priority: i % 2 === 0 ? 'high' : 'low'
|
|
595
|
-
});
|
|
596
|
-
tasks.push(taskId);
|
|
597
|
-
}
|
|
598
|
-
expect(tasks).toHaveLength(20);
|
|
599
|
-
// Update all tasks concurrently
|
|
600
|
-
tasks.forEach(taskId => {
|
|
601
|
-
taskStore.updateTaskStatus(taskId, 'completed');
|
|
602
|
-
});
|
|
603
|
-
// Verify all updates
|
|
604
|
-
const allCompleted = tasks.every(taskId => {
|
|
605
|
-
const task = taskStore.getTask(taskId);
|
|
606
|
-
return task?.status === 'completed';
|
|
607
|
-
});
|
|
608
|
-
expect(allCompleted).toBe(true);
|
|
609
|
-
});
|
|
610
|
-
it('should handle empty or malformed task IDs', () => {
|
|
611
|
-
expect(taskStore.getTask('')).toBeUndefined();
|
|
612
|
-
expect(taskStore.getTask('invalid-format')).toBeUndefined();
|
|
613
|
-
expect(taskStore.getTask('null')).toBeUndefined();
|
|
614
|
-
});
|
|
615
|
-
it('should validate task status transitions', () => {
|
|
616
|
-
const taskId = taskStore.createTask({
|
|
617
|
-
title: 'Status Validation',
|
|
618
|
-
frameId: 'frame-validation'
|
|
619
|
-
});
|
|
620
|
-
// Valid transitions should work
|
|
621
|
-
expect(() => {
|
|
622
|
-
taskStore.updateTaskStatus(taskId, 'in_progress');
|
|
623
|
-
}).not.toThrow();
|
|
624
|
-
expect(() => {
|
|
625
|
-
taskStore.updateTaskStatus(taskId, 'blocked');
|
|
626
|
-
}).not.toThrow();
|
|
627
|
-
expect(() => {
|
|
628
|
-
taskStore.updateTaskStatus(taskId, 'in_progress');
|
|
629
|
-
}).not.toThrow();
|
|
630
|
-
expect(() => {
|
|
631
|
-
taskStore.updateTaskStatus(taskId, 'completed');
|
|
632
|
-
}).not.toThrow();
|
|
633
|
-
// Invalid transition: completed -> in_progress
|
|
634
|
-
expect(() => {
|
|
635
|
-
taskStore.updateTaskStatus(taskId, 'in_progress');
|
|
636
|
-
}).toThrow(TaskError);
|
|
637
|
-
});
|
|
638
|
-
it('should handle tasks with complex metadata', () => {
|
|
639
|
-
const taskId = taskStore.createTask({
|
|
640
|
-
title: 'Complex Metadata Task',
|
|
641
|
-
frameId: 'frame-complex',
|
|
642
|
-
tags: ['complex', 'metadata', 'test'],
|
|
643
|
-
dependsOn: [],
|
|
644
|
-
});
|
|
645
|
-
// Add dependency to test complex relationships
|
|
646
|
-
const depTaskId = taskStore.createTask({
|
|
647
|
-
title: 'Dependency Task',
|
|
648
|
-
frameId: 'frame-dep'
|
|
649
|
-
});
|
|
650
|
-
taskStore.addDependency(taskId, depTaskId);
|
|
651
|
-
const task = taskStore.getTask(taskId);
|
|
652
|
-
expect(task.tags).toEqual(['complex', 'metadata', 'test']);
|
|
653
|
-
expect(task.depends_on).toContain(depTaskId);
|
|
654
|
-
});
|
|
655
|
-
});
|
|
656
|
-
describe('Data Integrity and Consistency', () => {
|
|
657
|
-
it('should maintain referential integrity in dependencies', () => {
|
|
658
|
-
const task1Id = taskStore.createTask({
|
|
659
|
-
title: 'Task 1',
|
|
660
|
-
frameId: 'frame-1'
|
|
661
|
-
});
|
|
662
|
-
const task2Id = taskStore.createTask({
|
|
663
|
-
title: 'Task 2',
|
|
664
|
-
frameId: 'frame-2'
|
|
665
|
-
});
|
|
666
|
-
taskStore.addDependency(task2Id, task1Id);
|
|
667
|
-
// Check both sides of relationship
|
|
668
|
-
const task1 = taskStore.getTask(task1Id);
|
|
669
|
-
const task2 = taskStore.getTask(task2Id);
|
|
670
|
-
expect(task2.depends_on).toContain(task1Id);
|
|
671
|
-
expect(task1.blocks).toContain(task2Id);
|
|
672
|
-
});
|
|
673
|
-
it('should ensure content-based task IDs are deterministic for same content', () => {
|
|
674
|
-
const now = Math.floor(Date.now() / 1000);
|
|
675
|
-
// Mock Math.random to return consistent value
|
|
676
|
-
const originalRandom = Math.random;
|
|
677
|
-
Math.random = vi.fn(() => 0.5);
|
|
678
|
-
// Mock Date.now to return consistent value
|
|
679
|
-
vi.spyOn(Date, 'now').mockReturnValue(now * 1000);
|
|
680
|
-
try {
|
|
681
|
-
const taskId1 = taskStore.createTask({
|
|
682
|
-
title: 'Deterministic Task',
|
|
683
|
-
frameId: 'frame-det'
|
|
684
|
-
});
|
|
685
|
-
const taskId2 = taskStore.createTask({
|
|
686
|
-
title: 'Deterministic Task',
|
|
687
|
-
frameId: 'frame-det'
|
|
688
|
-
});
|
|
689
|
-
// Should be different due to timestamp and random components
|
|
690
|
-
expect(taskId1).not.toBe(taskId2);
|
|
691
|
-
// But should follow consistent format
|
|
692
|
-
expect(taskId1).toMatch(/^tsk-[a-f0-9]{8}$/);
|
|
693
|
-
expect(taskId2).toMatch(/^tsk-[a-f0-9]{8}$/);
|
|
694
|
-
}
|
|
695
|
-
finally {
|
|
696
|
-
Math.random = originalRandom;
|
|
697
|
-
vi.restoreAllMocks();
|
|
698
|
-
}
|
|
699
|
-
});
|
|
700
|
-
it('should handle JSON serialization edge cases', () => {
|
|
701
|
-
const taskId = taskStore.createTask({
|
|
702
|
-
title: 'JSON Edge Cases',
|
|
703
|
-
frameId: 'frame-json',
|
|
704
|
-
tags: ['tag with spaces', 'tag"with"quotes', 'tag\\with\\slashes'],
|
|
705
|
-
dependsOn: []
|
|
706
|
-
});
|
|
707
|
-
const task = taskStore.getTask(taskId);
|
|
708
|
-
expect(task.tags).toEqual(['tag with spaces', 'tag"with"quotes', 'tag\\with\\slashes']);
|
|
709
|
-
});
|
|
710
|
-
});
|
|
711
|
-
});
|
|
712
|
-
//# sourceMappingURL=pebbles-task-store.test.js.map
|