@diegonogueiradev_/mcp-graph 1.0.0
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/dist/core/context/compact-context.d.ts +51 -0
- package/dist/core/context/compact-context.d.ts.map +1 -0
- package/dist/core/context/compact-context.js +115 -0
- package/dist/core/context/compact-context.js.map +1 -0
- package/dist/core/context/rag-context.d.ts +36 -0
- package/dist/core/context/rag-context.d.ts.map +1 -0
- package/dist/core/context/rag-context.js +69 -0
- package/dist/core/context/rag-context.js.map +1 -0
- package/dist/core/context/token-estimator.d.ts +7 -0
- package/dist/core/context/token-estimator.d.ts.map +1 -0
- package/dist/core/context/token-estimator.js +9 -0
- package/dist/core/context/token-estimator.js.map +1 -0
- package/dist/core/graph/graph-indexes.d.ts +3 -0
- package/dist/core/graph/graph-indexes.d.ts.map +1 -0
- package/dist/core/graph/graph-indexes.js +28 -0
- package/dist/core/graph/graph-indexes.js.map +1 -0
- package/dist/core/graph/graph-types.d.ts +72 -0
- package/dist/core/graph/graph-types.d.ts.map +1 -0
- package/dist/core/graph/graph-types.js +2 -0
- package/dist/core/graph/graph-types.js.map +1 -0
- package/dist/core/importer/import-prd.d.ts +7 -0
- package/dist/core/importer/import-prd.d.ts.map +1 -0
- package/dist/core/importer/import-prd.js +7 -0
- package/dist/core/importer/import-prd.js.map +1 -0
- package/dist/core/importer/prd-to-graph.d.ts +18 -0
- package/dist/core/importer/prd-to-graph.d.ts.map +1 -0
- package/dist/core/importer/prd-to-graph.js +188 -0
- package/dist/core/importer/prd-to-graph.js.map +1 -0
- package/dist/core/parser/classify.d.ts +31 -0
- package/dist/core/parser/classify.d.ts.map +1 -0
- package/dist/core/parser/classify.js +128 -0
- package/dist/core/parser/classify.js.map +1 -0
- package/dist/core/parser/extract.d.ts +21 -0
- package/dist/core/parser/extract.d.ts.map +1 -0
- package/dist/core/parser/extract.js +66 -0
- package/dist/core/parser/extract.js.map +1 -0
- package/dist/core/parser/normalize.d.ts +9 -0
- package/dist/core/parser/normalize.d.ts.map +1 -0
- package/dist/core/parser/normalize.js +22 -0
- package/dist/core/parser/normalize.js.map +1 -0
- package/dist/core/parser/read-file.d.ts +7 -0
- package/dist/core/parser/read-file.d.ts.map +1 -0
- package/dist/core/parser/read-file.js +17 -0
- package/dist/core/parser/read-file.js.map +1 -0
- package/dist/core/parser/segment.d.ts +13 -0
- package/dist/core/parser/segment.d.ts.map +1 -0
- package/dist/core/parser/segment.js +53 -0
- package/dist/core/parser/segment.js.map +1 -0
- package/dist/core/planner/decompose.d.ts +25 -0
- package/dist/core/planner/decompose.d.ts.map +1 -0
- package/dist/core/planner/decompose.js +100 -0
- package/dist/core/planner/decompose.js.map +1 -0
- package/dist/core/planner/dependency-chain.d.ts +20 -0
- package/dist/core/planner/dependency-chain.d.ts.map +1 -0
- package/dist/core/planner/dependency-chain.js +163 -0
- package/dist/core/planner/dependency-chain.js.map +1 -0
- package/dist/core/planner/next-task.d.ts +16 -0
- package/dist/core/planner/next-task.d.ts.map +1 -0
- package/dist/core/planner/next-task.js +76 -0
- package/dist/core/planner/next-task.js.map +1 -0
- package/dist/core/planner/velocity.d.ts +38 -0
- package/dist/core/planner/velocity.d.ts.map +1 -0
- package/dist/core/planner/velocity.js +92 -0
- package/dist/core/planner/velocity.js.map +1 -0
- package/dist/core/search/fts-search.d.ts +16 -0
- package/dist/core/search/fts-search.d.ts.map +1 -0
- package/dist/core/search/fts-search.js +57 -0
- package/dist/core/search/fts-search.js.map +1 -0
- package/dist/core/search/tfidf.d.ts +32 -0
- package/dist/core/search/tfidf.d.ts.map +1 -0
- package/dist/core/search/tfidf.js +67 -0
- package/dist/core/search/tfidf.js.map +1 -0
- package/dist/core/search/tokenizer.d.ts +9 -0
- package/dist/core/search/tokenizer.d.ts.map +1 -0
- package/dist/core/search/tokenizer.js +37 -0
- package/dist/core/search/tokenizer.js.map +1 -0
- package/dist/core/store/migrations.d.ts +4 -0
- package/dist/core/store/migrations.d.ts.map +1 -0
- package/dist/core/store/migrations.js +137 -0
- package/dist/core/store/migrations.js.map +1 -0
- package/dist/core/store/sqlite-store.d.ts +68 -0
- package/dist/core/store/sqlite-store.d.ts.map +1 -0
- package/dist/core/store/sqlite-store.js +608 -0
- package/dist/core/store/sqlite-store.js.map +1 -0
- package/dist/core/utils/errors.d.ts +20 -0
- package/dist/core/utils/errors.d.ts.map +1 -0
- package/dist/core/utils/errors.js +39 -0
- package/dist/core/utils/errors.js.map +1 -0
- package/dist/core/utils/fs.d.ts +2 -0
- package/dist/core/utils/fs.d.ts.map +1 -0
- package/dist/core/utils/fs.js +11 -0
- package/dist/core/utils/fs.js.map +1 -0
- package/dist/core/utils/id.d.ts +2 -0
- package/dist/core/utils/id.d.ts.map +1 -0
- package/dist/core/utils/id.js +6 -0
- package/dist/core/utils/id.js.map +1 -0
- package/dist/core/utils/logger.d.ts +7 -0
- package/dist/core/utils/logger.d.ts.map +1 -0
- package/dist/core/utils/logger.js +23 -0
- package/dist/core/utils/logger.js.map +1 -0
- package/dist/core/utils/time.d.ts +2 -0
- package/dist/core/utils/time.d.ts.map +1 -0
- package/dist/core/utils/time.js +4 -0
- package/dist/core/utils/time.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/init-project.d.ts +2 -0
- package/dist/mcp/init-project.d.ts.map +1 -0
- package/dist/mcp/init-project.js +104 -0
- package/dist/mcp/init-project.js.map +1 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +30 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/stdio.d.ts +3 -0
- package/dist/mcp/stdio.d.ts.map +1 -0
- package/dist/mcp/stdio.js +20 -0
- package/dist/mcp/stdio.js.map +1 -0
- package/dist/mcp/tools/add-edge.d.ts +4 -0
- package/dist/mcp/tools/add-edge.d.ts.map +1 -0
- package/dist/mcp/tools/add-edge.js +62 -0
- package/dist/mcp/tools/add-edge.js.map +1 -0
- package/dist/mcp/tools/add-node.d.ts +4 -0
- package/dist/mcp/tools/add-node.d.ts.map +1 -0
- package/dist/mcp/tools/add-node.js +80 -0
- package/dist/mcp/tools/add-node.js.map +1 -0
- package/dist/mcp/tools/bulk-update-status.d.ts +4 -0
- package/dist/mcp/tools/bulk-update-status.d.ts.map +1 -0
- package/dist/mcp/tools/bulk-update-status.js +19 -0
- package/dist/mcp/tools/bulk-update-status.js.map +1 -0
- package/dist/mcp/tools/clone-node.d.ts +4 -0
- package/dist/mcp/tools/clone-node.d.ts.map +1 -0
- package/dist/mcp/tools/clone-node.js +107 -0
- package/dist/mcp/tools/clone-node.js.map +1 -0
- package/dist/mcp/tools/context.d.ts +4 -0
- package/dist/mcp/tools/context.d.ts.map +1 -0
- package/dist/mcp/tools/context.js +26 -0
- package/dist/mcp/tools/context.js.map +1 -0
- package/dist/mcp/tools/decompose.d.ts +4 -0
- package/dist/mcp/tools/decompose.d.ts.map +1 -0
- package/dist/mcp/tools/decompose.js +22 -0
- package/dist/mcp/tools/decompose.js.map +1 -0
- package/dist/mcp/tools/delete-edge.d.ts +4 -0
- package/dist/mcp/tools/delete-edge.d.ts.map +1 -0
- package/dist/mcp/tools/delete-edge.js +25 -0
- package/dist/mcp/tools/delete-edge.js.map +1 -0
- package/dist/mcp/tools/delete-node.d.ts +4 -0
- package/dist/mcp/tools/delete-node.d.ts.map +1 -0
- package/dist/mcp/tools/delete-node.js +25 -0
- package/dist/mcp/tools/delete-node.js.map +1 -0
- package/dist/mcp/tools/dependencies.d.ts +4 -0
- package/dist/mcp/tools/dependencies.d.ts.map +1 -0
- package/dist/mcp/tools/dependencies.js +42 -0
- package/dist/mcp/tools/dependencies.js.map +1 -0
- package/dist/mcp/tools/export-graph.d.ts +4 -0
- package/dist/mcp/tools/export-graph.d.ts.map +1 -0
- package/dist/mcp/tools/export-graph.js +14 -0
- package/dist/mcp/tools/export-graph.js.map +1 -0
- package/dist/mcp/tools/import-prd.d.ts +4 -0
- package/dist/mcp/tools/import-prd.d.ts.map +1 -0
- package/dist/mcp/tools/import-prd.js +72 -0
- package/dist/mcp/tools/import-prd.js.map +1 -0
- package/dist/mcp/tools/index.d.ts +4 -0
- package/dist/mcp/tools/index.d.ts.map +1 -0
- package/dist/mcp/tools/index.js +53 -0
- package/dist/mcp/tools/index.js.map +1 -0
- package/dist/mcp/tools/init.d.ts +4 -0
- package/dist/mcp/tools/init.d.ts.map +1 -0
- package/dist/mcp/tools/init.js +15 -0
- package/dist/mcp/tools/init.js.map +1 -0
- package/dist/mcp/tools/list-edges.d.ts +4 -0
- package/dist/mcp/tools/list-edges.d.ts.map +1 -0
- package/dist/mcp/tools/list-edges.js +49 -0
- package/dist/mcp/tools/list-edges.js.map +1 -0
- package/dist/mcp/tools/list.d.ts +4 -0
- package/dist/mcp/tools/list.d.ts.map +1 -0
- package/dist/mcp/tools/list.js +53 -0
- package/dist/mcp/tools/list.js.map +1 -0
- package/dist/mcp/tools/move-node.d.ts +4 -0
- package/dist/mcp/tools/move-node.d.ts.map +1 -0
- package/dist/mcp/tools/move-node.js +107 -0
- package/dist/mcp/tools/move-node.js.map +1 -0
- package/dist/mcp/tools/next.d.ts +4 -0
- package/dist/mcp/tools/next.d.ts.map +1 -0
- package/dist/mcp/tools/next.js +28 -0
- package/dist/mcp/tools/next.js.map +1 -0
- package/dist/mcp/tools/rag-context.d.ts +4 -0
- package/dist/mcp/tools/rag-context.d.ts.map +1 -0
- package/dist/mcp/tools/rag-context.js +25 -0
- package/dist/mcp/tools/rag-context.js.map +1 -0
- package/dist/mcp/tools/search.d.ts +4 -0
- package/dist/mcp/tools/search.d.ts.map +1 -0
- package/dist/mcp/tools/search.js +38 -0
- package/dist/mcp/tools/search.js.map +1 -0
- package/dist/mcp/tools/show.d.ts +4 -0
- package/dist/mcp/tools/show.d.ts.map +1 -0
- package/dist/mcp/tools/show.js +38 -0
- package/dist/mcp/tools/show.js.map +1 -0
- package/dist/mcp/tools/snapshot.d.ts +6 -0
- package/dist/mcp/tools/snapshot.d.ts.map +1 -0
- package/dist/mcp/tools/snapshot.js +43 -0
- package/dist/mcp/tools/snapshot.js.map +1 -0
- package/dist/mcp/tools/stats.d.ts +4 -0
- package/dist/mcp/tools/stats.d.ts.map +1 -0
- package/dist/mcp/tools/stats.js +43 -0
- package/dist/mcp/tools/stats.js.map +1 -0
- package/dist/mcp/tools/update-node.d.ts +4 -0
- package/dist/mcp/tools/update-node.d.ts.map +1 -0
- package/dist/mcp/tools/update-node.js +41 -0
- package/dist/mcp/tools/update-node.js.map +1 -0
- package/dist/mcp/tools/update-status.d.ts +4 -0
- package/dist/mcp/tools/update-status.d.ts.map +1 -0
- package/dist/mcp/tools/update-status.js +29 -0
- package/dist/mcp/tools/update-status.js.map +1 -0
- package/dist/mcp/tools/velocity.d.ts +4 -0
- package/dist/mcp/tools/velocity.d.ts.map +1 -0
- package/dist/mcp/tools/velocity.js +22 -0
- package/dist/mcp/tools/velocity.js.map +1 -0
- package/dist/schemas/edge.schema.d.ts +31 -0
- package/dist/schemas/edge.schema.d.ts.map +1 -0
- package/dist/schemas/edge.schema.js +16 -0
- package/dist/schemas/edge.schema.js.map +1 -0
- package/dist/schemas/graph.schema.d.ts +102 -0
- package/dist/schemas/graph.schema.d.ts.map +1 -0
- package/dist/schemas/graph.schema.js +28 -0
- package/dist/schemas/graph.schema.js.map +1 -0
- package/dist/schemas/node.schema.d.ts +80 -0
- package/dist/schemas/node.schema.d.ts.map +1 -0
- package/dist/schemas/node.schema.js +38 -0
- package/dist/schemas/node.schema.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task decomposition: detects large tasks and suggests breakdown.
|
|
3
|
+
*
|
|
4
|
+
* A task is considered "large" if:
|
|
5
|
+
* - estimateMinutes > 120 (2 hours)
|
|
6
|
+
* - xpSize is L or XL
|
|
7
|
+
* - Has more than 5 acceptance criteria (complex scope)
|
|
8
|
+
*/
|
|
9
|
+
import type { GraphDocument, GraphNode } from "../graph/graph-types.js";
|
|
10
|
+
export interface DecomposeResult {
|
|
11
|
+
node: GraphNode;
|
|
12
|
+
reasons: string[];
|
|
13
|
+
suggestedSubtasks: SuggestedSubtask[];
|
|
14
|
+
}
|
|
15
|
+
export interface SuggestedSubtask {
|
|
16
|
+
title: string;
|
|
17
|
+
estimateMinutes?: number;
|
|
18
|
+
xpSize: string;
|
|
19
|
+
basedOn: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Detect large tasks in the graph that should be decomposed.
|
|
23
|
+
*/
|
|
24
|
+
export declare function detectLargeTasks(doc: GraphDocument): DecomposeResult[];
|
|
25
|
+
//# sourceMappingURL=decompose.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decompose.d.ts","sourceRoot":"","sources":["../../../src/core/planner/decompose.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAWxE,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,iBAAiB,EAAE,gBAAgB,EAAE,CAAC;CACvC;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,eAAe,EAAE,CAqCtE"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task decomposition: detects large tasks and suggests breakdown.
|
|
3
|
+
*
|
|
4
|
+
* A task is considered "large" if:
|
|
5
|
+
* - estimateMinutes > 120 (2 hours)
|
|
6
|
+
* - xpSize is L or XL
|
|
7
|
+
* - Has more than 5 acceptance criteria (complex scope)
|
|
8
|
+
*/
|
|
9
|
+
import { logger } from "../utils/logger.js";
|
|
10
|
+
const XP_SIZE_ORDER = {
|
|
11
|
+
XS: 1, S: 2, M: 3, L: 4, XL: 5,
|
|
12
|
+
};
|
|
13
|
+
const ESTIMATE_THRESHOLD = 120; // minutes
|
|
14
|
+
const AC_THRESHOLD = 5;
|
|
15
|
+
const LARGE_XP_THRESHOLD = 4; // L=4, XL=5
|
|
16
|
+
/**
|
|
17
|
+
* Detect large tasks in the graph that should be decomposed.
|
|
18
|
+
*/
|
|
19
|
+
export function detectLargeTasks(doc) {
|
|
20
|
+
const results = [];
|
|
21
|
+
const tasks = doc.nodes.filter((n) => (n.type === "task" || n.type === "subtask") && n.status !== "done");
|
|
22
|
+
for (const node of tasks) {
|
|
23
|
+
const reasons = [];
|
|
24
|
+
if (node.estimateMinutes && node.estimateMinutes > ESTIMATE_THRESHOLD) {
|
|
25
|
+
reasons.push(`estimate ${node.estimateMinutes}min > ${ESTIMATE_THRESHOLD}min threshold`);
|
|
26
|
+
}
|
|
27
|
+
const sizeOrder = XP_SIZE_ORDER[node.xpSize || "M"] ?? 3;
|
|
28
|
+
if (sizeOrder >= LARGE_XP_THRESHOLD) {
|
|
29
|
+
reasons.push(`XP size ${node.xpSize} is large`);
|
|
30
|
+
}
|
|
31
|
+
const acCount = node.acceptanceCriteria?.length ?? 0;
|
|
32
|
+
if (acCount > AC_THRESHOLD) {
|
|
33
|
+
reasons.push(`${acCount} acceptance criteria > ${AC_THRESHOLD} threshold`);
|
|
34
|
+
}
|
|
35
|
+
if (reasons.length === 0)
|
|
36
|
+
continue;
|
|
37
|
+
// Check if already decomposed (has children)
|
|
38
|
+
const hasChildren = doc.nodes.some((n) => n.parentId === node.id);
|
|
39
|
+
if (hasChildren)
|
|
40
|
+
continue;
|
|
41
|
+
const suggestedSubtasks = suggestDecomposition(node);
|
|
42
|
+
results.push({ node, reasons, suggestedSubtasks });
|
|
43
|
+
}
|
|
44
|
+
logger.info(`Decomposition: ${results.length} large tasks detected from ${tasks.length} total`);
|
|
45
|
+
return results;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Suggest subtask breakdown based on acceptance criteria and estimate.
|
|
49
|
+
*/
|
|
50
|
+
function suggestDecomposition(node) {
|
|
51
|
+
const subtasks = [];
|
|
52
|
+
const ac = node.acceptanceCriteria ?? [];
|
|
53
|
+
if (ac.length > 0) {
|
|
54
|
+
// Group acceptance criteria into subtasks (2-3 AC per subtask)
|
|
55
|
+
const chunkSize = Math.max(2, Math.ceil(ac.length / Math.ceil(ac.length / 3)));
|
|
56
|
+
for (let i = 0; i < ac.length; i += chunkSize) {
|
|
57
|
+
const chunk = ac.slice(i, i + chunkSize);
|
|
58
|
+
const title = chunk.length === 1
|
|
59
|
+
? chunk[0]
|
|
60
|
+
: `${chunk[0].slice(0, 60)}${chunk.length > 1 ? ` (+${chunk.length - 1} criteria)` : ""}`;
|
|
61
|
+
const estPerSubtask = node.estimateMinutes
|
|
62
|
+
? Math.ceil(node.estimateMinutes / Math.ceil(ac.length / chunkSize))
|
|
63
|
+
: undefined;
|
|
64
|
+
subtasks.push({
|
|
65
|
+
title,
|
|
66
|
+
estimateMinutes: estPerSubtask,
|
|
67
|
+
xpSize: estimateSubtaskSize(estPerSubtask),
|
|
68
|
+
basedOn: "acceptance_criteria",
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else if (node.estimateMinutes && node.estimateMinutes > ESTIMATE_THRESHOLD) {
|
|
73
|
+
// No AC — split by time estimate
|
|
74
|
+
const numParts = Math.ceil(node.estimateMinutes / 60);
|
|
75
|
+
const estPerPart = Math.ceil(node.estimateMinutes / numParts);
|
|
76
|
+
for (let i = 0; i < numParts; i++) {
|
|
77
|
+
subtasks.push({
|
|
78
|
+
title: `${node.title} — part ${i + 1}/${numParts}`,
|
|
79
|
+
estimateMinutes: estPerPart,
|
|
80
|
+
xpSize: estimateSubtaskSize(estPerPart),
|
|
81
|
+
basedOn: "time_split",
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return subtasks;
|
|
86
|
+
}
|
|
87
|
+
function estimateSubtaskSize(minutes) {
|
|
88
|
+
if (!minutes)
|
|
89
|
+
return "S";
|
|
90
|
+
if (minutes <= 15)
|
|
91
|
+
return "XS";
|
|
92
|
+
if (minutes <= 30)
|
|
93
|
+
return "S";
|
|
94
|
+
if (minutes <= 60)
|
|
95
|
+
return "M";
|
|
96
|
+
if (minutes <= 120)
|
|
97
|
+
return "L";
|
|
98
|
+
return "XL";
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=decompose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decompose.js","sourceRoot":"","sources":["../../../src/core/planner/decompose.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency chain analysis: transitive blockers, cycle detection, critical path.
|
|
3
|
+
*/
|
|
4
|
+
import type { GraphDocument, GraphNode } from "../graph/graph-types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Find all transitive blockers for a given node.
|
|
7
|
+
* Follows depends_on edges backwards and blocks edges forwards.
|
|
8
|
+
*/
|
|
9
|
+
export declare function findTransitiveBlockers(doc: GraphDocument, nodeId: string): GraphNode[];
|
|
10
|
+
/**
|
|
11
|
+
* Detect cycles in the dependency graph using DFS.
|
|
12
|
+
* Only considers depends_on and blocks edges.
|
|
13
|
+
*/
|
|
14
|
+
export declare function detectCycles(doc: GraphDocument): string[][];
|
|
15
|
+
/**
|
|
16
|
+
* Find the critical path (longest path by estimateMinutes) through depends_on edges.
|
|
17
|
+
* Uses topological sort on the dependency DAG.
|
|
18
|
+
*/
|
|
19
|
+
export declare function findCriticalPath(doc: GraphDocument): GraphNode[];
|
|
20
|
+
//# sourceMappingURL=dependency-chain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dependency-chain.d.ts","sourceRoot":"","sources":["../../../src/core/planner/dependency-chain.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAGxE;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,CA+BtF;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,EAAE,EAAE,CAgD3D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,SAAS,EAAE,CAmFhE"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency chain analysis: transitive blockers, cycle detection, critical path.
|
|
3
|
+
*/
|
|
4
|
+
import { logger } from "../utils/logger.js";
|
|
5
|
+
/**
|
|
6
|
+
* Find all transitive blockers for a given node.
|
|
7
|
+
* Follows depends_on edges backwards and blocks edges forwards.
|
|
8
|
+
*/
|
|
9
|
+
export function findTransitiveBlockers(doc, nodeId) {
|
|
10
|
+
const visited = new Set();
|
|
11
|
+
const queue = [nodeId];
|
|
12
|
+
visited.add(nodeId);
|
|
13
|
+
const blockers = [];
|
|
14
|
+
const nodeMap = new Map(doc.nodes.map((n) => [n.id, n]));
|
|
15
|
+
while (queue.length > 0) {
|
|
16
|
+
const current = queue.shift();
|
|
17
|
+
for (const edge of doc.edges) {
|
|
18
|
+
let blockerId = null;
|
|
19
|
+
// "from depends_on to" means to is a blocker of from
|
|
20
|
+
if (edge.from === current && edge.relationType === "depends_on") {
|
|
21
|
+
blockerId = edge.to;
|
|
22
|
+
}
|
|
23
|
+
else if (edge.to === current && edge.relationType === "blocks") {
|
|
24
|
+
blockerId = edge.from;
|
|
25
|
+
}
|
|
26
|
+
if (blockerId && !visited.has(blockerId)) {
|
|
27
|
+
visited.add(blockerId);
|
|
28
|
+
queue.push(blockerId);
|
|
29
|
+
const node = nodeMap.get(blockerId);
|
|
30
|
+
if (node)
|
|
31
|
+
blockers.push(node);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
logger.info(`Transitive blockers for ${nodeId}: ${blockers.length} found`);
|
|
36
|
+
return blockers;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Detect cycles in the dependency graph using DFS.
|
|
40
|
+
* Only considers depends_on and blocks edges.
|
|
41
|
+
*/
|
|
42
|
+
export function detectCycles(doc) {
|
|
43
|
+
const adj = new Map();
|
|
44
|
+
for (const edge of doc.edges) {
|
|
45
|
+
if (edge.relationType === "depends_on") {
|
|
46
|
+
// from depends_on to → dependency direction: from → to
|
|
47
|
+
const list = adj.get(edge.from) ?? [];
|
|
48
|
+
list.push(edge.to);
|
|
49
|
+
adj.set(edge.from, list);
|
|
50
|
+
}
|
|
51
|
+
else if (edge.relationType === "blocks") {
|
|
52
|
+
// from blocks to → dependency direction: to → from
|
|
53
|
+
const list = adj.get(edge.to) ?? [];
|
|
54
|
+
list.push(edge.from);
|
|
55
|
+
adj.set(edge.to, list);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const allNodes = new Set(doc.nodes.map((n) => n.id));
|
|
59
|
+
const visited = new Set();
|
|
60
|
+
const inStack = new Set();
|
|
61
|
+
const cycles = [];
|
|
62
|
+
function dfs(node, path) {
|
|
63
|
+
visited.add(node);
|
|
64
|
+
inStack.add(node);
|
|
65
|
+
path.push(node);
|
|
66
|
+
for (const neighbor of adj.get(node) ?? []) {
|
|
67
|
+
if (inStack.has(neighbor)) {
|
|
68
|
+
const cycleStart = path.indexOf(neighbor);
|
|
69
|
+
cycles.push([...path.slice(cycleStart), neighbor]);
|
|
70
|
+
}
|
|
71
|
+
else if (!visited.has(neighbor)) {
|
|
72
|
+
dfs(neighbor, path);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
path.pop();
|
|
76
|
+
inStack.delete(node);
|
|
77
|
+
}
|
|
78
|
+
for (const node of allNodes) {
|
|
79
|
+
if (!visited.has(node)) {
|
|
80
|
+
dfs(node, []);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
logger.info(`Cycle detection: ${cycles.length} cycles found`);
|
|
84
|
+
return cycles;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Find the critical path (longest path by estimateMinutes) through depends_on edges.
|
|
88
|
+
* Uses topological sort on the dependency DAG.
|
|
89
|
+
*/
|
|
90
|
+
export function findCriticalPath(doc) {
|
|
91
|
+
const DEFAULT_ESTIMATE = 60;
|
|
92
|
+
const nodeMap = new Map(doc.nodes.map((n) => [n.id, n]));
|
|
93
|
+
// Build adjacency list for depends_on edges (from depends on to → to must finish before from)
|
|
94
|
+
// Direction: dependency edges point from dependent to prerequisite
|
|
95
|
+
// We want longest path, so build forward graph: prerequisite → dependent
|
|
96
|
+
const forward = new Map();
|
|
97
|
+
const inDegree = new Map();
|
|
98
|
+
for (const node of doc.nodes) {
|
|
99
|
+
forward.set(node.id, []);
|
|
100
|
+
inDegree.set(node.id, 0);
|
|
101
|
+
}
|
|
102
|
+
for (const edge of doc.edges) {
|
|
103
|
+
if (edge.relationType === "depends_on") {
|
|
104
|
+
// edge.from depends_on edge.to → edge.to is prerequisite
|
|
105
|
+
const list = forward.get(edge.to) ?? [];
|
|
106
|
+
list.push(edge.from);
|
|
107
|
+
forward.set(edge.to, list);
|
|
108
|
+
inDegree.set(edge.from, (inDegree.get(edge.from) ?? 0) + 1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Topological sort (Kahn's algorithm) + longest path
|
|
112
|
+
const dist = new Map();
|
|
113
|
+
const prev = new Map();
|
|
114
|
+
const queue = [];
|
|
115
|
+
for (const node of doc.nodes) {
|
|
116
|
+
const est = node.estimateMinutes ?? DEFAULT_ESTIMATE;
|
|
117
|
+
dist.set(node.id, est);
|
|
118
|
+
prev.set(node.id, null);
|
|
119
|
+
if ((inDegree.get(node.id) ?? 0) === 0) {
|
|
120
|
+
queue.push(node.id);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
while (queue.length > 0) {
|
|
124
|
+
const current = queue.shift();
|
|
125
|
+
const currentDist = dist.get(current) ?? 0;
|
|
126
|
+
for (const neighbor of forward.get(current) ?? []) {
|
|
127
|
+
const neighborEst = nodeMap.get(neighbor)?.estimateMinutes ?? DEFAULT_ESTIMATE;
|
|
128
|
+
const newDist = currentDist + neighborEst;
|
|
129
|
+
if (newDist > (dist.get(neighbor) ?? 0)) {
|
|
130
|
+
dist.set(neighbor, newDist);
|
|
131
|
+
prev.set(neighbor, current);
|
|
132
|
+
}
|
|
133
|
+
const deg = (inDegree.get(neighbor) ?? 1) - 1;
|
|
134
|
+
inDegree.set(neighbor, deg);
|
|
135
|
+
if (deg === 0) {
|
|
136
|
+
queue.push(neighbor);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Find the node with maximum distance
|
|
141
|
+
let maxNode = null;
|
|
142
|
+
let maxDist = 0;
|
|
143
|
+
for (const [id, d] of dist) {
|
|
144
|
+
if (d > maxDist) {
|
|
145
|
+
maxDist = d;
|
|
146
|
+
maxNode = id;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (!maxNode)
|
|
150
|
+
return [];
|
|
151
|
+
// Reconstruct path
|
|
152
|
+
const path = [];
|
|
153
|
+
let current = maxNode;
|
|
154
|
+
while (current) {
|
|
155
|
+
const node = nodeMap.get(current);
|
|
156
|
+
if (node)
|
|
157
|
+
path.unshift(node);
|
|
158
|
+
current = prev.get(current) ?? null;
|
|
159
|
+
}
|
|
160
|
+
logger.info(`Critical path: ${path.length} nodes, ${maxDist} total minutes`);
|
|
161
|
+
return path;
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=dependency-chain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dependency-chain.js","sourceRoot":"","sources":["../../../src/core/planner/dependency-chain.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,GAAkB,EAAE,MAAc;IACvE,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,MAAM,KAAK,GAAa,CAAC,MAAM,CAAC,CAAC;IACjC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACpB,MAAM,QAAQ,GAAgB,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAEzD,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,EAAG,CAAC;QAE/B,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YAC7B,IAAI,SAAS,GAAkB,IAAI,CAAC;YAEpC,qDAAqD;YACrD,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,IAAI,CAAC,YAAY,KAAK,YAAY,EAAE,CAAC;gBAChE,SAAS,GAAG,IAAI,CAAC,EAAE,CAAC;YACtB,CAAC;iBAAM,IAAI,IAAI,CAAC,EAAE,KAAK,OAAO,IAAI,IAAI,CAAC,YAAY,KAAK,QAAQ,EAAE,CAAC;gBACjE,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC;YACxB,CAAC;YAED,IAAI,SAAS,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;gBACzC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBACvB,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBACtB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBACpC,IAAI,IAAI;oBAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAChC,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,2BAA2B,MAAM,KAAK,QAAQ,CAAC,MAAM,QAAQ,CAAC,CAAC;IAC3E,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,GAAkB;IAC7C,MAAM,GAAG,GAAG,IAAI,GAAG,EAAoB,CAAC;IAExC,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;QAC7B,IAAI,IAAI,CAAC,YAAY,KAAK,YAAY,EAAE,CAAC;YACvC,uDAAuD;YACvD,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACtC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACnB,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC3B,CAAC;aAAM,IAAI,IAAI,CAAC,YAAY,KAAK,QAAQ,EAAE,CAAC;YAC1C,mDAAmD;YACnD,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;YACpC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrB,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAS,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7D,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,MAAM,MAAM,GAAe,EAAE,CAAC;IAE9B,SAAS,GAAG,CAAC,IAAY,EAAE,IAAc;QACvC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEhB,KAAK,MAAM,QAAQ,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;YAC3C,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC1B,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;gBAC1C,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;YACrD,CAAC;iBAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAClC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YACtB,CAAC;QACH,CAAC;QAED,IAAI,CAAC,GAAG,EAAE,CAAC;QACX,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACvB,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAChB,CAAC;IACH,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,oBAAoB,MAAM,CAAC,MAAM,eAAe,CAAC,CAAC;IAC9D,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAkB;IACjD,MAAM,gBAAgB,GAAG,EAAE,CAAC;IAC5B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAEzD,8FAA8F;IAC9F,mEAAmE;IACnE,yEAAyE;IACzE,MAAM,OAAO,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC5C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE3C,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;QAC7B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACzB,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IAC3B,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;QAC7B,IAAI,IAAI,CAAC,YAAY,KAAK,YAAY,EAAE,CAAC;YACvC,yDAAyD;YACzD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;YACxC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YAC3B,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;IAED,qDAAqD;IACrD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAyB,CAAC;IAC9C,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,eAAe,IAAI,gBAAgB,CAAC;QACrD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;QACvB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QACxB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;YACvC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,EAAG,CAAC;QAC/B,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAE3C,KAAK,MAAM,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YAClD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,eAAe,IAAI,gBAAgB,CAAC;YAC/E,MAAM,OAAO,GAAG,WAAW,GAAG,WAAW,CAAC;YAE1C,IAAI,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;gBACxC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBAC5B,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAC9B,CAAC;YAED,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;YAC9C,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;YAC5B,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;gBACd,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACvB,CAAC;QACH,CAAC;IACH,CAAC;IAED,sCAAsC;IACtC,IAAI,OAAO,GAAkB,IAAI,CAAC;IAClC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;QAC3B,IAAI,CAAC,GAAG,OAAO,EAAE,CAAC;YAChB,OAAO,GAAG,CAAC,CAAC;YACZ,OAAO,GAAG,EAAE,CAAC;QACf,CAAC;IACH,CAAC;IAED,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IAExB,mBAAmB;IACnB,MAAM,IAAI,GAAgB,EAAE,CAAC;IAC7B,IAAI,OAAO,GAAkB,OAAO,CAAC;IACrC,OAAO,OAAO,EAAE,CAAC;QACf,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAClC,IAAI,IAAI;YAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC7B,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;IACtC,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,kBAAkB,IAAI,CAAC,MAAM,WAAW,OAAO,gBAAgB,CAAC,CAAC;IAC7E,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution planner: suggests the next best task to work on.
|
|
3
|
+
*
|
|
4
|
+
* Algorithm:
|
|
5
|
+
* 1. Filter nodes of type task/subtask with status backlog or ready
|
|
6
|
+
* 2. Eliminate nodes with unresolved depends_on edges (dependency target not done)
|
|
7
|
+
* 3. Eliminate nodes with blocked = true
|
|
8
|
+
* 4. Sort by: priority ASC, xpSize ASC, estimateMinutes ASC, createdAt ASC
|
|
9
|
+
*/
|
|
10
|
+
import type { GraphDocument, GraphNode } from "../graph/graph-types.js";
|
|
11
|
+
export interface NextTaskResult {
|
|
12
|
+
node: GraphNode;
|
|
13
|
+
reason: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function findNextTask(doc: GraphDocument): NextTaskResult | null;
|
|
16
|
+
//# sourceMappingURL=next-task.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next-task.d.ts","sourceRoot":"","sources":["../../../src/core/planner/next-task.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAU,MAAM,yBAAyB,CAAC;AAOhF,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,GAAG,IAAI,CA0EtE"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution planner: suggests the next best task to work on.
|
|
3
|
+
*
|
|
4
|
+
* Algorithm:
|
|
5
|
+
* 1. Filter nodes of type task/subtask with status backlog or ready
|
|
6
|
+
* 2. Eliminate nodes with unresolved depends_on edges (dependency target not done)
|
|
7
|
+
* 3. Eliminate nodes with blocked = true
|
|
8
|
+
* 4. Sort by: priority ASC, xpSize ASC, estimateMinutes ASC, createdAt ASC
|
|
9
|
+
*/
|
|
10
|
+
import { logger } from "../utils/logger.js";
|
|
11
|
+
const XP_SIZE_ORDER = {
|
|
12
|
+
XS: 1, S: 2, M: 3, L: 4, XL: 5,
|
|
13
|
+
};
|
|
14
|
+
export function findNextTask(doc) {
|
|
15
|
+
// Step 1: Filter eligible nodes
|
|
16
|
+
const eligible = doc.nodes.filter((n) => (n.type === "task" || n.type === "subtask") &&
|
|
17
|
+
(n.status === "backlog" || n.status === "ready") &&
|
|
18
|
+
!n.blocked);
|
|
19
|
+
logger.info(`Next task: ${eligible.length} eligible from ${doc.nodes.length} total`);
|
|
20
|
+
if (eligible.length === 0)
|
|
21
|
+
return null;
|
|
22
|
+
// Step 2: Build a set of done node IDs for dependency checking
|
|
23
|
+
const doneIds = new Set(doc.nodes.filter((n) => n.status === "done").map((n) => n.id));
|
|
24
|
+
// Step 3: Find depends_on edges and filter out nodes with unresolved deps
|
|
25
|
+
const unblocked = eligible.filter((node) => {
|
|
26
|
+
const depsEdges = doc.edges.filter((e) => e.from === node.id && e.relationType === "depends_on");
|
|
27
|
+
// All dependency targets must be done
|
|
28
|
+
return depsEdges.every((e) => doneIds.has(e.to));
|
|
29
|
+
});
|
|
30
|
+
if (unblocked.length === 0) {
|
|
31
|
+
// All eligible tasks have unresolved dependencies — return the one with fewest deps
|
|
32
|
+
const withDepCount = eligible.map((node) => {
|
|
33
|
+
const deps = doc.edges.filter((e) => e.from === node.id && e.relationType === "depends_on" && !doneIds.has(e.to));
|
|
34
|
+
return { node, pendingDeps: deps.length };
|
|
35
|
+
});
|
|
36
|
+
withDepCount.sort((a, b) => a.pendingDeps - b.pendingDeps);
|
|
37
|
+
return {
|
|
38
|
+
node: withDepCount[0].node,
|
|
39
|
+
reason: `Todas as tasks tĂŞm dependĂŞncias pendentes. Esta tem menos (${withDepCount[0].pendingDeps}).`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// Step 4: Sort
|
|
43
|
+
unblocked.sort((a, b) => {
|
|
44
|
+
// Priority ASC (1 = critical, 5 = optional)
|
|
45
|
+
if (a.priority !== b.priority)
|
|
46
|
+
return a.priority - b.priority;
|
|
47
|
+
// XP size ASC
|
|
48
|
+
const sizeA = XP_SIZE_ORDER[a.xpSize || "M"] ?? 3;
|
|
49
|
+
const sizeB = XP_SIZE_ORDER[b.xpSize || "M"] ?? 3;
|
|
50
|
+
if (sizeA !== sizeB)
|
|
51
|
+
return sizeA - sizeB;
|
|
52
|
+
// Estimate ASC
|
|
53
|
+
const estA = a.estimateMinutes ?? 999;
|
|
54
|
+
const estB = b.estimateMinutes ?? 999;
|
|
55
|
+
if (estA !== estB)
|
|
56
|
+
return estA - estB;
|
|
57
|
+
// Prefer tasks with more acceptance criteria (clearer definition)
|
|
58
|
+
const acA = a.acceptanceCriteria?.length ?? 0;
|
|
59
|
+
const acB = b.acceptanceCriteria?.length ?? 0;
|
|
60
|
+
if (acA !== acB)
|
|
61
|
+
return acB - acA;
|
|
62
|
+
// CreatedAt ASC (older first)
|
|
63
|
+
return a.createdAt.localeCompare(b.createdAt);
|
|
64
|
+
});
|
|
65
|
+
const best = unblocked[0];
|
|
66
|
+
const reasons = ["desbloqueada"];
|
|
67
|
+
if (best.priority <= 2)
|
|
68
|
+
reasons.push("alta prioridade");
|
|
69
|
+
if (best.xpSize && XP_SIZE_ORDER[best.xpSize] <= 2)
|
|
70
|
+
reasons.push("baixa complexidade");
|
|
71
|
+
return {
|
|
72
|
+
node: best,
|
|
73
|
+
reason: reasons.join(", "),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=next-task.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next-task.js","sourceRoot":"","sources":["../../../src/core/planner/next-task.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Velocity tracking: measures sprint completion metrics.
|
|
3
|
+
*
|
|
4
|
+
* Computes:
|
|
5
|
+
* - Tasks completed per sprint
|
|
6
|
+
* - Average XP size completed
|
|
7
|
+
* - Estimated completion time (based on created→done timestamps)
|
|
8
|
+
*/
|
|
9
|
+
import type { GraphDocument } from "../graph/graph-types.js";
|
|
10
|
+
export interface SprintVelocity {
|
|
11
|
+
sprint: string;
|
|
12
|
+
tasksCompleted: number;
|
|
13
|
+
totalPoints: number;
|
|
14
|
+
avgPointsPerTask: number;
|
|
15
|
+
avgCompletionHours: number | null;
|
|
16
|
+
tasks: VelocityTask[];
|
|
17
|
+
}
|
|
18
|
+
export interface VelocityTask {
|
|
19
|
+
id: string;
|
|
20
|
+
title: string;
|
|
21
|
+
xpSize: string;
|
|
22
|
+
points: number;
|
|
23
|
+
completionHours: number | null;
|
|
24
|
+
}
|
|
25
|
+
export interface VelocitySummary {
|
|
26
|
+
sprints: SprintVelocity[];
|
|
27
|
+
overall: {
|
|
28
|
+
totalTasksCompleted: number;
|
|
29
|
+
totalPoints: number;
|
|
30
|
+
avgPointsPerSprint: number;
|
|
31
|
+
avgCompletionHours: number | null;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Calculate velocity metrics for all sprints in the graph.
|
|
36
|
+
*/
|
|
37
|
+
export declare function calculateVelocity(doc: GraphDocument): VelocitySummary;
|
|
38
|
+
//# sourceMappingURL=velocity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"velocity.d.ts","sourceRoot":"","sources":["../../../src/core/planner/velocity.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,yBAAyB,CAAC;AAOxE,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,OAAO,EAAE;QACP,mBAAmB,EAAE,MAAM,CAAC;QAC5B,WAAW,EAAE,MAAM,CAAC;QACpB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;KACnC,CAAC;CACH;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,aAAa,GAAG,eAAe,CAwErE"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Velocity tracking: measures sprint completion metrics.
|
|
3
|
+
*
|
|
4
|
+
* Computes:
|
|
5
|
+
* - Tasks completed per sprint
|
|
6
|
+
* - Average XP size completed
|
|
7
|
+
* - Estimated completion time (based on created→done timestamps)
|
|
8
|
+
*/
|
|
9
|
+
import { logger } from "../utils/logger.js";
|
|
10
|
+
const XP_SIZE_POINTS = {
|
|
11
|
+
XS: 1, S: 2, M: 3, L: 5, XL: 8,
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Calculate velocity metrics for all sprints in the graph.
|
|
15
|
+
*/
|
|
16
|
+
export function calculateVelocity(doc) {
|
|
17
|
+
// Group done tasks by sprint
|
|
18
|
+
const doneTasks = doc.nodes.filter((n) => n.status === "done" && (n.type === "task" || n.type === "subtask"));
|
|
19
|
+
const bySprint = new Map();
|
|
20
|
+
for (const node of doneTasks) {
|
|
21
|
+
const sprint = node.sprint ?? "(no sprint)";
|
|
22
|
+
const group = bySprint.get(sprint) ?? [];
|
|
23
|
+
group.push(node);
|
|
24
|
+
bySprint.set(sprint, group);
|
|
25
|
+
}
|
|
26
|
+
const sprints = [];
|
|
27
|
+
for (const [sprint, tasks] of bySprint) {
|
|
28
|
+
const velocityTasks = tasks.map((t) => {
|
|
29
|
+
const points = XP_SIZE_POINTS[t.xpSize ?? "M"] ?? 3;
|
|
30
|
+
const completionHours = computeCompletionHours(t);
|
|
31
|
+
return {
|
|
32
|
+
id: t.id,
|
|
33
|
+
title: t.title,
|
|
34
|
+
xpSize: t.xpSize ?? "M",
|
|
35
|
+
points,
|
|
36
|
+
completionHours,
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
const totalPoints = velocityTasks.reduce((sum, t) => sum + t.points, 0);
|
|
40
|
+
const hoursValues = velocityTasks
|
|
41
|
+
.map((t) => t.completionHours)
|
|
42
|
+
.filter((h) => h !== null);
|
|
43
|
+
sprints.push({
|
|
44
|
+
sprint,
|
|
45
|
+
tasksCompleted: tasks.length,
|
|
46
|
+
totalPoints,
|
|
47
|
+
avgPointsPerTask: tasks.length > 0 ? Math.round((totalPoints / tasks.length) * 10) / 10 : 0,
|
|
48
|
+
avgCompletionHours: hoursValues.length > 0
|
|
49
|
+
? Math.round((hoursValues.reduce((a, b) => a + b, 0) / hoursValues.length) * 10) / 10
|
|
50
|
+
: null,
|
|
51
|
+
tasks: velocityTasks,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
// Sort sprints by name
|
|
55
|
+
sprints.sort((a, b) => a.sprint.localeCompare(b.sprint));
|
|
56
|
+
const totalTasksCompleted = doneTasks.length;
|
|
57
|
+
const totalPoints = sprints.reduce((sum, s) => sum + s.totalPoints, 0);
|
|
58
|
+
const sprintCount = sprints.filter((s) => s.sprint !== "(no sprint)").length || 1;
|
|
59
|
+
const allHours = sprints
|
|
60
|
+
.flatMap((s) => s.tasks)
|
|
61
|
+
.map((t) => t.completionHours)
|
|
62
|
+
.filter((h) => h !== null);
|
|
63
|
+
logger.info(`Velocity: ${totalTasksCompleted} tasks done, ${totalPoints} points across ${sprints.length} sprints`);
|
|
64
|
+
return {
|
|
65
|
+
sprints,
|
|
66
|
+
overall: {
|
|
67
|
+
totalTasksCompleted,
|
|
68
|
+
totalPoints,
|
|
69
|
+
avgPointsPerSprint: Math.round((totalPoints / sprintCount) * 10) / 10,
|
|
70
|
+
avgCompletionHours: allHours.length > 0
|
|
71
|
+
? Math.round((allHours.reduce((a, b) => a + b, 0) / allHours.length) * 10) / 10
|
|
72
|
+
: null,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Estimate completion time in hours from createdAt to updatedAt.
|
|
78
|
+
* Returns null if timestamps are invalid or equal.
|
|
79
|
+
*/
|
|
80
|
+
function computeCompletionHours(node) {
|
|
81
|
+
try {
|
|
82
|
+
const created = new Date(node.createdAt).getTime();
|
|
83
|
+
const updated = new Date(node.updatedAt).getTime();
|
|
84
|
+
if (isNaN(created) || isNaN(updated) || updated <= created)
|
|
85
|
+
return null;
|
|
86
|
+
return Math.round(((updated - created) / (1000 * 60 * 60)) * 10) / 10;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=velocity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"velocity.js","sourceRoot":"","sources":["../../../src/core/planner/velocity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { SqliteStore } from "../store/sqlite-store.js";
|
|
2
|
+
import type { GraphNode } from "../graph/graph-types.js";
|
|
3
|
+
export interface SearchResult {
|
|
4
|
+
node: GraphNode;
|
|
5
|
+
score: number;
|
|
6
|
+
}
|
|
7
|
+
export interface SearchOptions {
|
|
8
|
+
limit?: number;
|
|
9
|
+
rerank?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Search nodes using FTS5 full-text search with BM25 ranking.
|
|
13
|
+
* Optionally applies TF-IDF reranking for better relevance.
|
|
14
|
+
*/
|
|
15
|
+
export declare function searchNodes(store: SqliteStore, query: string, options?: SearchOptions): SearchResult[];
|
|
16
|
+
//# sourceMappingURL=fts-search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fts-search.d.ts","sourceRoot":"","sources":["../../../src/core/search/fts-search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAIzD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAqBD;;;GAGG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,WAAW,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,aAAkB,GAC1B,YAAY,EAAE,CAqChB"}
|