@brainervirus/workit-core 4.0.0 → 6.0.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/package.json +1 -1
- package/skills/workit-bdd/SKILL.md +35 -29
- package/skills/workit-continue/SKILL.md +53 -0
- package/skills/workit-debug/SKILL.md +41 -36
- package/skills/workit-deslop/SKILL.md +35 -25
- package/skills/workit-fanout/SKILL.md +62 -0
- package/skills/workit-fanout/references/brief.md +56 -0
- package/skills/workit-implement/SKILL.md +48 -44
- package/skills/workit-review/SKILL.md +42 -51
- package/skills/workit-review/references/impact.md +24 -0
- package/skills/workit-shape/SKILL.md +71 -0
- package/skills/workit-shape/references/diagrams.md +17 -0
- package/skills/workit-shape/references/knowledge.md +58 -0
- package/skills/workit-shape/references/mockups.md +15 -0
- package/skills/workit-shape/references/slicing.md +42 -0
- package/skills/workit-ship/SKILL.md +52 -0
- package/skills/workit-test-audit/SKILL.md +10 -2
- package/skills/workit-verify-app/SKILL.md +63 -0
- package/skills/workit-verify-app/references/template.md +49 -0
- package/src/autonomy.ts +309 -44
- package/src/core/context-read.ts +144 -0
- package/src/core/methods.ts +71 -98
- package/src/core/skill-manifests.ts +49 -25
- package/src/core/store-lock.ts +1 -1
- package/src/core/task-context.ts +1 -1
- package/src/core/task-contract.ts +8 -130
- package/src/core/task-engine.ts +57 -879
- package/src/core/task-evaluation.ts +63 -5
- package/src/core/task-store.ts +2 -107
- package/src/core/workers.ts +12 -158
- package/src/core/workspaces.ts +15 -0
- package/src/core/youtrack-writes.ts +92 -0
- package/src/core.ts +2 -79
- package/src/forge/pr-ops.ts +2 -2
- package/src/hooks/handle.ts +29 -5
- package/src/hooks/hosts/claude-code.ts +0 -8
- package/src/hooks/hosts/codex.ts +2 -11
- package/src/hooks/hosts/cursor.ts +0 -8
- package/src/hooks/hosts/opencode.ts +0 -8
- package/src/hooks/hosts/pi.ts +0 -10
- package/src/ledger.ts +19 -10
- package/templates/plan-template.md +17 -18
- package/templates/spec-template.md +4 -3
- package/templates/workit-contract.md +3 -2
- package/skills/workit-babysit/SKILL.md +0 -37
- package/skills/workit-behavioral-tdd/SKILL.md +0 -56
- package/skills/workit-blast-radius/SKILL.md +0 -26
- package/skills/workit-challenge/SKILL.md +0 -47
- package/skills/workit-diagram/SKILL.md +0 -27
- package/skills/workit-green-run/SKILL.md +0 -24
- package/skills/workit-handoff/SKILL.md +0 -37
- package/skills/workit-mockup/SKILL.md +0 -23
- package/skills/workit-plan/SKILL.md +0 -45
- package/skills/workit-steer/SKILL.md +0 -39
- package/src/core/authority.ts +0 -1033
- package/src/core/auto-approval.ts +0 -338
- package/src/core/external-action-effects.ts +0 -2441
- package/src/core/external-action.ts +0 -1204
- package/templates/execution-contract.md +0 -40
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { readFileSync, realpathSync } from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { resolveWorkspaceFrom, type WorkspaceConfig } from "./workspaces";
|
|
5
|
-
import { configDir } from "./config";
|
|
6
|
-
import { verifyPushIdentity } from "./branch";
|
|
7
|
-
import {
|
|
8
|
-
failure,
|
|
9
|
-
success,
|
|
10
|
-
type Caller,
|
|
11
|
-
type Id,
|
|
12
|
-
type Provenance,
|
|
13
|
-
type Result,
|
|
14
|
-
type Revision,
|
|
15
|
-
type TaskRecord,
|
|
16
|
-
} from "./task-contract";
|
|
17
|
-
import { TaskStore } from "./task-store";
|
|
18
|
-
import type { WorkitCore } from "./task-engine";
|
|
19
|
-
import type { Decision } from "./task-contract";
|
|
20
|
-
import { currentWriterOwnsTask } from "./workers";
|
|
21
|
-
|
|
22
|
-
/** Action classes auto-approval can cover. Unknown classes never match. */
|
|
23
|
-
export const AUTO_CLASSES = ["branch", "commit", "push", "pr", "merge"] as const;
|
|
24
|
-
export type AutoClass = (typeof AUTO_CLASSES)[number];
|
|
25
|
-
|
|
26
|
-
export type AutoApproval =
|
|
27
|
-
| { status: "off" }
|
|
28
|
-
| {
|
|
29
|
-
status: "on";
|
|
30
|
-
workspace: string;
|
|
31
|
-
classes: AutoClass[];
|
|
32
|
-
account: string | null;
|
|
33
|
-
configDigest: string | null;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export type StandingReceipt = {
|
|
37
|
-
kind: "standing";
|
|
38
|
-
workspace: string;
|
|
39
|
-
class: string;
|
|
40
|
-
configDigest: string | null;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const OPERATION_CLASS: Record<string, AutoClass> = {
|
|
44
|
-
"git.branch_setup": "branch",
|
|
45
|
-
"git.commit": "commit",
|
|
46
|
-
"git.push": "push",
|
|
47
|
-
"hosting.pull_request": "pr",
|
|
48
|
-
"hosting.merge": "merge",
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/** Map an external-action operation to its auto-approval class, if any. */
|
|
52
|
-
export const operationAutoClass = (operation: unknown): AutoClass | null =>
|
|
53
|
-
typeof operation === "string" ? (OPERATION_CLASS[operation] ?? null) : null;
|
|
54
|
-
|
|
55
|
-
const digestOf = (configDirPath: string): string | null => {
|
|
56
|
-
try {
|
|
57
|
-
const raw = readFileSync(`${configDirPath}/workspaces.json`, "utf8");
|
|
58
|
-
return createHash("sha256").update(raw).digest("hex");
|
|
59
|
-
} catch {
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const normalizeClasses = (value: unknown): AutoClass[] => {
|
|
65
|
-
if (value === true) return [...AUTO_CLASSES];
|
|
66
|
-
if (!Array.isArray(value)) return [];
|
|
67
|
-
const seen = new Set<AutoClass>();
|
|
68
|
-
for (const item of value) {
|
|
69
|
-
if (typeof item === "string" && (AUTO_CLASSES as readonly string[]).includes(item))
|
|
70
|
-
seen.add(item as AutoClass);
|
|
71
|
-
}
|
|
72
|
-
return [...seen];
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
/** Resolve the auto-approval rule for a checkout root (live read, never cached). */
|
|
76
|
-
export const resolveAutoApproval = (root: string, dir: string = configDir()): AutoApproval => {
|
|
77
|
-
let match: WorkspaceConfig | null;
|
|
78
|
-
try {
|
|
79
|
-
match = resolveWorkspaceFrom(root, dir);
|
|
80
|
-
} catch {
|
|
81
|
-
return { status: "off" };
|
|
82
|
-
}
|
|
83
|
-
if (!match) return { status: "off" };
|
|
84
|
-
const classes = normalizeClasses(match.autoApprove);
|
|
85
|
-
if (classes.length === 0) return { status: "off" };
|
|
86
|
-
return {
|
|
87
|
-
status: "on",
|
|
88
|
-
workspace: match.name,
|
|
89
|
-
classes,
|
|
90
|
-
account: typeof match.vcs?.account === "string" && match.vcs.account ? match.vcs.account : null,
|
|
91
|
-
configDigest: digestOf(dir),
|
|
92
|
-
};
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
/** True when the workspace rule covers the class right now. */
|
|
96
|
-
export const autoApproves = (root: string, cls: unknown, dir?: string): boolean => {
|
|
97
|
-
if (typeof cls !== "string" || !(AUTO_CLASSES as readonly string[]).includes(cls)) return false;
|
|
98
|
-
const resolved = resolveAutoApproval(root, dir);
|
|
99
|
-
return resolved.status === "on" && resolved.classes.includes(cls as AutoClass);
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const isStandingReceipt = (value: unknown): value is StandingReceipt =>
|
|
103
|
-
typeof value === "object" && value !== null && (value as { kind?: unknown }).kind === "standing";
|
|
104
|
-
|
|
105
|
-
const targetOf = (root: string, descriptor: unknown): string | null => {
|
|
106
|
-
try {
|
|
107
|
-
const parsed = typeof descriptor === "string" ? JSON.parse(descriptor) : descriptor;
|
|
108
|
-
const cwd = (parsed as { payload?: { cwd?: unknown } })?.payload?.cwd;
|
|
109
|
-
if (cwd === undefined) return root;
|
|
110
|
-
if (typeof cwd !== "string" || !path.isAbsolute(cwd)) return null;
|
|
111
|
-
return realpathSync(cwd);
|
|
112
|
-
} catch {
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* A standing receipt authorizes only while the referenced workspace rule
|
|
119
|
-
* still covers the class. Removing the flag (or narrowing the list) fails
|
|
120
|
-
* closed on the very next reserve — no revocation step needed.
|
|
121
|
-
*/
|
|
122
|
-
export const standingApprovalLive = (
|
|
123
|
-
root: string,
|
|
124
|
-
receipt: unknown,
|
|
125
|
-
cls: unknown,
|
|
126
|
-
dir?: string,
|
|
127
|
-
descriptor?: unknown,
|
|
128
|
-
): boolean => {
|
|
129
|
-
if (!isStandingReceipt(receipt)) return false;
|
|
130
|
-
if (typeof cls !== "string" || !(AUTO_CLASSES as readonly string[]).includes(cls)) return false;
|
|
131
|
-
if (receipt.class !== cls) return false;
|
|
132
|
-
const target = targetOf(root, descriptor);
|
|
133
|
-
if (!target) return false;
|
|
134
|
-
const resolved = resolveAutoApproval(target, dir);
|
|
135
|
-
if (resolved.status !== "on") return false;
|
|
136
|
-
if (resolved.workspace !== receipt.workspace) return false;
|
|
137
|
-
return resolved.classes.includes(cls as AutoClass);
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
/** Build the receipt an auto-recorded decision carries (current digest). */
|
|
141
|
-
export const standingReceiptFor = (
|
|
142
|
-
root: string,
|
|
143
|
-
workspace: string,
|
|
144
|
-
cls: AutoClass,
|
|
145
|
-
dir?: string,
|
|
146
|
-
): StandingReceipt => {
|
|
147
|
-
const resolved = resolveAutoApproval(root, dir);
|
|
148
|
-
return {
|
|
149
|
-
kind: "standing",
|
|
150
|
-
workspace,
|
|
151
|
-
class: cls,
|
|
152
|
-
configDigest:
|
|
153
|
-
resolved.status === "on" && resolved.workspace === workspace ? resolved.configDigest : null,
|
|
154
|
-
};
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Verify a standing approval record request: active task, lead session
|
|
159
|
-
* match (helpers never auto-approve), operation class equals the claimed
|
|
160
|
-
* class, rule live right now, push identity enforced for push.
|
|
161
|
-
* Returns the host-observed provenance the entry carries on success.
|
|
162
|
-
*/
|
|
163
|
-
export const verifyStandingApproval = (
|
|
164
|
-
root: string,
|
|
165
|
-
task: TaskRecord,
|
|
166
|
-
caller: Caller,
|
|
167
|
-
binding: {
|
|
168
|
-
standing?: { workspace?: unknown; class?: unknown };
|
|
169
|
-
approvedContent?: unknown;
|
|
170
|
-
},
|
|
171
|
-
): Result<Provenance> => {
|
|
172
|
-
if (task.status !== "active")
|
|
173
|
-
return failure("invalid_transition", "only active tasks can authorize actions");
|
|
174
|
-
const workspace = new TaskStore(root).readWorkspace();
|
|
175
|
-
if (!workspace.ok) return workspace;
|
|
176
|
-
if (!workspace.data || !currentWriterOwnsTask(task, workspace.data, caller))
|
|
177
|
-
return failure("permission_denied", "standing approval requires the current writer session");
|
|
178
|
-
const standing = binding.standing;
|
|
179
|
-
if (!standing || typeof standing.workspace !== "string" || typeof standing.class !== "string")
|
|
180
|
-
return failure("invalid_input", "standing approval needs a workspace and class");
|
|
181
|
-
let operation: unknown;
|
|
182
|
-
try {
|
|
183
|
-
operation =
|
|
184
|
-
typeof binding.approvedContent === "string"
|
|
185
|
-
? (JSON.parse(binding.approvedContent) as { operation?: unknown }).operation
|
|
186
|
-
: null;
|
|
187
|
-
} catch {
|
|
188
|
-
operation = null;
|
|
189
|
-
}
|
|
190
|
-
const cls = operationAutoClass(operation);
|
|
191
|
-
if (!cls || cls !== standing.class)
|
|
192
|
-
return failure("permission_denied", "standing approval does not cover this operation");
|
|
193
|
-
const target = targetOf(root, binding.approvedContent);
|
|
194
|
-
if (!target) return failure("permission_denied", "standing action target is invalid");
|
|
195
|
-
const resolved = resolveAutoApproval(target);
|
|
196
|
-
if (
|
|
197
|
-
resolved.status !== "on" ||
|
|
198
|
-
resolved.workspace !== standing.workspace ||
|
|
199
|
-
!resolved.classes.includes(cls)
|
|
200
|
-
)
|
|
201
|
-
return failure("permission_denied", "standing auto-approval is not live for this operation");
|
|
202
|
-
if (cls === "push") {
|
|
203
|
-
const targetWorkspace = resolveWorkspaceFrom(target, configDir());
|
|
204
|
-
const provider = targetWorkspace?.vcs?.provider;
|
|
205
|
-
const account = resolved.account;
|
|
206
|
-
if (!provider || !account)
|
|
207
|
-
return failure(
|
|
208
|
-
"permission_denied",
|
|
209
|
-
"auto-push requires a workspace provider and area account",
|
|
210
|
-
);
|
|
211
|
-
const identity = verifyPushIdentity(target, provider, account);
|
|
212
|
-
if (!identity.ok) return failure("permission_denied", identity.error);
|
|
213
|
-
}
|
|
214
|
-
return success(null, null, {
|
|
215
|
-
kind: "host_observed",
|
|
216
|
-
host: caller.host,
|
|
217
|
-
session: { kind: "host", host: caller.host, handle: caller.actor },
|
|
218
|
-
workerId: null,
|
|
219
|
-
receipts: [standingReceiptFor(target, standing.workspace, cls)],
|
|
220
|
-
});
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
export type StandingAutoBinding = {
|
|
224
|
-
taskId: Id;
|
|
225
|
-
decisionId: Id;
|
|
226
|
-
binding: Decision["binding"];
|
|
227
|
-
expectedRevision: Revision;
|
|
228
|
-
expectedWorkspaceRevision: Revision;
|
|
229
|
-
step?: string;
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
type StandingMatch = {
|
|
233
|
-
task: TaskRecord;
|
|
234
|
-
workspaceId: Id;
|
|
235
|
-
workspaceRevision: Revision;
|
|
236
|
-
cls: AutoClass;
|
|
237
|
-
workspaceName: string;
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Dry-run half of standingAutoBinding: same matching, no recording. Tool
|
|
242
|
-
* gates use this to decide between executing and asking; the runner records.
|
|
243
|
-
*/
|
|
244
|
-
export const standingAutoApplies = (
|
|
245
|
-
store: TaskStore,
|
|
246
|
-
host: string,
|
|
247
|
-
actor: string,
|
|
248
|
-
operation: string,
|
|
249
|
-
): StandingMatch | null => {
|
|
250
|
-
let descriptor: { operation?: unknown; payload?: unknown };
|
|
251
|
-
try {
|
|
252
|
-
descriptor = JSON.parse(operation) as { operation?: unknown; payload?: unknown };
|
|
253
|
-
} catch {
|
|
254
|
-
return null;
|
|
255
|
-
}
|
|
256
|
-
const cls = operationAutoClass(descriptor.operation);
|
|
257
|
-
const target = targetOf(store.root, descriptor);
|
|
258
|
-
if (!cls || !target || !autoApproves(target, cls)) return null;
|
|
259
|
-
const listed = store.listTasks();
|
|
260
|
-
const workspace = store.readWorkspace();
|
|
261
|
-
if (!listed.ok || !workspace.ok || !workspace.data) return null;
|
|
262
|
-
const task = listed.data.find((entry) =>
|
|
263
|
-
currentWriterOwnsTask(entry, workspace.data!, { host, actor }),
|
|
264
|
-
);
|
|
265
|
-
if (!task) return null;
|
|
266
|
-
const resolved = resolveAutoApproval(target);
|
|
267
|
-
if (resolved.status !== "on") return null;
|
|
268
|
-
return {
|
|
269
|
-
task,
|
|
270
|
-
workspaceId: workspace.data.id,
|
|
271
|
-
workspaceRevision: workspace.data.revision,
|
|
272
|
-
cls,
|
|
273
|
-
workspaceName: resolved.workspace,
|
|
274
|
-
};
|
|
275
|
-
};
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Shared adapter path: resolve one gated operation under the workspace
|
|
279
|
-
* standing rule without asking. Returns the exact binding the runner needs
|
|
280
|
-
* for reserve/settle, or null when anything is uncovered, ambiguous, or
|
|
281
|
-
* unresolvable — callers fall through to the question proposal.
|
|
282
|
-
*
|
|
283
|
-
* Single active task per session (ambiguity asks), plan-before-mutation
|
|
284
|
-
* (no task asks), guardrails enforced at record time, revocation instant.
|
|
285
|
-
*/
|
|
286
|
-
export const standingAutoBinding = (
|
|
287
|
-
core: WorkitCore,
|
|
288
|
-
store: TaskStore,
|
|
289
|
-
host: string,
|
|
290
|
-
actor: string,
|
|
291
|
-
operation: string,
|
|
292
|
-
): StandingAutoBinding | null => {
|
|
293
|
-
let descriptor: { operation?: unknown; payload?: unknown };
|
|
294
|
-
try {
|
|
295
|
-
descriptor = JSON.parse(operation) as { operation?: unknown; payload?: unknown };
|
|
296
|
-
} catch {
|
|
297
|
-
return null;
|
|
298
|
-
}
|
|
299
|
-
const cls = operationAutoClass(descriptor.operation);
|
|
300
|
-
const target = targetOf(store.root, descriptor);
|
|
301
|
-
if (!cls || !target || !autoApproves(target, cls)) return null;
|
|
302
|
-
const applies = standingAutoApplies(store, host, actor, operation);
|
|
303
|
-
if (!applies) return null;
|
|
304
|
-
const { task } = applies;
|
|
305
|
-
const resolved = resolveAutoApproval(target);
|
|
306
|
-
if (resolved.status !== "on") return null;
|
|
307
|
-
const presented =
|
|
308
|
-
`Workit decision: action — auto-approved ${String(descriptor.operation)} ` +
|
|
309
|
-
`under workspace rule "${resolved.workspace}" (no question asked).`;
|
|
310
|
-
const recorded = core.observeStandingDecision({
|
|
311
|
-
schemaVersion: 1,
|
|
312
|
-
action: "record",
|
|
313
|
-
taskId: task.id,
|
|
314
|
-
expectedRevision: task.revision,
|
|
315
|
-
purpose: "action",
|
|
316
|
-
binding: {
|
|
317
|
-
taskId: task.id,
|
|
318
|
-
workspaceId: applies.workspaceId,
|
|
319
|
-
scope: task.intent.data.scope,
|
|
320
|
-
presented,
|
|
321
|
-
approvedContent: operation,
|
|
322
|
-
contentRefs: [],
|
|
323
|
-
standing: { workspace: resolved.workspace, class: cls },
|
|
324
|
-
},
|
|
325
|
-
response: "approved",
|
|
326
|
-
requirementIds: [],
|
|
327
|
-
});
|
|
328
|
-
if (!recorded.ok) return null;
|
|
329
|
-
const fresh = store.readTask(task.id);
|
|
330
|
-
if (!fresh.ok) return null;
|
|
331
|
-
return {
|
|
332
|
-
taskId: task.id,
|
|
333
|
-
decisionId: recorded.data.id,
|
|
334
|
-
binding: recorded.data.data.binding,
|
|
335
|
-
expectedRevision: fresh.data.revision,
|
|
336
|
-
expectedWorkspaceRevision: applies.workspaceRevision,
|
|
337
|
-
};
|
|
338
|
-
};
|