@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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/dist/cli/block.js +114 -0
  4. package/dist/cli/context.js +139 -0
  5. package/dist/cli/done.js +98 -0
  6. package/dist/cli/edge.js +99 -0
  7. package/dist/cli/export.js +97 -0
  8. package/dist/cli/import.js +123 -0
  9. package/dist/cli/index.js +78 -0
  10. package/dist/cli/init.js +106 -0
  11. package/dist/cli/next.js +97 -0
  12. package/dist/cli/note.js +72 -0
  13. package/dist/cli/plan.js +108 -0
  14. package/dist/cli/portfolio.js +159 -0
  15. package/dist/cli/setup.js +142 -0
  16. package/dist/cli/show.js +142 -0
  17. package/dist/cli/split.js +191 -0
  18. package/dist/cli/start.js +94 -0
  19. package/dist/cli/status.js +149 -0
  20. package/dist/cli/task.js +92 -0
  21. package/dist/cli/utils.js +74 -0
  22. package/dist/db/commit.js +18 -0
  23. package/dist/db/connection.js +22 -0
  24. package/dist/db/escape.js +6 -0
  25. package/dist/db/migrate.js +159 -0
  26. package/dist/db/query.js +102 -0
  27. package/dist/domain/errors.js +33 -0
  28. package/dist/domain/invariants.js +103 -0
  29. package/dist/domain/types.js +120 -0
  30. package/dist/export/dot.js +21 -0
  31. package/dist/export/graph-data.js +41 -0
  32. package/dist/export/markdown.js +108 -0
  33. package/dist/export/mermaid.js +27 -0
  34. package/dist/plan-import/importer.js +155 -0
  35. package/dist/plan-import/parser.js +213 -0
  36. package/dist/template/.cursor/memory.md +14 -0
  37. package/dist/template/.cursor/rules/memory.mdc +11 -0
  38. package/dist/template/.cursor/rules/plan-authoring.mdc +42 -0
  39. package/dist/template/.cursor/rules/session-start.mdc +18 -0
  40. package/dist/template/.cursor/rules/taskgraph-workflow.mdc +35 -0
  41. package/dist/template/AGENT.md +73 -0
  42. package/dist/template/docs/backend.md +33 -0
  43. package/dist/template/docs/frontend.md +31 -0
  44. package/dist/template/docs/infra.md +26 -0
  45. package/dist/template/docs/skills/README.md +14 -0
  46. package/dist/template/docs/skills/plan-authoring.md +38 -0
  47. package/dist/template/docs/skills/refactoring-safely.md +21 -0
  48. package/dist/template/docs/skills/taskgraph-lifecycle-execution.md +23 -0
  49. package/package.json +47 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 danalexilewis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # Taskgraph
