@workflow-cannon/workspace-kit 0.4.0 → 0.5.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 (38) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +10 -6
  3. package/dist/core/config-cli.d.ts +6 -0
  4. package/dist/core/config-cli.js +479 -0
  5. package/dist/core/config-metadata.d.ts +35 -0
  6. package/dist/core/config-metadata.js +162 -0
  7. package/dist/core/config-mutations.d.ts +18 -0
  8. package/dist/core/config-mutations.js +32 -0
  9. package/dist/core/index.d.ts +7 -2
  10. package/dist/core/index.js +7 -2
  11. package/dist/core/lineage-contract.d.ts +59 -0
  12. package/dist/core/lineage-contract.js +9 -0
  13. package/dist/core/lineage-store.d.ts +16 -0
  14. package/dist/core/lineage-store.js +74 -0
  15. package/dist/core/policy.d.ts +12 -2
  16. package/dist/core/policy.js +37 -2
  17. package/dist/core/workspace-kit-config.d.ts +9 -1
  18. package/dist/core/workspace-kit-config.js +52 -4
  19. package/dist/modules/approvals/decisions-store.d.ts +20 -0
  20. package/dist/modules/approvals/decisions-store.js +52 -0
  21. package/dist/modules/approvals/index.js +57 -1
  22. package/dist/modules/approvals/review-runtime.d.ts +24 -0
  23. package/dist/modules/approvals/review-runtime.js +146 -0
  24. package/dist/modules/improvement/confidence.d.ts +23 -0
  25. package/dist/modules/improvement/confidence.js +50 -0
  26. package/dist/modules/improvement/generate-recommendations-runtime.d.ts +13 -0
  27. package/dist/modules/improvement/generate-recommendations-runtime.js +92 -0
  28. package/dist/modules/improvement/improvement-state.d.ts +11 -0
  29. package/dist/modules/improvement/improvement-state.js +42 -0
  30. package/dist/modules/improvement/index.js +47 -1
  31. package/dist/modules/improvement/ingest.d.ts +18 -0
  32. package/dist/modules/improvement/ingest.js +223 -0
  33. package/dist/modules/index.d.ts +1 -0
  34. package/dist/modules/index.js +1 -0
  35. package/dist/modules/task-engine/index.js +1 -1
  36. package/dist/modules/task-engine/transitions.js +1 -0
  37. package/dist/modules/workspace-config/index.js +37 -9
  38. package/package.json +1 -1
@@ -0,0 +1,52 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import crypto from "node:crypto";
4
+ export const APPROVAL_DECISION_SCHEMA_VERSION = 1;
5
+ const DECISIONS_REL = ".workspace-kit/approvals/decisions.jsonl";
6
+ function decisionsPath(workspacePath) {
7
+ return path.join(workspacePath, DECISIONS_REL);
8
+ }
9
+ export function computeDecisionFingerprint(taskId, decisionVerb, evidenceKey, editedSummary) {
10
+ const norm = (editedSummary ?? "").trim();
11
+ return crypto
12
+ .createHash("sha256")
13
+ .update([taskId, decisionVerb, evidenceKey, norm].join("\0"), "utf8")
14
+ .digest("hex");
15
+ }
16
+ export async function readDecisionFingerprints(workspacePath) {
17
+ const fp = decisionsPath(workspacePath);
18
+ const set = new Set();
19
+ let raw;
20
+ try {
21
+ raw = await fs.readFile(fp, "utf8");
22
+ }
23
+ catch (e) {
24
+ if (e.code === "ENOENT")
25
+ return set;
26
+ throw e;
27
+ }
28
+ for (const line of raw.split("\n")) {
29
+ const t = line.trim();
30
+ if (!t)
31
+ continue;
32
+ try {
33
+ const rec = JSON.parse(t);
34
+ if (rec.fingerprint)
35
+ set.add(rec.fingerprint);
36
+ }
37
+ catch {
38
+ /* skip */
39
+ }
40
+ }
41
+ return set;
42
+ }
43
+ export async function appendDecisionRecord(workspacePath, record) {
44
+ const fp = decisionsPath(workspacePath);
45
+ await fs.mkdir(path.dirname(fp), { recursive: true });
46
+ const full = {
47
+ schemaVersion: APPROVAL_DECISION_SCHEMA_VERSION,
48
+ timestamp: record.timestamp ?? new Date().toISOString(),
49
+ ...record
50
+ };
51
+ await fs.appendFile(fp, `${JSON.stringify(full)}\n`, "utf8");
52
+ }
@@ -1,7 +1,9 @@
1
+ import { resolveActor } from "../../core/policy.js";
2
+ import { runReviewItem } from "./review-runtime.js";
1
3
  export const approvalsModule = {
2
4
  registration: {
3
5
  id: "approvals",
4
- version: "0.1.0",
6
+ version: "0.5.0",
5
7
  contractVersion: "1",
6
8
  capabilities: ["approvals"],
7
9
  dependsOn: ["task-engine"],
@@ -26,5 +28,59 @@ export const approvalsModule = {
26
28
  }
27
29
  ]
28
30
  }
