@workflow-cannon/workspace-kit 0.4.1 → 0.6.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 CHANGED
@@ -63,7 +63,8 @@ This keeps automation adaptive without sacrificing safety, governance, or develo
63
63
 
64
64
  - **Phase 0** and **Phase 1** (task engine, `v0.3.0`) are complete.
65
65
  - **Phase 2** (layered config, policy gates, cutover docs, `v0.4.0`) is complete in-repo; see `docs/maintainers/TASKS.md` and `docs/maintainers/ROADMAP.md`.
66
- - **Phase 2b** (policy + config UX, `v0.4.1`) is complete in-repo. **Phase 3** (enhancement loop MVP, `v0.5.0`) is next (`T190` onward).
66
+ - **Phase 2b** (policy + config UX, `v0.4.1`) and **Phase 3** (enhancement loop MVP, `v0.5.0`) are complete in-repo: evidence-driven **improvement** tasks, **`approvals`** (`review-item`), heuristic confidence, and append-only lineage.
67
+ - **Phase 4** (`v0.6.0`) is complete in-repo: compatibility matrix/gates, diagnostics/SLO baseline evidence, release-channel mapping, and planning-doc consistency checks.
67
68
 
68
69
  ## Goals
69
70
 
@@ -5,4 +5,6 @@ export { appendPolicyTrace, getExtraSensitiveModuleCommandsFromEffective, getOpe
5
5
  export { assertWritableKey, getConfigKeyMetadata, getConfigRegistryExport, listConfigMetadata, validatePersistedConfigDocument, validateValueForMetadata, type ConfigKeyExposure, type ConfigKeyMetadata, type ConfigValueType } from "./config-metadata.js";
6
6
  export { appendConfigMutation, CONFIG_MUTATIONS_SCHEMA_VERSION, summarizeForEvidence, type ConfigMutationRecord } from "./config-mutations.js";
7
7
  export { generateConfigReferenceDocs, runWorkspaceConfigCli, type ConfigCliIo } from "./config-cli.js";
8
+ export { LINEAGE_SCHEMA_VERSION, lineageCorrelationRoot, type LineageAppPayload, type LineageCorrPayload, type LineageDecPayload, type LineageEvent, type LineageEventType, type LineageRecPayload } from "./lineage-contract.js";
9
+ export { appendLineageEvent, newLineageEventId, queryLineageChain, readLineageEvents } from "./lineage-store.js";
8
10
  export type CoreRuntimeVersion = "0.1";
@@ -5,3 +5,5 @@ export { appendPolicyTrace, getExtraSensitiveModuleCommandsFromEffective, getOpe
5
5
  export { assertWritableKey, getConfigKeyMetadata, getConfigRegistryExport, listConfigMetadata, validatePersistedConfigDocument, validateValueForMetadata } from "./config-metadata.js";
6
6
  export { appendConfigMutation, CONFIG_MUTATIONS_SCHEMA_VERSION, summarizeForEvidence } from "./config-mutations.js";
7
7
  export { generateConfigReferenceDocs, runWorkspaceConfigCli } from "./config-cli.js";
8
+ export { LINEAGE_SCHEMA_VERSION, lineageCorrelationRoot } from "./lineage-contract.js";
9
+ export { appendLineageEvent, newLineageEventId, queryLineageChain, readLineageEvents } from "./lineage-store.js";
@@ -0,0 +1,59 @@
1
+ /**
2
+ * T203: Immutable lineage event contract (append-only store, correlation fields).
3
+ * @see docs/maintainers/TASKS.md T192, T203
4
+ */
5
+ export declare const LINEAGE_SCHEMA_VERSION: 1;
6
+ export type LineageEventType = "rec" | "dec" | "app" | "corr";
7
+ /** Recommendation enqueued (Task Engine improvement task created). */
8
+ export type LineageRecPayload = {
9
+ recommendationTaskId: string;
10
+ evidenceKey: string;
11
+ title: string;
12
+ confidence: number;
13
+ confidenceTier: string;
14
+ provenanceRefs: Record<string, string>;
15
+ };
16
+ /** Human approval decision recorded (before or alongside task transition). */
17
+ export type LineageDecPayload = {
18
+ recommendationTaskId: string;
19
+ evidenceKey: string;
20
+ decisionVerb: "accept" | "decline" | "accept_edited";
21
+ actor: string;
22
+ decisionFingerprint: string;
23
+ policyTraceRef?: {
24
+ operationId: string;
25
+ timestamp: string;
26
+ };
27
+ /** Optional link to a config mutation evidence row when the reviewer supplies it. */
28
+ configMutationRef?: {
29
+ timestamp: string;
30
+ key: string;
31
+ };
32
+ };
33
+ /** Applied change marker (task reached completed via acceptance). */
34
+ export type LineageAppPayload = {
35
+ recommendationTaskId: string;
36
+ evidenceKey: string;
37
+ decisionFingerprint: string;
38
+ finalTaskStatus: "completed";
39
+ };
40
+ /** Optional correlation enrichment (trace linkage). */
41
+ export type LineageCorrPayload = {
42
+ recommendationTaskId: string;
43
+ evidenceKey: string;
44
+ policySchemaVersion?: number;
45
+ policyOperationId?: string;
46
+ policyTimestamp?: string;
47
+ mutationRecordTimestamp?: string;
48
+ mutationKey?: string;
49
+ };
50
+ export type LineageEvent = {
51
+ schemaVersion: typeof LINEAGE_SCHEMA_VERSION;
52
+ eventId: string;
53
+ eventType: LineageEventType;
54
+ timestamp: string;
55
+ correlationRoot: string;
56
+ payload: LineageRecPayload | LineageDecPayload | LineageAppPayload | LineageCorrPayload;
57
+ };
58
+ /** Stable correlation root: ties chain to recommendation + evidence identity. */
59
+ export declare function lineageCorrelationRoot(recommendationTaskId: string, evidenceKey: string): string;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * T203: Immutable lineage event contract (append-only store, correlation fields).
3
+ * @see docs/maintainers/TASKS.md T192, T203
4
+ */
5
+ export const LINEAGE_SCHEMA_VERSION = 1;
6
+ /** Stable correlation root: ties chain to recommendation + evidence identity. */
7
+ export function lineageCorrelationRoot(recommendationTaskId, evidenceKey) {
8
+ return `${recommendationTaskId}::${evidenceKey}`;
9
+ }
@@ -0,0 +1,16 @@
1
+ import type { LineageEvent, LineageEventType } from "./lineage-contract.js";
2
+ export declare function newLineageEventId(): string;
3
+ export declare function appendLineageEvent(workspacePath: string, input: {
4
+ eventType: LineageEventType;
5
+ recommendationTaskId: string;
6
+ evidenceKey: string;
7
+ payload: LineageEvent["payload"];
8
+ eventId?: string;
9
+ timestamp?: string;
10
+ }): Promise<LineageEvent>;
11
+ export declare function readLineageEvents(workspacePath: string): Promise<LineageEvent[]>;
12
+ /** Reconstruct rec → dec → app chain for a recommendation task id (deterministic sort by timestamp). */
13
+ export declare function queryLineageChain(workspacePath: string, recommendationTaskId: string): Promise<{
14
+ events: LineageEvent[];
15
+ byType: Record<LineageEventType, LineageEvent[]>;
16
+ }>;
@@ -0,0 +1,74 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import crypto from "node:crypto";
4
+ import { LINEAGE_SCHEMA_VERSION, lineageCorrelationRoot } from "./lineage-contract.js";
5
+ const LINEAGE_DIR = ".workspace-kit/lineage";
6
+ const EVENTS_FILE = "events.jsonl";
7
+ function eventsPath(workspacePath) {
8
+ return path.join(workspacePath, LINEAGE_DIR, EVENTS_FILE);
9
+ }
10
+ export function newLineageEventId() {
11
+ return `lev-${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
12
+ }
13
+ export async function appendLineageEvent(workspacePath, input) {
14
+ const timestamp = input.timestamp ?? new Date().toISOString();
15
+ const correlationRoot = lineageCorrelationRoot(input.recommendationTaskId, input.evidenceKey);
16
+ const event = {
17
+ schemaVersion: LINEAGE_SCHEMA_VERSION,
18
+ eventId: input.eventId ?? newLineageEventId(),
19
+ eventType: input.eventType,
20
+ timestamp,
21
+ correlationRoot,
22
+ payload: input.payload
23
+ };
24
+ const fp = eventsPath(workspacePath);
25
+ await fs.mkdir(path.dirname(fp), { recursive: true });
26
+ await fs.appendFile(fp, `${JSON.stringify(event)}\n`, "utf8");
27
+ return event;
28
+ }
29
+ export async function readLineageEvents(workspacePath) {
30
+ const fp = eventsPath(workspacePath);
31
+ let raw;
32
+ try {
33
+ raw = await fs.readFile(fp, "utf8");
34
+ }
35
+ catch (e) {
36
+ if (e.code === "ENOENT")
37
+ return [];
38
+ throw e;
39
+ }
40
+ const out = [];
41
+ for (const line of raw.split("\n")) {
42
+ const t = line.trim();
43
+ if (!t)
44
+ continue;
45
+ try {
46
+ const ev = JSON.parse(t);
47
+ if (ev.schemaVersion === LINEAGE_SCHEMA_VERSION)
48
+ out.push(ev);
49
+ }
50
+ catch {
51
+ /* skip corrupt line */
52
+ }
53
+ }
54
+ return out;
55
+ }
56
+ /** Reconstruct rec → dec → app chain for a recommendation task id (deterministic sort by timestamp). */
57
+ export async function queryLineageChain(workspacePath, recommendationTaskId) {
58
+ const all = await readLineageEvents(workspacePath);
59
+ const chain = all.filter((e) => {
60
+ const p = e.payload;
61
+ return p.recommendationTaskId === recommendationTaskId;
62
+ });
63
+ chain.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
64
+ const byType = {
65
+ rec: [],
66
+ dec: [],
67
+ app: [],
68
+ corr: []
69
+ };
70
+ for (const e of chain) {
71
+ byType[e.eventType].push(e);
72
+ }
73
+ return { events: chain, byType };
74
+ }
@@ -1,5 +1,5 @@
1
1
  export declare const POLICY_TRACE_SCHEMA_VERSION: 1;
2
- export type PolicyOperationId = "cli.upgrade" | "cli.init" | "cli.config-mutate" | "policy.dynamic-sensitive" | "doc.document-project" | "doc.generate-document" | "tasks.import-tasks" | "tasks.generate-tasks-md" | "tasks.run-transition";
2
+ export type PolicyOperationId = "cli.upgrade" | "cli.init" | "cli.config-mutate" | "policy.dynamic-sensitive" | "doc.document-project" | "doc.generate-document" | "tasks.import-tasks" | "tasks.generate-tasks-md" | "tasks.run-transition" | "approvals.review-item" | "improvement.generate-recommendations";
3
3
  export declare function getOperationIdForCommand(commandName: string): PolicyOperationId | undefined;
4
4
  export declare function getExtraSensitiveModuleCommandsFromEffective(effective: Record<string, unknown>): string[];
5
5
  /** Resolve operation id for tracing, including config-declared sensitive module commands. */
@@ -7,7 +7,9 @@ const COMMAND_TO_OPERATION = {
7
7
  "generate-document": "doc.generate-document",
8
8
  "import-tasks": "tasks.import-tasks",
9
9
  "generate-tasks-md": "tasks.generate-tasks-md",
10
- "run-transition": "tasks.run-transition"
10
+ "run-transition": "tasks.run-transition",
11
+ "review-item": "approvals.review-item",
12
+ "generate-recommendations": "improvement.generate-recommendations"
11
13
  };
12
14
  export function getOperationIdForCommand(commandName) {
13
15
  return COMMAND_TO_OPERATION[commandName];
@@ -0,0 +1,20 @@
1
+ export declare const APPROVAL_DECISION_SCHEMA_VERSION: 1;
2
+ export type ApprovalDecisionRecord = {
3
+ schemaVersion: typeof APPROVAL_DECISION_SCHEMA_VERSION;
4
+ fingerprint: string;
5
+ taskId: string;
6
+ evidenceKey: string;
7
+ decisionVerb: "accept" | "decline" | "accept_edited";
8
+ actor: string;
9
+ timestamp: string;
10
+ editedSummary?: string;
11
+ policyTraceRef?: {
12
+ operationId: string;
13
+ timestamp: string;
14
+ };
15
+ };
16
+ export declare function computeDecisionFingerprint(taskId: string, decisionVerb: string, evidenceKey: string, editedSummary?: string): string;
17
+ export declare function readDecisionFingerprints(workspacePath: string): Promise<Set<string>>;
18
+ export declare function appendDecisionRecord(workspacePath: string, record: Omit<ApprovalDecisionRecord, "schemaVersion" | "timestamp"> & {
19
+ timestamp?: string;
20
+ }): Promise<void>;
@@ -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;
@@ -0,0 +1,223 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { execFileSync } from "node:child_process";
4
+ import crypto from "node:crypto";
5
+ import { computeHeuristicConfidence, shouldAdmitRecommendation } from "./confidence.js";
6
+ function sha256Hex(s) {
7
+ return crypto.createHash("sha256").update(s, "utf8").digest("hex");
8
+ }
9
+ function stableEvidenceKey(kind, parts) {
10
+ return `${kind}:${sha256Hex(parts.join("\0")).slice(0, 40)}`;
11
+ }
12
+ async function readJsonlLines(filePath) {
13
+ try {
14
+ const raw = await fs.readFile(filePath, "utf8");
15
+ return raw.split("\n").filter((l) => l.trim().length > 0);
16
+ }
17
+ catch (e) {
18
+ if (e.code === "ENOENT")
19
+ return [];
20
+ throw e;
21
+ }
22
+ }
23
+ async function globJsonlRecursive(dir, acc = []) {
24
+ let entries;
25
+ try {
26
+ entries = await fs.readdir(dir, { withFileTypes: true });
27
+ }
28
+ catch (e) {
29
+ if (e.code === "ENOENT")
30
+ return acc;
31
+ throw e;
32
+ }
33
+ for (const ent of entries) {
34
+ const p = path.join(dir, ent.name);
35
+ if (ent.isDirectory()) {
36
+ await globJsonlRecursive(p, acc);
37
+ }
38
+ else if (ent.isFile() && ent.name.endsWith(".jsonl")) {
39
+ acc.push(p);
40
+ }
41
+ }
42
+ return acc;
43
+ }
44
+ const FRICTION = /\b(error|fail|broken|hosed|bug|crash|denied|invalid|exception)\b/i;
45
+ function scoreTranscriptLine(line) {
46
+ if (!FRICTION.test(line))
47
+ return 0;
48
+ let s = 0.45;
49
+ if (/\b(always|again|still|never)\b/i.test(line))
50
+ s += 0.15;
51
+ return Math.min(1, s);
52
+ }
53
+ export async function ingestAgentTranscripts(workspacePath, transcriptsRootRel, state) {
54
+ const root = path.resolve(workspacePath, transcriptsRootRel);
55
+ const files = await globJsonlRecursive(root);
56
+ const out = [];
57
+ for (const abs of files.sort()) {
58
+ const rel = path.relative(workspacePath, abs);
59
+ const lines = await readJsonlLines(abs);
60
+ const start = state.transcriptLineCursors[rel] ?? 0;
61
+ const slice = lines.slice(start);
62
+ let maxScore = 0;
63
+ let sampleLine = "";
64
+ for (const line of slice) {
65
+ const sc = scoreTranscriptLine(line);
66
+ if (sc > maxScore) {
67
+ maxScore = sc;
68
+ sampleLine = line.slice(0, 200);
69
+ }
70
+ }
71
+ state.transcriptLineCursors[rel] = lines.length;
72
+ if (maxScore === 0)
73
+ continue;
74
+ const evidenceKey = stableEvidenceKey("transcript", [rel, String(start), String(lines.length)]);
75
+ const signals = { transcriptFriction: maxScore };
76
+ const confidence = computeHeuristicConfidence("transcript", signals);
77
+ if (!shouldAdmitRecommendation(confidence))
78
+ continue;
79
+ out.push({
80
+ evidenceKind: "transcript",
81
+ evidenceKey,
82
+ title: `Reduce friction hinted in transcript (${path.basename(rel)})`,
83
+ provenanceRefs: { transcriptPath: rel, sampleLine },
84
+ signals,
85
+ confidence
86
+ });
87
+ }
88
+ return out;
89
+ }
90
+ export function ingestGitDiffBetweenTags(workspacePath, fromTag, toTag) {
91
+ let names;
92
+ try {
93
+ names = execFileSync("git", ["-C", workspacePath, "diff", `${fromTag}..${toTag}`, "--name-only"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
94
+ }
95
+ catch {
96
+ return null;
97
+ }
98
+ const fileList = names.split("\n").filter(Boolean);
99
+ const churn = Math.min(1, fileList.length / 25);
100
+ const impact = 0.4 + churn * 0.55;
101
+ const evidenceKey = stableEvidenceKey("git_diff", [fromTag, toTag, fileList.slice(0, 30).join(",")]);
102
+ const signals = { diffImpact: impact };
103
+ const confidence = computeHeuristicConfidence("git_diff", signals);
104
+ if (!shouldAdmitRecommendation(confidence))
105
+ return null;
106
+ return {
107
+ evidenceKind: "git_diff",
108
+ evidenceKey,
109
+ title: `Review workflow impact of changes ${fromTag} → ${toTag} (${fileList.length} paths)`,
110
+ provenanceRefs: { fromTag, toTag, pathCount: String(fileList.length) },
111
+ signals,
112
+ confidence
113
+ };
114
+ }
115
+ export async function ingestPolicyDenials(workspacePath, state) {
116
+ const fp = path.join(workspacePath, ".workspace-kit/policy/traces.jsonl");
117
+ const lines = await readJsonlLines(fp);
118
+ const start = state.policyTraceLineCursor;
119
+ const slice = lines.slice(start);
120
+ state.policyTraceLineCursor = lines.length;
121
+ const out = [];
122
+ for (const line of slice) {
123
+ let rec;
124
+ try {
125
+ rec = JSON.parse(line);
126
+ }
127
+ catch {
128
+ continue;
129
+ }
130
+ if (rec.allowed !== false)
131
+ continue;
132
+ const op = typeof rec.operationId === "string" ? rec.operationId : "unknown";
133
+ const ts = typeof rec.timestamp === "string" ? rec.timestamp : "";
134
+ const evidenceKey = stableEvidenceKey("policy_deny", [op, ts, line.slice(0, 120)]);
135
+ const hasRationale = typeof rec.rationale === "string" && rec.rationale.length > 0;
136
+ const policyDenial = hasRationale ? 0.72 : 0.55;
137
+ const signals = { policyDenial };
138
+ const confidence = computeHeuristicConfidence("policy_deny", signals);
139
+ if (!shouldAdmitRecommendation(confidence))
140
+ continue;
141
+ out.push({
142
+ evidenceKind: "policy_deny",
143
+ evidenceKey,
144
+ title: `Soften or document policy friction for ${op}`,
145
+ provenanceRefs: {
146
+ operationId: op,
147
+ traceTimestamp: ts,
148
+ command: typeof rec.command === "string" ? rec.command : ""
149
+ },
150
+ signals,
151
+ confidence
152
+ });
153
+ }
154
+ return out;
155
+ }
156
+ export async function ingestConfigMutations(workspacePath, state) {
157
+ const fp = path.join(workspacePath, ".workspace-kit/config/mutations.jsonl");
158
+ const lines = await readJsonlLines(fp);
159
+ const start = state.mutationLineCursor;
160
+ const slice = lines.slice(start);
161
+ state.mutationLineCursor = lines.length;
162
+ const out = [];
163
+ for (const line of slice) {
164
+ let rec;
165
+ try {
166
+ rec = JSON.parse(line);
167
+ }
168
+ catch {
169
+ continue;
170
+ }
171
+ if (rec.ok === true)
172
+ continue;
173
+ const ts = typeof rec.timestamp === "string" ? rec.timestamp : "";
174
+ const k = typeof rec.key === "string" ? rec.key : "";
175
+ const evidenceKey = stableEvidenceKey("config_mutation", ["mutations.jsonl", ts, k, String(rec.code ?? "")]);
176
+ const mutationRejection = 0.62;
177
+ const signals = { mutationRejection };
178
+ const confidence = computeHeuristicConfidence("config_mutation", signals);
179
+ if (!shouldAdmitRecommendation(confidence))
180
+ continue;
181
+ out.push({
182
+ evidenceKind: "config_mutation",
183
+ evidenceKey,
184
+ title: `Improve config UX or validation for key ${k || "(unknown)"}`,
185
+ provenanceRefs: { mutationsFile: "mutations.jsonl", timestamp: ts, key: k },
186
+ signals,
187
+ confidence
188
+ });
189
+ }
190
+ return out;
191
+ }
192
+ export function ingestTaskTransitionFriction(transitionLog, state) {
193
+ const start = state.transitionLogLengthCursor;
194
+ const slice = transitionLog.slice(start);
195
+ state.transitionLogLengthCursor = transitionLog.length;
196
+ const counts = new Map();
197
+ for (const ev of slice) {
198
+ counts.set(ev.taskId, (counts.get(ev.taskId) ?? 0) + 1);
199
+ }
200
+ const out = [];
201
+ for (const [taskId, count] of counts) {
202
+ if (count < 4)
203
+ continue;
204
+ const taskFriction = Math.min(1, 0.38 + count * 0.08);
205
+ const evidenceKey = stableEvidenceKey("task_transition", [taskId, String(count)]);
206
+ const signals = { taskFriction };
207
+ const confidence = computeHeuristicConfidence("task_transition", signals);
208
+ if (!shouldAdmitRecommendation(confidence))
209
+ continue;
210
+ out.push({
211
+ evidenceKind: "task_transition",
212
+ evidenceKey,
213
+ title: `Stabilize transitions for task ${taskId} (high churn: ${count} events)`,
214
+ provenanceRefs: { taskId, transitionEventCount: String(count) },
215
+ signals,
216
+ confidence
217
+ });
218
+ }
219
+ return out;
220
+ }
221
+ export function taskIdForEvidenceKey(evidenceKey) {
222
+ return `imp-${sha256Hex(evidenceKey).slice(0, 14)}`;
223
+ }
@@ -2,6 +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 { computeHeuristicConfidence, HEURISTIC_1_ADMISSION_THRESHOLD, shouldAdmitRecommendation, type ConfidenceResult, type ConfidenceSignals, type EvidenceKind } from "./improvement/confidence.js";
5
6
  export { workspaceConfigModule } from "./workspace-config/index.js";
6
7
  export { planningModule } from "./planning/index.js";
7
8
  export { taskEngineModule, TaskStore, TransitionService, TaskEngineError, TransitionValidator, isTransitionAllowed, getTransitionAction, resolveTargetState, getAllowedTransitionsFrom, stateValidityGuard, dependencyCheckGuard, generateTasksMd, importTasksFromMarkdown, getNextActions } from "./task-engine/index.js";
@@ -1,6 +1,7 @@
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 { computeHeuristicConfidence, HEURISTIC_1_ADMISSION_THRESHOLD, shouldAdmitRecommendation } from "./improvement/confidence.js";
4
5
  export { workspaceConfigModule } from "./workspace-config/index.js";
5
6
  export { planningModule } from "./planning/index.js";
6
7
  export { taskEngineModule, TaskStore, TransitionService, TaskEngineError, TransitionValidator, isTransitionAllowed, getTransitionAction, resolveTargetState, getAllowedTransitionsFrom, stateValidityGuard, dependencyCheckGuard, generateTasksMd, importTasksFromMarkdown, getNextActions } from "./task-engine/index.js";
@@ -23,7 +23,7 @@ function taskStorePath(ctx) {
23
23
  export const taskEngineModule = {
24
24
  registration: {
25
25
  id: "task-engine",
26
- version: "0.4.0",
26
+ version: "0.5.0",
27
27
  contractVersion: "1",
28
28
  capabilities: ["task-engine"],
29
29
  dependsOn: [],
@@ -13,6 +13,7 @@ const ALLOWED_TRANSITIONS = {
13
13
  "ready->blocked": { action: "block" },
14
14
  "ready->cancelled": { action: "cancel" },
15
15
  "in_progress->completed": { action: "complete" },
16
+ "in_progress->cancelled": { action: "decline" },
16
17
  "in_progress->blocked": { action: "block" },
17
18
  "in_progress->ready": { action: "pause" },
18
19
  "blocked->ready": { action: "unblock" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workflow-cannon/workspace-kit",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "private": false,
5
5
  "packageManager": "pnpm@10.0.0",
6
6
  "license": "MIT",
@@ -24,7 +24,13 @@
24
24
  "test": "pnpm run build && node --test test/**/*.test.mjs",
25
25
  "pack:dry-run": "pnpm run build && pnpm pack --pack-destination ./artifacts/workspace-kit-pack",
26
26
  "check-release-metadata": "node scripts/check-release-metadata.mjs",
27
- "parity": "node scripts/run-parity.mjs"
27
+ "parity": "node scripts/run-parity.mjs",
28
+ "check-compatibility": "pnpm run build && node scripts/check-compatibility.mjs",
29
+ "check-planning-consistency": "node scripts/check-planning-doc-consistency.mjs",
30
+ "check-release-channel": "node scripts/check-release-channel.mjs",
31
+ "generate-runtime-diagnostics": "node scripts/generate-runtime-diagnostics.mjs",
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"
28
34
  },
29
35
  "devDependencies": {
30
36
  "@types/node": "^25.5.0",