2
+
3
+ Inspired by [Beads](https://github.com/steveyegge/beads) and [Gastown.dev](https://gastown.dev) — Task Graph is for **Centaur Development** (human + agent).
4
+
5
+ ## Why this repo
6
+
7
+ I wanted a small, local-first way to manage plans and tasks during agent-assisted coding without adopting full Beads/Gastown orchestration. Task-Graph borrows from Beads (atomic claims, structured notes, status visibility) but stays minimal: one working copy, no mayor/orchestrator, no swarms. It’s a Dolt-backed CLI that fits into Cursor workflows so agents and humans can share the same task graph and execution state.
8
+
9
+ ## What this is
10
+
11
+ TaskGraph is a small CLI (`tg`) + Dolt-backed schema for managing **plans, tasks, dependencies, and execution state** during agent-assisted (“centaur”) development.
12
+
13
+ ## Quick start
14
+
15
+ 1. Install Dolt (`brew install dolt`)
16
+ 2. Initialize TaskGraph in your repo:
17
+
18
+ ```bash
19
+ tg init
20
+ ```
21
+
22
+ 1. Scaffold recommended conventions (example domain docs, skill guides, and Cursor rules):
23
+
24
+ ```bash
25
+ tg setup
26
+ ```
27
+
28
+ ## Conventions (domain + skill guides)
29
+
30
+ Tasks can optionally declare:
31
+
32
+ - `domain`: slug(s) that map to `docs/<domain>.md`
33
+ - `skill`: slug(s) that map to `docs/skills/<skill>.md`
34
+
35
+ Agents can read the docs printed by `tg context <taskId>` to load repo-specific conventions before making changes.
36
+
37
+ ## Publishing the CLI to npm
38
+
39
+ The repo root is the publishable package. **Run from the repo root**:
40
+
41
+ 1. **Package name**: The name `taskgraph` is available. If it were taken, use a scoped name in `package.json` (e.g. `@yourname/taskgraph`).
42
+ 2. **Build**: `pnpm build`
43
+ 3. **Dry-run** (see what will be in the tarball): `npm pack --dry-run`
44
+ 4. **Publish**: `npm publish`
45
+ (Requires `npm login` and 2FA if enabled.)
46
+
47
+ The `prepublishOnly` script runs `npm run build` when you publish, so `dist/` is built before packing. The tarball includes only `dist`, `templates`, and `README.md` (see `files` in package.json).
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.blockCommand = blockCommand;
4
+ const uuid_1 = require("uuid");
5
+ const commit_1 = require("../db/commit");
6
+ const utils_1 = require("./utils"); // Import Config
7
+ const invariants_1 = require("../domain/invariants");
8
+ const neverthrow_1 = require("neverthrow");
9
+ const errors_1 = require("../domain/errors");
10
+ const query_1 = require("../db/query");
11
+ function blockCommand(program) {
12
+ program
13
+ .command("block")
14
+ .description("Block a task on another task")
15
+ .argument("<taskId>", "ID of the task to be blocked")
16
+ .requiredOption("--on <blockerTaskId>", "ID of the task that is blocking")
17
+ .option("--reason <reason>", "Reason for the block")
18
+ .action(async (taskId, options, cmd) => {
19
+ const result = await (0, utils_1.readConfig)().asyncAndThen((config) => {
20
+ // Removed async, added type
21
+ const currentTimestamp = (0, query_1.now)();
22
+ const q = (0, query_1.query)(config.doltRepoPath);
23
+ return neverthrow_1.ResultAsync.fromPromise((async () => {
24
+ const existingEdgesResult = await q.select("edge", {
25
+ where: { type: "blocks" },
26
+ });
27
+ if (existingEdgesResult.isErr())
28
+ throw existingEdgesResult.error;
29
+ const existingEdges = existingEdgesResult.value;
30
+ const cycleCheckResult = (0, invariants_1.checkNoBlockerCycle)(options.on, taskId, existingEdges);
31
+ if (cycleCheckResult.isErr())
32
+ throw cycleCheckResult.error;
33
+ const edgeExistsResult = await q.count("edge", {
34
+ from_task_id: options.on,
35
+ to_task_id: taskId,
36
+ type: "blocks",
37
+ });
38
+ if (edgeExistsResult.isErr())
39
+ throw edgeExistsResult.error;
40
+ const edgeExists = edgeExistsResult.value;
41
+ if (edgeExists === 0) {
42
+ const insertResult = await q.insert("edge", {
43
+ from_task_id: options.on,
44
+ to_task_id: taskId,
45
+ type: "blocks",
46
+ reason: options.reason ?? null,
47
+ });
48
+ if (insertResult.isErr())
49
+ throw insertResult.error;
50
+ }
51
+ else {
52
+ console.log(`Edge from ${options.on} to ${taskId} of type 'blocks' already exists. Skipping edge creation.`);
53
+ }
54
+ const currentStatusResult = await q.select("task", { columns: ["status"], where: { task_id: taskId } });
55
+ if (currentStatusResult.isErr())
56
+ throw currentStatusResult.error;
57
+ if (currentStatusResult.value.length === 0) {
58
+ throw (0, errors_1.buildError)(errors_1.ErrorCode.TASK_NOT_FOUND, `Task with ID ${taskId} not found.`);
59
+ }
60
+ const currentStatus = currentStatusResult.value[0].status;
61
+ if (currentStatus !== "blocked") {
62
+ const transitionResult = (0, invariants_1.checkValidTransition)(currentStatus, "blocked");
63
+ if (transitionResult.isErr())
64
+ throw transitionResult.error;
65
+ const updateStatusResult = await q.update("task", { status: "blocked", updated_at: currentTimestamp }, { task_id: taskId });
66
+ if (updateStatusResult.isErr())
67
+ throw updateStatusResult.error;
68
+ }
69
+ const insertEventResult = await q.insert("event", {
70
+ event_id: (0, uuid_1.v4)(),
71
+ task_id: taskId,
72
+ kind: "blocked",
73
+ body: (0, query_1.jsonObj)({
74
+ blockerTaskId: options.on,
75
+ reason: options.reason ?? null,
76
+ timestamp: currentTimestamp,
77
+ }),
78
+ created_at: currentTimestamp,
79
+ });
80
+ if (insertEventResult.isErr())
81
+ throw insertEventResult.error;
82
+ const commitResult = await (0, commit_1.doltCommit)(`task: block ${taskId} on ${options.on}`, config.doltRepoPath, cmd.parent?.opts().noCommit);
83
+ if (commitResult.isErr())
84
+ throw commitResult.error;
85
+ return {
86
+ task_id: taskId,
87
+ blocker_task_id: options.on,
88
+ reason: options.reason,
89
+ status: "blocked",
90
+ };
91
+ })(), (e) => e);
92
+ });
93
+ result.match((data) => {
94
+ const resultData = data;
95
+ if (!cmd.parent?.opts().json) {
96
+ console.log(`Task ${resultData.task_id} blocked by ${resultData.blocker_task_id}.`);
97
+ }
98
+ else {
99
+ console.log(JSON.stringify(resultData, null, 2));
100
+ }
101
+ }, (error) => {
102
+ console.error(`Error blocking task: ${error.message}`);
103
+ if (cmd.parent?.opts().json) {
104
+ console.log(JSON.stringify({
105
+ status: "error",
106
+ code: error.code,
107
+ message: error.message,
108
+ cause: error.cause,
109
+ }));
110
+ }
111
+ process.exit(1);
112
+ });
113
+ });
114
+ }
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.contextCommand = contextCommand;
4
+ const utils_1 = require("./utils");
5
+ const neverthrow_1 = require("neverthrow");
6
+ const errors_1 = require("../domain/errors");
7
+ const query_1 = require("../db/query");
8
+ const escape_1 = require("../db/escape");
9
+ function contextCommand(program) {
10
+ program
11
+ .command("context")
12
+ .description("Output domain doc paths, skill guide paths, and related done tasks for a task (run before starting work)")
13
+ .argument("<taskId>", "Task ID")
14
+ .action(async (taskId, options, cmd) => {
15
+ const result = await (0, utils_1.readConfig)().asyncAndThen((config) => {
16
+ const q = (0, query_1.query)(config.doltRepoPath);
17
+ return q
18
+ .select("task", {
19
+ columns: [
20
+ "task_id",
21
+ "title",
22
+ "change_type",
23
+ "plan_id",
24
+ "suggested_changes",
25
+ ],
26
+ where: { task_id: taskId },
27
+ })
28
+ .andThen((taskRows) => {
29
+ if (taskRows.length === 0) {
30
+ return (0, neverthrow_1.errAsync)((0, errors_1.buildError)(errors_1.ErrorCode.TASK_NOT_FOUND, `Task ${taskId} not found`));
31
+ }
32
+ const task = taskRows[0];
33
+ return q
34
+ .select("plan", {
35
+ columns: ["file_tree", "risks"],
36
+ where: { plan_id: task.plan_id },
37
+ })
38
+ .andThen((planRows) => {
39
+ const plan = planRows[0];
40
+ const file_tree = plan?.file_tree ?? null;
41
+ let risks = null;
42
+ if (plan?.risks != null && typeof plan.risks === "string") {
43
+ try {
44
+ risks = JSON.parse(plan.risks);
45
+ }
46
+ catch {
47
+ risks = null;
48
+ }
49
+ }
50
+ return q
51
+ .select("task_domain", {
52
+ columns: ["domain"],
53
+ where: { task_id: taskId },
54
+ })
55
+ .andThen((domainRows) => q
56
+ .select("task_skill", {
57
+ columns: ["skill"],
58
+ where: { task_id: taskId },
59
+ })
60
+ .map((skillRows) => ({
61
+ task,
62
+ file_tree,
63
+ risks,
64
+ domains: domainRows.map((r) => r.domain),
65
+ skills: skillRows.map((r) => r.skill),
66
+ })));
67
+ });
68
+ })
69
+ .andThen(({ task, file_tree, risks, domains, skills }) => {
70
+ const domain_docs = domains.map((d) => `docs/${d}.md`);
71
+ const skill_docs = skills.map((s) => `docs/skills/${s}.md`);
72
+ const relatedByDomainSql = domains.length > 0
73
+ ? `SELECT DISTINCT t.task_id, t.title, t.plan_id FROM \`task\` t JOIN \`task_domain\` td ON t.task_id = td.task_id WHERE t.status = 'done' AND t.task_id != '${(0, escape_1.sqlEscape)(taskId)}' AND td.domain IN (${domains.map((d) => `'${(0, escape_1.sqlEscape)(d)}'`).join(",")}) ORDER BY t.updated_at DESC LIMIT 5`
74
+ : null;
75
+ const relatedBySkillSql = skills.length > 0
76
+ ? `SELECT DISTINCT t.task_id, t.title, t.plan_id FROM \`task\` t JOIN \`task_skill\` ts ON t.task_id = ts.task_id WHERE t.status = 'done' AND t.task_id != '${(0, escape_1.sqlEscape)(taskId)}' AND ts.skill IN (${skills.map((s) => `'${(0, escape_1.sqlEscape)(s)}'`).join(",")}) ORDER BY t.updated_at DESC LIMIT 5`
77
+ : null;
78
+ const runDomain = relatedByDomainSql
79
+ ? q.raw(relatedByDomainSql)
80
+ : neverthrow_1.ResultAsync.fromSafePromise(Promise.resolve([]));
81
+ const runSkill = relatedBySkillSql
82
+ ? q.raw(relatedBySkillSql)
83
+ : neverthrow_1.ResultAsync.fromSafePromise(Promise.resolve([]));
84
+ return runDomain.andThen((relatedByDomain) => runSkill.map((relatedBySkill) => ({
85
+ task_id: task.task_id,
86
+ title: task.title,
87
+ domains,
88
+ skills,
89
+ change_type: task.change_type ?? null,
90
+ suggested_changes: task.suggested_changes ?? null,
91
+ file_tree,
92
+ risks,
93
+ domain_docs,
94
+ skill_docs,
95
+ related_done_by_domain: relatedByDomain,
96
+ related_done_by_skill: relatedBySkill,
97
+ })));
98
+ });
99
+ });
100
+ result.match((data) => {
101
+ const d = data;
102
+ if ((0, utils_1.rootOpts)(cmd).json) {
103
+ console.log(JSON.stringify(d, null, 2));
104
+ return;
105
+ }
106
+ console.log(`Task: ${d.title} (${d.task_id})`);
107
+ if (d.change_type)
108
+ console.log(`Change type: ${d.change_type}`);
109
+ d.domain_docs.forEach((doc) => console.log(`Domain doc: ${doc}`));
110
+ d.skill_docs.forEach((doc) => console.log(`Skill guide: ${doc}`));
111
+ if (d.suggested_changes) {
112
+ console.log(`Suggested changes:`);
113
+ console.log(d.suggested_changes);
114
+ }
115
+ if (d.file_tree) {
116
+ console.log(`Plan file tree:`);
117
+ console.log(d.file_tree);
118
+ }
119
+ if (d.risks != null && Array.isArray(d.risks) && d.risks.length > 0) {
120
+ console.log(`Plan risks:`);
121
+ d.risks.forEach((r) => console.log(` - ${r.severity ?? "?"}: ${r.description ?? ""} (${r.mitigation ?? ""})`));
122
+ }
123
+ if (d.related_done_by_domain.length > 0) {
124
+ console.log(`Related done (same domain):`);
125
+ d.related_done_by_domain.forEach((t) => console.log(` ${t.task_id} ${t.title}`));
126
+ }
127
+ if (d.related_done_by_skill.length > 0) {
128
+ console.log(`Related done (same skill):`);
129
+ d.related_done_by_skill.forEach((t) => console.log(` ${t.task_id} ${t.title}`));
130
+ }
131
+ }, (error) => {
132
+ console.error(`Error: ${error.message}`);
133
+ if ((0, utils_1.rootOpts)(cmd).json) {
134
+ console.log(JSON.stringify({ status: "error", message: error.message }, null, 2));
135
+ }
136
+ process.exit(1);
137
+ });
138
+ });
139
+ }
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.doneCommand = doneCommand;
4
+ const uuid_1 = require("uuid");
5
+ const commit_1 = require("../db/commit");
6
+ const utils_1 = require("./utils"); // Import Config
7
+ const invariants_1 = require("../domain/invariants");
8
+ const neverthrow_1 = require("neverthrow");
9
+ const errors_1 = require("../domain/errors");
10
+ const query_1 = require("../db/query");
11
+ function doneCommand(program) {
12
+ program
13
+ .command("done")
14
+ .description("Mark a task as done")
15
+ .argument("<taskId>", "ID of the task to mark as done")
16
+ .option("--evidence <text>", "Evidence of completion", "")
17
+ .option("--checks <json>", "JSON array of acceptance checks")
18
+ .option("--force", "Allow marking as done even if not in 'doing' status", false)
19
+ .action(async (taskId, options, cmd) => {
20
+ const result = await (0, utils_1.readConfig)().asyncAndThen((config) => {
21
+ // Removed async, added type
22
+ const currentTimestamp = (0, query_1.now)();
23
+ const q = (0, query_1.query)(config.doltRepoPath);
24
+ return q
25
+ .select("task", {
26
+ columns: ["status"],
27
+ where: { task_id: taskId },
28
+ })
29
+ .andThen((currentStatusResult) => {
30
+ if (currentStatusResult.length === 0) {
31
+ return (0, neverthrow_1.err)((0, errors_1.buildError)(errors_1.ErrorCode.TASK_NOT_FOUND, `Task with ID ${taskId} not found.`));
32
+ }
33
+ const currentStatus = currentStatusResult[0].status;
34
+ if (!options.force) {
35
+ const transitionResult = (0, invariants_1.checkValidTransition)(currentStatus, "done");
36
+ if (transitionResult.isErr())
37
+ return (0, neverthrow_1.err)(transitionResult.error);
38
+ }
39
+ return (0, neverthrow_1.ok)(currentStatus);
40
+ })
41
+ .andThen(() => q.update("task", { status: "done", updated_at: currentTimestamp }, { task_id: taskId }))
42
+ .andThen(() => {
43
+ let parsedChecks = null;
44
+ if (options.checks) {
45
+ try {
46
+ parsedChecks = JSON.parse(options.checks);
47
+ }
48
+ catch (e) {
49
+ return (0, neverthrow_1.err)((0, errors_1.buildError)(errors_1.ErrorCode.VALIDATION_FAILED, `Invalid JSON for acceptance checks: ${options.checks}`, e));
50
+ }
51
+ }
52
+ const eventBody = {
53
+ evidence: options.evidence,
54
+ checks: parsedChecks,
55
+ timestamp: currentTimestamp,
56
+ };
57
+ return q.insert("event", {
58
+ event_id: (0, uuid_1.v4)(),
59
+ task_id: taskId,
60
+ kind: "done",
61
+ body: (0, query_1.jsonObj)({
62
+ evidence: eventBody.evidence,
63
+ checks: eventBody.checks,
64
+ timestamp: eventBody.timestamp,
65
+ }),
66
+ created_at: currentTimestamp,
67
+ });
68
+ })
69
+ .andThen(() => (0, commit_1.doltCommit)(`task: done ${taskId}`, config.doltRepoPath, cmd.parent?.opts().noCommit))
70
+ .map(() => ({
71
+ task_id: taskId,
72
+ status: "done",
73
+ evidence: options.evidence,
74
+ checks: options.checks,
75
+ }));
76
+ });
77
+ result.match((data) => {
78
+ const resultData = data;
79
+ if (!cmd.parent?.opts().json) {
80
+ console.log(`Task ${resultData.task_id} marked as done.`);
81
+ }
82
+ else {
83
+ console.log(JSON.stringify(resultData, null, 2));
84
+ }
85
+ }, (error) => {
86
+ console.error(`Error marking task ${taskId} as done: ${error.message}`);
87
+ if (cmd.parent?.opts().json) {
88
+ console.log(JSON.stringify({
89
+ status: "error",
90
+ code: error.code,
91
+ message: error.message,
92
+ cause: error.cause,
93
+ }));
94
+ }
95
+ process.exit(1);
96
+ });
97
+ });
98
+ }
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.edgeCommand = edgeCommand;
4
+ const commander_1 = require("commander");
5
+ const commit_1 = require("../db/commit");
6
+ const utils_1 = require("./utils");
7
+ const types_1 = require("../domain/types");
8
+ const neverthrow_1 = require("neverthrow");
9
+ const invariants_1 = require("../domain/invariants");
10
+ const query_1 = require("../db/query");
11
+ function edgeCommand(program) {
12
+ program
13
+ .command("edge")
14
+ .description("Manage task dependencies")
15
+ .addCommand(edgeAddCommand());
16
+ }
17
+ function edgeAddCommand() {
18
+ return new commander_1.Command("add")
19
+ .description("Add a dependency edge between tasks")
20
+ .argument("<fromTaskId>", "ID of the blocking task")
21
+ .argument("<type>", "Type of edge (blocks or relates)", (value) => {
22
+ const parsed = types_1.EdgeTypeSchema.safeParse(value);
23
+ if (!parsed.success) {
24
+ throw new Error(`Invalid edge type: ${value}. Must be one of: ${types_1.EdgeTypeSchema.options.join(", ")}`);
25
+ }
26
+ return value;
27
+ })
28
+ .argument("<toTaskId>", "ID of the blocked task")
29
+ .option("--reason <reason>", "Reason for the dependency")
30
+ .action(async (fromTaskId, type, toTaskId, options, cmd) => {
31
+ const result = await (0, utils_1.readConfig)().asyncAndThen((config) => {
32
+ const q = (0, query_1.query)(config.doltRepoPath);
33
+ if (type === "blocks") {
34
+ return neverthrow_1.ResultAsync.fromPromise((async () => {
35
+ const existingEdgesResult = await q.select("edge", {
36
+ where: { type: "blocks" },
37
+ });
38
+ if (existingEdgesResult.isErr()) {
39
+ throw existingEdgesResult.error;
40
+ }
41
+ const existingEdges = existingEdgesResult.value;
42
+ const cycleCheckResult = (0, invariants_1.checkNoBlockerCycle)(fromTaskId, toTaskId, existingEdges);
43
+ if (cycleCheckResult.isErr())
44
+ throw cycleCheckResult.error;
45
+ return (0, neverthrow_1.ok)(undefined); // Return an Ok Result to continue the chain
46
+ })(), (e) => e).andThen(() => {
47
+ // Continue the original chain here after cycle check
48
+ return q
49
+ .insert("edge", {
50
+ from_task_id: fromTaskId,
51
+ to_task_id: toTaskId,
52
+ type,
53
+ reason: options.reason ?? null,
54
+ })
55
+ .map(() => ({
56
+ from_task_id: fromTaskId,
57
+ to_task_id: toTaskId,
58
+ type,
59
+ reason: options.reason,
60
+ }));
61
+ });
62
+ }
63
+ return q
64
+ .insert("edge", {
65
+ from_task_id: fromTaskId,
66
+ to_task_id: toTaskId,
67
+ type,
68
+ reason: options.reason ?? null,
69
+ })
70
+ .andThen(() => (0, commit_1.doltCommit)(`edge: add ${fromTaskId} ${type} ${toTaskId}`, config.doltRepoPath, cmd.parent?.opts().noCommit))
71
+ .map(() => ({
72
+ from_task_id: fromTaskId,
73
+ to_task_id: toTaskId,
74
+ type,
75
+ reason: options.reason,
76
+ }));
77
+ });
78
+ result.match((data) => {
79
+ const resultData = data;
80
+ if (!cmd.parent?.opts().json) {
81
+ console.log(`Edge added: ${resultData.from_task_id} ${resultData.type} ${resultData.to_task_id}`);
82
+ }
83
+ else {
84
+ console.log(JSON.stringify(resultData, null, 2));
85
+ }
86
+ }, (error) => {
87
+ console.error(`Error adding edge: ${error.message}`);
88
+ if (cmd.parent?.opts().json) {
89
+ console.log(JSON.stringify({
90
+ status: "error",
91
+ code: error.code,
92
+ message: error.message,
93
+ cause: error.cause,
94
+ }));
95
+ }
96
+ process.exit(1);
97
+ });
98
+ });
99
+ }
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.exportCommand = exportCommand;
4
+ const commander_1 = require("commander");
5
+ const fs_1 = require("fs");
6
+ const mermaid_1 = require("../export/mermaid");
7
+ const dot_1 = require("../export/dot");
8
+ const markdown_1 = require("../export/markdown");
9
+ const utils_1 = require("./utils");
10
+ function exportCommand(program) {
11
+ program
12
+ .command("export")
13
+ .description("Export graph visualizations and markdown")
14
+ .addCommand(exportMermaidCommand())
15
+ .addCommand(exportDotCommand())
16
+ .addCommand(exportMarkdownCommand());
17
+ }
18
+ function exportMermaidCommand() {
19
+ return new commander_1.Command("mermaid")
20
+ .description("Output Mermaid graph TD text to stdout")
21
+ .option("--plan <planId>", "Filter by plan ID")
22
+ .option("--feature <featureKey>", "Filter by feature key")
23
+ .action(async (options, cmd) => {
24
+ const result = await (0, utils_1.readConfig)().asyncAndThen((config) => {
25
+ // Removed config.doltRepoPath from generateMermaidGraph call
26
+ return (0, mermaid_1.generateMermaidGraph)(options.plan, options.feature);
27
+ });
28
+ result.match((mermaidGraph) => {
29
+ console.log(mermaidGraph);
30
+ }, (error) => {
31
+ console.error(`Error generating Mermaid graph: ${error.message}`);
32
+ if (cmd.parent?.opts().json) {
33
+ console.log(JSON.stringify({
34
+ status: "error",
35
+ code: error.code,
36
+ message: error.message,
37
+ cause: error.cause,
38
+ }));
39
+ }
40
+ process.exit(1);
41
+ });
42
+ });
43
+ }
44
+ function exportDotCommand() {
45
+ return new commander_1.Command("dot")
46
+ .description("Output Graphviz DOT text to stdout")
47
+ .option("--plan <planId>", "Filter by plan ID")
48
+ .option("--feature <featureKey>", "Filter by feature key")
49
+ .action(async (options, cmd) => {
50
+ const result = await (0, utils_1.readConfig)().asyncAndThen((config) => {
51
+ // Removed config.doltRepoPath from generateDotGraph call
52
+ return (0, dot_1.generateDotGraph)(options.plan, options.feature);
53
+ });
54
+ result.match((dotGraph) => {
55
+ console.log(dotGraph);
56
+ }, (error) => {
57
+ console.error(`Error generating DOT graph: ${error.message}`);
58
+ if (cmd.parent?.opts().json) {
59
+ console.log(JSON.stringify({
60
+ status: "error",
61
+ code: error.code,
62
+ message: error.message,
63
+ cause: error.cause,
64
+ }));
65
+ }
66
+ process.exit(1);
67
+ });
68
+ });
69
+ }
70
+ function exportMarkdownCommand() {
71
+ return new commander_1.Command("markdown")
72
+ .description("Output plan and tasks in Cursor format (for round-trip import)")
73
+ .requiredOption("--plan <planId>", "Plan ID to export")
74
+ .option("--out <path>", "Write to file instead of stdout")
75
+ .action(async (options, cmd) => {
76
+ const result = await (0, markdown_1.generateMarkdown)(options.plan);
77
+ result.match((markdown) => {
78
+ if (options.out) {
79
+ (0, fs_1.writeFileSync)(options.out, markdown);
80
+ }
81
+ else {
82
+ console.log(markdown);
83
+ }
84
+ }, (error) => {
85
+ console.error(`Error exporting markdown: ${error.message}`);
86
+ if (cmd.parent?.opts().json) {
87
+ console.log(JSON.stringify({
88
+ status: "error",
89
+ code: error.code,
90
+ message: error.message,
91
+ cause: error.cause,
92
+ }));
93
+ }
94
+ process.exit(1);
95
+ });
96
+ });
97
+ }