@workflow-cannon/workspace-kit 0.2.0 → 0.3.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/cli.js CHANGED
@@ -2,6 +2,13 @@
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
+ import { ModuleRegistry } from "./core/module-registry.js";
6
+ import { ModuleCommandRouter } from "./core/module-command-router.js";
7
+ import { documentationModule } from "./modules/documentation/index.js";
8
+ import { taskEngineModule } from "./modules/task-engine/index.js";
9
+ import { approvalsModule } from "./modules/approvals/index.js";
10
+ import { planningModule } from "./modules/planning/index.js";
11
+ import { improvementModule } from "./modules/improvement/index.js";
5
12
  const EXIT_SUCCESS = 0;
6
13
  const EXIT_VALIDATION_FAILURE = 1;
7
14
  const EXIT_USAGE_ERROR = 2;
@@ -305,7 +312,7 @@ export async function runCli(args, options = {}) {
305
312
  const writeError = options.writeError ?? console.error;
306
313
  const [command] = args;
307
314
  if (!command) {
308
- writeError("Usage: workspace-kit <init|doctor|check|upgrade>");
315
+ writeError("Usage: workspace-kit <init|doctor|check|upgrade|drift-check|run>");
309
316
  return EXIT_USAGE_ERROR;
310
317
  }
311
318
  if (command === "init") {
@@ -525,8 +532,59 @@ export async function runCli(args, options = {}) {
525
532
  }
526
533
  return EXIT_SUCCESS;
527
534
  }
535
+ if (command === "run") {
536
+ const allModules = [
537
+ documentationModule,
538
+ taskEngineModule,
539
+ approvalsModule,
540
+ planningModule,
541
+ improvementModule
542
+ ];
543
+ const registry = new ModuleRegistry(allModules);
544
+ const router = new ModuleCommandRouter(registry);
545
+ const subcommand = args[1];
546
+ if (!subcommand) {
547
+ const commands = router.listCommands();
548
+ writeLine("Available module commands:");
549
+ for (const cmd of commands) {
550
+ const desc = cmd.description ? ` — ${cmd.description}` : "";
551
+ writeLine(` ${cmd.name} (${cmd.moduleId})${desc}`);
552
+ }
553
+ writeLine("");
554
+ writeLine("Usage: workspace-kit run <command> [json-args]");
555
+ return EXIT_SUCCESS;
556
+ }
557
+ let commandArgs = {};
558
+ if (args[2]) {
559
+ try {
560
+ const parsed = JSON.parse(args[2]);
561
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
562
+ commandArgs = parsed;
563
+ }
564
+ else {
565
+ writeError("Command args must be a JSON object.");
566
+ return EXIT_USAGE_ERROR;
567
+ }
568
+ }
569
+ catch {
570
+ writeError(`Invalid JSON args: ${args[2]}`);
571
+ return EXIT_USAGE_ERROR;
572
+ }
573
+ }
574
+ const ctx = { runtimeVersion: "0.1", workspacePath: cwd };
575
+ try {
576
+ const result = await router.execute(subcommand, commandArgs, ctx);
577
+ writeLine(JSON.stringify(result, null, 2));
578
+ return result.ok ? EXIT_SUCCESS : EXIT_VALIDATION_FAILURE;
579
+ }
580
+ catch (error) {
581
+ const message = error instanceof Error ? error.message : String(error);
582
+ writeError(`Module command failed: ${message}`);
583
+ return EXIT_INTERNAL_ERROR;
584
+ }
585
+ }
528
586
  if (command !== "doctor") {
529
- writeError(`Unknown command '${command}'. Supported commands: init, doctor, check, upgrade, drift-check.`);
587
+ writeError(`Unknown command '${command}'. Supported commands: init, doctor, check, upgrade, drift-check, run.`);
530
588
  return EXIT_USAGE_ERROR;
531
589
  }
532
590
  const issues = [];
@@ -1,3 +1,3 @@
1
1
  import type { WorkflowModule } from "../../contracts/module-contract.js";
2
- export type { DocumentationConflict, DocumentationGenerateOptions, DocumentationGenerateResult, DocumentationGenerationEvidence, DocumentationValidationIssue } from "./types.js";
2
+ export type { DocumentationBatchResult, DocumentationConflict, DocumentationGenerateOptions, DocumentationGenerateResult, DocumentationGenerationEvidence, DocumentationValidationIssue } from "./types.js";
3
3
  export declare const documentationModule: WorkflowModule;
