@danalexilewis/taskgraph 0.1.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/LICENSE +21 -0
- package/README.md +47 -0
- package/dist/cli/block.js +114 -0
- package/dist/cli/context.js +139 -0
- package/dist/cli/done.js +98 -0
- package/dist/cli/edge.js +99 -0
- package/dist/cli/export.js +97 -0
- package/dist/cli/import.js +123 -0
- package/dist/cli/index.js +78 -0
- package/dist/cli/init.js +106 -0
- package/dist/cli/next.js +97 -0
- package/dist/cli/note.js +72 -0
- package/dist/cli/plan.js +108 -0
- package/dist/cli/portfolio.js +159 -0
- package/dist/cli/setup.js +142 -0
- package/dist/cli/show.js +142 -0
- package/dist/cli/split.js +191 -0
- package/dist/cli/start.js +94 -0
- package/dist/cli/status.js +149 -0
- package/dist/cli/task.js +92 -0
- package/dist/cli/utils.js +74 -0
- package/dist/db/commit.js +18 -0
- package/dist/db/connection.js +22 -0
- package/dist/db/escape.js +6 -0
- package/dist/db/migrate.js +159 -0
- package/dist/db/query.js +102 -0
- package/dist/domain/errors.js +33 -0
- package/dist/domain/invariants.js +103 -0
- package/dist/domain/types.js +120 -0
- package/dist/export/dot.js +21 -0
- package/dist/export/graph-data.js +41 -0
- package/dist/export/markdown.js +108 -0
- package/dist/export/mermaid.js +27 -0
- package/dist/plan-import/importer.js +155 -0
- package/dist/plan-import/parser.js +213 -0
- package/dist/template/.cursor/memory.md +14 -0
- package/dist/template/.cursor/rules/memory.mdc +11 -0
- package/dist/template/.cursor/rules/plan-authoring.mdc +42 -0
- package/dist/template/.cursor/rules/session-start.mdc +18 -0
- package/dist/template/.cursor/rules/taskgraph-workflow.mdc +35 -0
- package/dist/template/AGENT.md +73 -0
- package/dist/template/docs/backend.md +33 -0
- package/dist/template/docs/frontend.md +31 -0
- package/dist/template/docs/infra.md +26 -0
- package/dist/template/docs/skills/README.md +14 -0
- package/dist/template/docs/skills/plan-authoring.md +38 -0
- package/dist/template/docs/skills/refactoring-safely.md +21 -0
- package/dist/template/docs/skills/taskgraph-lifecycle-execution.md +23 -0
- package/package.json +47 -0
package/dist/db/query.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.jsonObj = jsonObj;
|
|
4
|
+
exports.now = now;
|
|
5
|
+
exports.query = query;
|
|
6
|
+
const connection_1 = require("./connection");
|
|
7
|
+
const escape_1 = require("./escape");
|
|
8
|
+
function jsonObj(value) {
|
|
9
|
+
return { _type: "json", value };
|
|
10
|
+
}
|
|
11
|
+
function now() {
|
|
12
|
+
return new Date().toISOString().slice(0, 19).replace("T", " ");
|
|
13
|
+
}
|
|
14
|
+
function formatValue(value) {
|
|
15
|
+
if (value === null) {
|
|
16
|
+
return "NULL";
|
|
17
|
+
}
|
|
18
|
+
if (typeof value === "boolean" || typeof value === "number") {
|
|
19
|
+
return String(value);
|
|
20
|
+
}
|
|
21
|
+
if (typeof value === "string") {
|
|
22
|
+
return `'${(0, escape_1.sqlEscape)(value)}'`;
|
|
23
|
+
}
|
|
24
|
+
if (value._type === "json") {
|
|
25
|
+
const jsonPairs = Object.entries(value.value)
|
|
26
|
+
.map(([key, val]) => {
|
|
27
|
+
return `'${(0, escape_1.sqlEscape)(key)}', '${(0, escape_1.sqlEscape)(JSON.stringify(val))}'`;
|
|
28
|
+
})
|
|
29
|
+
.join(", ");
|
|
30
|
+
return `JSON_OBJECT(${jsonPairs})`;
|
|
31
|
+
}
|
|
32
|
+
// Should not happen with SqlValue type
|
|
33
|
+
return `'${(0, escape_1.sqlEscape)(String(value))}'`;
|
|
34
|
+
}
|
|
35
|
+
function backtickWrap(name) {
|
|
36
|
+
return `\`${name}\``;
|
|
37
|
+
}
|
|
38
|
+
function buildWhereClause(where) {
|
|
39
|
+
const parts = [];
|
|
40
|
+
for (const [key, val] of Object.entries(where)) {
|
|
41
|
+
if (typeof val === "object" && val !== null && "op" in val) {
|
|
42
|
+
parts.push(`${backtickWrap(key)} ${val.op} ${formatValue(val.value)}`);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
parts.push(`${backtickWrap(key)} = ${formatValue(val)}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return parts.join(" AND ");
|
|
49
|
+
}
|
|
50
|
+
function query(repoPath) {
|
|
51
|
+
return {
|
|
52
|
+
insert: (table, data) => {
|
|
53
|
+
const cols = Object.keys(data).map(backtickWrap).join(", ");
|
|
54
|
+
const vals = Object.values(data).map(formatValue).join(", ");
|
|
55
|
+
const sql = `INSERT INTO ${backtickWrap(table)} (${cols}) VALUES (${vals})`;
|
|
56
|
+
return (0, connection_1.doltSql)(sql, repoPath).map((res) => res);
|
|
57
|
+
},
|
|
58
|
+
update: (table, data, where) => {
|
|
59
|
+
const setParts = Object.entries(data)
|
|
60
|
+
.map(([key, val]) => `${backtickWrap(key)} = ${formatValue(val)}`)
|
|
61
|
+
.join(", ");
|
|
62
|
+
const whereClause = buildWhereClause(where);
|
|
63
|
+
const sql = `UPDATE ${backtickWrap(table)} SET ${setParts} WHERE ${whereClause}`;
|
|
64
|
+
return (0, connection_1.doltSql)(sql, repoPath).map((res) => res);
|
|
65
|
+
},
|
|
66
|
+
select: (table, options) => {
|
|
67
|
+
let sql = `SELECT ${options?.columns?.map(backtickWrap).join(", ") ?? "*"} FROM ${backtickWrap(table)}`;
|
|
68
|
+
const whereClause = options?.where && Object.keys(options.where).length > 0
|
|
69
|
+
? buildWhereClause(options.where)
|
|
70
|
+
: "";
|
|
71
|
+
if (whereClause) {
|
|
72
|
+
sql += ` WHERE ${whereClause}`;
|
|
73
|
+
}
|
|
74
|
+
if (options?.groupBy && options.groupBy.length > 0) {
|
|
75
|
+
sql += ` GROUP BY ${options.groupBy.map(backtickWrap).join(", ")}`;
|
|
76
|
+
}
|
|
77
|
+
if (options?.having) {
|
|
78
|
+
sql += ` HAVING ${options.having}`;
|
|
79
|
+
}
|
|
80
|
+
if (options?.orderBy) {
|
|
81
|
+
sql += ` ORDER BY ${options.orderBy}`; // orderBy expected to be already escaped/formatted
|
|
82
|
+
}
|
|
83
|
+
if (options?.limit) {
|
|
84
|
+
sql += ` LIMIT ${options.limit}`;
|
|
85
|
+
}
|
|
86
|
+
if (options?.offset) {
|
|
87
|
+
sql += ` OFFSET ${options.offset}`;
|
|
88
|
+
}
|
|
89
|
+
return (0, connection_1.doltSql)(sql, repoPath).map((res) => res);
|
|
90
|
+
},
|
|
91
|
+
count: (table, where) => {
|
|
92
|
+
let sql = `SELECT COUNT(*) AS count FROM ${backtickWrap(table)}`;
|
|
93
|
+
if (where) {
|
|
94
|
+
sql += ` WHERE ${buildWhereClause(where)}`;
|
|
95
|
+
}
|
|
96
|
+
return (0, connection_1.doltSql)(sql, repoPath).map((res) => res[0]?.count ?? 0);
|
|
97
|
+
},
|
|
98
|
+
raw: (sql) => {
|
|
99
|
+
return (0, connection_1.doltSql)(sql, repoPath).map((res) => res);
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildError = exports.ErrorCode = void 0;
|
|
4
|
+
var ErrorCode;
|
|
5
|
+
(function (ErrorCode) {
|
|
6
|
+
// DB errors
|
|
7
|
+
ErrorCode["DB_QUERY_FAILED"] = "DB_QUERY_FAILED";
|
|
8
|
+
ErrorCode["DB_COMMIT_FAILED"] = "DB_COMMIT_FAILED";
|
|
9
|
+
ErrorCode["DB_PARSE_FAILED"] = "DB_PARSE_FAILED";
|
|
10
|
+
// Domain errors
|
|
11
|
+
ErrorCode["TASK_NOT_FOUND"] = "TASK_NOT_FOUND";
|
|
12
|
+
ErrorCode["TASK_ALREADY_CLAIMED"] = "TASK_ALREADY_CLAIMED";
|
|
13
|
+
ErrorCode["PLAN_NOT_FOUND"] = "PLAN_NOT_FOUND";
|
|
14
|
+
ErrorCode["INVALID_TRANSITION"] = "INVALID_TRANSITION";
|
|
15
|
+
ErrorCode["TASK_NOT_RUNNABLE"] = "TASK_NOT_RUNNABLE";
|
|
16
|
+
ErrorCode["CYCLE_DETECTED"] = "CYCLE_DETECTED";
|
|
17
|
+
ErrorCode["EDGE_EXISTS"] = "EDGE_EXISTS";
|
|
18
|
+
// Config errors
|
|
19
|
+
ErrorCode["CONFIG_NOT_FOUND"] = "CONFIG_NOT_FOUND";
|
|
20
|
+
ErrorCode["CONFIG_PARSE_FAILED"] = "CONFIG_PARSE_FAILED";
|
|
21
|
+
// Import errors
|
|
22
|
+
ErrorCode["FILE_READ_FAILED"] = "FILE_READ_FAILED";
|
|
23
|
+
ErrorCode["PARSE_FAILED"] = "PARSE_FAILED";
|
|
24
|
+
// Validation
|
|
25
|
+
ErrorCode["VALIDATION_FAILED"] = "VALIDATION_FAILED";
|
|
26
|
+
ErrorCode["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
|
|
27
|
+
})(ErrorCode || (exports.ErrorCode = ErrorCode = {}));
|
|
28
|
+
const buildError = (code, message, cause) => ({
|
|
29
|
+
code,
|
|
30
|
+
message,
|
|
31
|
+
cause,
|
|
32
|
+
});
|
|
33
|
+
exports.buildError = buildError;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkNoBlockerCycle = checkNoBlockerCycle;
|
|
4
|
+
exports.checkRunnable = checkRunnable;
|
|
5
|
+
exports.checkValidTransition = checkValidTransition;
|
|
6
|
+
const neverthrow_1 = require("neverthrow");
|
|
7
|
+
const neverthrow_2 = require("neverthrow");
|
|
8
|
+
const errors_1 = require("./errors");
|
|
9
|
+
const query_1 = require("../db/query");
|
|
10
|
+
const escape_1 = require("../db/escape");
|
|
11
|
+
// Helper for cycle detection (DFS)
|
|
12
|
+
function hasCycleDFS(start, current, graph, visited, recursionStack) {
|
|
13
|
+
visited.add(current);
|
|
14
|
+
recursionStack.add(current);
|
|
15
|
+
const neighbors = graph.get(current) || [];
|
|
16
|
+
for (const neighbor of neighbors) {
|
|
17
|
+
if (!visited.has(neighbor)) {
|
|
18
|
+
if (hasCycleDFS(start, neighbor, graph, visited, recursionStack)) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else if (recursionStack.has(neighbor)) {
|
|
23
|
+
return true; // Cycle detected
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
recursionStack.delete(current);
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
function checkNoBlockerCycle(fromTaskId, toTaskId, existingEdges) {
|
|
30
|
+
const allEdges = [
|
|
31
|
+
...existingEdges,
|
|
32
|
+
{
|
|
33
|
+
from_task_id: fromTaskId,
|
|
34
|
+
to_task_id: toTaskId,
|
|
35
|
+
type: "blocks",
|
|
36
|
+
reason: null,
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
const graph = new Map();
|
|
40
|
+
for (const edge of allEdges) {
|
|
41
|
+
if (edge.type === "blocks") {
|
|
42
|
+
if (!graph.has(edge.from_task_id)) {
|
|
43
|
+
graph.set(edge.from_task_id, []);
|
|
44
|
+
}
|
|
45
|
+
graph.get(edge.from_task_id)?.push(edge.to_task_id);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Check for cycles starting from each node
|
|
49
|
+
for (const node of graph.keys()) {
|
|
50
|
+
const visited = new Set();
|
|
51
|
+
const recursionStack = new Set();
|
|
52
|
+
if (hasCycleDFS(node, node, graph, visited, recursionStack)) {
|
|
53
|
+
return (0, neverthrow_1.err)((0, errors_1.buildError)(errors_1.ErrorCode.CYCLE_DETECTED, `Blocking edge from ${fromTaskId} to ${toTaskId} would create a cycle.`));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return (0, neverthrow_1.ok)(undefined);
|
|
57
|
+
}
|
|
58
|
+
function checkRunnable(taskId, repoPath) {
|
|
59
|
+
const q = (0, query_1.query)(repoPath);
|
|
60
|
+
return q
|
|
61
|
+
.select("task", {
|
|
62
|
+
columns: ["status"],
|
|
63
|
+
where: { task_id: taskId },
|
|
64
|
+
})
|
|
65
|
+
.andThen((taskResult) => {
|
|
66
|
+
if (taskResult.length === 0) {
|
|
67
|
+
return (0, neverthrow_2.errAsync)((0, errors_1.buildError)(errors_1.ErrorCode.TASK_NOT_FOUND, `Task with ID ${taskId} not found.`));
|
|
68
|
+
}
|
|
69
|
+
const taskStatus = taskResult[0].status;
|
|
70
|
+
if (taskStatus !== "todo") {
|
|
71
|
+
return (0, neverthrow_2.errAsync)((0, errors_1.buildError)(errors_1.ErrorCode.INVALID_TRANSITION, `Task ${taskId} is not in 'todo' status. Current status: ${taskStatus}.`));
|
|
72
|
+
}
|
|
73
|
+
const unmetBlockersQuery = `
|
|
74
|
+
SELECT COUNT(*)
|
|
75
|
+
FROM \`edge\` e
|
|
76
|
+
JOIN \`task\` bt ON e.from_task_id = bt.task_id
|
|
77
|
+
WHERE e.to_task_id = '${(0, escape_1.sqlEscape)(taskId)}'
|
|
78
|
+
AND e.type = 'blocks'
|
|
79
|
+
AND bt.status NOT IN ('done','canceled');
|
|
80
|
+
`;
|
|
81
|
+
return q.raw(unmetBlockersQuery);
|
|
82
|
+
})
|
|
83
|
+
.andThen((blockerCountResult) => {
|
|
84
|
+
const unmetBlockers = blockerCountResult[0]["COUNT(*)"];
|
|
85
|
+
if (unmetBlockers > 0) {
|
|
86
|
+
return (0, neverthrow_2.errAsync)((0, errors_1.buildError)(errors_1.ErrorCode.TASK_NOT_RUNNABLE, `Task ${taskId} has ${unmetBlockers} unmet blockers and is not runnable.`));
|
|
87
|
+
}
|
|
88
|
+
return (0, neverthrow_2.okAsync)(undefined);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function checkValidTransition(currentStatus, nextStatus) {
|
|
92
|
+
const validTransitions = {
|
|
93
|
+
todo: ["doing", "blocked", "canceled"],
|
|
94
|
+
doing: ["done", "blocked", "canceled"],
|
|
95
|
+
blocked: ["todo", "canceled"], // Can only go to todo when unblocked, or canceled
|
|
96
|
+
done: [], // Terminal state
|
|
97
|
+
canceled: [], // Terminal state
|
|
98
|
+
};
|
|
99
|
+
if (!validTransitions[currentStatus].includes(nextStatus)) {
|
|
100
|
+
return (0, neverthrow_1.err)((0, errors_1.buildError)(errors_1.ErrorCode.INVALID_TRANSITION, `Invalid task status transition from '${currentStatus}' to '${nextStatus}'.`));
|
|
101
|
+
}
|
|
102
|
+
return (0, neverthrow_1.ok)(undefined);
|
|
103
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DecisionSchema = exports.EventSchema = exports.EdgeSchema = exports.TaskSkillSchema = exports.TaskDomainSchema = exports.TaskSchema = exports.PlanSchema = exports.PlanRiskEntrySchema = exports.ActorSchema = exports.EventKindSchema = exports.EdgeTypeSchema = exports.ChangeTypeSchema = exports.RiskSchema = exports.OwnerSchema = exports.TaskStatusSchema = exports.PlanStatusSchema = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
// Enums
|
|
6
|
+
exports.PlanStatusSchema = zod_1.z.enum([
|
|
7
|
+
"draft",
|
|
8
|
+
"active",
|
|
9
|
+
"paused",
|
|
10
|
+
"done",
|
|
11
|
+
"abandoned",
|
|
12
|
+
]);
|
|
13
|
+
exports.TaskStatusSchema = zod_1.z.enum([
|
|
14
|
+
"todo",
|
|
15
|
+
"doing",
|
|
16
|
+
"blocked",
|
|
17
|
+
"done",
|
|
18
|
+
"canceled",
|
|
19
|
+
]);
|
|
20
|
+
exports.OwnerSchema = zod_1.z.enum(["human", "agent"]);
|
|
21
|
+
exports.RiskSchema = zod_1.z.enum(["low", "medium", "high"]);
|
|
22
|
+
exports.ChangeTypeSchema = zod_1.z.enum([
|
|
23
|
+
"create",
|
|
24
|
+
"modify",
|
|
25
|
+
"refactor",
|
|
26
|
+
"fix",
|
|
27
|
+
"investigate",
|
|
28
|
+
"test",
|
|
29
|
+
"document",
|
|
30
|
+
]);
|
|
31
|
+
exports.EdgeTypeSchema = zod_1.z.enum(["blocks", "relates"]);
|
|
32
|
+
exports.EventKindSchema = zod_1.z.enum([
|
|
33
|
+
"created",
|
|
34
|
+
"started",
|
|
35
|
+
"progress",
|
|
36
|
+
"blocked",
|
|
37
|
+
"unblocked",
|
|
38
|
+
"done",
|
|
39
|
+
"split",
|
|
40
|
+
"decision_needed",
|
|
41
|
+
"note",
|
|
42
|
+
]);
|
|
43
|
+
exports.ActorSchema = zod_1.z.enum(["human", "agent"]);
|
|
44
|
+
// Risk entry for plan.risks (rich planning)
|
|
45
|
+
exports.PlanRiskEntrySchema = zod_1.z.object({
|
|
46
|
+
description: zod_1.z.string(),
|
|
47
|
+
severity: exports.RiskSchema,
|
|
48
|
+
mitigation: zod_1.z.string(),
|
|
49
|
+
});
|
|
50
|
+
// Schemas
|
|
51
|
+
exports.PlanSchema = zod_1.z.object({
|
|
52
|
+
plan_id: zod_1.z.string().uuid(),
|
|
53
|
+
title: zod_1.z.string().max(255),
|
|
54
|
+
intent: zod_1.z.string(),
|
|
55
|
+
status: exports.PlanStatusSchema.default("draft"),
|
|
56
|
+
priority: zod_1.z.number().int().default(0),
|
|
57
|
+
source_path: zod_1.z.string().max(512).nullable(),
|
|
58
|
+
source_commit: zod_1.z.string().max(64).nullable(),
|
|
59
|
+
created_at: zod_1.z.string().datetime(),
|
|
60
|
+
updated_at: zod_1.z.string().datetime(),
|
|
61
|
+
file_tree: zod_1.z.string().nullable(),
|
|
62
|
+
risks: zod_1.z.array(exports.PlanRiskEntrySchema).nullable(),
|
|
63
|
+
tests: zod_1.z.array(zod_1.z.string()).nullable(),
|
|
64
|
+
});
|
|
65
|
+
exports.TaskSchema = zod_1.z.object({
|
|
66
|
+
task_id: zod_1.z.string().uuid(),
|
|
67
|
+
plan_id: zod_1.z.string().uuid(),
|
|
68
|
+
feature_key: zod_1.z.string().max(64).nullable(),
|
|
69
|
+
title: zod_1.z.string().max(255),
|
|
70
|
+
intent: zod_1.z.string().nullable(),
|
|
71
|
+
scope_in: zod_1.z.string().nullable(),
|
|
72
|
+
scope_out: zod_1.z.string().nullable(),
|
|
73
|
+
acceptance: zod_1.z.array(zod_1.z.string()).nullable(), // Assuming acceptance is an array of strings
|
|
74
|
+
status: exports.TaskStatusSchema.default("todo"),
|
|
75
|
+
owner: exports.OwnerSchema.default("agent"),
|
|
76
|
+
area: zod_1.z.string().max(64).nullable(),
|
|
77
|
+
risk: exports.RiskSchema.default("low"),
|
|
78
|
+
estimate_mins: zod_1.z.number().int().nullable(),
|
|
79
|
+
created_at: zod_1.z.string().datetime(),
|
|
80
|
+
updated_at: zod_1.z.string().datetime(),
|
|
81
|
+
external_key: zod_1.z.string().max(128).nullable(),
|
|
82
|
+
change_type: exports.ChangeTypeSchema.nullable(),
|
|
83
|
+
suggested_changes: zod_1.z.string().nullable(),
|
|
84
|
+
});
|
|
85
|
+
/** Junction: task_id + domain (task can have many domains). */
|
|
86
|
+
exports.TaskDomainSchema = zod_1.z.object({
|
|
87
|
+
task_id: zod_1.z.string().uuid(),
|
|
88
|
+
domain: zod_1.z.string().max(64),
|
|
89
|
+
});
|
|
90
|
+
/** Junction: task_id + skill (task can have many skills). */
|
|
91
|
+
exports.TaskSkillSchema = zod_1.z.object({
|
|
92
|
+
task_id: zod_1.z.string().uuid(),
|
|
93
|
+
skill: zod_1.z.string().max(64),
|
|
94
|
+
});
|
|
95
|
+
exports.EdgeSchema = zod_1.z.object({
|
|
96
|
+
from_task_id: zod_1.z.string().uuid(),
|
|
97
|
+
to_task_id: zod_1.z.string().uuid(),
|
|
98
|
+
type: exports.EdgeTypeSchema.default("blocks"),
|
|
99
|
+
reason: zod_1.z.string().nullable(),
|
|
100
|
+
});
|
|
101
|
+
exports.EventSchema = zod_1.z.object({
|
|
102
|
+
event_id: zod_1.z.string().uuid(),
|
|
103
|
+
task_id: zod_1.z.string().uuid(),
|
|
104
|
+
kind: exports.EventKindSchema,
|
|
105
|
+
body: zod_1.z.record(zod_1.z.any()), // JSON type in Dolt maps to a generic record
|
|
106
|
+
actor: exports.ActorSchema.default("agent"),
|
|
107
|
+
created_at: zod_1.z.string().datetime(),
|
|
108
|
+
});
|
|
109
|
+
exports.DecisionSchema = zod_1.z.object({
|
|
110
|
+
decision_id: zod_1.z.string().uuid(),
|
|
111
|
+
plan_id: zod_1.z.string().uuid(),
|
|
112
|
+
task_id: zod_1.z.string().uuid().nullable(),
|
|
113
|
+
summary: zod_1.z.string().max(255),
|
|
114
|
+
context: zod_1.z.string(),
|
|
115
|
+
options: zod_1.z.array(zod_1.z.string()).nullable(), // Assuming options is an array of strings
|
|
116
|
+
decision: zod_1.z.string(),
|
|
117
|
+
consequences: zod_1.z.string().nullable(),
|
|
118
|
+
source_ref: zod_1.z.string().max(512).nullable(),
|
|
119
|
+
created_at: zod_1.z.string().datetime(),
|
|
120
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatDotGraph = formatDotGraph;
|
|
4
|
+
exports.generateDotGraph = generateDotGraph;
|
|
5
|
+
const graph_data_1 = require("./graph-data");
|
|
6
|
+
function formatDotGraph(nodes, edges) {
|
|
7
|
+
let dot = "digraph TaskGraph {\n";
|
|
8
|
+
dot += " rankdir=LR;\n";
|
|
9
|
+
dot += " node [shape=box];\n";
|
|
10
|
+
nodes.forEach((node) => {
|
|
11
|
+
dot += ` \"${node.id}\" [label=\"${node.label}\"];\n`;
|
|
12
|
+
});
|
|
13
|
+
edges.forEach((edge) => {
|
|
14
|
+
dot += ` \"${edge.from}\" -> \"${edge.to}\" [label=\"${edge.type}\"];\n`;
|
|
15
|
+
});
|
|
16
|
+
dot += "}\n";
|
|
17
|
+
return dot;
|
|
18
|
+
}
|
|
19
|
+
function generateDotGraph(planId, featureKey, basePath) {
|
|
20
|
+
return (0, graph_data_1.getGraphData)(planId, featureKey, basePath).map(({ nodes, edges }) => formatDotGraph(nodes, edges));
|
|
21
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getGraphData = getGraphData;
|
|
4
|
+
const utils_1 = require("../cli/utils");
|
|
5
|
+
const query_1 = require("../db/query");
|
|
6
|
+
function getGraphData(planId, featureKey, basePath) {
|
|
7
|
+
return (0, utils_1.readConfig)(basePath).asyncAndThen((config) => {
|
|
8
|
+
// Passed basePath to readConfig
|
|
9
|
+
const q = (0, query_1.query)(config.doltRepoPath);
|
|
10
|
+
const whereClause = {};
|
|
11
|
+
if (planId) {
|
|
12
|
+
whereClause.plan_id = planId;
|
|
13
|
+
}
|
|
14
|
+
if (featureKey) {
|
|
15
|
+
whereClause.feature_key = featureKey;
|
|
16
|
+
}
|
|
17
|
+
return q
|
|
18
|
+
.select("task", {
|
|
19
|
+
columns: ["task_id", "title", "status"],
|
|
20
|
+
where: whereClause,
|
|
21
|
+
})
|
|
22
|
+
.andThen((tasksResult) => {
|
|
23
|
+
const tasks = tasksResult;
|
|
24
|
+
const edgesQuery = `SELECT from_task_id, to_task_id, type FROM \`edge\`;`;
|
|
25
|
+
return q.raw(edgesQuery).map((edgesResult) => {
|
|
26
|
+
const edges = edgesResult;
|
|
27
|
+
const nodes = tasks.map((task) => ({
|
|
28
|
+
id: task.task_id,
|
|
29
|
+
label: `${task.title} (${task.status})`,
|
|
30
|
+
status: task.status,
|
|
31
|
+
}));
|
|
32
|
+
const graphEdges = edges.map((edge) => ({
|
|
33
|
+
from: edge.from_task_id,
|
|
34
|
+
to: edge.to_task_id,
|
|
35
|
+
type: edge.type,
|
|
36
|
+
}));
|
|
37
|
+
return { nodes, edges: graphEdges };
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateMarkdown = generateMarkdown;
|
|
7
|
+
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
8
|
+
const utils_1 = require("../cli/utils");
|
|
9
|
+
const neverthrow_1 = require("neverthrow");
|
|
10
|
+
const errors_1 = require("../domain/errors");
|
|
11
|
+
const query_1 = require("../db/query");
|
|
12
|
+
const escape_1 = require("../db/escape");
|
|
13
|
+
/** Generates Cursor-format markdown from plan and tasks. */
|
|
14
|
+
function generateMarkdown(planId, basePath) {
|
|
15
|
+
return (0, utils_1.readConfig)(basePath).asyncAndThen((config) => {
|
|
16
|
+
const q = (0, query_1.query)(config.doltRepoPath);
|
|
17
|
+
return q
|
|
18
|
+
.select("plan", {
|
|
19
|
+
columns: ["plan_id", "title", "intent"],
|
|
20
|
+
where: { plan_id: planId },
|
|
21
|
+
})
|
|
22
|
+
.andThen((plans) => {
|
|
23
|
+
if (plans.length === 0) {
|
|
24
|
+
return (0, neverthrow_1.errAsync)((0, errors_1.buildError)(errors_1.ErrorCode.PLAN_NOT_FOUND, `Plan ${planId} not found`));
|
|
25
|
+
}
|
|
26
|
+
const plan = plans[0];
|
|
27
|
+
return q
|
|
28
|
+
.select("task", {
|
|
29
|
+
columns: ["task_id", "external_key", "title", "status"],
|
|
30
|
+
where: { plan_id: planId },
|
|
31
|
+
})
|
|
32
|
+
.andThen((tasks) => {
|
|
33
|
+
const taskIds = tasks.map((t) => t.task_id);
|
|
34
|
+
return q
|
|
35
|
+
.raw(taskIds.length > 0
|
|
36
|
+
? `SELECT task_id, domain FROM \`task_domain\` WHERE task_id IN (${taskIds.map((id) => `'${(0, escape_1.sqlEscape)(id)}'`).join(",")})`
|
|
37
|
+
: "SELECT task_id, domain FROM `task_domain` WHERE 1=0")
|
|
38
|
+
.andThen((domainRows) => q
|
|
39
|
+
.raw(taskIds.length > 0 ? `SELECT task_id, skill FROM \`task_skill\` WHERE task_id IN (${taskIds.map((id) => `'${(0, escape_1.sqlEscape)(id)}'`).join(",")})` : "SELECT task_id, skill FROM `task_skill` WHERE 1=0")
|
|
40
|
+
.map((skillRows) => {
|
|
41
|
+
const domainsByTask = new Map();
|
|
42
|
+
domainRows.forEach((r) => {
|
|
43
|
+
const arr = domainsByTask.get(r.task_id) ?? [];
|
|
44
|
+
arr.push(r.domain);
|
|
45
|
+
domainsByTask.set(r.task_id, arr);
|
|
46
|
+
});
|
|
47
|
+
const skillsByTask = new Map();
|
|
48
|
+
skillRows.forEach((r) => {
|
|
49
|
+
const arr = skillsByTask.get(r.task_id) ?? [];
|
|
50
|
+
arr.push(r.skill);
|
|
51
|
+
skillsByTask.set(r.task_id, arr);
|
|
52
|
+
});
|
|
53
|
+
return { domainsByTask, skillsByTask };
|
|
54
|
+
}))
|
|
55
|
+
.andThen(({ domainsByTask, skillsByTask }) => q
|
|
56
|
+
.raw("SELECT from_task_id, to_task_id, type FROM `edge` WHERE type = 'blocks'")
|
|
57
|
+
.map((edges) => {
|
|
58
|
+
const taskIdToKey = new Map();
|
|
59
|
+
tasks.forEach((t) => {
|
|
60
|
+
if (t.external_key) {
|
|
61
|
+
taskIdToKey.set(t.task_id, t.external_key);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
const blockedByMap = new Map();
|
|
65
|
+
edges.forEach((e) => {
|
|
66
|
+
if (e.to_task_id &&
|
|
67
|
+
taskIdToKey.has(e.from_task_id) &&
|
|
68
|
+
taskIdToKey.has(e.to_task_id)) {
|
|
69
|
+
const key = taskIdToKey.get(e.to_task_id);
|
|
70
|
+
const blockerKey = taskIdToKey.get(e.from_task_id);
|
|
71
|
+
if (!blockedByMap.has(key)) {
|
|
72
|
+
blockedByMap.set(key, []);
|
|
73
|
+
}
|
|
74
|
+
blockedByMap.get(key).push(blockerKey);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
const todos = tasks
|
|
78
|
+
.filter((t) => t.external_key)
|
|
79
|
+
.map((t) => {
|
|
80
|
+
const status = t.status === "done" ? "completed" : "pending";
|
|
81
|
+
const blockedBy = blockedByMap.get(t.external_key) ?? [];
|
|
82
|
+
const domains = domainsByTask.get(t.task_id);
|
|
83
|
+
const skills = skillsByTask.get(t.task_id);
|
|
84
|
+
return {
|
|
85
|
+
id: t.external_key,
|
|
86
|
+
content: t.title,
|
|
87
|
+
status,
|
|
88
|
+
...(blockedBy.length > 0 && { blockedBy }),
|
|
89
|
+
...(domains &&
|
|
90
|
+
domains.length > 0 && { domain: domains }),
|
|
91
|
+
...(skills && skills.length > 0 && { skill: skills }),
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
const frontmatter = {
|
|
95
|
+
name: plan.title,
|
|
96
|
+
overview: plan.intent || "",
|
|
97
|
+
todos,
|
|
98
|
+
isProject: false,
|
|
99
|
+
};
|
|
100
|
+
const yamlStr = js_yaml_1.default.dump(frontmatter, { lineWidth: -1 });
|
|
101
|
+
return `---
|
|
102
|
+
${yamlStr}---
|
|
103
|
+
`;
|
|
104
|
+
}));
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatMermaidGraph = formatMermaidGraph;
|
|
4
|
+
exports.generateMermaidGraph = generateMermaidGraph;
|
|
5
|
+
const graph_data_1 = require("./graph-data");
|
|
6
|
+
function formatMermaidGraph(nodes, edges) {
|
|
7
|
+
let mermaid = "graph TD\n";
|
|
8
|
+
nodes.forEach((node) => {
|
|
9
|
+
// Node IDs in Mermaid typically cannot have special characters or spaces
|
|
10
|
+
const nodeId = node.id.replace(/[^a-zA-Z0-9]/g, "");
|
|
11
|
+
mermaid += ` ${nodeId}[\"${node.label}\"]\n`;
|
|
12
|
+
});
|
|
13
|
+
edges.forEach((edge) => {
|
|
14
|
+
const fromNodeId = edge.from.replace(/[^a-zA-Z0-9]/g, "");
|
|
15
|
+
const toNodeId = edge.to.replace(/[^a-zA-Z0-9]/g, "");
|
|
16
|
+
if (edge.type === "blocks") {
|
|
17
|
+
mermaid += ` ${fromNodeId} --> ${toNodeId}\n`;
|
|
18
|
+
}
|
|
19
|
+
else if (edge.type === "relates") {
|
|
20
|
+
mermaid += ` ${fromNodeId} --- ${toNodeId}\n`;
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
return mermaid;
|
|
24
|
+
}
|
|
25
|
+
function generateMermaidGraph(planId, featureKey, basePath) {
|
|
26
|
+
return (0, graph_data_1.getGraphData)(planId, featureKey, basePath).map(({ nodes, edges }) => formatMermaidGraph(nodes, edges));
|
|
27
|
+
}
|