@workflow-cannon/workspace-kit 0.6.0 → 0.8.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/README.md +3 -3
- package/dist/cli.js +31 -21
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/module-contract.d.ts +13 -0
- package/dist/core/config-metadata.js +303 -1
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.js +6 -0
- package/dist/core/instruction-template-mapper.d.ts +9 -0
- package/dist/core/instruction-template-mapper.js +35 -0
- package/dist/core/lineage-contract.d.ts +1 -1
- package/dist/core/lineage-contract.js +1 -1
- package/dist/core/policy.d.ts +4 -1
- package/dist/core/policy.js +5 -4
- package/dist/core/response-template-contract.d.ts +15 -0
- package/dist/core/response-template-contract.js +10 -0
- package/dist/core/response-template-registry.d.ts +4 -0
- package/dist/core/response-template-registry.js +44 -0
- package/dist/core/response-template-shaping.d.ts +6 -0
- package/dist/core/response-template-shaping.js +128 -0
- package/dist/core/session-policy.d.ts +18 -0
- package/dist/core/session-policy.js +57 -0
- package/dist/core/transcript-completion-hook.d.ts +7 -0
- package/dist/core/transcript-completion-hook.js +90 -0
- package/dist/core/workspace-kit-config.js +42 -2
- package/dist/modules/documentation/runtime.js +383 -14
- package/dist/modules/improvement/generate-recommendations-runtime.d.ts +7 -0
- package/dist/modules/improvement/generate-recommendations-runtime.js +51 -7
- package/dist/modules/improvement/improvement-state.d.ts +12 -1
- package/dist/modules/improvement/improvement-state.js +38 -7
- package/dist/modules/improvement/index.js +124 -2
- package/dist/modules/improvement/ingest.js +2 -1
- package/dist/modules/improvement/transcript-redaction.d.ts +4 -0
- package/dist/modules/improvement/transcript-redaction.js +10 -0
- package/dist/modules/improvement/transcript-sync-runtime.d.ts +60 -0
- package/dist/modules/improvement/transcript-sync-runtime.js +320 -0
- package/dist/modules/index.d.ts +1 -1
- package/dist/modules/index.js +1 -1
- package/dist/modules/task-engine/index.d.ts +0 -2
- package/dist/modules/task-engine/index.js +4 -70
- package/package.json +6 -2
- package/dist/modules/task-engine/generator.d.ts +0 -2
- package/dist/modules/task-engine/generator.js +0 -101
- package/dist/modules/task-engine/importer.d.ts +0 -8
- package/dist/modules/task-engine/importer.js +0 -157
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import fs from "node:fs/promises";
|
|
1
|
+
import { maybeSpawnTranscriptHookAfterCompletion } from "../../core/transcript-completion-hook.js";
|
|
3
2
|
import { TaskStore } from "./store.js";
|
|
4
3
|
import { TransitionService } from "./service.js";
|
|
5
4
|
import { TaskEngineError } from "./transitions.js";
|
|
6
|
-
import { generateTasksMd } from "./generator.js";
|
|
7
|
-
import { importTasksFromMarkdown } from "./importer.js";
|
|
8
5
|
import { getNextActions } from "./suggestions.js";
|
|
9
6
|
export { TaskStore } from "./store.js";
|
|
10
7
|
export { TransitionService } from "./service.js";
|
|
11
8
|
export { TaskEngineError, TransitionValidator, isTransitionAllowed, getTransitionAction, resolveTargetState, getAllowedTransitionsFrom, stateValidityGuard, dependencyCheckGuard } from "./transitions.js";
|
|
12
|
-
export { generateTasksMd } from "./generator.js";
|
|
13
|
-
export { importTasksFromMarkdown } from "./importer.js";
|
|
14
9
|
export { getNextActions } from "./suggestions.js";
|
|
15
10
|
function taskStorePath(ctx) {
|
|
16
11
|
const tasks = ctx.effectiveConfig?.tasks;
|
|
@@ -61,16 +56,6 @@ export const taskEngineModule = {
|
|
|
61
56
|
file: "get-ready-queue.md",
|
|
62
57
|
description: "Get ready tasks sorted by priority."
|
|
63
58
|
},
|
|
64
|
-
{
|
|
65
|
-
name: "import-tasks",
|
|
66
|
-
file: "import-tasks.md",
|
|
67
|
-
description: "One-time import from TASKS.md into engine state."
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
name: "generate-tasks-md",
|
|
71
|
-
file: "generate-tasks-md.md",
|
|
72
|
-
description: "Generate read-only TASKS.md from engine state."
|
|
73
|
-
},
|
|
74
59
|
{
|
|
75
60
|
name: "get-next-actions",
|
|
76
61
|
file: "get-next-actions.md",
|
|
@@ -113,6 +98,9 @@ export const taskEngineModule = {
|
|
|
113
98
|
try {
|
|
114
99
|
const service = new TransitionService(store);
|
|
115
100
|
const result = await service.runTransition({ taskId, action, actor });
|
|
101
|
+
if (result.evidence.toState === "completed") {
|
|
102
|
+
maybeSpawnTranscriptHookAfterCompletion(ctx.workspacePath, (ctx.effectiveConfig ?? {}));
|
|
103
|
+
}
|
|
116
104
|
return {
|
|
117
105
|
ok: true,
|
|
118
106
|
code: "transition-applied",
|
|
@@ -190,60 +178,6 @@ export const taskEngineModule = {
|
|
|
190
178
|
data: { tasks: ready, count: ready.length }
|
|
191
179
|
};
|
|
192
180
|
}
|
|
193
|
-
if (command.name === "import-tasks") {
|
|
194
|
-
const sourcePath = typeof args.sourcePath === "string"
|
|
195
|
-
? path.resolve(ctx.workspacePath, args.sourcePath)
|
|
196
|
-
: path.resolve(ctx.workspacePath, "docs/maintainers/TASKS.md");
|
|
197
|
-
try {
|
|
198
|
-
const result = await importTasksFromMarkdown(sourcePath);
|
|
199
|
-
store.replaceAllTasks(result.tasks);
|
|
200
|
-
await store.save();
|
|
201
|
-
return {
|
|
202
|
-
ok: true,
|
|
203
|
-
code: "tasks-imported",
|
|
204
|
-
message: `Imported ${result.imported} tasks (${result.skipped} skipped)`,
|
|
205
|
-
data: {
|
|
206
|
-
imported: result.imported,
|
|
207
|
-
skipped: result.skipped,
|
|
208
|
-
errors: result.errors
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
catch (err) {
|
|
213
|
-
if (err instanceof TaskEngineError) {
|
|
214
|
-
return { ok: false, code: err.code, message: err.message };
|
|
215
|
-
}
|
|
216
|
-
return {
|
|
217
|
-
ok: false,
|
|
218
|
-
code: "import-parse-error",
|
|
219
|
-
message: err.message
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
if (command.name === "generate-tasks-md") {
|
|
224
|
-
const outputPath = typeof args.outputPath === "string"
|
|
225
|
-
? path.resolve(ctx.workspacePath, args.outputPath)
|
|
226
|
-
: path.resolve(ctx.workspacePath, "docs/maintainers/TASKS.md");
|
|
227
|
-
const tasks = store.getAllTasks();
|
|
228
|
-
const markdown = generateTasksMd(tasks);
|
|
229
|
-
try {
|
|
230
|
-
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
231
|
-
await fs.writeFile(outputPath, markdown, "utf8");
|
|
232
|
-
}
|
|
233
|
-
catch (err) {
|
|
234
|
-
return {
|
|
235
|
-
ok: false,
|
|
236
|
-
code: "storage-write-error",
|
|
237
|
-
message: `Failed to write TASKS.md: ${err.message}`
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
return {
|
|
241
|
-
ok: true,
|
|
242
|
-
code: "tasks-md-generated",
|
|
243
|
-
message: `Generated TASKS.md with ${tasks.length} tasks`,
|
|
244
|
-
data: { outputPath, taskCount: tasks.length }
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
181
|
if (command.name === "get-next-actions") {
|
|
248
182
|
const tasks = store.getAllTasks();
|
|
249
183
|
const suggestion = getNextActions(tasks);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workflow-cannon/workspace-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"packageManager": "pnpm@10.0.0",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,7 +30,11 @@
|
|
|
30
30
|
"check-release-channel": "node scripts/check-release-channel.mjs",
|
|
31
31
|
"generate-runtime-diagnostics": "node scripts/generate-runtime-diagnostics.mjs",
|
|
32
32
|
"prune-evidence": "node scripts/prune-evidence.mjs",
|
|
33
|
-
"phase4-gates": "pnpm run check-compatibility && pnpm run check-planning-consistency && pnpm run check-release-channel"
|
|
33
|
+
"phase4-gates": "pnpm run check-compatibility && pnpm run check-planning-consistency && pnpm run check-release-channel",
|
|
34
|
+
"phase5-gates": "pnpm run phase4-gates && pnpm run test",
|
|
35
|
+
"pre-release-transcript-hook": "pnpm run build && node scripts/pre-release-transcript-hook.mjs",
|
|
36
|
+
"transcript:sync": "node scripts/run-transcript-cli.mjs sync-transcripts",
|
|
37
|
+
"transcript:ingest": "node scripts/run-transcript-cli.mjs ingest-transcripts"
|
|
34
38
|
},
|
|
35
39
|
"devDependencies": {
|
|
36
40
|
"@types/node": "^25.5.0",
|
|
@@ -1,101 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,157 +0,0 @@
|
|
|
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
|
-
}
|