@@ -1,8 +1,19 @@
1
- import { generateDocument } from "./runtime.js";
1
+ import { generateDocument, generateAllDocuments } from "./runtime.js";
2
+ function parseOptions(raw) {
3
+ return {
4
+ dryRun: typeof raw.dryRun === "boolean" ? raw.dryRun : undefined,
5
+ overwrite: typeof raw.overwrite === "boolean" ? raw.overwrite : undefined,
6
+ overwriteAi: typeof raw.overwriteAi === "boolean" ? raw.overwriteAi : undefined,
7
+ overwriteHuman: typeof raw.overwriteHuman === "boolean" ? raw.overwriteHuman : undefined,
8
+ strict: typeof raw.strict === "boolean" ? raw.strict : undefined,
9
+ maxValidationAttempts: typeof raw.maxValidationAttempts === "number" ? raw.maxValidationAttempts : undefined,
10
+ allowWithoutTemplate: typeof raw.allowWithoutTemplate === "boolean" ? raw.allowWithoutTemplate : undefined,
11
+ };
12
+ }
2
13
  export const documentationModule = {
3
14
  registration: {
4
15
  id: "documentation",
5
- version: "0.1.0",
16
+ version: "0.2.0",
6
17
  contractVersion: "1",
7
18
  capabilities: ["documentation"],
8
19
  dependsOn: [],
@@ -23,51 +34,65 @@ export const documentationModule = {
23
34
  {
24
35
  name: "document-project",
25
36
  file: "document-project.md",
26
- description: "Generate aligned project docs for .ai and docs surfaces."
37
+ description: "Generate all project docs from templates to .ai and docs/maintainers surfaces."
38
+ },
39
+ {
40
+ name: "generate-document",
41
+ file: "generate-document.md",
42
+ description: "Generate a single document by type for .ai and docs/maintainers surfaces."
27
43
  }
28
44
  ]
29
45
  }
30
46
  },
31
47
  async onCommand(command, ctx) {
32
- if (command.name !== "document-project" && command.name !== "generate-document") {
33
- return {
34
- ok: false,
35
- code: "unsupported-command",
36
- message: `Documentation module does not support command '${command.name}'`
37
- };
38
- }
39
48
  const args = command.args ?? {};
40
49
  const rawOptions = typeof args.options === "object" && args.options !== null
41
50
  ? args.options
42
- : undefined;
43
- const options = rawOptions
44
- ? {
45
- dryRun: typeof rawOptions.dryRun === "boolean" ? rawOptions.dryRun : undefined,
46
- overwrite: typeof rawOptions.overwrite === "boolean" ? rawOptions.overwrite : undefined,
47
- strict: typeof rawOptions.strict === "boolean" ? rawOptions.strict : undefined,
48
- maxValidationAttempts: typeof rawOptions.maxValidationAttempts === "number"
49
- ? rawOptions.maxValidationAttempts
50
- : undefined,
51
- allowWithoutTemplate: typeof rawOptions.allowWithoutTemplate === "boolean"
52
- ? rawOptions.allowWithoutTemplate
53
- : undefined
54
- }
55
- : undefined;
56
- const result = await generateDocument({
57
- documentType: typeof args.documentType === "string" ? args.documentType : undefined,
58
- options
59
- }, ctx);
51
+ : {};
52
+ const options = parseOptions(rawOptions);
53
+ if (command.name === "document-project") {
54
+ const batchResult = await generateAllDocuments({ options }, ctx);
55
+ return {
56
+ ok: batchResult.ok,
57
+ code: batchResult.ok ? "documented-project" : "documentation-batch-failed",
58
+ message: batchResult.ok
59
+ ? `Generated ${batchResult.summary.succeeded} documents (${batchResult.summary.skipped} skipped)`
60
+ : `Batch failed: ${batchResult.summary.failed} of ${batchResult.summary.total} documents failed`,
61
+ data: {
62
+ summary: batchResult.summary,
63
+ results: batchResult.results.map((r) => ({
64
+ documentType: r.evidence.documentType,
65
+ ok: r.ok,
66
+ aiOutputPath: r.aiOutputPath,
67
+ humanOutputPath: r.humanOutputPath,
68
+ filesWritten: r.evidence.filesWritten,
69
+ filesSkipped: r.evidence.filesSkipped,
70
+ }))
71
+ }
72
+ };
73
+ }
74
+ if (command.name === "generate-document") {
75
+ const result = await generateDocument({
76
+ documentType: typeof args.documentType === "string" ? args.documentType : undefined,
77
+ options
78
+ }, ctx);
79
+ return {
80
+ ok: result.ok,
81
+ code: result.ok ? "generated-document" : "generation-failed",
82
+ message: result.ok
83
+ ? `Generated document '${args.documentType ?? "unknown"}'`
84
+ : `Failed to generate document '${args.documentType ?? "unknown"}'`,
85
+ data: {
86
+ aiOutputPath: result.aiOutputPath,
87
+ humanOutputPath: result.humanOutputPath,
88
+ evidence: result.evidence
89
+ }
90
+ };
91
+ }
60
92
  return {
61
- ok: result.ok,
62
- code: result.ok ? "generated-document" : "generation-failed",
63
- message: result.ok
64
- ? `Generated document '${args.documentType ?? "unknown"}'`
65
- : `Failed to generate document '${args.documentType ?? "unknown"}'`,
66
- data: {
67
- aiOutputPath: result.aiOutputPath,
68
- humanOutputPath: result.humanOutputPath,
69
- evidence: result.evidence
70
- }
93
+ ok: false,
94
+ code: "unsupported-command",
95
+ message: `Documentation module does not support command '${command.name}'`
71
96
  };
72
97
  }
