obsidian-accomplishments-mcp 0.1.9 → 0.1.11
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 +154 -182
- package/dist/index.js +207 -38
- package/dist/index.js.map +1 -1
- package/dist/integration.test.d.ts +8 -0
- package/dist/integration.test.d.ts.map +1 -0
- package/dist/integration.test.js +979 -0
- package/dist/integration.test.js.map +1 -0
- package/dist/models/types.d.ts +1 -2
- package/dist/models/types.d.ts.map +1 -1
- package/dist/models/types.js.map +1 -1
- package/dist/models/v2-types.d.ts +460 -0
- package/dist/models/v2-types.d.ts.map +1 -0
- package/dist/models/v2-types.js +137 -0
- package/dist/models/v2-types.js.map +1 -0
- package/dist/models/v2-types.test.d.ts +5 -0
- package/dist/models/v2-types.test.d.ts.map +1 -0
- package/dist/models/v2-types.test.js +133 -0
- package/dist/models/v2-types.test.js.map +1 -0
- package/dist/parsers/canvas-parser.d.ts +1 -1
- package/dist/parsers/canvas-parser.d.ts.map +1 -1
- package/dist/parsers/canvas-parser.js +1 -1
- package/dist/parsers/canvas-parser.js.map +1 -1
- package/dist/parsers/markdown-parser.js +9 -9
- package/dist/parsers/markdown-parser.js.map +1 -1
- package/dist/services/v2/archive-manager.d.ts +96 -0
- package/dist/services/v2/archive-manager.d.ts.map +1 -0
- package/dist/services/v2/archive-manager.js +281 -0
- package/dist/services/v2/archive-manager.js.map +1 -0
- package/dist/services/v2/canvas-manager.d.ts +155 -0
- package/dist/services/v2/canvas-manager.d.ts.map +1 -0
- package/dist/services/v2/canvas-manager.js +540 -0
- package/dist/services/v2/canvas-manager.js.map +1 -0
- package/dist/services/v2/canvas-manager.test.d.ts +5 -0
- package/dist/services/v2/canvas-manager.test.d.ts.map +1 -0
- package/dist/services/v2/canvas-manager.test.js +327 -0
- package/dist/services/v2/canvas-manager.test.js.map +1 -0
- package/dist/services/v2/cascade-manager.d.ts +54 -0
- package/dist/services/v2/cascade-manager.d.ts.map +1 -0
- package/dist/services/v2/cascade-manager.js +220 -0
- package/dist/services/v2/cascade-manager.js.map +1 -0
- package/dist/services/v2/cycle-detector.d.ts +76 -0
- package/dist/services/v2/cycle-detector.d.ts.map +1 -0
- package/dist/services/v2/cycle-detector.js +183 -0
- package/dist/services/v2/cycle-detector.js.map +1 -0
- package/dist/services/v2/cycle-detector.test.d.ts +7 -0
- package/dist/services/v2/cycle-detector.test.d.ts.map +1 -0
- package/dist/services/v2/cycle-detector.test.js +125 -0
- package/dist/services/v2/cycle-detector.test.js.map +1 -0
- package/dist/services/v2/entity-parser.d.ts +54 -0
- package/dist/services/v2/entity-parser.d.ts.map +1 -0
- package/dist/services/v2/entity-parser.js +418 -0
- package/dist/services/v2/entity-parser.js.map +1 -0
- package/dist/services/v2/entity-parser.test.d.ts +5 -0
- package/dist/services/v2/entity-parser.test.d.ts.map +1 -0
- package/dist/services/v2/entity-parser.test.js +637 -0
- package/dist/services/v2/entity-parser.test.js.map +1 -0
- package/dist/services/v2/entity-serializer.d.ts +94 -0
- package/dist/services/v2/entity-serializer.d.ts.map +1 -0
- package/dist/services/v2/entity-serializer.js +583 -0
- package/dist/services/v2/entity-serializer.js.map +1 -0
- package/dist/services/v2/entity-serializer.test.d.ts +5 -0
- package/dist/services/v2/entity-serializer.test.d.ts.map +1 -0
- package/dist/services/v2/entity-serializer.test.js +241 -0
- package/dist/services/v2/entity-serializer.test.js.map +1 -0
- package/dist/services/v2/entity-validator.d.ts +65 -0
- package/dist/services/v2/entity-validator.d.ts.map +1 -0
- package/dist/services/v2/entity-validator.js +573 -0
- package/dist/services/v2/entity-validator.js.map +1 -0
- package/dist/services/v2/entity-validator.test.d.ts +5 -0
- package/dist/services/v2/entity-validator.test.d.ts.map +1 -0
- package/dist/services/v2/entity-validator.test.js +519 -0
- package/dist/services/v2/entity-validator.test.js.map +1 -0
- package/dist/services/v2/file-manager.d.ts +73 -0
- package/dist/services/v2/file-manager.d.ts.map +1 -0
- package/dist/services/v2/file-manager.js +310 -0
- package/dist/services/v2/file-manager.js.map +1 -0
- package/dist/services/v2/file-manager.test.d.ts +5 -0
- package/dist/services/v2/file-manager.test.d.ts.map +1 -0
- package/dist/services/v2/file-manager.test.js +339 -0
- package/dist/services/v2/file-manager.test.js.map +1 -0
- package/dist/services/v2/index-manager.d.ts +68 -0
- package/dist/services/v2/index-manager.d.ts.map +1 -0
- package/dist/services/v2/index-manager.js +228 -0
- package/dist/services/v2/index-manager.js.map +1 -0
- package/dist/services/v2/index-manager.test.d.ts +5 -0
- package/dist/services/v2/index-manager.test.d.ts.map +1 -0
- package/dist/services/v2/index-manager.test.js +386 -0
- package/dist/services/v2/index-manager.test.js.map +1 -0
- package/dist/services/v2/index-service.d.ts +82 -0
- package/dist/services/v2/index-service.d.ts.map +1 -0
- package/dist/services/v2/index-service.js +274 -0
- package/dist/services/v2/index-service.js.map +1 -0
- package/dist/services/v2/index-service.test.d.ts +5 -0
- package/dist/services/v2/index-service.test.d.ts.map +1 -0
- package/dist/services/v2/index-service.test.js +117 -0
- package/dist/services/v2/index-service.test.js.map +1 -0
- package/dist/services/v2/lifecycle-manager.d.ts +59 -0
- package/dist/services/v2/lifecycle-manager.d.ts.map +1 -0
- package/dist/services/v2/lifecycle-manager.js +310 -0
- package/dist/services/v2/lifecycle-manager.js.map +1 -0
- package/dist/services/v2/lifecycle-manager.test.d.ts +5 -0
- package/dist/services/v2/lifecycle-manager.test.d.ts.map +1 -0
- package/dist/services/v2/lifecycle-manager.test.js +141 -0
- package/dist/services/v2/lifecycle-manager.test.js.map +1 -0
- package/dist/services/v2/path-resolver.d.ts +64 -0
- package/dist/services/v2/path-resolver.d.ts.map +1 -0
- package/dist/services/v2/path-resolver.js +174 -0
- package/dist/services/v2/path-resolver.js.map +1 -0
- package/dist/services/v2/progress-computer.d.ts +46 -0
- package/dist/services/v2/progress-computer.d.ts.map +1 -0
- package/dist/services/v2/progress-computer.js +200 -0
- package/dist/services/v2/progress-computer.js.map +1 -0
- package/dist/services/v2/search-service.d.ts +68 -0
- package/dist/services/v2/search-service.d.ts.map +1 -0
- package/dist/services/v2/search-service.js +194 -0
- package/dist/services/v2/search-service.js.map +1 -0
- package/dist/services/v2/transitive-dependency-remover.d.ts +54 -0
- package/dist/services/v2/transitive-dependency-remover.d.ts.map +1 -0
- package/dist/services/v2/transitive-dependency-remover.js +156 -0
- package/dist/services/v2/transitive-dependency-remover.js.map +1 -0
- package/dist/services/v2/transitive-dependency-remover.test.d.ts +7 -0
- package/dist/services/v2/transitive-dependency-remover.test.d.ts.map +1 -0
- package/dist/services/v2/transitive-dependency-remover.test.js +119 -0
- package/dist/services/v2/transitive-dependency-remover.test.js.map +1 -0
- package/dist/services/v2/v2-runtime.d.ts +374 -0
- package/dist/services/v2/v2-runtime.d.ts.map +1 -0
- package/dist/services/v2/v2-runtime.js +1908 -0
- package/dist/services/v2/v2-runtime.js.map +1 -0
- package/dist/services/v2/v2-runtime.test.d.ts +5 -0
- package/dist/services/v2/v2-runtime.test.d.ts.map +1 -0
- package/dist/services/v2/v2-runtime.test.js +658 -0
- package/dist/services/v2/v2-runtime.test.js.map +1 -0
- package/dist/services/v2/workstream-normalizer.d.ts +59 -0
- package/dist/services/v2/workstream-normalizer.d.ts.map +1 -0
- package/dist/services/v2/workstream-normalizer.js +137 -0
- package/dist/services/v2/workstream-normalizer.js.map +1 -0
- package/dist/services/v2/workstream-normalizer.test.d.ts +7 -0
- package/dist/services/v2/workstream-normalizer.test.d.ts.map +1 -0
- package/dist/services/v2/workstream-normalizer.test.js +130 -0
- package/dist/services/v2/workstream-normalizer.test.js.map +1 -0
- package/dist/test-runner.d.ts +4 -1
- package/dist/test-runner.d.ts.map +1 -1
- package/dist/test-runner.js +44 -249
- package/dist/test-runner.js.map +1 -1
- package/dist/tools/batch-operations-tools.d.ts +54 -0
- package/dist/tools/batch-operations-tools.d.ts.map +1 -0
- package/dist/tools/batch-operations-tools.js +370 -0
- package/dist/tools/batch-operations-tools.js.map +1 -0
- package/dist/tools/decision-document-tools.d.ts +78 -0
- package/dist/tools/decision-document-tools.d.ts.map +1 -0
- package/dist/tools/decision-document-tools.js +260 -0
- package/dist/tools/decision-document-tools.js.map +1 -0
- package/dist/tools/entity-management-tools.d.ts +79 -0
- package/dist/tools/entity-management-tools.d.ts.map +1 -0
- package/dist/tools/entity-management-tools.js +851 -0
- package/dist/tools/entity-management-tools.js.map +1 -0
- package/dist/tools/entity-management-tools.test.d.ts +5 -0
- package/dist/tools/entity-management-tools.test.d.ts.map +1 -0
- package/dist/tools/entity-management-tools.test.js +530 -0
- package/dist/tools/entity-management-tools.test.js.map +1 -0
- package/dist/tools/index.d.ts +15 -271
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +510 -47
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/index.test.d.ts +8 -0
- package/dist/tools/index.test.d.ts.map +1 -0
- package/dist/tools/index.test.js +429 -0
- package/dist/tools/index.test.js.map +1 -0
- package/dist/tools/project-understanding-tools.d.ts +75 -0
- package/dist/tools/project-understanding-tools.d.ts.map +1 -0
- package/dist/tools/project-understanding-tools.js +751 -0
- package/dist/tools/project-understanding-tools.js.map +1 -0
- package/dist/tools/search-navigation-tools.d.ts +77 -0
- package/dist/tools/search-navigation-tools.d.ts.map +1 -0
- package/dist/tools/search-navigation-tools.js +379 -0
- package/dist/tools/search-navigation-tools.js.map +1 -0
- package/dist/tools/tool-types.d.ts +703 -0
- package/dist/tools/tool-types.d.ts.map +1 -0
- package/dist/tools/tool-types.js +7 -0
- package/dist/tools/tool-types.js.map +1 -0
- package/dist/utils/config.d.ts +0 -4
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +2 -19
- package/dist/utils/config.js.map +1 -1
- package/package.json +16 -1
- package/dist/services/accomplishment-service.d.ts +0 -33
- package/dist/services/accomplishment-service.d.ts.map +0 -1
- package/dist/services/accomplishment-service.js +0 -293
- package/dist/services/accomplishment-service.js.map +0 -1
- package/dist/services/canvas-service.d.ts +0 -96
- package/dist/services/canvas-service.d.ts.map +0 -1
- package/dist/services/canvas-service.js +0 -231
- package/dist/services/canvas-service.js.map +0 -1
- package/dist/services/context-doc-service.d.ts +0 -70
- package/dist/services/context-doc-service.d.ts.map +0 -1
- package/dist/services/context-doc-service.js +0 -229
- package/dist/services/context-doc-service.js.map +0 -1
- package/dist/services/dependency-service.d.ts +0 -22
- package/dist/services/dependency-service.d.ts.map +0 -1
- package/dist/services/dependency-service.js +0 -99
- package/dist/services/dependency-service.js.map +0 -1
- package/dist/services/status-indicator-service.d.ts +0 -40
- package/dist/services/status-indicator-service.d.ts.map +0 -1
- package/dist/services/status-indicator-service.js +0 -173
- package/dist/services/status-indicator-service.js.map +0 -1
- package/dist/services/task-service.d.ts +0 -32
- package/dist/services/task-service.d.ts.map +0 -1
- package/dist/services/task-service.js +0 -152
- package/dist/services/task-service.js.map +0 -1
- package/dist/test-real-vault.d.ts +0 -6
- package/dist/test-real-vault.d.ts.map +0 -1
- package/dist/test-real-vault.js +0 -30
- package/dist/test-real-vault.js.map +0 -1
- package/dist/tools/batch-operations.d.ts +0 -246
- package/dist/tools/batch-operations.d.ts.map +0 -1
- package/dist/tools/batch-operations.js +0 -235
- package/dist/tools/batch-operations.js.map +0 -1
- package/dist/tools/get-accomplishment.d.ts +0 -26
- package/dist/tools/get-accomplishment.d.ts.map +0 -1
- package/dist/tools/get-accomplishment.js +0 -53
- package/dist/tools/get-accomplishment.js.map +0 -1
- package/dist/tools/get-accomplishments-graph.d.ts +0 -26
- package/dist/tools/get-accomplishments-graph.d.ts.map +0 -1
- package/dist/tools/get-accomplishments-graph.js +0 -137
- package/dist/tools/get-accomplishments-graph.js.map +0 -1
- package/dist/tools/get-blocked-items.d.ts +0 -15
- package/dist/tools/get-blocked-items.d.ts.map +0 -1
- package/dist/tools/get-blocked-items.js +0 -73
- package/dist/tools/get-blocked-items.js.map +0 -1
- package/dist/tools/get-current-work.d.ts +0 -15
- package/dist/tools/get-current-work.d.ts.map +0 -1
- package/dist/tools/get-current-work.js +0 -68
- package/dist/tools/get-current-work.js.map +0 -1
- package/dist/tools/get-project-status.d.ts +0 -26
- package/dist/tools/get-project-status.d.ts.map +0 -1
- package/dist/tools/get-project-status.js +0 -98
- package/dist/tools/get-project-status.js.map +0 -1
- package/dist/tools/get-ready-to-start.d.ts +0 -15
- package/dist/tools/get-ready-to-start.d.ts.map +0 -1
- package/dist/tools/get-ready-to-start.js +0 -47
- package/dist/tools/get-ready-to-start.js.map +0 -1
- package/dist/tools/list-accomplishments.d.ts +0 -34
- package/dist/tools/list-accomplishments.d.ts.map +0 -1
- package/dist/tools/list-accomplishments.js +0 -34
- package/dist/tools/list-accomplishments.js.map +0 -1
- package/dist/tools/manage-accomplishment.d.ts +0 -147
- package/dist/tools/manage-accomplishment.d.ts.map +0 -1
- package/dist/tools/manage-accomplishment.js +0 -153
- package/dist/tools/manage-accomplishment.js.map +0 -1
- package/dist/tools/manage-dependency.d.ts +0 -41
- package/dist/tools/manage-dependency.d.ts.map +0 -1
- package/dist/tools/manage-dependency.js +0 -66
- package/dist/tools/manage-dependency.js.map +0 -1
- package/dist/tools/manage-task.d.ts +0 -119
- package/dist/tools/manage-task.d.ts.map +0 -1
- package/dist/tools/manage-task.js +0 -126
- package/dist/tools/manage-task.js.map +0 -1
- package/dist/tools/reconcile-canvas.d.ts +0 -33
- package/dist/tools/reconcile-canvas.d.ts.map +0 -1
- package/dist/tools/reconcile-canvas.js +0 -41
- package/dist/tools/reconcile-canvas.js.map +0 -1
- package/dist/tools/set-work-focus.d.ts +0 -48
- package/dist/tools/set-work-focus.d.ts.map +0 -1
- package/dist/tools/set-work-focus.js +0 -78
- package/dist/tools/set-work-focus.js.map +0 -1
- package/dist/tools/sync-dependencies.d.ts +0 -33
- package/dist/tools/sync-dependencies.d.ts.map +0 -1
- package/dist/tools/sync-dependencies.js +0 -144
- package/dist/tools/sync-dependencies.js.map +0 -1
|
@@ -0,0 +1,979 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration Tests for MCP Tools
|
|
3
|
+
*
|
|
4
|
+
* These tests exercise the full stack: tools → runtime → services
|
|
5
|
+
* Each scenario covers multiple components to maximize coverage efficiently.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
8
|
+
import * as fs from 'fs/promises';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import * as os from 'os';
|
|
11
|
+
import { getV2Runtime, resetV2Runtime } from './services/v2/v2-runtime.js';
|
|
12
|
+
// Tool implementations
|
|
13
|
+
import { createEntity, updateEntity, updateEntityStatus, archiveEntity, restoreFromArchive, } from './tools/entity-management-tools.js';
|
|
14
|
+
import { searchEntities, getEntity, } from './tools/search-navigation-tools.js';
|
|
15
|
+
import { getDecisionHistory, } from './tools/decision-document-tools.js';
|
|
16
|
+
import { batchUpdate, } from './tools/batch-operations-tools.js';
|
|
17
|
+
import { getProjectOverview, getWorkstreamStatus, analyzeProjectState, } from './tools/project-understanding-tools.js';
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// Test Setup
|
|
20
|
+
// =============================================================================
|
|
21
|
+
describe('MCP Integration Tests', () => {
|
|
22
|
+
let tempDir;
|
|
23
|
+
let runtime;
|
|
24
|
+
let config;
|
|
25
|
+
beforeEach(async () => {
|
|
26
|
+
// Reset the runtime singleton to ensure clean state between tests
|
|
27
|
+
resetV2Runtime();
|
|
28
|
+
// Create temp directory structure
|
|
29
|
+
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mcp-integration-test-'));
|
|
30
|
+
config = {
|
|
31
|
+
vaultPath: tempDir,
|
|
32
|
+
entitiesFolder: 'accomplishments',
|
|
33
|
+
archiveFolder: 'accomplishments/archive',
|
|
34
|
+
canvasFolder: 'accomplishments',
|
|
35
|
+
defaultCanvas: 'canvas.canvas',
|
|
36
|
+
workspaces: {},
|
|
37
|
+
};
|
|
38
|
+
// Create folder structure
|
|
39
|
+
await fs.mkdir(path.join(tempDir, 'accomplishments', 'milestones'), { recursive: true });
|
|
40
|
+
await fs.mkdir(path.join(tempDir, 'accomplishments', 'stories'), { recursive: true });
|
|
41
|
+
await fs.mkdir(path.join(tempDir, 'accomplishments', 'tasks'), { recursive: true });
|
|
42
|
+
await fs.mkdir(path.join(tempDir, 'accomplishments', 'decisions'), { recursive: true });
|
|
43
|
+
await fs.mkdir(path.join(tempDir, 'accomplishments', 'documents'), { recursive: true });
|
|
44
|
+
await fs.mkdir(path.join(tempDir, 'accomplishments', 'archive'), { recursive: true });
|
|
45
|
+
// Get runtime
|
|
46
|
+
runtime = await getV2Runtime(config);
|
|
47
|
+
await runtime.initialize();
|
|
48
|
+
});
|
|
49
|
+
afterEach(async () => {
|
|
50
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
51
|
+
});
|
|
52
|
+
// ===========================================================================
|
|
53
|
+
// Scenario 1: Entity CRUD Workflow
|
|
54
|
+
// ===========================================================================
|
|
55
|
+
describe('Scenario 1: Entity CRUD Workflow', () => {
|
|
56
|
+
it('should create, read, update, and delete a milestone', async () => {
|
|
57
|
+
const deps = runtime.getEntityManagementDeps();
|
|
58
|
+
const searchDeps = runtime.getSearchNavigationDeps();
|
|
59
|
+
// CREATE
|
|
60
|
+
const createResult = await createEntity({
|
|
61
|
+
type: 'milestone',
|
|
62
|
+
data: {
|
|
63
|
+
title: 'Q1 Release',
|
|
64
|
+
workstream: 'engineering',
|
|
65
|
+
objective: 'Deliver Q1 features',
|
|
66
|
+
},
|
|
67
|
+
}, deps);
|
|
68
|
+
expect(createResult.id).toMatch(/^M-\d{3}$/);
|
|
69
|
+
expect(createResult.entity.title).toBe('Q1 Release');
|
|
70
|
+
expect(createResult.entity.status).toBe('Not Started');
|
|
71
|
+
// READ via get_entity
|
|
72
|
+
const fullResult = await getEntity({
|
|
73
|
+
id: createResult.id,
|
|
74
|
+
fields: ['id', 'title', 'status', 'type', 'workstream'],
|
|
75
|
+
}, searchDeps);
|
|
76
|
+
// getEntity returns entity fields directly
|
|
77
|
+
expect(fullResult.id).toBe(createResult.id);
|
|
78
|
+
expect(fullResult.title).toBe('Q1 Release');
|
|
79
|
+
// UPDATE
|
|
80
|
+
const updateResult = await updateEntity({
|
|
81
|
+
id: createResult.id,
|
|
82
|
+
data: {
|
|
83
|
+
title: 'Q1 Release - Updated',
|
|
84
|
+
},
|
|
85
|
+
}, deps);
|
|
86
|
+
expect(updateResult.entity.title).toBe('Q1 Release - Updated');
|
|
87
|
+
expect(updateResult.id).toBe(createResult.id);
|
|
88
|
+
// UPDATE STATUS
|
|
89
|
+
const statusResult = await updateEntityStatus({
|
|
90
|
+
id: createResult.id,
|
|
91
|
+
status: 'In Progress',
|
|
92
|
+
}, deps);
|
|
93
|
+
expect(statusResult.old_status).toBe('Not Started');
|
|
94
|
+
expect(statusResult.new_status).toBe('In Progress');
|
|
95
|
+
// Verify file exists (filename is based on title, not ID)
|
|
96
|
+
const files = await fs.readdir(path.join(tempDir, 'accomplishments', 'milestones'));
|
|
97
|
+
expect(files.length).toBeGreaterThanOrEqual(1);
|
|
98
|
+
// After update, title is "Q1 Release - Updated" which becomes "Q1_Release_-_Updated.md"
|
|
99
|
+
expect(files.some(f => f.includes('Q1_Release'))).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
it('should create a full hierarchy: milestone → story → task', async () => {
|
|
102
|
+
const deps = runtime.getEntityManagementDeps();
|
|
103
|
+
const searchDeps = runtime.getSearchNavigationDeps();
|
|
104
|
+
// Create milestone
|
|
105
|
+
const milestone = await createEntity({
|
|
106
|
+
type: 'milestone',
|
|
107
|
+
data: { title: 'Q1 Release', workstream: 'engineering' },
|
|
108
|
+
}, deps);
|
|
109
|
+
// Create story under milestone
|
|
110
|
+
const story = await createEntity({
|
|
111
|
+
type: 'story',
|
|
112
|
+
data: {
|
|
113
|
+
title: 'User Authentication',
|
|
114
|
+
workstream: 'engineering',
|
|
115
|
+
parent: milestone.id,
|
|
116
|
+
outcome: 'Users can log in securely',
|
|
117
|
+
},
|
|
118
|
+
}, deps);
|
|
119
|
+
// Verify story was created with correct ID
|
|
120
|
+
expect(story.id).toMatch(/^S-\d{3}$/);
|
|
121
|
+
expect(story.entity.title).toBe('User Authentication');
|
|
122
|
+
// Create task under story
|
|
123
|
+
const task = await createEntity({
|
|
124
|
+
type: 'task',
|
|
125
|
+
data: {
|
|
126
|
+
title: 'Implement JWT tokens',
|
|
127
|
+
workstream: 'engineering',
|
|
128
|
+
parent: story.id,
|
|
129
|
+
goal: 'Add JWT authentication',
|
|
130
|
+
},
|
|
131
|
+
}, deps);
|
|
132
|
+
// Verify task was created
|
|
133
|
+
expect(task.id).toMatch(/^T-\d{3}$/);
|
|
134
|
+
expect(task.entity.title).toBe('Implement JWT tokens');
|
|
135
|
+
// Navigate hierarchy - get children using searchEntities
|
|
136
|
+
const navDown = await searchEntities({
|
|
137
|
+
from_id: story.id,
|
|
138
|
+
direction: 'down',
|
|
139
|
+
depth: 2,
|
|
140
|
+
}, searchDeps);
|
|
141
|
+
expect(navDown.results.length).toBe(1);
|
|
142
|
+
expect(navDown.results[0].id).toBe(task.id);
|
|
143
|
+
// Navigate hierarchy - get parent using searchEntities
|
|
144
|
+
const navUp = await searchEntities({
|
|
145
|
+
from_id: story.id,
|
|
146
|
+
direction: 'up',
|
|
147
|
+
depth: 2,
|
|
148
|
+
}, searchDeps);
|
|
149
|
+
expect(navUp.results.length).toBe(1);
|
|
150
|
+
expect(navUp.results[0].id).toBe(milestone.id);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
// ===========================================================================
|
|
154
|
+
// Scenario 2: Search & Navigation
|
|
155
|
+
// ===========================================================================
|
|
156
|
+
describe('Scenario 2: Search & Navigation', () => {
|
|
157
|
+
it('should search entities by query and filters', async () => {
|
|
158
|
+
const deps = runtime.getEntityManagementDeps();
|
|
159
|
+
const searchDeps = runtime.getSearchNavigationDeps();
|
|
160
|
+
// Create multiple entities
|
|
161
|
+
await createEntity({
|
|
162
|
+
type: 'milestone',
|
|
163
|
+
data: { title: 'Authentication System', workstream: 'security' },
|
|
164
|
+
}, deps);
|
|
165
|
+
await createEntity({
|
|
166
|
+
type: 'story',
|
|
167
|
+
data: { title: 'OAuth Integration', workstream: 'security', outcome: 'Support OAuth2 authentication' },
|
|
168
|
+
}, deps);
|
|
169
|
+
await createEntity({
|
|
170
|
+
type: 'task',
|
|
171
|
+
data: { title: 'Database Migration', workstream: 'infrastructure', goal: 'Migrate to PostgreSQL' },
|
|
172
|
+
}, deps);
|
|
173
|
+
// Re-initialize to index new entities
|
|
174
|
+
await runtime.initialize();
|
|
175
|
+
// Search for "authentication"
|
|
176
|
+
const authResults = await searchEntities({
|
|
177
|
+
query: 'authentication',
|
|
178
|
+
limit: 10,
|
|
179
|
+
}, searchDeps);
|
|
180
|
+
expect(authResults.results.length).toBeGreaterThanOrEqual(1);
|
|
181
|
+
// Search with type filter
|
|
182
|
+
const milestoneResults = await searchEntities({
|
|
183
|
+
query: 'system',
|
|
184
|
+
filters: { type: ['milestone'] },
|
|
185
|
+
limit: 10,
|
|
186
|
+
}, searchDeps);
|
|
187
|
+
expect(milestoneResults.results.every(r => r.type === 'milestone')).toBe(true);
|
|
188
|
+
});
|
|
189
|
+
it('should get entity summary and full details', async () => {
|
|
190
|
+
const deps = runtime.getEntityManagementDeps();
|
|
191
|
+
const searchDeps = runtime.getSearchNavigationDeps();
|
|
192
|
+
const milestone = await createEntity({
|
|
193
|
+
type: 'milestone',
|
|
194
|
+
data: {
|
|
195
|
+
title: 'Q2 Goals',
|
|
196
|
+
workstream: 'product',
|
|
197
|
+
objective: 'Complete Q2 deliverables',
|
|
198
|
+
priority: 'High',
|
|
199
|
+
},
|
|
200
|
+
}, deps);
|
|
201
|
+
// Get entity with summary fields
|
|
202
|
+
const summary = await getEntity({ id: milestone.id, fields: ['id', 'title', 'type', 'status'] }, searchDeps);
|
|
203
|
+
expect(summary.id).toBe(milestone.id);
|
|
204
|
+
expect(summary.title).toBe('Q2 Goals');
|
|
205
|
+
expect(summary.type).toBe('milestone');
|
|
206
|
+
// Get entity with all fields
|
|
207
|
+
const full = await getEntity({
|
|
208
|
+
id: milestone.id,
|
|
209
|
+
fields: ['id', 'title', 'type', 'status', 'workstream', 'content'],
|
|
210
|
+
}, searchDeps);
|
|
211
|
+
expect(full.id).toBe(milestone.id);
|
|
212
|
+
// Content may be empty or contain the objective
|
|
213
|
+
expect(full.title).toBe('Q2 Goals');
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
// ===========================================================================
|
|
217
|
+
// Scenario 3: Decision & Document Workflow
|
|
218
|
+
// ===========================================================================
|
|
219
|
+
describe('Scenario 3: Decision & Document Workflow', () => {
|
|
220
|
+
it('should create decisions and track history', async () => {
|
|
221
|
+
const deps = runtime.getEntityManagementDeps();
|
|
222
|
+
const decisionDeps = runtime.getDecisionDocumentDeps();
|
|
223
|
+
// Create a decision using createEntity
|
|
224
|
+
const decision = await createEntity({
|
|
225
|
+
type: 'decision',
|
|
226
|
+
data: {
|
|
227
|
+
title: 'Use PostgreSQL for persistence',
|
|
228
|
+
context: 'We need a reliable database for production',
|
|
229
|
+
decision: 'Adopt PostgreSQL as our primary database',
|
|
230
|
+
rationale: 'PostgreSQL offers ACID compliance and excellent performance',
|
|
231
|
+
workstream: 'infrastructure',
|
|
232
|
+
decided_by: 'Engineering Team',
|
|
233
|
+
},
|
|
234
|
+
}, deps);
|
|
235
|
+
expect(decision.id).toMatch(/^DEC-\d{3}$/);
|
|
236
|
+
expect(decision.entity.title).toBe('Use PostgreSQL for persistence');
|
|
237
|
+
// Decisions are created with 'Pending' status (initial status)
|
|
238
|
+
expect(decision.entity.status).toBe('Pending');
|
|
239
|
+
// Get decision history - takes topic/workstream, not id
|
|
240
|
+
const history = await getDecisionHistory({
|
|
241
|
+
topic: 'PostgreSQL',
|
|
242
|
+
workstream: 'infrastructure',
|
|
243
|
+
}, decisionDeps);
|
|
244
|
+
expect(history.decisions.length).toBeGreaterThanOrEqual(1);
|
|
245
|
+
expect(history.decisions[0].id).toBe(decision.id);
|
|
246
|
+
});
|
|
247
|
+
it('should create decision that enables entities', async () => {
|
|
248
|
+
const deps = runtime.getEntityManagementDeps();
|
|
249
|
+
// Create a story first
|
|
250
|
+
const story = await createEntity({
|
|
251
|
+
type: 'story',
|
|
252
|
+
data: {
|
|
253
|
+
title: 'Implement caching',
|
|
254
|
+
workstream: 'engineering',
|
|
255
|
+
outcome: 'Improve performance with caching',
|
|
256
|
+
},
|
|
257
|
+
}, deps);
|
|
258
|
+
// Create decision that blocks the story using createEntity
|
|
259
|
+
const decision = await createEntity({
|
|
260
|
+
type: 'decision',
|
|
261
|
+
data: {
|
|
262
|
+
title: 'Use Redis for caching',
|
|
263
|
+
context: 'Need fast caching solution',
|
|
264
|
+
decision: 'Adopt Redis',
|
|
265
|
+
rationale: 'Redis is fast and well-supported',
|
|
266
|
+
workstream: 'engineering',
|
|
267
|
+
decided_by: 'Engineering Team',
|
|
268
|
+
blocks: [story.id],
|
|
269
|
+
},
|
|
270
|
+
}, deps);
|
|
271
|
+
expect(decision.entity.title).toBe('Use Redis for caching');
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
// ===========================================================================
|
|
275
|
+
// Scenario 4: Batch Operations
|
|
276
|
+
// ===========================================================================
|
|
277
|
+
describe('Scenario 4: Batch Operations', () => {
|
|
278
|
+
it('should perform batch create operations', async () => {
|
|
279
|
+
const batchDeps = runtime.getBatchOperationsDeps();
|
|
280
|
+
// batchUpdate with create operations
|
|
281
|
+
const result = await batchUpdate({
|
|
282
|
+
ops: [
|
|
283
|
+
{
|
|
284
|
+
op: 'create',
|
|
285
|
+
client_id: 'batch-m1',
|
|
286
|
+
type: 'milestone',
|
|
287
|
+
payload: { title: 'Batch Milestone 1', workstream: 'engineering' },
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
op: 'create',
|
|
291
|
+
client_id: 'batch-m2',
|
|
292
|
+
type: 'milestone',
|
|
293
|
+
payload: { title: 'Batch Milestone 2', workstream: 'engineering' },
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
op: 'create',
|
|
297
|
+
client_id: 'batch-s1',
|
|
298
|
+
type: 'story',
|
|
299
|
+
payload: { title: 'Batch Story 1', workstream: 'engineering', outcome: 'Test outcome' },
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
}, batchDeps);
|
|
303
|
+
expect(result.results.length).toBe(3);
|
|
304
|
+
expect(result.summary.succeeded).toBe(3);
|
|
305
|
+
});
|
|
306
|
+
it('should batch update status', async () => {
|
|
307
|
+
const deps = runtime.getEntityManagementDeps();
|
|
308
|
+
const batchDeps = runtime.getBatchOperationsDeps();
|
|
309
|
+
// Create entities
|
|
310
|
+
const m1 = await createEntity({
|
|
311
|
+
type: 'milestone',
|
|
312
|
+
data: { title: 'M1', workstream: 'eng' },
|
|
313
|
+
}, deps);
|
|
314
|
+
const m2 = await createEntity({
|
|
315
|
+
type: 'milestone',
|
|
316
|
+
data: { title: 'M2', workstream: 'eng' },
|
|
317
|
+
}, deps);
|
|
318
|
+
// Batch update status using batchUpdate
|
|
319
|
+
const result = await batchUpdate({
|
|
320
|
+
ops: [
|
|
321
|
+
{ op: 'update', client_id: 'upd-m1', id: m1.id, payload: { status: 'In Progress' } },
|
|
322
|
+
{ op: 'update', client_id: 'upd-m2', id: m2.id, payload: { status: 'In Progress' } },
|
|
323
|
+
],
|
|
324
|
+
}, batchDeps);
|
|
325
|
+
expect(result.results.length).toBe(2);
|
|
326
|
+
expect(result.summary.succeeded).toBe(2);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
// ===========================================================================
|
|
330
|
+
// Scenario 5: Project Understanding
|
|
331
|
+
// ===========================================================================
|
|
332
|
+
describe('Scenario 5: Project Understanding', () => {
|
|
333
|
+
it('should get project overview', async () => {
|
|
334
|
+
const deps = runtime.getEntityManagementDeps();
|
|
335
|
+
const projectDeps = runtime.getProjectUnderstandingDeps();
|
|
336
|
+
// Create some entities
|
|
337
|
+
await createEntity({
|
|
338
|
+
type: 'milestone',
|
|
339
|
+
data: { title: 'Q1 Goals', workstream: 'product' },
|
|
340
|
+
}, deps);
|
|
341
|
+
await createEntity({
|
|
342
|
+
type: 'story',
|
|
343
|
+
data: { title: 'Feature A', workstream: 'product', outcome: 'Test outcome' },
|
|
344
|
+
}, deps);
|
|
345
|
+
await runtime.initialize();
|
|
346
|
+
const overview = await getProjectOverview({}, projectDeps);
|
|
347
|
+
// GetProjectOverviewOutput has summary.milestones, summary.stories, etc.
|
|
348
|
+
expect(overview.summary.milestones.total).toBeGreaterThanOrEqual(1);
|
|
349
|
+
expect(overview.summary.stories.total).toBeGreaterThanOrEqual(1);
|
|
350
|
+
});
|
|
351
|
+
it('should get workstream status', async () => {
|
|
352
|
+
const deps = runtime.getEntityManagementDeps();
|
|
353
|
+
const projectDeps = runtime.getProjectUnderstandingDeps();
|
|
354
|
+
await createEntity({
|
|
355
|
+
type: 'milestone',
|
|
356
|
+
data: { title: 'Security Audit', workstream: 'security' },
|
|
357
|
+
}, deps);
|
|
358
|
+
await createEntity({
|
|
359
|
+
type: 'task',
|
|
360
|
+
data: { title: 'Pen Testing', workstream: 'security', goal: 'Run penetration tests' },
|
|
361
|
+
}, deps);
|
|
362
|
+
await runtime.initialize();
|
|
363
|
+
const status = await getWorkstreamStatus({
|
|
364
|
+
workstream: 'security',
|
|
365
|
+
}, projectDeps);
|
|
366
|
+
expect(status.workstream).toBe('security');
|
|
367
|
+
// GetWorkstreamStatusOutput has summary.total
|
|
368
|
+
expect(status.summary.total).toBeGreaterThanOrEqual(2);
|
|
369
|
+
});
|
|
370
|
+
it('should analyze project state', async () => {
|
|
371
|
+
const deps = runtime.getEntityManagementDeps();
|
|
372
|
+
const projectDeps = runtime.getProjectUnderstandingDeps();
|
|
373
|
+
// Create milestone with story
|
|
374
|
+
const milestone = await createEntity({
|
|
375
|
+
type: 'milestone',
|
|
376
|
+
data: { title: 'Release 1.0', workstream: 'engineering' },
|
|
377
|
+
}, deps);
|
|
378
|
+
await createEntity({
|
|
379
|
+
type: 'story',
|
|
380
|
+
data: { title: 'Core Feature', workstream: 'engineering', parent: milestone.id, outcome: 'Test' },
|
|
381
|
+
}, deps);
|
|
382
|
+
await runtime.initialize();
|
|
383
|
+
const analysis = await analyzeProjectState({}, projectDeps);
|
|
384
|
+
// AnalyzeProjectStateOutput has health.overall
|
|
385
|
+
expect(analysis.health.overall).toBeDefined();
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
// ===========================================================================
|
|
389
|
+
// Scenario 6: Archive & Restore Workflow
|
|
390
|
+
// ===========================================================================
|
|
391
|
+
describe('Scenario 6: Archive & Restore Workflow', () => {
|
|
392
|
+
it('should archive an entity', async () => {
|
|
393
|
+
const deps = runtime.getEntityManagementDeps();
|
|
394
|
+
// Create entity
|
|
395
|
+
const milestone = await createEntity({
|
|
396
|
+
type: 'milestone',
|
|
397
|
+
data: { title: 'Old Project', workstream: 'legacy' },
|
|
398
|
+
}, deps);
|
|
399
|
+
// Update to In Progress first, then Completed (valid transition)
|
|
400
|
+
await updateEntityStatus({
|
|
401
|
+
id: milestone.id,
|
|
402
|
+
status: 'In Progress',
|
|
403
|
+
}, deps);
|
|
404
|
+
await updateEntityStatus({
|
|
405
|
+
id: milestone.id,
|
|
406
|
+
status: 'Completed',
|
|
407
|
+
}, deps);
|
|
408
|
+
// Archive
|
|
409
|
+
const archiveResult = await archiveEntity({
|
|
410
|
+
id: milestone.id,
|
|
411
|
+
force: true,
|
|
412
|
+
}, deps);
|
|
413
|
+
expect(archiveResult.archived).toBe(true);
|
|
414
|
+
expect(archiveResult.archive_path).toContain('archive');
|
|
415
|
+
// Verify entity is archived
|
|
416
|
+
const entity = await deps.getEntity(milestone.id);
|
|
417
|
+
expect(entity?.archived).toBe(true);
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
// ===========================================================================
|
|
421
|
+
// Scenario 8: Dependencies & Relationships
|
|
422
|
+
// ===========================================================================
|
|
423
|
+
describe('Scenario 8: Dependencies & Relationships', () => {
|
|
424
|
+
it('should create entities with dependencies', async () => {
|
|
425
|
+
const deps = runtime.getEntityManagementDeps();
|
|
426
|
+
const searchDeps = runtime.getSearchNavigationDeps();
|
|
427
|
+
// Create decision
|
|
428
|
+
await createEntity({
|
|
429
|
+
type: 'decision',
|
|
430
|
+
data: {
|
|
431
|
+
title: 'Use TypeScript',
|
|
432
|
+
workstream: 'engineering',
|
|
433
|
+
context: 'Need type safety',
|
|
434
|
+
decision: 'Adopt TypeScript',
|
|
435
|
+
rationale: 'Better developer experience',
|
|
436
|
+
},
|
|
437
|
+
}, deps);
|
|
438
|
+
// Create story
|
|
439
|
+
const story = await createEntity({
|
|
440
|
+
type: 'story',
|
|
441
|
+
data: {
|
|
442
|
+
title: 'Refactor to TypeScript',
|
|
443
|
+
workstream: 'engineering',
|
|
444
|
+
outcome: 'Codebase uses TypeScript',
|
|
445
|
+
},
|
|
446
|
+
}, deps);
|
|
447
|
+
// Verify entities were created
|
|
448
|
+
const full = await getEntity({
|
|
449
|
+
id: story.id,
|
|
450
|
+
fields: ['id', 'title', 'type', 'status', 'workstream'],
|
|
451
|
+
}, searchDeps);
|
|
452
|
+
expect(full.id).toBe(story.id);
|
|
453
|
+
expect(full.title).toBe('Refactor to TypeScript');
|
|
454
|
+
});
|
|
455
|
+
it('should handle implements relationship', async () => {
|
|
456
|
+
const deps = runtime.getEntityManagementDeps();
|
|
457
|
+
// Create document
|
|
458
|
+
const doc = await createEntity({
|
|
459
|
+
type: 'document',
|
|
460
|
+
data: {
|
|
461
|
+
title: 'API Specification',
|
|
462
|
+
workstream: 'engineering',
|
|
463
|
+
doc_type: 'spec',
|
|
464
|
+
content: 'API design document',
|
|
465
|
+
},
|
|
466
|
+
}, deps);
|
|
467
|
+
// Create story that implements the document
|
|
468
|
+
const story = await createEntity({
|
|
469
|
+
type: 'story',
|
|
470
|
+
data: {
|
|
471
|
+
title: 'Implement API',
|
|
472
|
+
workstream: 'engineering',
|
|
473
|
+
implements: [doc.id],
|
|
474
|
+
outcome: 'API matches specification',
|
|
475
|
+
},
|
|
476
|
+
}, deps);
|
|
477
|
+
expect(story.entity.title).toBe('Implement API');
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
// ===========================================================================
|
|
481
|
+
// Scenario 9: Error Handling
|
|
482
|
+
// ===========================================================================
|
|
483
|
+
describe('Scenario 9: Error Handling', () => {
|
|
484
|
+
it('should handle invalid entity ID', async () => {
|
|
485
|
+
const searchDeps = runtime.getSearchNavigationDeps();
|
|
486
|
+
await expect(getEntity({
|
|
487
|
+
id: 'INVALID-999',
|
|
488
|
+
}, searchDeps)).rejects.toThrow();
|
|
489
|
+
});
|
|
490
|
+
it('should handle invalid status transition', async () => {
|
|
491
|
+
const deps = runtime.getEntityManagementDeps();
|
|
492
|
+
const milestone = await createEntity({
|
|
493
|
+
type: 'milestone',
|
|
494
|
+
data: { title: 'Test', workstream: 'test' },
|
|
495
|
+
}, deps);
|
|
496
|
+
// Try to go directly to Completed (should fail - need In Progress first)
|
|
497
|
+
await expect(updateEntityStatus({
|
|
498
|
+
id: milestone.id,
|
|
499
|
+
status: 'Completed',
|
|
500
|
+
}, deps)).rejects.toThrow();
|
|
501
|
+
});
|
|
502
|
+
it('should handle missing required fields', async () => {
|
|
503
|
+
const deps = runtime.getEntityManagementDeps();
|
|
504
|
+
await expect(createEntity({
|
|
505
|
+
type: 'milestone',
|
|
506
|
+
data: {
|
|
507
|
+
// Missing title
|
|
508
|
+
workstream: 'test',
|
|
509
|
+
},
|
|
510
|
+
}, deps)).rejects.toThrow();
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
// ===========================================================================
|
|
514
|
+
// Scenario 10: Status Cascade
|
|
515
|
+
// ===========================================================================
|
|
516
|
+
describe('Scenario 10: Status Cascade', () => {
|
|
517
|
+
it('should cascade status updates to children', async () => {
|
|
518
|
+
const deps = runtime.getEntityManagementDeps();
|
|
519
|
+
// Create hierarchy
|
|
520
|
+
const milestone = await createEntity({
|
|
521
|
+
type: 'milestone',
|
|
522
|
+
data: { title: 'Parent Milestone', workstream: 'eng' },
|
|
523
|
+
}, deps);
|
|
524
|
+
const story = await createEntity({
|
|
525
|
+
type: 'story',
|
|
526
|
+
data: { title: 'Child Story', workstream: 'eng', parent: milestone.id },
|
|
527
|
+
}, deps);
|
|
528
|
+
const task = await createEntity({
|
|
529
|
+
type: 'task',
|
|
530
|
+
data: { title: 'Grandchild Task', workstream: 'eng', parent: story.id, goal: 'Do work' },
|
|
531
|
+
}, deps);
|
|
532
|
+
// Update milestone to In Progress
|
|
533
|
+
await updateEntityStatus({
|
|
534
|
+
id: milestone.id,
|
|
535
|
+
status: 'In Progress',
|
|
536
|
+
}, deps);
|
|
537
|
+
// Update story to In Progress
|
|
538
|
+
await updateEntityStatus({
|
|
539
|
+
id: story.id,
|
|
540
|
+
status: 'In Progress',
|
|
541
|
+
}, deps);
|
|
542
|
+
// Update task to Completed
|
|
543
|
+
await updateEntityStatus({
|
|
544
|
+
id: task.id,
|
|
545
|
+
status: 'In Progress',
|
|
546
|
+
}, deps);
|
|
547
|
+
await updateEntityStatus({
|
|
548
|
+
id: task.id,
|
|
549
|
+
status: 'Completed',
|
|
550
|
+
}, deps);
|
|
551
|
+
// Verify task is completed
|
|
552
|
+
const taskEntity = await deps.getEntity(task.id);
|
|
553
|
+
expect(taskEntity?.status).toBe('Completed');
|
|
554
|
+
});
|
|
555
|
+
});
|
|
556
|
+
// ===========================================================================
|
|
557
|
+
// Scenario 11: Extended Archive Operations
|
|
558
|
+
// ===========================================================================
|
|
559
|
+
describe('Scenario 11: Extended Archive Operations', () => {
|
|
560
|
+
it('should archive milestone with children', async () => {
|
|
561
|
+
const deps = runtime.getEntityManagementDeps();
|
|
562
|
+
// Create hierarchy
|
|
563
|
+
const milestone = await createEntity({
|
|
564
|
+
type: 'milestone',
|
|
565
|
+
data: { title: 'Archive Test Milestone', workstream: 'archive-test' },
|
|
566
|
+
}, deps);
|
|
567
|
+
const story = await createEntity({
|
|
568
|
+
type: 'story',
|
|
569
|
+
data: { title: 'Archive Test Story', workstream: 'archive-test', parent: milestone.id },
|
|
570
|
+
}, deps);
|
|
571
|
+
const task = await createEntity({
|
|
572
|
+
type: 'task',
|
|
573
|
+
data: { title: 'Archive Test Task', workstream: 'archive-test', parent: story.id, goal: 'Test' },
|
|
574
|
+
}, deps);
|
|
575
|
+
// Complete the hierarchy (required for archiving)
|
|
576
|
+
await updateEntityStatus({ id: milestone.id, status: 'In Progress' }, deps);
|
|
577
|
+
await updateEntityStatus({ id: story.id, status: 'In Progress' }, deps);
|
|
578
|
+
await updateEntityStatus({ id: task.id, status: 'In Progress' }, deps);
|
|
579
|
+
await updateEntityStatus({ id: task.id, status: 'Completed' }, deps);
|
|
580
|
+
await updateEntityStatus({ id: story.id, status: 'Completed' }, deps);
|
|
581
|
+
await updateEntityStatus({ id: milestone.id, status: 'Completed' }, deps);
|
|
582
|
+
// Archive the milestone
|
|
583
|
+
const archiveResult = await archiveEntity({
|
|
584
|
+
id: milestone.id,
|
|
585
|
+
force: true,
|
|
586
|
+
}, deps);
|
|
587
|
+
expect(archiveResult.archived).toBe(true);
|
|
588
|
+
});
|
|
589
|
+
it('should restore archived entity', async () => {
|
|
590
|
+
const deps = runtime.getEntityManagementDeps();
|
|
591
|
+
// Create and complete entity
|
|
592
|
+
const milestone = await createEntity({
|
|
593
|
+
type: 'milestone',
|
|
594
|
+
data: { title: 'Restore Test', workstream: 'restore-test' },
|
|
595
|
+
}, deps);
|
|
596
|
+
await updateEntityStatus({ id: milestone.id, status: 'In Progress' }, deps);
|
|
597
|
+
await updateEntityStatus({ id: milestone.id, status: 'Completed' }, deps);
|
|
598
|
+
// Archive
|
|
599
|
+
await archiveEntity({ id: milestone.id, force: true }, deps);
|
|
600
|
+
// Restore
|
|
601
|
+
const restoreResult = await restoreFromArchive({
|
|
602
|
+
id: milestone.id,
|
|
603
|
+
restore_children: false,
|
|
604
|
+
}, deps);
|
|
605
|
+
expect(restoreResult.restored).toBe(true);
|
|
606
|
+
// Verify entity is no longer archived
|
|
607
|
+
const entity = await deps.getEntity(milestone.id);
|
|
608
|
+
expect(entity?.archived).toBe(false);
|
|
609
|
+
});
|
|
610
|
+
});
|
|
611
|
+
// ===========================================================================
|
|
612
|
+
// Scenario 12: Extended Decision & Document Operations
|
|
613
|
+
// ===========================================================================
|
|
614
|
+
describe('Scenario 12: Extended Decision & Document Operations', () => {
|
|
615
|
+
it('should create decision with blocks relationship', async () => {
|
|
616
|
+
const deps = runtime.getEntityManagementDeps();
|
|
617
|
+
// Create a story first
|
|
618
|
+
const story = await createEntity({
|
|
619
|
+
type: 'story',
|
|
620
|
+
data: { title: 'Story to Enable', workstream: 'decisions' },
|
|
621
|
+
}, deps);
|
|
622
|
+
// Create decision that blocks the story using createEntity
|
|
623
|
+
const decision = await createEntity({
|
|
624
|
+
type: 'decision',
|
|
625
|
+
data: {
|
|
626
|
+
title: 'Enable Story Decision',
|
|
627
|
+
context: 'We need to decide on the approach',
|
|
628
|
+
decision: 'Use approach A',
|
|
629
|
+
rationale: 'It is simpler',
|
|
630
|
+
workstream: 'decisions',
|
|
631
|
+
decided_by: 'team',
|
|
632
|
+
blocks: [story.id],
|
|
633
|
+
},
|
|
634
|
+
}, deps);
|
|
635
|
+
expect(decision.id).toMatch(/^DEC-\d{3}$/);
|
|
636
|
+
});
|
|
637
|
+
it('should get decision history by workstream', async () => {
|
|
638
|
+
const deps = runtime.getEntityManagementDeps();
|
|
639
|
+
const decisionDeps = runtime.getDecisionDocumentDeps();
|
|
640
|
+
// Create multiple decisions using createEntity
|
|
641
|
+
await createEntity({
|
|
642
|
+
type: 'decision',
|
|
643
|
+
data: {
|
|
644
|
+
title: 'Decision 1',
|
|
645
|
+
context: 'Context 1',
|
|
646
|
+
decision: 'Decision 1',
|
|
647
|
+
rationale: 'Rationale 1',
|
|
648
|
+
workstream: 'history-test',
|
|
649
|
+
decided_by: 'team',
|
|
650
|
+
},
|
|
651
|
+
}, deps);
|
|
652
|
+
await createEntity({
|
|
653
|
+
type: 'decision',
|
|
654
|
+
data: {
|
|
655
|
+
title: 'Decision 2',
|
|
656
|
+
context: 'Context 2',
|
|
657
|
+
decision: 'Decision 2',
|
|
658
|
+
rationale: 'Rationale 2',
|
|
659
|
+
workstream: 'history-test',
|
|
660
|
+
decided_by: 'team',
|
|
661
|
+
},
|
|
662
|
+
}, deps);
|
|
663
|
+
// Get history
|
|
664
|
+
const history = await getDecisionHistory({
|
|
665
|
+
workstream: 'history-test',
|
|
666
|
+
}, decisionDeps);
|
|
667
|
+
expect(history.decisions.length).toBeGreaterThanOrEqual(2);
|
|
668
|
+
});
|
|
669
|
+
});
|
|
670
|
+
// ===========================================================================
|
|
671
|
+
// Scenario 13: Extended Batch Operations
|
|
672
|
+
// ===========================================================================
|
|
673
|
+
describe('Scenario 13: Extended Batch Operations', () => {
|
|
674
|
+
it('should create entities with dependencies in batch', async () => {
|
|
675
|
+
const batchDeps = runtime.getBatchOperationsDeps();
|
|
676
|
+
const result = await batchUpdate({
|
|
677
|
+
ops: [
|
|
678
|
+
{
|
|
679
|
+
op: 'create',
|
|
680
|
+
client_id: 'batch-m',
|
|
681
|
+
type: 'milestone',
|
|
682
|
+
payload: { title: 'Batch Milestone', workstream: 'batch-deps' },
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
op: 'create',
|
|
686
|
+
client_id: 'batch-s',
|
|
687
|
+
type: 'story',
|
|
688
|
+
payload: { title: 'Batch Story', workstream: 'batch-deps', depends_on: ['@batch-m'] },
|
|
689
|
+
},
|
|
690
|
+
],
|
|
691
|
+
}, batchDeps);
|
|
692
|
+
expect(result.results.length).toBe(2);
|
|
693
|
+
expect(result.summary.succeeded).toBe(2);
|
|
694
|
+
});
|
|
695
|
+
it('should batch update multiple entity statuses', async () => {
|
|
696
|
+
const deps = runtime.getEntityManagementDeps();
|
|
697
|
+
const batchDeps = runtime.getBatchOperationsDeps();
|
|
698
|
+
// Create entities
|
|
699
|
+
const m1 = await createEntity({
|
|
700
|
+
type: 'milestone',
|
|
701
|
+
data: { title: 'Batch Status M1', workstream: 'batch-status' },
|
|
702
|
+
}, deps);
|
|
703
|
+
const m2 = await createEntity({
|
|
704
|
+
type: 'milestone',
|
|
705
|
+
data: { title: 'Batch Status M2', workstream: 'batch-status' },
|
|
706
|
+
}, deps);
|
|
707
|
+
// Batch update to In Progress using batchUpdate
|
|
708
|
+
const result = await batchUpdate({
|
|
709
|
+
ops: [
|
|
710
|
+
{ op: 'update', client_id: 'upd-m1', id: m1.id, payload: { status: 'In Progress' } },
|
|
711
|
+
{ op: 'update', client_id: 'upd-m2', id: m2.id, payload: { status: 'In Progress' } },
|
|
712
|
+
],
|
|
713
|
+
}, batchDeps);
|
|
714
|
+
expect(result.results.length).toBe(2);
|
|
715
|
+
expect(result.summary.succeeded).toBe(2);
|
|
716
|
+
// Verify statuses
|
|
717
|
+
const entity1 = await deps.getEntity(m1.id);
|
|
718
|
+
const entity2 = await deps.getEntity(m2.id);
|
|
719
|
+
expect(entity1?.status).toBe('In Progress');
|
|
720
|
+
expect(entity2?.status).toBe('In Progress');
|
|
721
|
+
});
|
|
722
|
+
});
|
|
723
|
+
// ===========================================================================
|
|
724
|
+
// Scenario 14: Navigate Hierarchy
|
|
725
|
+
// ===========================================================================
|
|
726
|
+
describe('Scenario 14: Navigate Hierarchy', () => {
|
|
727
|
+
it('should navigate up the hierarchy', async () => {
|
|
728
|
+
const deps = runtime.getEntityManagementDeps();
|
|
729
|
+
const searchDeps = runtime.getSearchNavigationDeps();
|
|
730
|
+
// Create hierarchy
|
|
731
|
+
const milestone = await createEntity({
|
|
732
|
+
type: 'milestone',
|
|
733
|
+
data: { title: 'Nav Milestone', workstream: 'nav' },
|
|
734
|
+
}, deps);
|
|
735
|
+
const story = await createEntity({
|
|
736
|
+
type: 'story',
|
|
737
|
+
data: { title: 'Nav Story', workstream: 'nav', parent: milestone.id },
|
|
738
|
+
}, deps);
|
|
739
|
+
const task = await createEntity({
|
|
740
|
+
type: 'task',
|
|
741
|
+
data: { title: 'Nav Task', workstream: 'nav', parent: story.id, goal: 'Navigate' },
|
|
742
|
+
}, deps);
|
|
743
|
+
// Navigate up from task using searchEntities
|
|
744
|
+
const upResult = await searchEntities({
|
|
745
|
+
from_id: task.id,
|
|
746
|
+
direction: 'up',
|
|
747
|
+
depth: 2,
|
|
748
|
+
}, searchDeps);
|
|
749
|
+
expect(upResult.results.length).toBeGreaterThanOrEqual(1);
|
|
750
|
+
});
|
|
751
|
+
it('should navigate down the hierarchy', async () => {
|
|
752
|
+
const deps = runtime.getEntityManagementDeps();
|
|
753
|
+
const searchDeps = runtime.getSearchNavigationDeps();
|
|
754
|
+
// Create hierarchy
|
|
755
|
+
const milestone = await createEntity({
|
|
756
|
+
type: 'milestone',
|
|
757
|
+
data: { title: 'Nav Down Milestone', workstream: 'nav-down' },
|
|
758
|
+
}, deps);
|
|
759
|
+
await createEntity({
|
|
760
|
+
type: 'story',
|
|
761
|
+
data: { title: 'Nav Down Story 1', workstream: 'nav-down', parent: milestone.id },
|
|
762
|
+
}, deps);
|
|
763
|
+
await createEntity({
|
|
764
|
+
type: 'story',
|
|
765
|
+
data: { title: 'Nav Down Story 2', workstream: 'nav-down', parent: milestone.id },
|
|
766
|
+
}, deps);
|
|
767
|
+
// Navigate down from milestone using searchEntities
|
|
768
|
+
const downResult = await searchEntities({
|
|
769
|
+
from_id: milestone.id,
|
|
770
|
+
direction: 'down',
|
|
771
|
+
depth: 1,
|
|
772
|
+
}, searchDeps);
|
|
773
|
+
expect(downResult.results.length).toBeGreaterThanOrEqual(2);
|
|
774
|
+
});
|
|
775
|
+
it('should get siblings', async () => {
|
|
776
|
+
const deps = runtime.getEntityManagementDeps();
|
|
777
|
+
const searchDeps = runtime.getSearchNavigationDeps();
|
|
778
|
+
// Create parent and siblings
|
|
779
|
+
const milestone = await createEntity({
|
|
780
|
+
type: 'milestone',
|
|
781
|
+
data: { title: 'Sibling Parent', workstream: 'siblings' },
|
|
782
|
+
}, deps);
|
|
783
|
+
const story1 = await createEntity({
|
|
784
|
+
type: 'story',
|
|
785
|
+
data: { title: 'Sibling 1', workstream: 'siblings', parent: milestone.id },
|
|
786
|
+
}, deps);
|
|
787
|
+
await createEntity({
|
|
788
|
+
type: 'story',
|
|
789
|
+
data: { title: 'Sibling 2', workstream: 'siblings', parent: milestone.id },
|
|
790
|
+
}, deps);
|
|
791
|
+
// Get siblings of story1 using searchEntities
|
|
792
|
+
const siblingsResult = await searchEntities({
|
|
793
|
+
from_id: story1.id,
|
|
794
|
+
direction: 'siblings',
|
|
795
|
+
}, searchDeps);
|
|
796
|
+
expect(siblingsResult.results.length).toBeGreaterThanOrEqual(1);
|
|
797
|
+
});
|
|
798
|
+
});
|
|
799
|
+
// ===========================================================================
|
|
800
|
+
// Scenario 15: Efficiency Improvements
|
|
801
|
+
// ===========================================================================
|
|
802
|
+
describe('Scenario 15: Efficiency Improvements', () => {
|
|
803
|
+
it('should return entities with batch_update when include_entities=true', async () => {
|
|
804
|
+
const deps = runtime.getEntityManagementDeps();
|
|
805
|
+
const batchDeps = runtime.getBatchOperationsDeps();
|
|
806
|
+
// Create a story first
|
|
807
|
+
const story = await createEntity({
|
|
808
|
+
type: 'story',
|
|
809
|
+
data: { title: 'Batch Test Story', workstream: 'batch-test' },
|
|
810
|
+
}, deps);
|
|
811
|
+
// Update with include_entities=true
|
|
812
|
+
const result = await batchUpdate({
|
|
813
|
+
ops: [
|
|
814
|
+
{ client_id: 'u1', op: 'update', id: story.id, payload: { status: 'In Progress' } },
|
|
815
|
+
],
|
|
816
|
+
options: { include_entities: true },
|
|
817
|
+
}, batchDeps);
|
|
818
|
+
expect(result.summary.succeeded).toBe(1);
|
|
819
|
+
expect(result.results[0].entity).toBeDefined();
|
|
820
|
+
expect(result.results[0].entity?.id).toBe(story.id);
|
|
821
|
+
expect(result.results[0].entity?.title).toBe('Batch Test Story');
|
|
822
|
+
});
|
|
823
|
+
it('should NOT return entities with batch_update when include_entities=false', async () => {
|
|
824
|
+
const deps = runtime.getEntityManagementDeps();
|
|
825
|
+
const batchDeps = runtime.getBatchOperationsDeps();
|
|
826
|
+
// Create a story first
|
|
827
|
+
const story = await createEntity({
|
|
828
|
+
type: 'story',
|
|
829
|
+
data: { title: 'Batch Test Story 2', workstream: 'batch-test' },
|
|
830
|
+
}, deps);
|
|
831
|
+
// Update with include_entities=false (default)
|
|
832
|
+
const result = await batchUpdate({
|
|
833
|
+
ops: [
|
|
834
|
+
{ client_id: 'u1', op: 'update', id: story.id, payload: { status: 'In Progress' } },
|
|
835
|
+
],
|
|
836
|
+
options: { include_entities: false },
|
|
837
|
+
}, batchDeps);
|
|
838
|
+
expect(result.summary.succeeded).toBe(1);
|
|
839
|
+
expect(result.results[0].entity).toBeUndefined();
|
|
840
|
+
});
|
|
841
|
+
it('should filter fields with batch_update when fields option is provided', async () => {
|
|
842
|
+
const deps = runtime.getEntityManagementDeps();
|
|
843
|
+
const batchDeps = runtime.getBatchOperationsDeps();
|
|
844
|
+
// Create a story first
|
|
845
|
+
const story = await createEntity({
|
|
846
|
+
type: 'story',
|
|
847
|
+
data: { title: 'Batch Field Test', workstream: 'batch-test' },
|
|
848
|
+
}, deps);
|
|
849
|
+
// Update with include_entities=true and specific fields
|
|
850
|
+
const result = await batchUpdate({
|
|
851
|
+
ops: [
|
|
852
|
+
{ client_id: 'u1', op: 'update', id: story.id, payload: { status: 'In Progress' } },
|
|
853
|
+
],
|
|
854
|
+
options: { include_entities: true, fields: ['id', 'title', 'status'] },
|
|
855
|
+
}, batchDeps);
|
|
856
|
+
expect(result.summary.succeeded).toBe(1);
|
|
857
|
+
expect(result.results[0].entity).toBeDefined();
|
|
858
|
+
expect(result.results[0].entity?.id).toBe(story.id);
|
|
859
|
+
expect(result.results[0].entity?.title).toBe('Batch Field Test');
|
|
860
|
+
// Should not have content since we only requested id, title, status
|
|
861
|
+
expect(result.results[0].entity?.content).toBeUndefined();
|
|
862
|
+
});
|
|
863
|
+
it('should preview changes with batch_update dry_run=true', async () => {
|
|
864
|
+
const deps = runtime.getEntityManagementDeps();
|
|
865
|
+
const batchDeps = runtime.getBatchOperationsDeps();
|
|
866
|
+
// Create a story first
|
|
867
|
+
const story = await createEntity({
|
|
868
|
+
type: 'story',
|
|
869
|
+
data: { title: 'Dry Run Test', workstream: 'dry-run-test', priority: 'Medium' },
|
|
870
|
+
}, deps);
|
|
871
|
+
// Update with dry_run=true - test multiple fields including non-standard ones
|
|
872
|
+
const result = await batchUpdate({
|
|
873
|
+
ops: [
|
|
874
|
+
{ client_id: 'u1', op: 'update', id: story.id, payload: { title: 'Changed Title', priority: 'High' } },
|
|
875
|
+
],
|
|
876
|
+
options: { dry_run: true },
|
|
877
|
+
}, batchDeps);
|
|
878
|
+
// Should have dry_run flag and would_update array
|
|
879
|
+
expect(result.dry_run).toBe(true);
|
|
880
|
+
expect(result.would_update).toBeDefined();
|
|
881
|
+
expect(result.would_update?.length).toBe(1);
|
|
882
|
+
expect(result.would_update?.[0].client_id).toBe('u1');
|
|
883
|
+
expect(result.would_update?.[0].id).toBe(story.id);
|
|
884
|
+
expect(result.would_update?.[0].op).toBe('update');
|
|
885
|
+
expect(result.would_update?.[0].changes.length).toBeGreaterThan(0);
|
|
886
|
+
// Verify the changes array contains the actual field changes
|
|
887
|
+
const titleChange = result.would_update?.[0].changes.find(c => c.field === 'title');
|
|
888
|
+
expect(titleChange).toBeDefined();
|
|
889
|
+
expect(titleChange?.before).toBe('Dry Run Test');
|
|
890
|
+
expect(titleChange?.after).toBe('Changed Title');
|
|
891
|
+
const priorityChange = result.would_update?.[0].changes.find(c => c.field === 'priority');
|
|
892
|
+
expect(priorityChange).toBeDefined();
|
|
893
|
+
expect(priorityChange?.before).toBe('Medium');
|
|
894
|
+
expect(priorityChange?.after).toBe('High');
|
|
895
|
+
// Verify entity was NOT actually updated - title should still be 'Dry Run Test'
|
|
896
|
+
const entityAfter = await deps.getEntity(story.id);
|
|
897
|
+
expect(entityAfter?.title).toBe('Dry Run Test');
|
|
898
|
+
});
|
|
899
|
+
it('should return detailed changes with reconcile_relationships', async () => {
|
|
900
|
+
// Test reconcile_relationships returns enhanced output
|
|
901
|
+
const result = await runtime.reconcileImplementsRelationships({ dry_run: true });
|
|
902
|
+
// Should have the new output format
|
|
903
|
+
expect(result).toHaveProperty('scanned');
|
|
904
|
+
expect(result).toHaveProperty('updated');
|
|
905
|
+
expect(result).toHaveProperty('dry_run');
|
|
906
|
+
expect(result).toHaveProperty('changes');
|
|
907
|
+
expect(result).toHaveProperty('warnings');
|
|
908
|
+
expect(result).toHaveProperty('details'); // Legacy format
|
|
909
|
+
// dry_run should be true
|
|
910
|
+
expect(result.dry_run).toBe(true);
|
|
911
|
+
// updated should be 0 in dry_run mode
|
|
912
|
+
expect(result.updated).toBe(0);
|
|
913
|
+
});
|
|
914
|
+
it('should return changes array with update_entity', async () => {
|
|
915
|
+
const deps = runtime.getEntityManagementDeps();
|
|
916
|
+
// Create a story
|
|
917
|
+
const story = await createEntity({
|
|
918
|
+
type: 'story',
|
|
919
|
+
data: {
|
|
920
|
+
title: 'Changes Test Story',
|
|
921
|
+
workstream: 'core',
|
|
922
|
+
status: 'Not Started',
|
|
923
|
+
priority: 'Medium',
|
|
924
|
+
},
|
|
925
|
+
}, deps);
|
|
926
|
+
// Update the story
|
|
927
|
+
const result = await updateEntity({
|
|
928
|
+
id: story.id,
|
|
929
|
+
data: { title: 'Updated Title', priority: 'High' },
|
|
930
|
+
}, deps);
|
|
931
|
+
// Should have changes array
|
|
932
|
+
expect(result.changes).toBeDefined();
|
|
933
|
+
expect(result.changes.length).toBeGreaterThan(0);
|
|
934
|
+
// Find title change
|
|
935
|
+
const titleChange = result.changes.find(c => c.field === 'title');
|
|
936
|
+
expect(titleChange).toBeDefined();
|
|
937
|
+
expect(titleChange.before).toBe('Changes Test Story');
|
|
938
|
+
expect(titleChange.after).toBe('Updated Title');
|
|
939
|
+
// Find priority change
|
|
940
|
+
const priorityChange = result.changes.find(c => c.field === 'priority');
|
|
941
|
+
expect(priorityChange).toBeDefined();
|
|
942
|
+
expect(priorityChange.before).toBe('Medium');
|
|
943
|
+
expect(priorityChange.after).toBe('High');
|
|
944
|
+
});
|
|
945
|
+
it('should not duplicate dataview blocks on feature update', async () => {
|
|
946
|
+
const deps = runtime.getEntityManagementDeps();
|
|
947
|
+
// Create a feature
|
|
948
|
+
const feature = await createEntity({
|
|
949
|
+
type: 'feature',
|
|
950
|
+
data: {
|
|
951
|
+
title: 'Dataview Test Feature',
|
|
952
|
+
workstream: 'core',
|
|
953
|
+
status: 'Planned',
|
|
954
|
+
tier: 'OSS',
|
|
955
|
+
phase: 'MVP',
|
|
956
|
+
user_story: 'As a user, I want to test dataview blocks',
|
|
957
|
+
},
|
|
958
|
+
}, deps);
|
|
959
|
+
// Update the feature multiple times
|
|
960
|
+
await updateEntity({
|
|
961
|
+
id: feature.id,
|
|
962
|
+
data: { tier: 'Premium' },
|
|
963
|
+
}, deps);
|
|
964
|
+
await updateEntity({
|
|
965
|
+
id: feature.id,
|
|
966
|
+
data: { phase: 'GA' },
|
|
967
|
+
}, deps);
|
|
968
|
+
// Get the entity content
|
|
969
|
+
const entity = await deps.getEntity(feature.id);
|
|
970
|
+
expect(entity).toBeDefined();
|
|
971
|
+
// Check that dataview blocks are not duplicated
|
|
972
|
+
// The content should have at most one instance of each dataview section
|
|
973
|
+
const content = entity.content || '';
|
|
974
|
+
const implementedByMatches = content.match(/## 🔗 Implemented By/g);
|
|
975
|
+
expect(implementedByMatches?.length || 0).toBeLessThanOrEqual(1);
|
|
976
|
+
});
|
|
977
|
+
});
|
|
978
|
+
});
|
|
979
|
+
//# sourceMappingURL=integration.test.js.map
|