obsidian-accomplishments-mcp 0.1.10 → 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 -331
- 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 -296
- 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 -42
- package/dist/tools/get-accomplishment.d.ts.map +0 -1
- package/dist/tools/get-accomplishment.js +0 -93
- 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 -42
- package/dist/tools/list-accomplishments.d.ts.map +0 -1
- package/dist/tools/list-accomplishments.js +0 -40
- 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,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cycle Detector Service
|
|
3
|
+
*
|
|
4
|
+
* Detects cycles in entity dependency graphs using DFS.
|
|
5
|
+
* Operates in strict mode: rejects entity creation/update if cycle detected.
|
|
6
|
+
*
|
|
7
|
+
* Algorithm:
|
|
8
|
+
* 1. Build dependency graph from entities
|
|
9
|
+
* 2. Use DFS with coloring (white/gray/black) to detect back edges
|
|
10
|
+
* 3. If cycle detected, return the cycle path and suggestions for breaking it
|
|
11
|
+
*/
|
|
12
|
+
import { EntityId, Entity } from '../../models/v2-types.js';
|
|
13
|
+
export interface CycleDetectionResult {
|
|
14
|
+
/** Whether a cycle was detected */
|
|
15
|
+
hasCycle: boolean;
|
|
16
|
+
/** The cycle path if detected (e.g., [A, B, C, A]) */
|
|
17
|
+
cyclePath?: EntityId[];
|
|
18
|
+
/** Human-readable message describing the cycle */
|
|
19
|
+
message?: string;
|
|
20
|
+
/** Suggestions for breaking the cycle */
|
|
21
|
+
suggestions?: CycleBreakSuggestion[];
|
|
22
|
+
}
|
|
23
|
+
export interface CycleBreakSuggestion {
|
|
24
|
+
/** The dependency to remove */
|
|
25
|
+
removeEdge: {
|
|
26
|
+
from: EntityId;
|
|
27
|
+
to: EntityId;
|
|
28
|
+
};
|
|
29
|
+
/** Priority of this suggestion (lower is better) */
|
|
30
|
+
priority: number;
|
|
31
|
+
/** Reason for this suggestion */
|
|
32
|
+
reason: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Detects cycles in dependency graphs using DFS.
|
|
36
|
+
*/
|
|
37
|
+
export declare class CycleDetector {
|
|
38
|
+
/**
|
|
39
|
+
* Check if adding a dependency would create a cycle.
|
|
40
|
+
*
|
|
41
|
+
* @param fromId - Entity that would depend on toId
|
|
42
|
+
* @param toId - Entity that fromId would depend on
|
|
43
|
+
* @param getDependencies - Function to get dependencies of an entity
|
|
44
|
+
* @returns CycleDetectionResult with cycle info if detected
|
|
45
|
+
*/
|
|
46
|
+
wouldCreateCycle(fromId: EntityId, toId: EntityId, getDependencies: (id: EntityId) => EntityId[]): CycleDetectionResult;
|
|
47
|
+
/**
|
|
48
|
+
* Detect cycles in the entire dependency graph.
|
|
49
|
+
*
|
|
50
|
+
* @param entities - All entities to check
|
|
51
|
+
* @param getDependencies - Function to get dependencies of an entity
|
|
52
|
+
* @returns CycleDetectionResult with first cycle found, if any
|
|
53
|
+
*/
|
|
54
|
+
detectCycles(entities: Entity[], getDependencies: (id: EntityId) => EntityId[]): CycleDetectionResult;
|
|
55
|
+
/**
|
|
56
|
+
* DFS to check if target is reachable from current.
|
|
57
|
+
*/
|
|
58
|
+
private dfsCanReach;
|
|
59
|
+
/**
|
|
60
|
+
* DFS to find a cycle starting from the given node.
|
|
61
|
+
* Uses three-color algorithm (white/gray/black).
|
|
62
|
+
*/
|
|
63
|
+
private dfsFindCycle;
|
|
64
|
+
/**
|
|
65
|
+
* Reconstruct the cycle path from parent pointers.
|
|
66
|
+
*/
|
|
67
|
+
private reconstructCycle;
|
|
68
|
+
/**
|
|
69
|
+
* Generate suggestions for breaking a cycle.
|
|
70
|
+
* Uses priority rules based on entity types.
|
|
71
|
+
*/
|
|
72
|
+
private generateBreakSuggestions;
|
|
73
|
+
}
|
|
74
|
+
/** Default detector instance */
|
|
75
|
+
export declare const cycleDetector: CycleDetector;
|
|
76
|
+
//# sourceMappingURL=cycle-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cycle-detector.d.ts","sourceRoot":"","sources":["../../../src/services/v2/cycle-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAmC,MAAM,0BAA0B,CAAC;AAM7F,MAAM,WAAW,oBAAoB;IACnC,mCAAmC;IACnC,QAAQ,EAAE,OAAO,CAAC;IAClB,sDAAsD;IACtD,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yCAAyC;IACzC,WAAW,CAAC,EAAE,oBAAoB,EAAE,CAAC;CACtC;AAED,MAAM,WAAW,oBAAoB;IACnC,+BAA+B;IAC/B,UAAU,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,EAAE,EAAE,QAAQ,CAAA;KAAE,CAAC;IAC7C,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;CAChB;AAgBD;;GAEG;AACH,qBAAa,aAAa;IACxB;;;;;;;OAOG;IACH,gBAAgB,CACd,MAAM,EAAE,QAAQ,EAChB,IAAI,EAAE,QAAQ,EACd,eAAe,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,QAAQ,EAAE,GAC5C,oBAAoB;IAqBvB;;;;;;OAMG;IACH,YAAY,CACV,QAAQ,EAAE,MAAM,EAAE,EAClB,eAAe,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,QAAQ,EAAE,GAC5C,oBAAoB;IA2BvB;;OAEG;IACH,OAAO,CAAC,WAAW;IA4BnB;;;OAGG;IACH,OAAO,CAAC,YAAY;IAgCpB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAmBxB;;;OAGG;IACH,OAAO,CAAC,wBAAwB;CAuCjC;AAED,gCAAgC;AAChC,eAAO,MAAM,aAAa,eAAsB,CAAC"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cycle Detector Service
|
|
3
|
+
*
|
|
4
|
+
* Detects cycles in entity dependency graphs using DFS.
|
|
5
|
+
* Operates in strict mode: rejects entity creation/update if cycle detected.
|
|
6
|
+
*
|
|
7
|
+
* Algorithm:
|
|
8
|
+
* 1. Build dependency graph from entities
|
|
9
|
+
* 2. Use DFS with coloring (white/gray/black) to detect back edges
|
|
10
|
+
* 3. If cycle detected, return the cycle path and suggestions for breaking it
|
|
11
|
+
*/
|
|
12
|
+
import { getEntityTypeFromId } from '../../models/v2-types.js';
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Node Colors for DFS
|
|
15
|
+
// =============================================================================
|
|
16
|
+
var NodeColor;
|
|
17
|
+
(function (NodeColor) {
|
|
18
|
+
NodeColor["WHITE"] = "white";
|
|
19
|
+
NodeColor["GRAY"] = "gray";
|
|
20
|
+
NodeColor["BLACK"] = "black";
|
|
21
|
+
})(NodeColor || (NodeColor = {}));
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// Cycle Detector
|
|
24
|
+
// =============================================================================
|
|
25
|
+
/**
|
|
26
|
+
* Detects cycles in dependency graphs using DFS.
|
|
27
|
+
*/
|
|
28
|
+
export class CycleDetector {
|
|
29
|
+
/**
|
|
30
|
+
* Check if adding a dependency would create a cycle.
|
|
31
|
+
*
|
|
32
|
+
* @param fromId - Entity that would depend on toId
|
|
33
|
+
* @param toId - Entity that fromId would depend on
|
|
34
|
+
* @param getDependencies - Function to get dependencies of an entity
|
|
35
|
+
* @returns CycleDetectionResult with cycle info if detected
|
|
36
|
+
*/
|
|
37
|
+
wouldCreateCycle(fromId, toId, getDependencies) {
|
|
38
|
+
// Check if toId can reach fromId (which would create a cycle)
|
|
39
|
+
const visited = new Set();
|
|
40
|
+
const path = [];
|
|
41
|
+
const canReach = this.dfsCanReach(toId, fromId, getDependencies, visited, path);
|
|
42
|
+
if (canReach) {
|
|
43
|
+
// Cycle would be: fromId -> toId -> ... -> fromId
|
|
44
|
+
const cyclePath = [fromId, toId, ...path, fromId];
|
|
45
|
+
return {
|
|
46
|
+
hasCycle: true,
|
|
47
|
+
cyclePath,
|
|
48
|
+
message: `Adding dependency ${fromId} → ${toId} would create a cycle: ${cyclePath.join(' → ')}`,
|
|
49
|
+
suggestions: this.generateBreakSuggestions(cyclePath, getDependencies),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return { hasCycle: false };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Detect cycles in the entire dependency graph.
|
|
56
|
+
*
|
|
57
|
+
* @param entities - All entities to check
|
|
58
|
+
* @param getDependencies - Function to get dependencies of an entity
|
|
59
|
+
* @returns CycleDetectionResult with first cycle found, if any
|
|
60
|
+
*/
|
|
61
|
+
detectCycles(entities, getDependencies) {
|
|
62
|
+
const colors = new Map();
|
|
63
|
+
const parent = new Map();
|
|
64
|
+
// Initialize all nodes as white
|
|
65
|
+
for (const entity of entities) {
|
|
66
|
+
colors.set(entity.id, NodeColor.WHITE);
|
|
67
|
+
}
|
|
68
|
+
// Run DFS from each unvisited node
|
|
69
|
+
for (const entity of entities) {
|
|
70
|
+
if (colors.get(entity.id) === NodeColor.WHITE) {
|
|
71
|
+
const cycle = this.dfsFindCycle(entity.id, getDependencies, colors, parent);
|
|
72
|
+
if (cycle) {
|
|
73
|
+
return {
|
|
74
|
+
hasCycle: true,
|
|
75
|
+
cyclePath: cycle,
|
|
76
|
+
message: `Cycle detected: ${cycle.join(' → ')}`,
|
|
77
|
+
suggestions: this.generateBreakSuggestions(cycle, getDependencies),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { hasCycle: false };
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* DFS to check if target is reachable from current.
|
|
86
|
+
*/
|
|
87
|
+
dfsCanReach(current, target, getDependencies, visited, path) {
|
|
88
|
+
if (current === target) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
if (visited.has(current)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
visited.add(current);
|
|
95
|
+
path.push(current);
|
|
96
|
+
for (const dep of getDependencies(current)) {
|
|
97
|
+
if (this.dfsCanReach(dep, target, getDependencies, visited, path)) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
path.pop();
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* DFS to find a cycle starting from the given node.
|
|
106
|
+
* Uses three-color algorithm (white/gray/black).
|
|
107
|
+
*/
|
|
108
|
+
dfsFindCycle(nodeId, getDependencies, colors, parent) {
|
|
109
|
+
colors.set(nodeId, NodeColor.GRAY);
|
|
110
|
+
for (const dep of getDependencies(nodeId)) {
|
|
111
|
+
// Ensure dep is in the color map
|
|
112
|
+
if (!colors.has(dep)) {
|
|
113
|
+
colors.set(dep, NodeColor.WHITE);
|
|
114
|
+
}
|
|
115
|
+
if (colors.get(dep) === NodeColor.GRAY) {
|
|
116
|
+
// Found a back edge - reconstruct cycle
|
|
117
|
+
return this.reconstructCycle(nodeId, dep, parent);
|
|
118
|
+
}
|
|
119
|
+
if (colors.get(dep) === NodeColor.WHITE) {
|
|
120
|
+
parent.set(dep, nodeId);
|
|
121
|
+
const cycle = this.dfsFindCycle(dep, getDependencies, colors, parent);
|
|
122
|
+
if (cycle) {
|
|
123
|
+
return cycle;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
colors.set(nodeId, NodeColor.BLACK);
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Reconstruct the cycle path from parent pointers.
|
|
132
|
+
*/
|
|
133
|
+
reconstructCycle(from, to, parent) {
|
|
134
|
+
const cycle = [to];
|
|
135
|
+
let current = from;
|
|
136
|
+
while (current !== to) {
|
|
137
|
+
cycle.unshift(current);
|
|
138
|
+
const p = parent.get(current);
|
|
139
|
+
if (!p)
|
|
140
|
+
break;
|
|
141
|
+
current = p;
|
|
142
|
+
}
|
|
143
|
+
cycle.push(to); // Complete the cycle
|
|
144
|
+
return cycle;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Generate suggestions for breaking a cycle.
|
|
148
|
+
* Uses priority rules based on entity types.
|
|
149
|
+
*/
|
|
150
|
+
generateBreakSuggestions(cyclePath, _getDependencies) {
|
|
151
|
+
const suggestions = [];
|
|
152
|
+
// Priority order for breaking: task > story > milestone > decision > document
|
|
153
|
+
const typePriority = {
|
|
154
|
+
task: 1,
|
|
155
|
+
story: 2,
|
|
156
|
+
milestone: 3,
|
|
157
|
+
decision: 4,
|
|
158
|
+
document: 5,
|
|
159
|
+
feature: 6,
|
|
160
|
+
};
|
|
161
|
+
// For each edge in the cycle, suggest removing it
|
|
162
|
+
for (let i = 0; i < cyclePath.length - 1; i++) {
|
|
163
|
+
const from = cyclePath[i];
|
|
164
|
+
const to = cyclePath[i + 1];
|
|
165
|
+
const fromType = getEntityTypeFromId(from);
|
|
166
|
+
const toType = getEntityTypeFromId(to);
|
|
167
|
+
// Calculate priority based on types
|
|
168
|
+
const fromPriority = fromType ? typePriority[fromType] : 10;
|
|
169
|
+
const toPriority = toType ? typePriority[toType] : 10;
|
|
170
|
+
suggestions.push({
|
|
171
|
+
removeEdge: { from, to },
|
|
172
|
+
priority: fromPriority + toPriority,
|
|
173
|
+
reason: `Remove dependency from ${from} (${fromType || 'unknown'}) to ${to} (${toType || 'unknown'})`,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
// Sort by priority (lower is better)
|
|
177
|
+
suggestions.sort((a, b) => a.priority - b.priority);
|
|
178
|
+
return suggestions;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/** Default detector instance */
|
|
182
|
+
export const cycleDetector = new CycleDetector();
|
|
183
|
+
//# sourceMappingURL=cycle-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cycle-detector.js","sourceRoot":"","sources":["../../../src/services/v2/cycle-detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cycle-detector.test.d.ts","sourceRoot":"","sources":["../../../src/services/v2/cycle-detector.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Cycle Detector Service
|
|
3
|
+
*
|
|
4
|
+
* Tests cycle detection algorithm as specified in MCP_PLUGIN_ALIGNMENT.md Section 9.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect } from 'vitest';
|
|
7
|
+
import { cycleDetector } from './cycle-detector.js';
|
|
8
|
+
// Helper to create a minimal story entity for testing
|
|
9
|
+
function createStory(id, dependsOn = []) {
|
|
10
|
+
return {
|
|
11
|
+
id: id,
|
|
12
|
+
type: 'story',
|
|
13
|
+
title: `Story ${id}`,
|
|
14
|
+
workstream: 'default',
|
|
15
|
+
status: 'Not Started',
|
|
16
|
+
priority: 'Medium',
|
|
17
|
+
depends_on: dependsOn,
|
|
18
|
+
archived: false,
|
|
19
|
+
canvas_source: '',
|
|
20
|
+
vault_path: '',
|
|
21
|
+
cssclasses: [],
|
|
22
|
+
created_at: new Date().toISOString(),
|
|
23
|
+
updated_at: new Date().toISOString(),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
describe('CycleDetector', () => {
|
|
27
|
+
describe('wouldCreateCycle', () => {
|
|
28
|
+
it('should detect cycle when adding A→B would create A→B→A', () => {
|
|
29
|
+
// B already depends on A
|
|
30
|
+
// Adding A→B would create a cycle
|
|
31
|
+
const getDependencies = (id) => {
|
|
32
|
+
if (id === 'S-002')
|
|
33
|
+
return ['S-001'];
|
|
34
|
+
return [];
|
|
35
|
+
};
|
|
36
|
+
const result = cycleDetector.wouldCreateCycle('S-001', 'S-002', getDependencies);
|
|
37
|
+
expect(result.hasCycle).toBe(true);
|
|
38
|
+
expect(result.cyclePath).toBeDefined();
|
|
39
|
+
expect(result.cyclePath).toContain('S-001');
|
|
40
|
+
expect(result.cyclePath).toContain('S-002');
|
|
41
|
+
expect(result.message).toContain('cycle');
|
|
42
|
+
});
|
|
43
|
+
it('should not detect cycle when no cycle would be created', () => {
|
|
44
|
+
// A→B, B→C (no cycle)
|
|
45
|
+
const getDependencies = (id) => {
|
|
46
|
+
if (id === 'S-002')
|
|
47
|
+
return ['S-003'];
|
|
48
|
+
return [];
|
|
49
|
+
};
|
|
50
|
+
const result = cycleDetector.wouldCreateCycle('S-001', 'S-002', getDependencies);
|
|
51
|
+
expect(result.hasCycle).toBe(false);
|
|
52
|
+
expect(result.cyclePath).toBeUndefined();
|
|
53
|
+
});
|
|
54
|
+
it('should detect longer cycles A→B→C→A', () => {
|
|
55
|
+
// B→C, C→A
|
|
56
|
+
// Adding A→B would create A→B→C→A
|
|
57
|
+
const getDependencies = (id) => {
|
|
58
|
+
if (id === 'S-002')
|
|
59
|
+
return ['S-003'];
|
|
60
|
+
if (id === 'S-003')
|
|
61
|
+
return ['S-001'];
|
|
62
|
+
return [];
|
|
63
|
+
};
|
|
64
|
+
const result = cycleDetector.wouldCreateCycle('S-001', 'S-002', getDependencies);
|
|
65
|
+
expect(result.hasCycle).toBe(true);
|
|
66
|
+
expect(result.cyclePath.length).toBeGreaterThan(2);
|
|
67
|
+
});
|
|
68
|
+
it('should provide suggestions for breaking the cycle', () => {
|
|
69
|
+
const getDependencies = (id) => {
|
|
70
|
+
if (id === 'S-002')
|
|
71
|
+
return ['S-001'];
|
|
72
|
+
return [];
|
|
73
|
+
};
|
|
74
|
+
const result = cycleDetector.wouldCreateCycle('S-001', 'S-002', getDependencies);
|
|
75
|
+
expect(result.suggestions).toBeDefined();
|
|
76
|
+
expect(result.suggestions.length).toBeGreaterThan(0);
|
|
77
|
+
expect(result.suggestions[0].removeEdge).toBeDefined();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
describe('detectCycles', () => {
|
|
81
|
+
it('should detect cycle in entity graph', () => {
|
|
82
|
+
const entities = [
|
|
83
|
+
createStory('S-001', ['S-002']),
|
|
84
|
+
createStory('S-002', ['S-003']),
|
|
85
|
+
createStory('S-003', ['S-001']), // Creates cycle
|
|
86
|
+
];
|
|
87
|
+
const getDependencies = (id) => {
|
|
88
|
+
const entity = entities.find((e) => e.id === id);
|
|
89
|
+
return entity?.depends_on || [];
|
|
90
|
+
};
|
|
91
|
+
const result = cycleDetector.detectCycles(entities, getDependencies);
|
|
92
|
+
expect(result.hasCycle).toBe(true);
|
|
93
|
+
expect(result.cyclePath).toBeDefined();
|
|
94
|
+
});
|
|
95
|
+
it('should not detect cycle in DAG (directed acyclic graph)', () => {
|
|
96
|
+
const entities = [
|
|
97
|
+
createStory('S-001', ['S-002', 'S-003']),
|
|
98
|
+
createStory('S-002', ['S-004']),
|
|
99
|
+
createStory('S-003', ['S-004']),
|
|
100
|
+
createStory('S-004', []),
|
|
101
|
+
];
|
|
102
|
+
const getDependencies = (id) => {
|
|
103
|
+
const entity = entities.find((e) => e.id === id);
|
|
104
|
+
return entity?.depends_on || [];
|
|
105
|
+
};
|
|
106
|
+
const result = cycleDetector.detectCycles(entities, getDependencies);
|
|
107
|
+
expect(result.hasCycle).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
it('should handle empty entity list', () => {
|
|
110
|
+
const result = cycleDetector.detectCycles([], () => []);
|
|
111
|
+
expect(result.hasCycle).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
it('should handle entities with no dependencies', () => {
|
|
114
|
+
const entities = [
|
|
115
|
+
createStory('S-001', []),
|
|
116
|
+
createStory('S-002', []),
|
|
117
|
+
createStory('S-003', []),
|
|
118
|
+
];
|
|
119
|
+
const getDependencies = (_id) => [];
|
|
120
|
+
const result = cycleDetector.detectCycles(entities, getDependencies);
|
|
121
|
+
expect(result.hasCycle).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
//# sourceMappingURL=cycle-detector.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cycle-detector.test.js","sourceRoot":"","sources":["../../../src/services/v2/cycle-detector.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V2 Entity Parser
|
|
3
|
+
*
|
|
4
|
+
* Parses markdown files into Entity objects.
|
|
5
|
+
*/
|
|
6
|
+
import { Entity, VaultPath } from '../../models/v2-types.js';
|
|
7
|
+
export interface ParseResult<T extends Entity = Entity> {
|
|
8
|
+
entity: T;
|
|
9
|
+
frontmatter: Record<string, any>;
|
|
10
|
+
content: string;
|
|
11
|
+
errors: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface FrontmatterData {
|
|
14
|
+
[key: string]: any;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Parses markdown files with YAML frontmatter into Entity objects.
|
|
18
|
+
*/
|
|
19
|
+
export declare class EntityParser {
|
|
20
|
+
/** Parse markdown content into an Entity */
|
|
21
|
+
parse(content: string, filePath: VaultPath): ParseResult;
|
|
22
|
+
/** Extract YAML frontmatter from markdown content */
|
|
23
|
+
private extractFrontmatter;
|
|
24
|
+
/** Parse a YAML value */
|
|
25
|
+
private parseValue;
|
|
26
|
+
/** Parse an inline YAML array like [M-001, M-002] */
|
|
27
|
+
private parseInlineArray;
|
|
28
|
+
/** Extract valid entity IDs from an array of strings */
|
|
29
|
+
private extractValidEntityIds;
|
|
30
|
+
/** Extract entity ID from frontmatter (ID is required in frontmatter) */
|
|
31
|
+
private extractId;
|
|
32
|
+
private parseMilestone;
|
|
33
|
+
private parseStory;
|
|
34
|
+
private parseTask;
|
|
35
|
+
private parseDecision;
|
|
36
|
+
private parseDocument;
|
|
37
|
+
private parseFeature;
|
|
38
|
+
private validateStatus;
|
|
39
|
+
private validatePriority;
|
|
40
|
+
private validateTier;
|
|
41
|
+
private validatePhase;
|
|
42
|
+
private extractTitleFromBody;
|
|
43
|
+
/**
|
|
44
|
+
* Extract content from a markdown section (## SectionName).
|
|
45
|
+
* Returns the content between this section header and the next section header (or end of file).
|
|
46
|
+
*/
|
|
47
|
+
private extractSection;
|
|
48
|
+
/**
|
|
49
|
+
* Extract body content before any section headers.
|
|
50
|
+
* Used for milestone objective which is written directly to body without a section header.
|
|
51
|
+
*/
|
|
52
|
+
private extractBodyContent;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=entity-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entity-parser.d.ts","sourceRoot":"","sources":["../../../src/services/v2/entity-parser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,MAAM,EAiBN,SAAS,EAGV,MAAM,0BAA0B,CAAC;AAMlC,MAAM,WAAW,WAAW,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;IACpD,MAAM,EAAE,CAAC,CAAC;IACV,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAMD;;GAEG;AACH,qBAAa,YAAY;IAKvB,4CAA4C;IAC5C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,GAAG,WAAW;IAkDxD,qDAAqD;IACrD,OAAO,CAAC,kBAAkB;IA0D1B,yBAAyB;IACzB,OAAO,CAAC,UAAU;IA4BlB,qDAAqD;IACrD,OAAO,CAAC,gBAAgB;IAqBxB,wDAAwD;IACxD,OAAO,CAAC,qBAAqB;IA0B7B,yEAAyE;IACzE,OAAO,CAAC,SAAS;IAYjB,OAAO,CAAC,cAAc;IAkCtB,OAAO,CAAC,UAAU;IAqClB,OAAO,CAAC,SAAS;IAoCjB,OAAO,CAAC,aAAa;IAyCrB,OAAO,CAAC,aAAa;IA8BrB,OAAO,CAAC,YAAY;IAkCpB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,oBAAoB;IAK5B;;;OAGG;IACH,OAAO,CAAC,cAAc;IAmBtB;;;OAGG;IACH,OAAO,CAAC,kBAAkB;CAc3B"}
|