31
+ },
32
+ async onCommand(command, ctx) {
33
+ if (command.name !== "review-item") {
34
+ return {
35
+ ok: false,
36
+ code: "unsupported-command",
37
+ message: `Approvals module does not support '${command.name}'`
38
+ };
39
+ }
40
+ const args = command.args ?? {};
41
+ const actor = typeof args.actor === "string" && args.actor.trim().length > 0
42
+ ? args.actor.trim()
43
+ : ctx.resolvedActor ?? resolveActor(ctx.workspacePath, args, process.env);
44
+ const taskId = typeof args.taskId === "string" ? args.taskId : "";
45
+ const decision = args.decision;
46
+ if (decision !== "accept" && decision !== "decline" && decision !== "accept_edited") {
47
+ return {
48
+ ok: false,
49
+ code: "invalid-args",
50
+ message: "decision must be accept, decline, or accept_edited"
51
+ };
52
+ }
53
+ const editedSummary = typeof args.editedSummary === "string" ? args.editedSummary : undefined;
54
+ let policyTraceRef;
55
+ const ptr = args.policyTraceRef;
56
+ if (ptr && typeof ptr === "object" && !Array.isArray(ptr)) {
57
+ const o = ptr;
58
+ if (typeof o.operationId === "string" && typeof o.timestamp === "string") {
59
+ policyTraceRef = { operationId: o.operationId, timestamp: o.timestamp };
60
+ }
61
+ }
62
+ let configMutationRef;
63
+ const cmr = args.configMutationRef;
64
+ if (cmr && typeof cmr === "object" && !Array.isArray(cmr)) {
65
+ const o = cmr;
66
+ if (typeof o.timestamp === "string" && typeof o.key === "string") {
67
+ configMutationRef = { timestamp: o.timestamp, key: o.key };
68
+ }
69
+ }
70
+ const result = await runReviewItem(ctx, {
71
+ taskId,
72
+ decision,
73
+ editedSummary,
74
+ policyTraceRef,
75
+ configMutationRef
76
+ }, actor);
77
+ return {
78
+ ok: result.ok,
79
+ code: result.code,
80
+ message: result.message,
81
+ data: "idempotent" in result && result.idempotent
82
+ ? { idempotent: true }
83
+ : undefined
84
+ };
29
85
  }
30
86
  };