73
98
  };
@@ -1,8 +1,12 @@
1
- import type { DocumentationGenerateOptions, DocumentationGenerateResult } from "./types.js";
1
+ import type { DocumentationBatchResult, DocumentationGenerateOptions, DocumentationGenerateResult } from "./types.js";
2
2
  import type { ModuleLifecycleContext } from "../../contracts/module-contract.js";
3
3
  type GenerateDocumentArgs = {
4
4
  documentType?: string;
5
5
  options?: DocumentationGenerateOptions;
6
6
  };
7
7
  export declare function generateDocument(args: GenerateDocumentArgs, ctx: ModuleLifecycleContext): Promise<DocumentationGenerateResult>;
8
+ type GenerateAllDocumentsArgs = {
9
+ options?: DocumentationGenerateOptions;
10
+ };
11
+ export declare function generateAllDocuments(args: GenerateAllDocumentsArgs, ctx: ModuleLifecycleContext): Promise<DocumentationBatchResult>;
8
12
  export {};
@@ -1,6 +1,7 @@
1
1
  import { mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { existsSync } from "node:fs";
3
3
  import { resolve, sep } from "node:path";
4
+ import { readdir } from "node:fs/promises";
4
5
  function isPathWithinRoot(path, root) {
5
6
  return path === root || path.startsWith(`${root}${sep}`);
6
7
  }
@@ -93,6 +94,7 @@ export async function generateDocument(args, ctx) {
93
94
  documentType: "unknown",
94
95
  filesRead: [],
95
96
  filesWritten: [],
97
+ filesSkipped: [],
96
98
  validationIssues: [
97
99
  {
98
100
  check: "template-resolution",
@@ -110,6 +112,7 @@ export async function generateDocument(args, ctx) {
110
112
  const config = await loadRuntimeConfig(ctx.workspacePath);
111
113
  const filesRead = [];
112
114
  const filesWritten = [];
115
+ const filesSkipped = [];
113
116
  const validationIssues = [];
114
117
  const conflicts = [];
115
118
  const aiRoot = resolve(ctx.workspacePath, config.aiRoot.replace(/^\//, ""));
@@ -124,6 +127,7 @@ export async function generateDocument(args, ctx) {
124
127
  documentType,
125
128
  filesRead,
126
129
  filesWritten,
130
+ filesSkipped,
127
131
  validationIssues: [
128
132
  {
129
133
  check: "write-boundary",
@@ -156,6 +160,7 @@ export async function generateDocument(args, ctx) {
156
160
  documentType,
157
161
  filesRead,
158
162
  filesWritten,
163
+ filesSkipped,
159
164
  validationIssues,
160
165
  conflicts,
161
166
  attemptsUsed: 0,
@@ -190,6 +195,7 @@ export async function generateDocument(args, ctx) {
190
195
  documentType,
191
196
  filesRead,
192
197
  filesWritten,
198
+ filesSkipped,
193
199
  validationIssues,
194
200
  conflicts,
195
201
  attemptsUsed,
@@ -218,6 +224,7 @@ export async function generateDocument(args, ctx) {
218
224
  documentType,
219
225
  filesRead,
220
226
  filesWritten,
227
+ filesSkipped,
221
228
  validationIssues,
222
229
  conflicts,
223
230
  attemptsUsed,
@@ -233,6 +240,7 @@ export async function generateDocument(args, ctx) {
233
240
  documentType,
234
241
  filesRead,
235
242
  filesWritten,
243
+ filesSkipped,
236
244
  validationIssues,
237
245
  conflicts,
238
246
  attemptsUsed,
@@ -241,13 +249,18 @@ export async function generateDocument(args, ctx) {
241
249
  };
242
250
  }
243
251
  if (!options.dryRun) {
244
- if (!options.overwrite && (existsSync(aiOutputPath) || existsSync(humanOutputPath))) {
252
+ const canOverwriteAi = options.overwriteAi ?? options.overwrite ?? true;
253
+ const canOverwriteHuman = options.overwriteHuman ?? options.overwrite ?? true;
254
+ const aiExists = existsSync(aiOutputPath);
255
+ const humanExists = existsSync(humanOutputPath);
256
+ if ((!canOverwriteAi && aiExists) && (!canOverwriteHuman && humanExists)) {
245
257
  return {
246
258
  ok: false,
247
259
  evidence: {
248
260
  documentType,
249
261
  filesRead,
250
262
  filesWritten,
263
+ filesSkipped: [aiOutputPath, humanOutputPath],
251
264
  validationIssues: [
252
265
  ...validationIssues,
253
266
  {
@@ -264,9 +277,20 @@ export async function generateDocument(args, ctx) {
264
277
  }
265
278
  await mkdir(aiRoot, { recursive: true });
266
279
  await mkdir(humanRoot, { recursive: true });
267
- await writeFile(aiOutputPath, `${aiOutput}\n`, "utf8");
268
- await writeFile(humanOutputPath, `${humanOutput}\n`, "utf8");
269
- filesWritten.push(aiOutputPath, humanOutputPath);
280
+ if (canOverwriteAi || !aiExists) {
281
+ await writeFile(aiOutputPath, `${aiOutput}\n`, "utf8");
282
+ filesWritten.push(aiOutputPath);
283
+ }
284
+ else {
285
+ filesSkipped.push(aiOutputPath);
286
+ }
287
+ if (canOverwriteHuman || !humanExists) {
288
+ await writeFile(humanOutputPath, `${humanOutput}\n`, "utf8");
289
+ filesWritten.push(humanOutputPath);
290
+ }
291
+ else {
292
+ filesSkipped.push(humanOutputPath);
293
+ }
270
294
  }
271
295
  return {
272
296
  ok: true,
@@ -276,6 +300,7 @@ export async function generateDocument(args, ctx) {
276
300
  documentType,
277
301
  filesRead,
278
302
  filesWritten,
303
+ filesSkipped,
279
304
  validationIssues,
280
305
  conflicts,
281
306
  attemptsUsed,
@@ -283,3 +308,61 @@ export async function generateDocument(args, ctx) {
283
308
  }
284
309
  };
285
310
  }
311
+ export async function generateAllDocuments(args, ctx) {
312
+ const config = await loadRuntimeConfig(ctx.workspacePath);
313
+ const templatesDir = resolve(ctx.workspacePath, config.templatesRoot);
314
+ let templateFiles = [];
315
+ try {
316
+ const entries = await readdir(templatesDir);
317
+ templateFiles = entries.filter((f) => f.endsWith(".md")).sort();
318
+ }
319
+ catch {
320
+ return {
321
+ ok: false,
322
+ results: [],
323
+ summary: {
324
+ total: 0,
325
+ succeeded: 0,
326
+ failed: 1,
327
+ skipped: 0,
328
+ timestamp: new Date().toISOString()
329
+ }
330
+ };
331
+ }
332
+ const results = [];
333
+ let succeeded = 0;
334
+ let failed = 0;
335
+ let skipped = 0;
336
+ const batchOptions = {
337
+ ...args.options,
338
+ overwriteAi: args.options?.overwriteAi ?? false,
339
+ overwriteHuman: args.options?.overwriteHuman ?? true,
340
+ strict: args.options?.strict ?? false,
341
+ };
342
+ for (const templateFile of templateFiles) {
343
+ const result = await generateDocument({ documentType: templateFile, options: batchOptions }, ctx);
344
+ results.push(result);
345
+ if (result.ok) {
346
+ if (result.evidence.filesWritten.length > 0) {
347
+ succeeded++;
348
+ }
349
+ else {
350
+ skipped++;
351
+ }
352
+ }
353
+ else {
354
+ failed++;
355
+ }
356
+ }
357
+ return {
358
+ ok: failed === 0,
359
+ results,
360
+ summary: {
361
+ total: templateFiles.length,
362
+ succeeded,
363
+ failed,
364
+ skipped,
365
+ timestamp: new Date().toISOString()
366
+ }
367
+ };
368
+ }
@@ -1,6 +1,8 @@
1
1
  export type DocumentationGenerateOptions = {
2
2
  dryRun?: boolean;
3
3
  overwrite?: boolean;
4
+ overwriteAi?: boolean;
5
+ overwriteHuman?: boolean;
4
6
  strict?: boolean;
5
7
  maxValidationAttempts?: number;
6
8
  allowWithoutTemplate?: boolean;
@@ -19,6 +21,7 @@ export type DocumentationGenerationEvidence = {
19
21
  documentType: string;
20
22
  filesRead: string[];
21
23
  filesWritten: string[];
24
+ filesSkipped: string[];
22
25
  validationIssues: DocumentationValidationIssue[];
23
26
  conflicts: DocumentationConflict[];
24
27
  attemptsUsed: number;
@@ -30,3 +33,14 @@ export type DocumentationGenerateResult = {
30
33
  humanOutputPath?: string;
31
34
  evidence: DocumentationGenerationEvidence;
32
35
  };
36
+ export type DocumentationBatchResult = {
37
+ ok: boolean;
38
+ results: DocumentationGenerateResult[];
39
+ summary: {
40
+ total: number;
41
+ succeeded: number;
42
+ failed: number;
43
+ skipped: number;
44
+ timestamp: string;
45
+ };
46
+ };
@@ -3,4 +3,5 @@ export { documentationModule } from "./documentation/index.js";
3
3
  export type { DocumentationConflict, DocumentationGenerateOptions, DocumentationGenerateResult, DocumentationGenerationEvidence, DocumentationValidationIssue } from "./documentation/types.js";
4
4
  export { improvementModule } from "./improvement/index.js";
5
5
  export { planningModule } from "./planning/index.js";
6
- export { taskEngineModule } from "./task-engine/index.js";
6
+ export { taskEngineModule, TaskStore, TransitionService, TaskEngineError, TransitionValidator, isTransitionAllowed, getTransitionAction, resolveTargetState, getAllowedTransitionsFrom, stateValidityGuard, dependencyCheckGuard, generateTasksMd, importTasksFromMarkdown, getNextActions } from "./task-engine/index.js";
7
+ export type { TaskEntity, TaskStatus, TaskPriority, TaskStoreDocument, TransitionEvidence, TransitionGuard, TransitionContext, GuardResult, TaskEngineErrorCode, TaskAdapter, TaskAdapterCapability, NextActionSuggestion, BlockingAnalysisEntry } from "./task-engine/index.js";
@@ -2,4 +2,4 @@ export { approvalsModule } from "./approvals/index.js";
2
2
  export { documentationModule } from "./documentation/index.js";
3
3
  export { improvementModule } from "./improvement/index.js";
4
4
  export { planningModule } from "./planning/index.js";
5
- export { taskEngineModule } from "./task-engine/index.js";
5
+ export { taskEngineModule, TaskStore, TransitionService, TaskEngineError, TransitionValidator, isTransitionAllowed, getTransitionAction, resolveTargetState, getAllowedTransitionsFrom, stateValidityGuard, dependencyCheckGuard, generateTasksMd, importTasksFromMarkdown, getNextActions } from "./task-engine/index.js";
@@ -0,0 +1,2 @@
1
+ import type { TaskEntity } from "./types.js";
2
+ export declare function generateTasksMd(tasks: TaskEntity[]): string;
@@ -0,0 +1,101 @@
1
+ const STATUS_MARKERS = {
2
+ proposed: "[p]",
3
+ ready: "[ ]",
4
+ in_progress: "[~]",
5
+ blocked: "[!]",
6
+ completed: "[x]",
7
+ cancelled: "[-]"
8
+ };
9
+ function groupByPhase(tasks) {
10
+ const groups = new Map();
11
+ for (const task of tasks) {
12
+ const phase = task.phase ?? "Uncategorized";
13
+ const group = groups.get(phase) ?? [];
14
+ group.push(task);
15
+ groups.set(phase, group);
16
+ }
17
+ return groups;
18
+ }
19
+ function buildReadyQueueLine(tasks) {
20
+ const ready = tasks
21
+ .filter((t) => t.status === "ready")
22
+ .sort((a, b) => {
23
+ const pa = a.priority ?? "P9";
24
+ const pb = b.priority ?? "P9";
25
+ return pa.localeCompare(pb);
26
+ });
27
+ if (ready.length === 0)
28
+ return "- Ready queue: _(empty)_";
29
+ return `- Ready queue: ${ready.map((t) => `\`${t.id}\``).join(", ")}`;
30
+ }
31
+ function buildCurrentPhase(tasks) {
32
+ const inProgress = tasks.filter((t) => t.status === "in_progress");
33
+ const ready = tasks.filter((t) => t.status === "ready");
34
+ const active = [...inProgress, ...ready];
35
+ if (active.length === 0)
36
+ return "- Current phase in execution: _(no active tasks)_";
37
+ const phases = [...new Set(active.map((t) => t.phase).filter(Boolean))];
38
+ if (phases.length === 0)
39
+ return "- Current phase in execution: _(unknown)_";
40
+ return `- Current phase in execution: _${phases[0]}_`;
41
+ }
42
+ function renderTask(task) {
43
+ const marker = STATUS_MARKERS[task.status] ?? "[ ]";
44
+ const lines = [];
45
+ lines.push(`### ${marker} ${task.id} ${task.title}`);
46
+ if (task.priority) {
47
+ lines.push(`- Priority: ${task.priority}`);
48
+ }
49
+ if (task.approach) {
50
+ lines.push(`- Approach: ${task.approach}`);
51
+ }
52
+ const deps = task.dependsOn ?? [];
53
+ lines.push(`- Depends on: ${deps.length > 0 ? deps.map((d) => `\`${d}\``).join(", ") : "none"}`);
54
+ const unblocks = task.unblocks ?? [];
55
+ if (unblocks.length > 0) {
56
+ lines.push(`- Unblocks: ${unblocks.map((u) => `\`${u}\``).join(", ")}`);
57
+ }
58
+ if (task.technicalScope && task.technicalScope.length > 0) {
59
+ lines.push("- Technical scope:");
60
+ for (const item of task.technicalScope) {
61
+ lines.push(` - ${item}`);
62
+ }
63
+ }
64
+ if (task.acceptanceCriteria && task.acceptanceCriteria.length > 0) {
65
+ lines.push("- Acceptance criteria:");
66
+ for (const item of task.acceptanceCriteria) {
67
+ lines.push(` - ${item}`);
68
+ }
69
+ }
70
+ return lines.join("\n");
71
+ }
72
+ export function generateTasksMd(tasks) {
73
+ const lines = [];
74
+ lines.push("# Workflow Cannon Tasks");
75
+ lines.push("");
76
+ lines.push("> This file is generated by the Task Engine. Do not edit manually.");
77
+ lines.push("");
78
+ lines.push("Status markers:");
79
+ lines.push("- `[p]` proposed");
80
+ lines.push("- `[ ]` ready");
81
+ lines.push("- `[~]` in progress");
82
+ lines.push("- `[!]` blocked");
83
+ lines.push("- `[x]` completed");
84
+ lines.push("- `[-]` cancelled");
85
+ lines.push("");
86
+ lines.push("## Current execution state");
87
+ lines.push("");
88
+ lines.push(buildCurrentPhase(tasks));
89
+ lines.push(buildReadyQueueLine(tasks));
90
+ lines.push("");
91
+ const phaseGroups = groupByPhase(tasks);
92
+ for (const [phase, phaseTasks] of phaseGroups) {
93
+ lines.push(`## ${phase}`);
94
+ lines.push("");
95
+ for (const task of phaseTasks) {
96
+ lines.push(renderTask(task));
97
+ lines.push("");
98
+ }
99
+ }
100
+ return lines.join("\n");
101
+ }
@@ -0,0 +1,8 @@
1
+ import type { TaskEntity } from "./types.js";
2
+ export type ImportResult = {
3
+ imported: number;
4
+ skipped: number;
5
+ errors: string[];
6
+ tasks: TaskEntity[];
7
+ };
8
+ export declare function importTasksFromMarkdown(sourcePath: string): Promise<ImportResult>;