@brainervirus/workit-core 1.3.0 → 1.3.1
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/external-action.ts +8 -1
- package/src/core/methods.ts +9 -0
- package/src/core/policy-resolver.ts +13 -5
- package/src/core/task-contract.ts +59 -5
- package/src/core/workspaces.ts +39 -33
- package/src/core.ts +2 -0
package/package.json
CHANGED
|
@@ -56,7 +56,7 @@ type WithExternalActionLock = <T>(
|
|
|
56
56
|
operation: () => Promise<ExternalActionResult<T>>,
|
|
57
57
|
) => Promise<ExternalActionResult<T>>;
|
|
58
58
|
|
|
59
|
-
const externalActionSchema = z.discriminatedUnion("operation", [
|
|
59
|
+
export const externalActionSchema = z.discriminatedUnion("operation", [
|
|
60
60
|
z
|
|
61
61
|
.object({
|
|
62
62
|
operation: z.literal("git.branch_setup"),
|
|
@@ -209,6 +209,13 @@ const externalActionSchema = z.discriminatedUnion("operation", [
|
|
|
209
209
|
]);
|
|
210
210
|
export type ExternalActionRequest = z.infer<typeof externalActionSchema>;
|
|
211
211
|
|
|
212
|
+
/** The complete external-action contract for hosts that register native tools. */
|
|
213
|
+
export const externalActionJsonSchema = (): z.core.JSONSchema.BaseSchema =>
|
|
214
|
+
({
|
|
215
|
+
type: "object",
|
|
216
|
+
...z.toJSONSchema(externalActionSchema, { target: "draft-2020-12" }),
|
|
217
|
+
}) as z.core.JSONSchema.BaseSchema;
|
|
218
|
+
|
|
212
219
|
export type ExternalActionOperation = ExternalActionRequest["operation"];
|
|
213
220
|
|
|
214
221
|
export const externalActionRequest = (value: unknown): Result<ExternalActionRequest> => {
|
package/src/core/methods.ts
CHANGED
|
@@ -95,6 +95,15 @@ need zero Workit task, assessment, or writer calls. Use one compact tracked
|
|
|
95
95
|
record only when handoff, dependent steps, concurrent actors, or meaningful
|
|
96
96
|
decisions make continuity useful; similar titles alone never merge tasks.
|
|
97
97
|
|
|
98
|
+
For a routine user-authorized branch or commit, prefer native host Git/shell
|
|
99
|
+
tools from the outset when managed coordination or outcome reconciliation is
|
|
100
|
+
not needed. Inspect the actual target checkout and its configured conventions;
|
|
101
|
+
use Workit's read-only context/policy tools when needed. Do not start a task,
|
|
102
|
+
acquire a writer, or mint a decision receipt solely for a native Git action.
|
|
103
|
+
Native permissions still apply. Never switch execution paths to evade a denial
|
|
104
|
+
or repeat an uncertain managed effect. A local-commit endpoint does not imply
|
|
105
|
+
PR readiness, a fresh review, or post-commit assessment/closure paperwork.
|
|
106
|
+
|
|
98
107
|
For tracked work, inspect its current state and use the shared operations for
|
|
99
108
|
task, policy, evidence, finding, decision, worker, writer, and state changes.
|
|
100
109
|
Start a record once for an explicit tracked objective; assess or reassess only
|
|
@@ -326,6 +326,11 @@ function resolveRequirements(input: NormalizedResolverInput): Requirement[] {
|
|
|
326
326
|
}),
|
|
327
327
|
);
|
|
328
328
|
|
|
329
|
+
const independentBehaviorReview =
|
|
330
|
+
input.preferences?.thorough === true ||
|
|
331
|
+
input.assessment.consequences.some((consequence) =>
|
|
332
|
+
["security", "data", "public_contract", "operations"].includes(consequence.area),
|
|
333
|
+
);
|
|
329
334
|
if (signals.behaviorChange.value === true)
|
|
330
335
|
requirements.push(
|
|
331
336
|
requirement({
|
|
@@ -340,15 +345,18 @@ function resolveRequirements(input: NormalizedResolverInput): Requirement[] {
|
|
|
340
345
|
acceptanceAllowed: false,
|
|
341
346
|
}),
|
|
342
347
|
requirement({
|
|
343
|
-
ruleId: "fresh-context-review",
|
|
348
|
+
ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
|
|
344
349
|
dimension: "review",
|
|
345
350
|
scope,
|
|
346
|
-
reason:
|
|
347
|
-
|
|
348
|
-
"
|
|
351
|
+
reason: independentBehaviorReview
|
|
352
|
+
? "Consequential behavior or the thorough preference requires fresh-context review."
|
|
353
|
+
: "Bounded behavior changes use self-review unless consequential risks or trusted rules require more.",
|
|
354
|
+
satisfaction: independentBehaviorReview
|
|
355
|
+
? "A separate review context examines the current candidate and records its result."
|
|
356
|
+
: "The lead reviews the resulting diff and records the relevant scope and checks.",
|
|
349
357
|
before: "close",
|
|
350
358
|
dependentAction: null,
|
|
351
|
-
acceptanceAllowed:
|
|
359
|
+
acceptanceAllowed: !independentBehaviorReview,
|
|
352
360
|
}),
|
|
353
361
|
);
|
|
354
362
|
else if (signals.mechanicalLowRisk.value === true)
|
|
@@ -1140,8 +1140,61 @@ export const OPERATION_SCHEMA_DEPTH = 1;
|
|
|
1140
1140
|
/** Provider nesting limit every advertised Workit schema must stay within. */
|
|
1141
1141
|
export const OPERATION_SCHEMA_MAX_DEPTH = 10;
|
|
1142
1142
|
|
|
1143
|
-
|
|
1144
|
-
|
|
1143
|
+
const schemaShapeDescription = (value: unknown, depth = 0): string => {
|
|
1144
|
+
if (!value || typeof value !== "object") return "value";
|
|
1145
|
+
const schema = value as Record<string, unknown>;
|
|
1146
|
+
if (Array.isArray(schema.oneOf) || Array.isArray(schema.anyOf)) {
|
|
1147
|
+
const options = (Array.isArray(schema.oneOf) ? schema.oneOf : schema.anyOf) as unknown[];
|
|
1148
|
+
return `(${options.map((option) => schemaShapeDescription(option, depth)).join("|")})`;
|
|
1149
|
+
}
|
|
1150
|
+
if (schema.const !== undefined) return `=${JSON.stringify(schema.const)}`;
|
|
1151
|
+
if (Array.isArray(schema.enum)) return `[${schema.enum.map(String).join("|")}]`;
|
|
1152
|
+
const type = Array.isArray(schema.type) ? schema.type.join("/") : schema.type;
|
|
1153
|
+
if (type === "array") return `arr<${schemaShapeDescription(schema.items, depth)}>`;
|
|
1154
|
+
if (type === "object" && schema.properties && typeof schema.properties === "object") {
|
|
1155
|
+
if (depth >= 2) return "obj";
|
|
1156
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
1157
|
+
const properties = schema.properties as Record<string, unknown>;
|
|
1158
|
+
const fields = Object.entries(properties).map(
|
|
1159
|
+
([name, child]) =>
|
|
1160
|
+
`${name}${required.has(name) ? "!" : ""}:${schemaShapeDescription(child, depth + 1)}`,
|
|
1161
|
+
);
|
|
1162
|
+
return `obj{${fields.join(",")}}`;
|
|
1163
|
+
}
|
|
1164
|
+
if (typeof type !== "string") return "?";
|
|
1165
|
+
return type
|
|
1166
|
+
.split("/")
|
|
1167
|
+
.map((part) => ({ string: "str", boolean: "bool", number: "num", null: "null" })[part] ?? part)
|
|
1168
|
+
.join("|");
|
|
1169
|
+
};
|
|
1170
|
+
|
|
1171
|
+
export const canonicalFieldsDescription = (
|
|
1172
|
+
value: string[] | Record<string, unknown> | z.ZodType,
|
|
1173
|
+
): string => {
|
|
1174
|
+
if (Array.isArray(value))
|
|
1175
|
+
return `Canonical object fields: ${value.join(", ")}. Workit validates the complete nested value.`;
|
|
1176
|
+
const isZodType = (schema: Record<string, unknown> | z.ZodType): schema is z.ZodType =>
|
|
1177
|
+
"_zod" in schema && typeof schema._zod === "object";
|
|
1178
|
+
const schema = isZodType(value)
|
|
1179
|
+
? (z.toJSONSchema(value, { target: "draft-2020-12" }) as Record<string, unknown>)
|
|
1180
|
+
: value;
|
|
1181
|
+
if (!schema.properties || typeof schema.properties !== "object") {
|
|
1182
|
+
const variants = Array.isArray(schema.oneOf)
|
|
1183
|
+
? schema.oneOf
|
|
1184
|
+
: Array.isArray(schema.anyOf)
|
|
1185
|
+
? schema.anyOf
|
|
1186
|
+
: null;
|
|
1187
|
+
return variants
|
|
1188
|
+
? `Canonical shapes: ${variants.map((variant) => schemaShapeDescription(variant)).join(" | ")}. Full nested value is validated.`
|
|
1189
|
+
: "Canonical nested value; Workit validates the complete value.";
|
|
1190
|
+
}
|
|
1191
|
+
const properties = schema.properties as Record<string, unknown>;
|
|
1192
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
1193
|
+
const fields = Object.entries(properties).map(
|
|
1194
|
+
([name, child]) => `${name}${required.has(name) ? "!" : ""}:${schemaShapeDescription(child)}`,
|
|
1195
|
+
);
|
|
1196
|
+
return `Fields (! required): ${fields.join(", ")}. Full nested value is validated.`;
|
|
1197
|
+
};
|
|
1145
1198
|
|
|
1146
1199
|
/**
|
|
1147
1200
|
* Provider-safe projection of an operation JSON schema. Objects deeper than
|
|
@@ -1178,11 +1231,12 @@ export function boundedOperationJsonSchema(
|
|
|
1178
1231
|
if (typeof node !== "object" || node === null) return node;
|
|
1179
1232
|
const record = node as Record<string, unknown>;
|
|
1180
1233
|
if (record.properties && typeof record.properties === "object") {
|
|
1181
|
-
const
|
|
1234
|
+
const properties = record.properties as Record<string, unknown>;
|
|
1235
|
+
const fields = Object.keys(properties);
|
|
1182
1236
|
if (currentDepth >= maxDepth || fields.length === 0) {
|
|
1183
1237
|
const description = stringifiedObjects
|
|
1184
|
-
? `${canonicalFieldsDescription(
|
|
1185
|
-
: canonicalFieldsDescription(
|
|
1238
|
+
? `${canonicalFieldsDescription(record)} A JSON-encoded string is also accepted.`
|
|
1239
|
+
: canonicalFieldsDescription(record);
|
|
1186
1240
|
return tolerate({ type: "object", description });
|
|
1187
1241
|
}
|
|
1188
1242
|
return tolerate({
|
package/src/core/workspaces.ts
CHANGED
|
@@ -409,6 +409,43 @@ const canonicalGlob = (glob: string): string => {
|
|
|
409
409
|
return real + (prefix.endsWith("/") ? "/" : "") + rest;
|
|
410
410
|
};
|
|
411
411
|
|
|
412
|
+
const globSpecificity = (glob: string): number =>
|
|
413
|
+
glob
|
|
414
|
+
.replaceAll("\\", "/")
|
|
415
|
+
.split("/")
|
|
416
|
+
.filter((segment) => segment.length > 0 && !/[*?[\]{]/.test(segment)).length;
|
|
417
|
+
|
|
418
|
+
const selectWorkspaceMatch = <T extends { name: string; glob: string }>(
|
|
419
|
+
matches: T[],
|
|
420
|
+
cwd: string,
|
|
421
|
+
workspaceName?: string,
|
|
422
|
+
): T | undefined => {
|
|
423
|
+
if (workspaceName !== undefined) {
|
|
424
|
+
const named = matches.filter((entry) => entry.name === workspaceName);
|
|
425
|
+
if (named.length === 1) return named[0];
|
|
426
|
+
if (named.length > 1)
|
|
427
|
+
throw new Error(`ambiguous workspace ${JSON.stringify(workspaceName)} for ${cwd}`);
|
|
428
|
+
if (matches.length > 0) {
|
|
429
|
+
throw new Error(
|
|
430
|
+
`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}; matching choices: ${matches.map((entry) => entry.name).join(", ")}`,
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}`);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (matches.length < 2) return matches[0];
|
|
437
|
+
const highestSpecificity = Math.max(...matches.map((entry) => globSpecificity(entry.glob)));
|
|
438
|
+
const mostSpecific = matches.filter(
|
|
439
|
+
(entry) => globSpecificity(entry.glob) === highestSpecificity,
|
|
440
|
+
);
|
|
441
|
+
if (mostSpecific.length > 1) {
|
|
442
|
+
throw new Error(
|
|
443
|
+
`ambiguous workspace for ${cwd}; choose one of: ${mostSpecific.map((entry) => entry.name).join(", ")}`,
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
return mostSpecific[0];
|
|
447
|
+
};
|
|
448
|
+
|
|
412
449
|
/** Match a cwd against the workspaces.json under an explicit config dir. */
|
|
413
450
|
export const resolveWorkspaceFrom = (
|
|
414
451
|
cwd: string,
|
|
@@ -440,23 +477,7 @@ export const resolveWorkspaceFrom = (
|
|
|
440
477
|
}
|
|
441
478
|
if (matched) matches.push(ws);
|
|
442
479
|
}
|
|
443
|
-
|
|
444
|
-
const selected = matches.find((ws) => ws.name === workspaceName);
|
|
445
|
-
if (!selected && matches.length > 0) {
|
|
446
|
-
const matchingNames = matches.map((ws) => ws.name);
|
|
447
|
-
throw new Error(
|
|
448
|
-
`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}; matching choices: ${matchingNames.join(", ")}`,
|
|
449
|
-
);
|
|
450
|
-
}
|
|
451
|
-
if (selected) return selected;
|
|
452
|
-
throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}`);
|
|
453
|
-
}
|
|
454
|
-
if (matches.length > 1) {
|
|
455
|
-
throw new Error(
|
|
456
|
-
`ambiguous workspace for ${cwd}; choose one of: ${matches.map((ws) => ws.name).join(", ")}`,
|
|
457
|
-
);
|
|
458
|
-
}
|
|
459
|
-
return matches[0] ?? null;
|
|
480
|
+
return selectWorkspaceMatch(matches, cwd, workspaceName) ?? null;
|
|
460
481
|
};
|
|
461
482
|
|
|
462
483
|
export const resolveWorkspace = (
|
|
@@ -560,22 +581,7 @@ const resolveRuntimeWorkspaceCandidate = (
|
|
|
560
581
|
);
|
|
561
582
|
});
|
|
562
583
|
const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
|
|
563
|
-
|
|
564
|
-
if (workspaceName) {
|
|
565
|
-
const named = matches.filter((entry) => entry.name === workspaceName);
|
|
566
|
-
if (named.length === 1) selected = named[0];
|
|
567
|
-
else if (named.length > 1)
|
|
568
|
-
throw new Error(`ambiguous workspace ${JSON.stringify(workspaceName)} for ${cwd}`);
|
|
569
|
-
else if (matches.length)
|
|
570
|
-
throw new Error(
|
|
571
|
-
`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}; matching choices: ${matches.map((entry) => entry.name).join(", ")}`,
|
|
572
|
-
);
|
|
573
|
-
else throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}`);
|
|
574
|
-
} else if (matches.length > 1)
|
|
575
|
-
throw new Error(
|
|
576
|
-
`ambiguous workspace for ${cwd}; choose one of: ${matches.map((entry) => entry.name).join(", ")}`,
|
|
577
|
-
);
|
|
578
|
-
else selected = matches[0];
|
|
584
|
+
const selected = selectWorkspaceMatch(matches, cwd, workspaceName);
|
|
579
585
|
if (!selected) return null;
|
|
580
586
|
const policy = runtimeWorkspacePolicySchema(kind).safeParse(selected);
|
|
581
587
|
if (!policy.success) {
|
package/src/core.ts
CHANGED