@@ -0,0 +1,24 @@
1
+ import type { ModuleLifecycleContext } from "../../contracts/module-contract.js";
2
+ export type ReviewItemArgs = {
3
+ taskId: string;
4
+ decision: "accept" | "decline" | "accept_edited";
5
+ editedSummary?: string;
6
+ policyTraceRef?: {
7
+ operationId: string;
8
+ timestamp: string;
9
+ };
10
+ configMutationRef?: {
11
+ timestamp: string;
12
+ key: string;
13
+ };
14
+ };
15
+ export declare function runReviewItem(ctx: ModuleLifecycleContext, args: ReviewItemArgs, actor: string): Promise<{
16
+ ok: true;
17
+ idempotent?: boolean;
18
+ code: string;
19
+ message: string;
20
+ } | {
21
+ ok: false;
22
+ code: string;
23
+ message: string;
24
+ }>;
@@ -0,0 +1,146 @@
1
+ import { appendLineageEvent } from "../../core/lineage-store.js";
2
+ import { TaskStore } from "../task-engine/store.js";
3
+ import { TransitionService } from "../task-engine/service.js";
4
+ import { appendDecisionRecord, computeDecisionFingerprint, readDecisionFingerprints } from "./decisions-store.js";
5
+ function taskStoreRelativePath(ctx) {
6
+ const tasks = ctx.effectiveConfig?.tasks;
7
+ if (!tasks || typeof tasks !== "object" || Array.isArray(tasks)) {
8
+ return undefined;
9
+ }
10
+ const p = tasks.storeRelativePath;
11
+ return typeof p === "string" && p.trim().length > 0 ? p.trim() : undefined;
12
+ }
13
+ function getEvidenceKey(task) {
14
+ const m = task.metadata;
15
+ const k = m && typeof m.evidenceKey === "string" ? m.evidenceKey : "";
16
+ return k || task.id;
17
+ }
18
+ export async function runReviewItem(ctx, args, actor) {
19
+ const taskId = typeof args.taskId === "string" ? args.taskId.trim() : "";
20
+ const decision = args.decision;
21
+ if (!taskId || !decision) {
22
+ return { ok: false, code: "invalid-args", message: "taskId and decision are required" };
23
+ }
24
+ if (decision === "accept_edited" && !(typeof args.editedSummary === "string" && args.editedSummary.trim())) {
25
+ return { ok: false, code: "invalid-args", message: "accept_edited requires non-empty editedSummary" };
26
+ }
27
+ const store = new TaskStore(ctx.workspacePath, taskStoreRelativePath(ctx));
28
+ await store.load();
29
+ const task = store.getTask(taskId);
30
+ if (!task) {
31
+ return { ok: false, code: "task-not-found", message: `Task '${taskId}' not found` };
32
+ }
33
+ if (task.type !== "improvement") {
34
+ return { ok: false, code: "invalid-task-type", message: `Task '${taskId}' is not type 'improvement'` };
35
+ }
36
+ const evidenceKey = getEvidenceKey(task);
37
+ const fingerprint = computeDecisionFingerprint(taskId, decision, evidenceKey, args.editedSummary);
38
+ const existing = await readDecisionFingerprints(ctx.workspacePath);
39
+ if (existing.has(fingerprint)) {
40
+ return {
41
+ ok: true,
42
+ idempotent: true,
43
+ code: "decision-idempotent",
44
+ message: "Decision already recorded (idempotent)"
45
+ };
46
+ }
47
+ const service = new TransitionService(store);
48
+ const run = async (action) => {
49
+ await service.runTransition({ taskId, action, actor });
50
+ };
51
+ try {
52
+ if (decision === "decline") {
53
+ if (task.status === "ready") {
54
+ await run("cancel");
55
+ }
56
+ else if (task.status === "in_progress") {
57
+ await run("decline");
58
+ }
59
+ else {
60
+ return {
61
+ ok: false,
62
+ code: "invalid-task-state",
63
+ message: `Cannot decline from status '${task.status}'`
64
+ };
65
+ }
66
+ }
67
+ else {
68
+ if (task.status === "ready") {
69
+ await run("start");
70
+ await run("complete");
71
+ }
72
+ else if (task.status === "in_progress") {
73
+ await run("complete");
74
+ }
75
+ else {
76
+ return {
77
+ ok: false,
78
+ code: "invalid-task-state",
79
+ message: `Cannot accept from status '${task.status}'`
80
+ };
81
+ }
82
+ }
83
+ }
84
+ catch (e) {
85
+ const msg = e instanceof Error ? e.message : String(e);
86
+ return { ok: false, code: "transition-failed", message: msg };
87
+ }
88
+ const finalTask = store.getTask(taskId);
89
+ const finalStatus = finalTask?.status ?? "unknown";
90
+ await appendDecisionRecord(ctx.workspacePath, {
91
+ fingerprint,
92
+ taskId,
93
+ evidenceKey,
94
+ decisionVerb: decision,
95
+ actor,
96
+ editedSummary: args.editedSummary?.trim() || undefined,
97
+ policyTraceRef: args.policyTraceRef
98
+ });
99
+ await appendLineageEvent(ctx.workspacePath, {
100
+ eventType: "dec",
101
+ recommendationTaskId: taskId,
102
+ evidenceKey,
103
+ payload: {
104
+ recommendationTaskId: taskId,
105
+ evidenceKey,
106
+ decisionVerb: decision,
107
+ actor,
108
+ decisionFingerprint: fingerprint,
109
+ policyTraceRef: args.policyTraceRef,
110
+ configMutationRef: args.configMutationRef
111
+ }
112
+ });
113
+ if (decision !== "decline" && finalStatus === "completed") {
114
+ await appendLineageEvent(ctx.workspacePath, {
115
+ eventType: "app",
116
+ recommendationTaskId: taskId,
117
+ evidenceKey,
118
+ payload: {
119
+ recommendationTaskId: taskId,
120
+ evidenceKey,
121
+ decisionFingerprint: fingerprint,
122
+ finalTaskStatus: "completed"
123
+ }
124
+ });
125
+ }
126
+ if (args.policyTraceRef || args.configMutationRef) {
127
+ await appendLineageEvent(ctx.workspacePath, {
128
+ eventType: "corr",
129
+ recommendationTaskId: taskId,
130
+ evidenceKey,
131
+ payload: {
132
+ recommendationTaskId: taskId,
133
+ evidenceKey,
134
+ policyOperationId: args.policyTraceRef?.operationId,
135
+ policyTimestamp: args.policyTraceRef?.timestamp,
136
+ mutationRecordTimestamp: args.configMutationRef?.timestamp,
137
+ mutationKey: args.configMutationRef?.key
138
+ }
139
+ });
140
+ }
141
+ return {
142
+ ok: true,
143
+ code: "decision-recorded",
144
+ message: `Recorded ${decision} for ${taskId} (${finalStatus})`
145
+ };
146
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * T202: Deterministic heuristic confidence (heuristic_1) — no ML.
3
+ * Callable from ingestion without coupling to file parsers.
4
+ */
5
+ export type EvidenceKind = "transcript" | "git_diff" | "policy_deny" | "config_mutation" | "task_transition";
6
+ /** Normalized numeric signals in [0,1]; undefined means not applicable. */
7
+ export type ConfidenceSignals = {
8
+ transcriptFriction?: number;
9
+ diffImpact?: number;
10
+ policyDenial?: number;
11
+ mutationRejection?: number;
12
+ taskFriction?: number;
13
+ };
14
+ export type ConfidenceResult = {
15
+ score: number;
16
+ tier: "high" | "medium" | "low";
17
+ reasons: string[];
18
+ };
19
+ /** Admission threshold on score; inclusive. */
20
+ export declare const HEURISTIC_1_ADMISSION_THRESHOLD = 0.35;
21
+ export declare function computeHeuristicConfidence(kind: EvidenceKind, signals: ConfidenceSignals): ConfidenceResult;
22
+ export declare function shouldAdmitRecommendation(result: ConfidenceResult): boolean;
23
+ export declare function priorityForTier(tier: ConfidenceResult["tier"]): "P1" | "P2" | "P3";
@@ -0,0 +1,50 @@
1
+ /**
2
+ * T202: Deterministic heuristic confidence (heuristic_1) — no ML.
3
+ * Callable from ingestion without coupling to file parsers.
4
+ */
5
+ /** Admission threshold on score; inclusive. */
6
+ export const HEURISTIC_1_ADMISSION_THRESHOLD = 0.35;
7
+ export function computeHeuristicConfidence(kind, signals) {
8
+ const reasons = [];
9
+ const candidates = [];
10
+ if (signals.transcriptFriction !== undefined) {
11
+ candidates.push(signals.transcriptFriction);
12
+ reasons.push(`transcriptFriction=${signals.transcriptFriction.toFixed(3)}`);
13
+ }
14
+ if (signals.diffImpact !== undefined) {
15
+ candidates.push(signals.diffImpact);
16
+ reasons.push(`diffImpact=${signals.diffImpact.toFixed(3)}`);
17
+ }
18
+ if (signals.policyDenial !== undefined) {
19
+ candidates.push(signals.policyDenial);
20
+ reasons.push(`policyDenial=${signals.policyDenial.toFixed(3)}`);
21
+ }
22
+ if (signals.mutationRejection !== undefined) {
23
+ candidates.push(signals.mutationRejection);
24
+ reasons.push(`mutationRejection=${signals.mutationRejection.toFixed(3)}`);
25
+ }
26
+ if (signals.taskFriction !== undefined) {
27
+ candidates.push(signals.taskFriction);
28
+ reasons.push(`taskFriction=${signals.taskFriction.toFixed(3)}`);
29
+ }
30
+ if (candidates.length === 0) {
31
+ return { score: 0, tier: "low", reasons: [`no-signals:${kind}`] };
32
+ }
33
+ const score = Math.max(...candidates);
34
+ let tier = "low";
35
+ if (score >= 0.72)
36
+ tier = "high";
37
+ else if (score >= 0.5)
38
+ tier = "medium";
39
+ return { score, tier, reasons };
40
+ }
41
+ export function shouldAdmitRecommendation(result) {
42
+ return result.score >= HEURISTIC_1_ADMISSION_THRESHOLD;
43
+ }
44
+ export function priorityForTier(tier) {
45
+ if (tier === "high")
46
+ return "P1";
47
+ if (tier === "medium")
48
+ return "P2";
49
+ return "P3";
50
+ }
@@ -0,0 +1,13 @@
1
+ import type { ModuleLifecycleContext } from "../../contracts/module-contract.js";
2
+ export type GenerateRecommendationsArgs = {
3
+ /** Directory relative to workspace containing agent `*.jsonl` transcripts (default: agent-transcripts). */
4
+ transcriptsRoot?: string;
5
+ /** When both set with toTag, run git diff evidence between tags. */
6
+ fromTag?: string;
7
+ toTag?: string;
8
+ };
9
+ export declare function runGenerateRecommendations(ctx: ModuleLifecycleContext, args: GenerateRecommendationsArgs): Promise<{
10
+ created: string[];
11
+ skipped: number;
12
+ candidates: number;
13
+ }>;
@@ -0,0 +1,92 @@
1
+ import { TaskStore } from "../task-engine/store.js";
2
+ import { appendLineageEvent } from "../../core/lineage-store.js";
3
+ import { loadImprovementState, saveImprovementState } from "./improvement-state.js";
4
+ import { ingestAgentTranscripts, ingestConfigMutations, ingestGitDiffBetweenTags, ingestPolicyDenials, ingestTaskTransitionFriction, taskIdForEvidenceKey } from "./ingest.js";
5
+ import { priorityForTier } from "./confidence.js";
6
+ function taskStoreRelativePath(ctx) {
7
+ const tasks = ctx.effectiveConfig?.tasks;
8
+ if (!tasks || typeof tasks !== "object" || Array.isArray(tasks)) {
9
+ return undefined;
10
+ }
11
+ const p = tasks.storeRelativePath;
12
+ return typeof p === "string" && p.trim().length > 0 ? p.trim() : undefined;
13
+ }
14
+ function hasEvidenceKey(tasks, key) {
15
+ return tasks.some((t) => {
16
+ const m = t.metadata;
17
+ if (!m || typeof m !== "object")
18
+ return false;
19
+ return m.evidenceKey === key;
20
+ });
21
+ }
22
+ export async function runGenerateRecommendations(ctx, args) {
23
+ const store = new TaskStore(ctx.workspacePath, taskStoreRelativePath(ctx));
24
+ await store.load();
25
+ const state = await loadImprovementState(ctx.workspacePath);
26
+ const transcriptsRoot = typeof args.transcriptsRoot === "string" && args.transcriptsRoot.trim()
27
+ ? args.transcriptsRoot.trim()
28
+ : "agent-transcripts";
29
+ const fromTag = typeof args.fromTag === "string" ? args.fromTag.trim() : undefined;
30
+ const toTag = typeof args.toTag === "string" ? args.toTag.trim() : undefined;
31
+ const candidates = [];
32
+ candidates.push(...(await ingestAgentTranscripts(ctx.workspacePath, transcriptsRoot, state)));
33
+ candidates.push(...(await ingestPolicyDenials(ctx.workspacePath, state)));
34
+ candidates.push(...(await ingestConfigMutations(ctx.workspacePath, state)));
35
+ candidates.push(...ingestTaskTransitionFriction(store.getTransitionLog(), state));
36
+ if (fromTag && toTag) {
37
+ const g = ingestGitDiffBetweenTags(ctx.workspacePath, fromTag, toTag);
38
+ if (g)
39
+ candidates.push(g);
40
+ }
41
+ const allTasks = store.getAllTasks();
42
+ const created = [];
43
+ let skipped = 0;
44
+ const now = new Date().toISOString();
45
+ for (const c of candidates) {
46
+ if (hasEvidenceKey(allTasks, c.evidenceKey)) {
47
+ skipped += 1;
48
+ continue;
49
+ }
50
+ const id = taskIdForEvidenceKey(c.evidenceKey);
51
+ if (store.getTask(id)) {
52
+ skipped += 1;
53
+ continue;
54
+ }
55
+ const task = {
56
+ id,
57
+ status: "ready",
58
+ type: "improvement",
59
+ title: c.title,
60
+ createdAt: now,
61
+ updatedAt: now,
62
+ priority: priorityForTier(c.confidence.tier),
63
+ metadata: {
64
+ evidenceKey: c.evidenceKey,
65
+ evidenceKind: c.evidenceKind,
66
+ confidence: c.confidence.score,
67
+ confidenceTier: c.confidence.tier,
68
+ confidenceReasons: c.confidence.reasons,
69
+ provenanceRefs: c.provenanceRefs
70
+ }
71
+ };
72
+ store.addTask(task);
73
+ allTasks.push(task);
74
+ created.push(id);
75
+ await appendLineageEvent(ctx.workspacePath, {
76
+ eventType: "rec",
77
+ recommendationTaskId: id,
78
+ evidenceKey: c.evidenceKey,
79
+ payload: {
80
+ recommendationTaskId: id,
81
+ evidenceKey: c.evidenceKey,
82
+ title: c.title,
83
+ confidence: c.confidence.score,
84
+ confidenceTier: c.confidence.tier,
85
+ provenanceRefs: c.provenanceRefs
86
+ }
87
+ });
88
+ }
89
+ await store.save();
90
+ await saveImprovementState(ctx.workspacePath, state);
91
+ return { created, skipped, candidates: candidates.length };
92
+ }
@@ -0,0 +1,11 @@
1
+ export declare const IMPROVEMENT_STATE_SCHEMA_VERSION: 1;
2
+ export type ImprovementStateDocument = {
3
+ schemaVersion: typeof IMPROVEMENT_STATE_SCHEMA_VERSION;
4
+ policyTraceLineCursor: number;
5
+ mutationLineCursor: number;
6
+ transitionLogLengthCursor: number;
7
+ transcriptLineCursors: Record<string, number>;
8
+ };
9
+ export declare function emptyImprovementState(): ImprovementStateDocument;
10
+ export declare function loadImprovementState(workspacePath: string): Promise<ImprovementStateDocument>;
11
+ export declare function saveImprovementState(workspacePath: string, doc: ImprovementStateDocument): Promise<void>;
@@ -0,0 +1,42 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ export const IMPROVEMENT_STATE_SCHEMA_VERSION = 1;
4
+ const DEFAULT_REL = ".workspace-kit/improvement/state.json";
5
+ function statePath(workspacePath) {
6
+ return path.join(workspacePath, DEFAULT_REL);
7
+ }
8
+ export function emptyImprovementState() {
9
+ return {
10
+ schemaVersion: IMPROVEMENT_STATE_SCHEMA_VERSION,
11
+ policyTraceLineCursor: 0,
12
+ mutationLineCursor: 0,
13
+ transitionLogLengthCursor: 0,
14
+ transcriptLineCursors: {}
15
+ };
16
+ }
17
+ export async function loadImprovementState(workspacePath) {
18
+ const fp = statePath(workspacePath);
19
+ try {
20
+ const raw = await fs.readFile(fp, "utf8");
21
+ const doc = JSON.parse(raw);
22
+ if (doc.schemaVersion !== IMPROVEMENT_STATE_SCHEMA_VERSION) {
23
+ return emptyImprovementState();
24
+ }
25
+ return {
26
+ ...emptyImprovementState(),
27
+ ...doc,
28
+ transcriptLineCursors: doc.transcriptLineCursors ?? {}
29
+ };
30
+ }
31
+ catch (e) {
32
+ if (e.code === "ENOENT") {
33
+ return emptyImprovementState();
34
+ }
35
+ throw e;
36
+ }
37
+ }
38
+ export async function saveImprovementState(workspacePath, doc) {
39
+ const fp = statePath(workspacePath);
40
+ await fs.mkdir(path.dirname(fp), { recursive: true });
41
+ await fs.writeFile(fp, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
42
+ }
@@ -1,7 +1,9 @@
1
+ import { queryLineageChain } from "../../core/lineage-store.js";
2
+ import { runGenerateRecommendations } from "./generate-recommendations-runtime.js";
1
3
  export const improvementModule = {
2
4
  registration: {
3
5
  id: "improvement",
4
- version: "0.1.0",
6
+ version: "0.5.0",
5
7
  contractVersion: "1",
6
8
  capabilities: ["improvement"],
7
9
  dependsOn: ["task-engine", "planning"],
@@ -23,8 +25,52 @@ export const improvementModule = {
23
25
  name: "generate-recommendations",
24
26
  file: "generate-recommendations.md",
25
27
  description: "Produce evidence-backed workflow recommendations."
28
+ },
29
+ {
30
+ name: "query-lineage",
31
+ file: "query-lineage.md",
32
+ description: "Reconstruct lineage chain for a recommendation task id."
26
33
  }
27
34
  ]
28
35
  }
36
+ },
37
+ async onCommand(command, ctx) {
38
+ const args = command.args ?? {};
39
+ if (command.name === "generate-recommendations") {
40
+ const transcriptsRoot = typeof args.transcriptsRoot === "string" ? args.transcriptsRoot : undefined;
41
+ const fromTag = typeof args.fromTag === "string" ? args.fromTag : undefined;
42
+ const toTag = typeof args.toTag === "string" ? args.toTag : undefined;
43
+ try {
44
+ const result = await runGenerateRecommendations(ctx, { transcriptsRoot, fromTag, toTag });
45
+ return {
46
+ ok: true,
47
+ code: "recommendations-generated",
48
+ message: `Created ${result.created.length} improvement task(s); skipped ${result.skipped} duplicate(s)`,
49
+ data: result
50
+ };
51
+ }
52
+ catch (e) {
53
+ const msg = e instanceof Error ? e.message : String(e);
54
+ return { ok: false, code: "generate-failed", message: msg };
55
+ }
56
+ }
57
+ if (command.name === "query-lineage") {
58
+ const taskId = typeof args.taskId === "string" ? args.taskId.trim() : "";
59
+ if (!taskId) {
60
+ return { ok: false, code: "invalid-args", message: "query-lineage requires taskId" };
61
+ }
62
+ const chain = await queryLineageChain(ctx.workspacePath, taskId);
63
+ return {
64
+ ok: true,
65
+ code: "lineage-queried",
66
+ message: `${chain.events.length} lineage event(s) for ${taskId}`,
67
+ data: chain
68
+ };
69
+ }
70
+ return {
71
+ ok: false,
72
+ code: "unsupported-command",
73
+ message: `Improvement module does not support '${command.name}'`
74
+ };
29
75
  }
30
76
  };
