@workflow-cannon/workspace-kit 0.2.0 → 0.4.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 (36) hide show
  1. package/README.md +3 -3
  2. package/dist/cli.js +168 -2
  3. package/dist/contracts/index.d.ts +1 -1
  4. package/dist/contracts/module-contract.d.ts +14 -0
  5. package/dist/core/index.d.ts +2 -0
  6. package/dist/core/index.js +2 -0
  7. package/dist/core/policy.d.ts +24 -0
  8. package/dist/core/policy.js +102 -0
  9. package/dist/core/workspace-kit-config.d.ts +49 -0
  10. package/dist/core/workspace-kit-config.js +218 -0
  11. package/dist/modules/documentation/index.d.ts +1 -1
  12. package/dist/modules/documentation/index.js +63 -38
  13. package/dist/modules/documentation/runtime.d.ts +5 -1
  14. package/dist/modules/documentation/runtime.js +87 -4
  15. package/dist/modules/documentation/types.d.ts +14 -0
  16. package/dist/modules/index.d.ts +3 -1
  17. package/dist/modules/index.js +2 -1
  18. package/dist/modules/task-engine/generator.d.ts +2 -0
  19. package/dist/modules/task-engine/generator.js +101 -0
  20. package/dist/modules/task-engine/importer.d.ts +8 -0
  21. package/dist/modules/task-engine/importer.js +157 -0
  22. package/dist/modules/task-engine/index.d.ts +7 -0
  23. package/dist/modules/task-engine/index.js +237 -2
  24. package/dist/modules/task-engine/service.d.ts +21 -0
  25. package/dist/modules/task-engine/service.js +105 -0
  26. package/dist/modules/task-engine/store.d.ts +16 -0
  27. package/dist/modules/task-engine/store.js +88 -0
  28. package/dist/modules/task-engine/suggestions.d.ts +2 -0
  29. package/dist/modules/task-engine/suggestions.js +51 -0
  30. package/dist/modules/task-engine/transitions.d.ts +23 -0
  31. package/dist/modules/task-engine/transitions.js +109 -0
  32. package/dist/modules/task-engine/types.d.ts +82 -0
  33. package/dist/modules/task-engine/types.js +1 -0
  34. package/dist/modules/workspace-config/index.d.ts +2 -0
  35. package/dist/modules/workspace-config/index.js +72 -0
  36. package/package.json +1 -1
@@ -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
+ };
@@ -2,5 +2,7 @@ export { approvalsModule } from "./approvals/index.js";
2
2
  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
+ export { workspaceConfigModule } from "./workspace-config/index.js";
5
6
  export { planningModule } from "./planning/index.js";
6
- export { taskEngineModule } from "./task-engine/index.js";
7
+ export { taskEngineModule, TaskStore, TransitionService, TaskEngineError, TransitionValidator, isTransitionAllowed, getTransitionAction, resolveTargetState, getAllowedTransitionsFrom, stateValidityGuard, dependencyCheckGuard, generateTasksMd, importTasksFromMarkdown, getNextActions } from "./task-engine/index.js";
8
+ export type { TaskEntity, TaskStatus, TaskPriority, TaskStoreDocument, TransitionEvidence, TransitionGuard, TransitionContext, GuardResult, TaskEngineErrorCode, TaskAdapter, TaskAdapterCapability, NextActionSuggestion, BlockingAnalysisEntry } from "./task-engine/index.js";
@@ -1,5 +1,6 @@
1
1
  export { approvalsModule } from "./approvals/index.js";
2
2
  export { documentationModule } from "./documentation/index.js";
3
3
  export { improvementModule } from "./improvement/index.js";
4
+ export { workspaceConfigModule } from "./workspace-config/index.js";
4
5
  export { planningModule } from "./planning/index.js";
