@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.
- package/README.md +1 -1
- package/dist/cli.js +10 -6
- package/dist/core/config-cli.d.ts +6 -0
- package/dist/core/config-cli.js +479 -0
- package/dist/core/config-metadata.d.ts +35 -0
- package/dist/core/config-metadata.js +162 -0
- package/dist/core/config-mutations.d.ts +18 -0
- package/dist/core/config-mutations.js +32 -0
- package/dist/core/index.d.ts +7 -2
- package/dist/core/index.js +7 -2
- package/dist/core/lineage-contract.d.ts +59 -0
- package/dist/core/lineage-contract.js +9 -0
- package/dist/core/lineage-store.d.ts +16 -0
- package/dist/core/lineage-store.js +74 -0
- package/dist/core/policy.d.ts +12 -2
- package/dist/core/policy.js +37 -2
- package/dist/core/workspace-kit-config.d.ts +9 -1
- package/dist/core/workspace-kit-config.js +52 -4
- package/dist/modules/approvals/decisions-store.d.ts +20 -0
- package/dist/modules/approvals/decisions-store.js +52 -0
- package/dist/modules/approvals/index.js +57 -1
- package/dist/modules/approvals/review-runtime.d.ts +24 -0
- package/dist/modules/approvals/review-runtime.js +146 -0
- package/dist/modules/improvement/confidence.d.ts +23 -0
- package/dist/modules/improvement/confidence.js +50 -0
- package/dist/modules/improvement/generate-recommendations-runtime.d.ts +13 -0
- package/dist/modules/improvement/generate-recommendations-runtime.js +92 -0
- package/dist/modules/improvement/improvement-state.d.ts +11 -0
- package/dist/modules/improvement/improvement-state.js +42 -0
- package/dist/modules/improvement/index.js +47 -1
- package/dist/modules/improvement/ingest.d.ts +18 -0
- package/dist/modules/improvement/ingest.js +223 -0
- package/dist/modules/index.d.ts +1 -0
- package/dist/modules/index.js +1 -0
- package/dist/modules/task-engine/index.js +1 -1
- package/dist/modules/task-engine/transitions.js +1 -0
- package/dist/modules/workspace-config/index.js +37 -9
- package/package.json +1 -1
|
@@ -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
|
+
}
|
package/dist/modules/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/modules/index.js
CHANGED
|
@@ -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";
|
|
@@ -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" },
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { explainConfigPath, resolveWorkspaceConfigWithLayers } from "../../core/workspace-kit-config.js";
|
|
1
|
+
import { explainConfigPath, normalizeConfigForExport, resolveWorkspaceConfigWithLayers } from "../../core/workspace-kit-config.js";
|
|
2
2
|
async function handleExplainConfig(args, ctx) {
|
|
3
3
|
const pathArg = typeof args.path === "string" ? args.path.trim() : "";
|
|
4
4
|
if (!pathArg) {
|
|
@@ -23,6 +23,24 @@ async function handleExplainConfig(args, ctx) {
|
|
|
23
23
|
data: explained
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
+
async function handleResolveConfig(args, ctx) {
|
|
27
|
+
const invocationConfig = typeof args.config === "object" && args.config !== null && !Array.isArray(args.config)
|
|
28
|
+
? args.config
|
|
29
|
+
: {};
|
|
30
|
+
const { effective, layers } = await resolveWorkspaceConfigWithLayers({
|
|
31
|
+
workspacePath: ctx.workspacePath,
|
|
32
|
+
registry: ctx.registry,
|
|
33
|
+
invocationConfig
|
|
34
|
+
});
|
|
35
|
+
return {
|
|
36
|
+
ok: true,
|
|
37
|
+
code: "config-resolved",
|
|
38
|
+
data: {
|
|
39
|
+
effective: normalizeConfigForExport(effective),
|
|
40
|
+
layers: layers.map((l) => ({ id: l.id }))
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
26
44
|
export const workspaceConfigModule = {
|
|
27
45
|
registration: {
|
|
28
46
|
id: "workspace-config",
|
|
@@ -48,25 +66,35 @@ export const workspaceConfigModule = {
|
|
|
48
66
|
name: "explain-config",
|
|
49
67
|
file: "explain-config.md",
|
|
50
68
|
description: "Agent-first JSON: effective config value and winning layer for a dotted path."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "resolve-config",
|
|
72
|
+
file: "resolve-config.md",
|
|
73
|
+
description: "Agent-first JSON: full effective config (sorted) and merge layer ids."
|
|
51
74
|
}
|
|
52
75
|
]
|
|
53
76
|
}
|
|
54
77
|
},
|
|
55
78
|
async onCommand(command, ctx) {
|
|
56
|
-
if (command.name !== "explain-config") {
|
|
57
|
-
return { ok: false, code: "unknown-command", message: "workspace-config only implements explain-config" };
|
|
58
|
-
}
|
|
59
79
|
const reg = ctx.moduleRegistry;
|
|
60
80
|
if (!reg) {
|
|
61
81
|
return {
|
|
62
82
|
ok: false,
|
|
63
83
|
code: "internal-error",
|
|
64
|
-
message: "
|
|
84
|
+
message: "workspace-config requires moduleRegistry on context (CLI wiring)"
|
|
65
85
|
};
|
|
66
86
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
87
|
+
const baseCtx = { workspacePath: ctx.workspacePath, registry: reg };
|
|
88
|
+
if (command.name === "explain-config") {
|
|
89
|
+
return handleExplainConfig(command.args ?? {}, baseCtx);
|
|
90
|
+
}
|
|
91
|
+
if (command.name === "resolve-config") {
|
|
92
|
+
return handleResolveConfig(command.args ?? {}, baseCtx);
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
code: "unknown-command",
|
|
97
|
+
message: `workspace-config: unknown command '${command.name}'`
|
|
98
|
+
};
|
|
71
99
|
}
|
|
72
100
|
};
|