@brainervirus/workit-core 1.0.11 → 1.1.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/package.json +1 -1
- package/src/core/authority.ts +84 -11
- package/src/core/auto-approval.ts +326 -0
- package/src/core/branch.ts +83 -2
- package/src/core/doctor.ts +25 -12
- package/src/core/external-action-effects.ts +179 -17
- package/src/core/external-action.ts +204 -16
- package/src/core/methods.ts +8 -8
- package/src/core/pr-create.ts +50 -0
- package/src/core/route-intent.ts +7 -1
- package/src/core/runtime-identity.ts +33 -0
- package/src/core/task-contract.ts +121 -0
- package/src/core/task-engine.ts +209 -223
- package/src/core/task-evaluation.ts +127 -22
- package/src/core/task-store.ts +4 -6
- package/src/core/workers.ts +11 -1
- package/src/core/workspaces.ts +7 -1
- package/src/core.ts +29 -0
package/src/core/route-intent.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { TaskStore } from "./task-store";
|
|
2
2
|
|
|
3
3
|
export type ShellRouteIntent = {
|
|
4
|
-
route: "git.branch_setup" | "hosting.pull_request";
|
|
4
|
+
route: "git.branch_setup" | "hosting.pull_request" | "hosting.merge";
|
|
5
5
|
guidance: string;
|
|
6
6
|
};
|
|
7
7
|
|
|
@@ -10,6 +10,8 @@ const GUIDANCE: Record<ShellRouteIntent["route"], string> = {
|
|
|
10
10
|
"use the Workit git.branch_setup action (workit_external_action in hosts, or `workit action git.branch_setup --payload '{}' --confirm --json` in the CLI)",
|
|
11
11
|
"hosting.pull_request":
|
|
12
12
|
"use the Workit hosting.pull_request action (workit_external_action in hosts, or `workit action hosting.pull_request --payload '{...}' --confirm --json` in the CLI), then drive the created PR through workit-babysit",
|
|
13
|
+
"hosting.merge":
|
|
14
|
+
"use the Workit hosting.merge action (workit_external_action in hosts, or `workit action hosting.merge --payload '{...}' --confirm --json` in the CLI)",
|
|
13
15
|
};
|
|
14
16
|
|
|
15
17
|
const unbalancedQuotes = (segment: string): boolean => {
|
|
@@ -48,6 +50,10 @@ export function shellRouteIntent(command: string): ShellRouteIntent | null {
|
|
|
48
50
|
return { route: "hosting.pull_request", guidance: GUIDANCE["hosting.pull_request"] };
|
|
49
51
|
if (head === "glab" && second === "mr" && third === "create")
|
|
50
52
|
return { route: "hosting.pull_request", guidance: GUIDANCE["hosting.pull_request"] };
|
|
53
|
+
if (head === "gh" && second === "pr" && third === "merge")
|
|
54
|
+
return { route: "hosting.merge", guidance: GUIDANCE["hosting.merge"] };
|
|
55
|
+
if (head === "glab" && second === "mr" && third === "merge")
|
|
56
|
+
return { route: "hosting.merge", guidance: GUIDANCE["hosting.merge"] };
|
|
51
57
|
}
|
|
52
58
|
return null;
|
|
53
59
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
|
|
4
|
+
/** sha256 of the exact bytes, lowercase hex. */
|
|
5
|
+
export const sha256Hex = (bytes: string | Uint8Array): string =>
|
|
6
|
+
createHash("sha256").update(bytes).digest("hex");
|
|
7
|
+
|
|
8
|
+
/** sha256 of a bundle file's bytes, or null when it cannot be read. */
|
|
9
|
+
export const bundleHashOfFile = (file: string): string | null => {
|
|
10
|
+
try {
|
|
11
|
+
return sha256Hex(readFileSync(file));
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Segments of package-manager ephemeral caches (pnpm dlx, npx, pacquet).
|
|
18
|
+
// Installs resolving through these break when the cache is cleared; one
|
|
19
|
+
// table here replaces the per-callsite substring lists.
|
|
20
|
+
const EPHEMERAL_CACHE_SEGMENTS = [
|
|
21
|
+
"/caches/pnpm/dlx/",
|
|
22
|
+
"/.cache/pnpm/dlx/",
|
|
23
|
+
"/_npx/",
|
|
24
|
+
"/.pacquet/",
|
|
25
|
+
"/library/caches/pnpm/",
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/** True when the entry resolves through a package-manager ephemeral cache. */
|
|
29
|
+
export const isEphemeralCachePath = (entry: string): boolean => {
|
|
30
|
+
if (!entry) return false;
|
|
31
|
+
const normalized = entry.replaceAll("\\", "/").toLowerCase();
|
|
32
|
+
return EPHEMERAL_CACHE_SEGMENTS.some((segment) => normalized.includes(segment));
|
|
33
|
+
};
|
|
@@ -144,9 +144,129 @@ export const refSchema = z.discriminatedUnion("kind", [
|
|
|
144
144
|
.strict(),
|
|
145
145
|
z.object({ kind: z.literal("host"), host: hostSchema, handle: nonEmpty }).strict(),
|
|
146
146
|
z.object({ kind: z.literal("external"), url: text.url() }).strict(),
|
|
147
|
+
// Standing auto-approval receipt: authorizes without a live question while
|
|
148
|
+
// the referenced workspace rule covers the operation class. Verified live
|
|
149
|
+
// at reserve time against current config; dropped on export/import (a rule
|
|
150
|
+
// from another machine must never authorize here). Scalar-only shape keeps
|
|
151
|
+
// advertised operation schemas within provider depth limits.
|
|
152
|
+
z
|
|
153
|
+
.object({
|
|
154
|
+
kind: z.literal("standing"),
|
|
155
|
+
workspace: nonEmpty,
|
|
156
|
+
class: nonEmpty,
|
|
157
|
+
configDigest: nullableDigest,
|
|
158
|
+
})
|
|
159
|
+
.strict(),
|
|
147
160
|
]);
|
|
148
161
|
export type Ref = z.infer<typeof refSchema>;
|
|
149
162
|
|
|
163
|
+
/** Maps one reference during record traversal. Returning null marks the
|
|
164
|
+
* reference unportable: arrays drop it, nullable fields null it, and
|
|
165
|
+
* non-nullable positions propagate the drop to their owner. */
|
|
166
|
+
export type RefMap = (ref: Ref) => Ref | null;
|
|
167
|
+
|
|
168
|
+
type RewriteOutcome = { drop: boolean; value?: unknown };
|
|
169
|
+
|
|
170
|
+
const rewriteUnknown = (value: unknown, map: RefMap): RewriteOutcome => {
|
|
171
|
+
if (Array.isArray(value)) {
|
|
172
|
+
const out: unknown[] = [];
|
|
173
|
+
for (const item of value) {
|
|
174
|
+
const rewritten = rewriteUnknown(item, map);
|
|
175
|
+
if (!rewritten.drop) out.push(rewritten.value);
|
|
176
|
+
}
|
|
177
|
+
return { drop: false, value: out };
|
|
178
|
+
}
|
|
179
|
+
if (typeof value === "object" && value !== null) {
|
|
180
|
+
if (refSchema.safeParse(value).success) {
|
|
181
|
+
const mapped = map(value as Ref);
|
|
182
|
+
return mapped === null ? { drop: true } : { drop: false, value: mapped };
|
|
183
|
+
}
|
|
184
|
+
const out: Record<string, unknown> = {};
|
|
185
|
+
for (const [key, item] of Object.entries(value)) {
|
|
186
|
+
const rewritten = rewriteUnknown(item, map);
|
|
187
|
+
out[key] = rewritten.drop ? null : rewritten.value;
|
|
188
|
+
}
|
|
189
|
+
return { drop: false, value: out };
|
|
190
|
+
}
|
|
191
|
+
return { drop: false, value };
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const rewriteNode = (value: unknown, schema: z.ZodType, map: RefMap): RewriteOutcome => {
|
|
195
|
+
// Note: this zod version's classic accessors return core types, hence the
|
|
196
|
+
// casts below. Runtime behavior is identical; only the annotations differ.
|
|
197
|
+
if (
|
|
198
|
+
typeof value === "object" &&
|
|
199
|
+
value !== null &&
|
|
200
|
+
!Array.isArray(value) &&
|
|
201
|
+
refSchema.safeParse(value).success
|
|
202
|
+
) {
|
|
203
|
+
const mapped = map(value as Ref);
|
|
204
|
+
return mapped === null ? { drop: true } : { drop: false, value: mapped };
|
|
205
|
+
}
|
|
206
|
+
let node: z.ZodType = schema;
|
|
207
|
+
while (
|
|
208
|
+
node instanceof z.ZodNullable ||
|
|
209
|
+
node instanceof z.ZodOptional ||
|
|
210
|
+
node instanceof z.ZodDefault
|
|
211
|
+
) {
|
|
212
|
+
node = node.unwrap() as z.ZodType;
|
|
213
|
+
}
|
|
214
|
+
if (node === refSchema) {
|
|
215
|
+
if (!refSchema.safeParse(value).success) return { drop: false, value };
|
|
216
|
+
const mapped = map(value as Ref);
|
|
217
|
+
return mapped === null ? { drop: true } : { drop: false, value: mapped };
|
|
218
|
+
}
|
|
219
|
+
if (node instanceof z.ZodArray) {
|
|
220
|
+
if (!Array.isArray(value)) return { drop: false, value };
|
|
221
|
+
const out: unknown[] = [];
|
|
222
|
+
for (const item of value) {
|
|
223
|
+
const rewritten = rewriteNode(item, node.element as z.ZodType, map);
|
|
224
|
+
if (!rewritten.drop) out.push(rewritten.value);
|
|
225
|
+
}
|
|
226
|
+
return { drop: false, value: out };
|
|
227
|
+
}
|
|
228
|
+
if (node instanceof z.ZodObject) {
|
|
229
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
230
|
+
return { drop: false, value };
|
|
231
|
+
const shape = node.shape as Record<string, z.ZodType>;
|
|
232
|
+
const record = value as Record<string, unknown>;
|
|
233
|
+
const out: Record<string, unknown> = {};
|
|
234
|
+
for (const [key, item] of Object.entries(record)) {
|
|
235
|
+
if (!(key in shape)) {
|
|
236
|
+
const scrubbed = rewriteUnknown(item, map);
|
|
237
|
+
out[key] = scrubbed.drop ? null : scrubbed.value;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const child = shape[key];
|
|
241
|
+
const rewritten = rewriteNode(item, child, map);
|
|
242
|
+
if (rewritten.drop) {
|
|
243
|
+
if (child instanceof z.ZodOptional || child instanceof z.ZodDefault) continue;
|
|
244
|
+
if (child instanceof z.ZodNullable) {
|
|
245
|
+
out[key] = null;
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
return { drop: true };
|
|
249
|
+
}
|
|
250
|
+
out[key] = rewritten.value;
|
|
251
|
+
}
|
|
252
|
+
return { drop: false, value: out };
|
|
253
|
+
}
|
|
254
|
+
return { drop: false, value };
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Schema-driven reference rewrite over an already-validated record subtree.
|
|
259
|
+
* Every `Ref` anywhere in the value is mapped; unportable references are
|
|
260
|
+
* dropped from arrays, nulled in nullable fields, and propagated upward
|
|
261
|
+
* anywhere else — so a new ref-bearing field can never silently slip
|
|
262
|
+
* through export or import. Unknown (schema-absent) keys are scrubbed the
|
|
263
|
+
* same way. Returns null only when the root itself is an unportable ref.
|
|
264
|
+
*/
|
|
265
|
+
export const rewriteRecordRefs = (value: unknown, schema: z.ZodType, map: RefMap): unknown => {
|
|
266
|
+
const rewritten = rewriteNode(value, schema, map);
|
|
267
|
+
return rewritten.drop ? null : rewritten.value;
|
|
268
|
+
};
|
|
269
|
+
|
|
150
270
|
export const provenanceSchema = z
|
|
151
271
|
.object({
|
|
152
272
|
kind: z.enum(["host_observed", "agent_reported", "imported"]),
|
|
@@ -456,6 +576,7 @@ export const decisionSchema = z
|
|
|
456
576
|
displayed: text.optional(),
|
|
457
577
|
contentRefs: z.array(refSchema),
|
|
458
578
|
statedChoice: z.object({ ref: text, text: text }).strict().optional(),
|
|
579
|
+
standing: z.object({ workspace: nonEmpty, class: nonEmpty }).strict().optional(),
|
|
459
580
|
})
|
|
460
581
|
.strict(),
|
|
461
582
|
digest,
|