5
- 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";
@@ -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>;
@@ -0,0 +1,157 @@
1
+ import fs from "node:fs/promises";
2
+ import { TaskEngineError } from "./transitions.js";
3
+ const STATUS_MAP = {
4
+ "[p]": "proposed",
5
+ "[ ]": "ready",
6
+ "[~]": "in_progress",
7
+ "[!]": "blocked",
8
+ "[x]": "completed",
9
+ "[-]": "cancelled"
10
+ };
11
+ function parseTaskId(heading) {
12
+ const match = heading.match(/^###\s+\[[^\]]*\]\s+(T\d+)/);
13
+ return match?.[1] ?? null;
14
+ }
15
+ function parseStatus(heading) {
16
+ for (const [marker, status] of Object.entries(STATUS_MAP)) {
17
+ if (heading.includes(marker))
18
+ return status;
19
+ }
20
+ return "ready";
21
+ }
22
+ function parseTitle(heading) {
23
+ const match = heading.match(/^###\s+\[[^\]]*\]\s+T\d+\s+(.+)/);
24
+ return match?.[1]?.trim() ?? "Untitled";
25
+ }
26
+ function extractField(lines, prefix) {
27
+ for (const line of lines) {
28
+ const trimmed = line.trim();
29
+ if (trimmed.startsWith(prefix)) {
30
+ return trimmed.slice(prefix.length).trim();
31
+ }
32
+ }
33
+ return undefined;
34
+ }
35
+ function extractListField(lines, fieldPrefix) {
36
+ const items = [];
37
+ let capturing = false;
38
+ for (const line of lines) {
39
+ const trimmed = line.trim();
40
+ if (trimmed.startsWith(fieldPrefix)) {
41
+ capturing = true;
42
+ continue;
43
+ }
44
+ if (capturing) {
45
+ if (/^\s{2,}-\s/.test(line)) {
46
+ items.push(line.replace(/^\s*-\s*/, "").trim());
47
+ continue;
48
+ }
49
+ if (trimmed.startsWith("- ")) {
50
+ capturing = false;
51
+ continue;
52
+ }
53
+ if (trimmed === "") {
54
+ continue;
55
+ }
56
+ }
57
+ }
58
+ return items;
59
+ }
60
+ function parseTaskIds(text) {
61
+ if (!text || text.trim() === "none")
62
+ return [];
63
+ const ids = [];
64
+ const matches = text.matchAll(/`?(T\d+)`?/g);
65
+ for (const m of matches) {
66
+ ids.push(m[1]);
67
+ }
68
+ return ids;
69
+ }
70
+ function parsePriority(text) {
71
+ if (!text)
72
+ return undefined;
73
+ const match = text.match(/(P[123])/);
74
+ return match?.[1];
75
+ }
76
+ function parsePhase(sectionHeading) {
77
+ const match = sectionHeading.match(/^##\s+(.+)/);
78
+ return match?.[1]?.trim();
79
+ }
80
+ export async function importTasksFromMarkdown(sourcePath) {
81
+ let content;
82
+ try {
83
+ content = await fs.readFile(sourcePath, "utf8");
84
+ }
85
+ catch (err) {
86
+ throw new TaskEngineError("import-parse-error", `Failed to read TASKS.md: ${err.message}`);
87
+ }
88
+ const lines = content.split("\n");
89
+ const tasks = [];
90
+ const errors = [];
91
+ let skipped = 0;
92
+ let currentPhase;
93
+ const now = new Date().toISOString();
94
+ let taskStartIdx = -1;
95
+ let taskLines = [];
96
+ function flushTask() {
97
+ if (taskStartIdx === -1 || taskLines.length === 0)
98
+ return;
99
+ const heading = taskLines[0];
100
+ const id = parseTaskId(heading);
101
+ if (!id) {
102
+ errors.push(`Line ${taskStartIdx + 1}: Could not parse task ID from heading`);
103
+ skipped++;
104
+ return;
105
+ }
106
+ const status = parseStatus(heading);
107
+ const title = parseTitle(heading);
108
+ const priorityStr = extractField(taskLines, "- Priority:");
109
+ const approach = extractField(taskLines, "- Approach:");
110
+ const dependsOnStr = extractField(taskLines, "- Depends on:");
111
+ const unblocksStr = extractField(taskLines, "- Unblocks:");
112
+ const technicalScope = extractListField(taskLines, "- Technical scope:");
113
+ const acceptanceCriteria = extractListField(taskLines, "- Acceptance criteria:");
114
+ const task = {
115
+ id,
116
+ status,
117
+ type: "workspace-kit",
118
+ title,
119
+ createdAt: now,
120
+ updatedAt: now,
121
+ priority: parsePriority(priorityStr),
122
+ dependsOn: parseTaskIds(dependsOnStr ?? ""),
123
+ unblocks: parseTaskIds(unblocksStr ?? ""),
124
+ phase: currentPhase,
125
+ approach: approach || undefined,
126
+ technicalScope: technicalScope.length > 0 ? technicalScope : undefined,
127
+ acceptanceCriteria: acceptanceCriteria.length > 0 ? acceptanceCriteria : undefined
128
+ };
129
+ tasks.push(task);
130
+ }
131
+ for (let i = 0; i < lines.length; i++) {
132
+ const line = lines[i];
133
+ if (line.startsWith("## ") && !line.startsWith("### ")) {
134
+ flushTask();
135
+ taskStartIdx = -1;
136
+ taskLines = [];
137
+ currentPhase = parsePhase(line);
138
+ continue;
139
+ }
140
+ if (line.startsWith("### ")) {
141
+ flushTask();
142
+ taskStartIdx = i;
143
+ taskLines = [line];
144
+ continue;
145
+ }
146
+ if (taskStartIdx !== -1) {
147
+ taskLines.push(line);
148
+ }
149
+ }
150
+ flushTask();
151
+ return {
152
+ imported: tasks.length,
153
+ skipped,
154
+ errors,
155
+ tasks
156
+ };
157
+ }
@@ -1,2 +1,9 @@
1
1
  import type { WorkflowModule } from "../../contracts/module-contract.js";
2
+ export type { TaskEntity, TaskStatus, TaskPriority, TaskStoreDocument, TransitionEvidence, TransitionGuard, TransitionContext, GuardResult, TaskEngineError as TaskEngineErrorType, TaskEngineErrorCode, TaskAdapter, TaskAdapterCapability, NextActionSuggestion, BlockingAnalysisEntry } from "./types.js";
3
+ export { TaskStore } from "./store.js";
4
+ export { TransitionService } from "./service.js";
5
+ export { TaskEngineError, TransitionValidator, isTransitionAllowed, getTransitionAction, resolveTargetState, getAllowedTransitionsFrom, stateValidityGuard, dependencyCheckGuard } from "./transitions.js";
6
+ export { generateTasksMd } from "./generator.js";
7
+ export { importTasksFromMarkdown } from "./importer.js";
8
+ export { getNextActions } from "./suggestions.js";
2
9
  export declare const taskEngineModule: WorkflowModule;