@@ -0,0 +1,18 @@
1
+ import type { TransitionEvidence } from "../task-engine/types.js";
2
+ import type { EvidenceKind, ConfidenceSignals } from "./confidence.js";
3
+ import { computeHeuristicConfidence } from "./confidence.js";
4
+ import type { ImprovementStateDocument } from "./improvement-state.js";
5
+ export type IngestCandidate = {
6
+ evidenceKind: EvidenceKind;
7
+ evidenceKey: string;
8
+ title: string;
9
+ provenanceRefs: Record<string, string>;
10
+ signals: ConfidenceSignals;
11
+ confidence: ReturnType<typeof computeHeuristicConfidence>;
12
+ };
13
+ export declare function ingestAgentTranscripts(workspacePath: string, transcriptsRootRel: string, state: ImprovementStateDocument): Promise<IngestCandidate[]>;
14
+ export declare function ingestGitDiffBetweenTags(workspacePath: string, fromTag: string, toTag: string): IngestCandidate | null;
15
+ export declare function ingestPolicyDenials(workspacePath: string, state: ImprovementStateDocument): Promise<IngestCandidate[]>;
16
+ export declare function ingestConfigMutations(workspacePath: string, state: ImprovementStateDocument): Promise<IngestCandidate[]>;
17
+ export declare function ingestTaskTransitionFriction(transitionLog: TransitionEvidence[], state: ImprovementStateDocument): IngestCandidate[];
18
+ export declare function taskIdForEvidenceKey(evidenceKey: string): string;