@brainervirus/workit-codex 2.1.0 → 2.1.2
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/.codex-plugin/plugin.json +1 -1
- package/dist/launch-mcp.js +1283 -1001
- package/dist/workit-hook.js +1372 -1226
- package/package.json +3 -3
package/dist/workit-hook.js
CHANGED
|
@@ -8783,6 +8783,45 @@ var metadataLockSchema = object({
|
|
|
8783
8783
|
nonce: string2().min(1),
|
|
8784
8784
|
externalAction: literal(true).optional()
|
|
8785
8785
|
}).strict();
|
|
8786
|
+
var INDEX_VERSION = 1;
|
|
8787
|
+
var hostSession = (value) => isObject2(value) && value.kind === "host" && typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
|
|
8788
|
+
var indexEntry = (task, file) => {
|
|
8789
|
+
const sessions = [];
|
|
8790
|
+
const intent = hostSession(task.intent.provenance.session);
|
|
8791
|
+
if (intent)
|
|
8792
|
+
sessions.push({ ...intent, workerId: null });
|
|
8793
|
+
for (const worker of task.workers) {
|
|
8794
|
+
const session = hostSession(worker.data.session);
|
|
8795
|
+
if (session)
|
|
8796
|
+
sessions.push({ ...session, workerId: worker.id });
|
|
8797
|
+
}
|
|
8798
|
+
return {
|
|
8799
|
+
id: task.id,
|
|
8800
|
+
revision: task.revision,
|
|
8801
|
+
status: task.status,
|
|
8802
|
+
createdAt: task.createdAt,
|
|
8803
|
+
updatedAt: task.updatedAt,
|
|
8804
|
+
objective: task.intent.data.objective,
|
|
8805
|
+
source: { host: task.intent.provenance.host, kind: task.intent.provenance.kind },
|
|
8806
|
+
progress: { summary: task.progress.summary, nextAction: task.progress.nextAction },
|
|
8807
|
+
sessions,
|
|
8808
|
+
file
|
|
8809
|
+
};
|
|
8810
|
+
};
|
|
8811
|
+
var fileSignature = (file) => {
|
|
8812
|
+
try {
|
|
8813
|
+
const stat = fs4.statSync(file, { bigint: true });
|
|
8814
|
+
return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
|
|
8815
|
+
} catch {
|
|
8816
|
+
return null;
|
|
8817
|
+
}
|
|
8818
|
+
};
|
|
8819
|
+
var RACY_WINDOW_NS = 2000000000n;
|
|
8820
|
+
var racySignature = (signature) => {
|
|
8821
|
+
const [mtime = 0n, ctime = 0n] = signature.split(":").slice(3, 5).map(BigInt);
|
|
8822
|
+
const changed = mtime > ctime ? mtime : ctime;
|
|
8823
|
+
return BigInt(Date.now()) * 1000000n - changed < RACY_WINDOW_NS;
|
|
8824
|
+
};
|
|
8786
8825
|
var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
8787
8826
|
var cachedRuntimeVersion = null;
|
|
8788
8827
|
var runtimeVersion = () => {
|
|
@@ -8911,6 +8950,57 @@ class TaskStore {
|
|
|
8911
8950
|
}
|
|
8912
8951
|
return success(null, null, tasks);
|
|
8913
8952
|
}
|
|
8953
|
+
listTaskIndex() {
|
|
8954
|
+
if (!fs4.existsSync(this.tasksDir))
|
|
8955
|
+
return success(null, null, []);
|
|
8956
|
+
let names;
|
|
8957
|
+
try {
|
|
8958
|
+
names = fs4.readdirSync(this.tasksDir).filter((name) => name.endsWith(".json"));
|
|
8959
|
+
} catch (error) {
|
|
8960
|
+
return failure("storage_error", `unable to list tasks: ${String(error)}`, {
|
|
8961
|
+
path: this.tasksDir
|
|
8962
|
+
});
|
|
8963
|
+
}
|
|
8964
|
+
const workspace = this.readWorkspace();
|
|
8965
|
+
if (!workspace.ok)
|
|
8966
|
+
return workspace;
|
|
8967
|
+
if (!workspace.data)
|
|
8968
|
+
return failure("recovery_required", "task workspace binding is invalid", {
|
|
8969
|
+
path: this.workspacePath
|
|
8970
|
+
});
|
|
8971
|
+
const stored = this.readIndex(workspace.data.id);
|
|
8972
|
+
const entries = [];
|
|
8973
|
+
let changed = Object.keys(stored).length !== names.length;
|
|
8974
|
+
for (const name of names.sort()) {
|
|
8975
|
+
const file = path4.join(this.tasksDir, name);
|
|
8976
|
+
const signature = fileSignature(file);
|
|
8977
|
+
if (signature === null) {
|
|
8978
|
+
changed = true;
|
|
8979
|
+
continue;
|
|
8980
|
+
}
|
|
8981
|
+
const cached = stored[name.slice(0, -5)];
|
|
8982
|
+
if (cached && cached.file === signature && !racySignature(signature)) {
|
|
8983
|
+
entries.push(cached);
|
|
8984
|
+
continue;
|
|
8985
|
+
}
|
|
8986
|
+
const item = this.readRecord(file, taskRecordSchema);
|
|
8987
|
+
if (!item.exists)
|
|
8988
|
+
continue;
|
|
8989
|
+
if (!item.result.ok)
|
|
8990
|
+
return item.result;
|
|
8991
|
+
if (!validId(name.slice(0, -5)) || item.result.data.id !== name.slice(0, -5))
|
|
8992
|
+
return failure("recovery_required", "task filename and record ID differ", { path: name });
|
|
8993
|
+
if (item.result.data.workspaceId !== workspace.data.id)
|
|
8994
|
+
return failure("recovery_required", "task workspace binding is invalid", { path: name });
|
|
8995
|
+
const entry = indexEntry(item.result.data, signature);
|
|
8996
|
+
if (!cached || canonicalJson(cached) !== canonicalJson(entry))
|
|
8997
|
+
changed = true;
|
|
8998
|
+
entries.push(entry);
|
|
8999
|
+
}
|
|
9000
|
+
if (changed)
|
|
9001
|
+
this.writeIndex(workspace.data.id, entries);
|
|
9002
|
+
return success(null, null, entries);
|
|
9003
|
+
}
|
|
8914
9004
|
readWorkspace() {
|
|
8915
9005
|
const item = this.readRecord(this.workspacePath, workspaceRecordSchema);
|
|
8916
9006
|
if (!item.exists)
|
|
@@ -9590,6 +9680,8 @@ class TaskStore {
|
|
|
9590
9680
|
fs4.renameSync(temporary, file);
|
|
9591
9681
|
temporary = undefined;
|
|
9592
9682
|
this.fsyncDirectory(path4.dirname(file));
|
|
9683
|
+
if (path4.dirname(file) === this.tasksDir)
|
|
9684
|
+
this.indexTaskWrite(file, value);
|
|
9593
9685
|
return success(null, null, value);
|
|
9594
9686
|
} catch (error) {
|
|
9595
9687
|
return failure("storage_error", `snapshot replacement failed: ${String(error)}`, {
|
|
@@ -9602,6 +9694,45 @@ class TaskStore {
|
|
|
9602
9694
|
} catch {}
|
|
9603
9695
|
}
|
|
9604
9696
|
}
|
|
9697
|
+
readIndex(workspaceId) {
|
|
9698
|
+
try {
|
|
9699
|
+
const value = JSON.parse(fs4.readFileSync(this.indexPath, "utf8"));
|
|
9700
|
+
if (!isObject2(value) || value.version !== INDEX_VERSION || value.workspaceId !== workspaceId || !isObject2(value.tasks))
|
|
9701
|
+
return {};
|
|
9702
|
+
const tasks = {};
|
|
9703
|
+
for (const [id, entry] of Object.entries(value.tasks))
|
|
9704
|
+
if (isObject2(entry) && entry.id === id && typeof entry.file === "string" && typeof entry.status === "string" && typeof entry.updatedAt === "string" && Array.isArray(entry.sessions) && isObject2(entry.progress) && isObject2(entry.source))
|
|
9705
|
+
tasks[id] = entry;
|
|
9706
|
+
return tasks;
|
|
9707
|
+
} catch {
|
|
9708
|
+
return {};
|
|
9709
|
+
}
|
|
9710
|
+
}
|
|
9711
|
+
writeIndex(workspaceId, entries) {
|
|
9712
|
+
const temporary = `${this.indexPath}.${process.pid}.${randomUUID2()}.tmp`;
|
|
9713
|
+
try {
|
|
9714
|
+
fs4.writeFileSync(temporary, JSON.stringify({
|
|
9715
|
+
version: INDEX_VERSION,
|
|
9716
|
+
workspaceId,
|
|
9717
|
+
tasks: Object.fromEntries(entries.map((entry) => [entry.id, entry]))
|
|
9718
|
+
}), { mode: 384, flag: "wx" });
|
|
9719
|
+
fs4.renameSync(temporary, this.indexPath);
|
|
9720
|
+
} catch {
|
|
9721
|
+
try {
|
|
9722
|
+
fs4.unlinkSync(temporary);
|
|
9723
|
+
} catch {}
|
|
9724
|
+
}
|
|
9725
|
+
}
|
|
9726
|
+
indexTaskWrite(file, task) {
|
|
9727
|
+
try {
|
|
9728
|
+
const signature = fileSignature(file);
|
|
9729
|
+
if (signature === null)
|
|
9730
|
+
return;
|
|
9731
|
+
const tasks = this.readIndex(task.workspaceId);
|
|
9732
|
+
tasks[task.id] = indexEntry(task, signature);
|
|
9733
|
+
this.writeIndex(task.workspaceId, Object.values(tasks));
|
|
9734
|
+
} catch {}
|
|
9735
|
+
}
|
|
9605
9736
|
saveRecovery(file, bytes) {
|
|
9606
9737
|
const target = path4.basename(file) === "workspace.json" ? "workspace" : "task";
|
|
9607
9738
|
const id = target === "task" ? path4.basename(file, ".json") : "workspace";
|
|
@@ -9760,6 +9891,9 @@ class TaskStore {
|
|
|
9760
9891
|
get workspacePath() {
|
|
9761
9892
|
return path4.join(this.workitDir, "workspace.json");
|
|
9762
9893
|
}
|
|
9894
|
+
get indexPath() {
|
|
9895
|
+
return path4.join(this.workitDir, "index.json");
|
|
9896
|
+
}
|
|
9763
9897
|
get lockPath() {
|
|
9764
9898
|
return path4.join(this.workitDir, "metadata.lock");
|
|
9765
9899
|
}
|
|
@@ -9767,71 +9901,8 @@ class TaskStore {
|
|
|
9767
9901
|
return path4.join(this.workitDir, ".gitignore");
|
|
9768
9902
|
}
|
|
9769
9903
|
}
|
|
9770
|
-
// packages/workit-core/src/core/
|
|
9771
|
-
|
|
9772
|
-
try {
|
|
9773
|
-
return canonicalJson(left) === canonicalJson(right);
|
|
9774
|
-
} catch {
|
|
9775
|
-
return false;
|
|
9776
|
-
}
|
|
9777
|
-
};
|
|
9778
|
-
var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
9779
|
-
var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
9780
|
-
var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
|
|
9781
|
-
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
9782
|
-
var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
9783
|
-
const code = char.charCodeAt(0);
|
|
9784
|
-
return code < 32 || code === 127;
|
|
9785
|
-
}) && !value.split(/[\\/]/).includes("..");
|
|
9786
|
-
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
9787
|
-
var workerIdOf = (value) => value.workerId ?? null;
|
|
9788
|
-
var currentWriterOwnsTask = (task, workspace, caller) => {
|
|
9789
|
-
const owner = workspace.writer?.owner;
|
|
9790
|
-
return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
|
|
9791
|
-
};
|
|
9792
|
-
function assertProductWriteAllowed(input) {
|
|
9793
|
-
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
9794
|
-
return failure("invalid_input", "invalid product write path");
|
|
9795
|
-
if (!input.store)
|
|
9796
|
-
return failure("permission_denied", "write authorization requires core state");
|
|
9797
|
-
const task = input.store.readTask(input.task.id);
|
|
9798
|
-
if (!task.ok)
|
|
9799
|
-
return task;
|
|
9800
|
-
const workspace = input.store.readWorkspace();
|
|
9801
|
-
if (!workspace.ok)
|
|
9802
|
-
return workspace;
|
|
9803
|
-
if (!workspace.data)
|
|
9804
|
-
return failure("not_found", "workspace not found");
|
|
9805
|
-
const workspaceRecord = workspace.data;
|
|
9806
|
-
if (task.data.status !== "active")
|
|
9807
|
-
return failure("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
9808
|
-
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
9809
|
-
return failure("recovery_required", "task and workspace bindings are invalid");
|
|
9810
|
-
const writer = workspaceRecord.writer;
|
|
9811
|
-
if (!writer)
|
|
9812
|
-
return failure("permission_denied", "checkout has no writer owner");
|
|
9813
|
-
if (writer.state === "uncertain")
|
|
9814
|
-
return failure("recovery_required", "checkout writer requires recovery", {
|
|
9815
|
-
owner: writer.owner
|
|
9816
|
-
});
|
|
9817
|
-
const caller = callerValue(input.caller);
|
|
9818
|
-
const workerId = workerIdOf(input.caller);
|
|
9819
|
-
const owner = writer.owner;
|
|
9820
|
-
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
9821
|
-
return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
9822
|
-
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
9823
|
-
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
9824
|
-
return failure("permission_denied", "only implementers can own product writes");
|
|
9825
|
-
if (workerId !== null && (!worker?.data.session || worker.data.state === "cancelling" || worker.data.state === "unknown" || worker.data.state === "stopped" || !same(worker.data.session, callerSession(input.caller))))
|
|
9826
|
-
return failure("recovery_required", "worker session is not an active writer");
|
|
9827
|
-
return success(null, workspaceRecord.revision, owner);
|
|
9828
|
-
}
|
|
9829
|
-
|
|
9830
|
-
// packages/workit-core/src/core/task-evaluation.ts
|
|
9831
|
-
import { createHash as createHash7 } from "node:crypto";
|
|
9832
|
-
import * as fs7 from "node:fs";
|
|
9833
|
-
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
9834
|
-
import path10 from "node:path";
|
|
9904
|
+
// packages/workit-core/src/core/task-engine.ts
|
|
9905
|
+
import { realpathSync as realpathSync6 } from "node:fs";
|
|
9835
9906
|
|
|
9836
9907
|
// packages/workit-core/src/core/authority.ts
|
|
9837
9908
|
import { createHash as createHash6 } from "node:crypto";
|
|
@@ -10656,6 +10727,66 @@ var verifyPushIdentity = (root, provider, account) => {
|
|
|
10656
10727
|
return { ok: true };
|
|
10657
10728
|
};
|
|
10658
10729
|
|
|
10730
|
+
// packages/workit-core/src/core/workers.ts
|
|
10731
|
+
var same = (left, right) => {
|
|
10732
|
+
try {
|
|
10733
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
10734
|
+
} catch {
|
|
10735
|
+
return false;
|
|
10736
|
+
}
|
|
10737
|
+
};
|
|
10738
|
+
var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
10739
|
+
var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
10740
|
+
var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
|
|
10741
|
+
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
10742
|
+
var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
10743
|
+
const code = char.charCodeAt(0);
|
|
10744
|
+
return code < 32 || code === 127;
|
|
10745
|
+
}) && !value.split(/[\\/]/).includes("..");
|
|
10746
|
+
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
10747
|
+
var workerIdOf = (value) => value.workerId ?? null;
|
|
10748
|
+
var currentWriterOwnsTask = (task, workspace, caller) => {
|
|
10749
|
+
const owner = workspace.writer?.owner;
|
|
10750
|
+
return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
|
|
10751
|
+
};
|
|
10752
|
+
function assertProductWriteAllowed(input) {
|
|
10753
|
+
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
10754
|
+
return failure("invalid_input", "invalid product write path");
|
|
10755
|
+
if (!input.store)
|
|
10756
|
+
return failure("permission_denied", "write authorization requires core state");
|
|
10757
|
+
const task = input.store.readTask(input.task.id);
|
|
10758
|
+
if (!task.ok)
|
|
10759
|
+
return task;
|
|
10760
|
+
const workspace = input.store.readWorkspace();
|
|
10761
|
+
if (!workspace.ok)
|
|
10762
|
+
return workspace;
|
|
10763
|
+
if (!workspace.data)
|
|
10764
|
+
return failure("not_found", "workspace not found");
|
|
10765
|
+
const workspaceRecord = workspace.data;
|
|
10766
|
+
if (task.data.status !== "active")
|
|
10767
|
+
return failure("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
10768
|
+
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
10769
|
+
return failure("recovery_required", "task and workspace bindings are invalid");
|
|
10770
|
+
const writer = workspaceRecord.writer;
|
|
10771
|
+
if (!writer)
|
|
10772
|
+
return failure("permission_denied", "checkout has no writer owner");
|
|
10773
|
+
if (writer.state === "uncertain")
|
|
10774
|
+
return failure("recovery_required", "checkout writer requires recovery", {
|
|
10775
|
+
owner: writer.owner
|
|
10776
|
+
});
|
|
10777
|
+
const caller = callerValue(input.caller);
|
|
10778
|
+
const workerId = workerIdOf(input.caller);
|
|
10779
|
+
const owner = writer.owner;
|
|
10780
|
+
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
10781
|
+
return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
10782
|
+
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
10783
|
+
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
10784
|
+
return failure("permission_denied", "only implementers can own product writes");
|
|
10785
|
+
if (workerId !== null && (!worker?.data.session || worker.data.state === "cancelling" || worker.data.state === "unknown" || worker.data.state === "stopped" || !same(worker.data.session, callerSession(input.caller))))
|
|
10786
|
+
return failure("recovery_required", "worker session is not an active writer");
|
|
10787
|
+
return success(null, workspaceRecord.revision, owner);
|
|
10788
|
+
}
|
|
10789
|
+
|
|
10659
10790
|
// packages/workit-core/src/core/auto-approval.ts
|
|
10660
10791
|
var AUTO_CLASSES = ["branch", "commit", "push", "pr", "merge"];
|
|
10661
10792
|
var OPERATION_CLASS = {
|
|
@@ -11346,41 +11477,18 @@ function reconcileAction(input) {
|
|
|
11346
11477
|
var bindingCovers = scopeCovers;
|
|
11347
11478
|
var verifyDecisionContent = verifyContentRefs;
|
|
11348
11479
|
|
|
11349
|
-
// packages/workit-core/src/core/
|
|
11350
|
-
var
|
|
11351
|
-
|
|
11352
|
-
|
|
11353
|
-
|
|
11354
|
-
|
|
11355
|
-
|
|
11356
|
-
|
|
11357
|
-
|
|
11358
|
-
|
|
11359
|
-
|
|
11360
|
-
|
|
11361
|
-
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
11362
|
-
};
|
|
11363
|
-
var canonicalScope = (scope) => {
|
|
11364
|
-
const paths = scope.paths.length ? scope.paths : ["."];
|
|
11365
|
-
const normalizedPaths = paths.map(canonicalPath);
|
|
11366
|
-
const normalizedExclusions = scope.exclusions.map(canonicalPath);
|
|
11367
|
-
if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
|
|
11368
|
-
return null;
|
|
11369
|
-
return {
|
|
11370
|
-
description: scope.description,
|
|
11371
|
-
paths: [...new Set(normalizedPaths)],
|
|
11372
|
-
exclusions: [...new Set(normalizedExclusions)]
|
|
11373
|
-
};
|
|
11374
|
-
};
|
|
11375
|
-
var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
|
|
11376
|
-
var scopeMatches = (value, scope) => {
|
|
11377
|
-
const normalized = canonicalScope(scope);
|
|
11378
|
-
if (!normalized)
|
|
11379
|
-
return false;
|
|
11380
|
-
const paths = normalized.paths;
|
|
11381
|
-
return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
|
|
11382
|
-
};
|
|
11383
|
-
var pathRelevant = (value, scope) => scopeMatches(value, scope);
|
|
11480
|
+
// packages/workit-core/src/core/policy-resolver.ts
|
|
11481
|
+
var resolverInputSchema = object({
|
|
11482
|
+
intent: intentSchema,
|
|
11483
|
+
assessment: assessmentSchema,
|
|
11484
|
+
constraints: array(constraintSchema),
|
|
11485
|
+
prior: object({
|
|
11486
|
+
decisions: array(decisionSchema),
|
|
11487
|
+
findings: array(findingSchema),
|
|
11488
|
+
requirements: array(requirementSchema)
|
|
11489
|
+
}).strict(),
|
|
11490
|
+
preferences: object({ fast: boolean2().optional(), thorough: boolean2().optional() }).strict().optional()
|
|
11491
|
+
}).strict();
|
|
11384
11492
|
var compareCodeUnits2 = (left, right) => {
|
|
11385
11493
|
const length = Math.min(left.length, right.length);
|
|
11386
11494
|
for (let index = 0;index < length; index += 1) {
|
|
@@ -11390,1152 +11498,1049 @@ var compareCodeUnits2 = (left, right) => {
|
|
|
11390
11498
|
}
|
|
11391
11499
|
return left.length - right.length;
|
|
11392
11500
|
};
|
|
11393
|
-
var
|
|
11394
|
-
|
|
11395
|
-
|
|
11396
|
-
|
|
11397
|
-
|
|
11398
|
-
|
|
11399
|
-
|
|
11400
|
-
|
|
11401
|
-
|
|
11402
|
-
|
|
11403
|
-
|
|
11404
|
-
};
|
|
11405
|
-
}
|
|
11406
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
|
|
11407
|
-
const stagedDeleted = spawnSync2("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
11408
|
-
if (stagedDeleted.status !== 0) {
|
|
11409
|
-
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
11410
|
-
if (!/not a git repository/i.test(stderr))
|
|
11411
|
-
return { paths, uncertain: true, git: true };
|
|
11412
|
-
} else if (stagedDeleted.stdout) {
|
|
11413
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")));
|
|
11414
|
-
}
|
|
11415
|
-
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
11416
|
-
};
|
|
11417
|
-
var walk = (root, directory, output) => {
|
|
11418
|
-
let names;
|
|
11419
|
-
try {
|
|
11420
|
-
names = fs7.readdirSync(directory);
|
|
11421
|
-
} catch {
|
|
11422
|
-
return true;
|
|
11423
|
-
}
|
|
11424
|
-
let uncertain = false;
|
|
11425
|
-
for (const name of names) {
|
|
11426
|
-
if (name === ".git" || name === ".workit")
|
|
11427
|
-
continue;
|
|
11428
|
-
const target = path10.join(directory, name);
|
|
11429
|
-
let stat;
|
|
11430
|
-
try {
|
|
11431
|
-
stat = fs7.lstatSync(target);
|
|
11432
|
-
} catch {
|
|
11433
|
-
uncertain = true;
|
|
11434
|
-
continue;
|
|
11435
|
-
}
|
|
11436
|
-
const item = relative(root, target);
|
|
11437
|
-
output.add(item);
|
|
11438
|
-
if (stat.isDirectory() && !stat.isSymbolicLink())
|
|
11439
|
-
uncertain = walk(root, target, output) || uncertain;
|
|
11440
|
-
}
|
|
11441
|
-
return uncertain;
|
|
11442
|
-
};
|
|
11443
|
-
var scopeRoots = (root, scope) => {
|
|
11444
|
-
const roots = [];
|
|
11445
|
-
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
11446
|
-
const target = path10.resolve(root, item);
|
|
11447
|
-
if (!inside(root, target))
|
|
11448
|
-
return failure("invalid_input", `candidate scope escapes checkout: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} is outside the checkout` }] });
|
|
11449
|
-
let ancestor = target;
|
|
11450
|
-
while (!fs7.existsSync(ancestor) && ancestor !== root)
|
|
11451
|
-
ancestor = path10.dirname(ancestor);
|
|
11452
|
-
try {
|
|
11453
|
-
if (!inside(root, fs7.realpathSync(ancestor)))
|
|
11454
|
-
return failure("invalid_input", `candidate scope escapes checkout through a symlink: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} resolves outside the checkout` }] });
|
|
11455
|
-
} catch {
|
|
11456
|
-
return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
11457
|
-
}
|
|
11458
|
-
roots.push(target);
|
|
11459
|
-
}
|
|
11460
|
-
return success(null, null, roots);
|
|
11461
|
-
};
|
|
11462
|
-
function captureCandidate(root, scope, environment = []) {
|
|
11463
|
-
let checkout;
|
|
11464
|
-
try {
|
|
11465
|
-
checkout = fs7.realpathSync(root);
|
|
11466
|
-
} catch {
|
|
11467
|
-
return failure("invalid_input", "candidate root does not exist");
|
|
11468
|
-
}
|
|
11469
|
-
const normalizedScope = canonicalScope(scope);
|
|
11470
|
-
if (!normalizedScope)
|
|
11471
|
-
return failure("invalid_input", "candidate scope is invalid: paths must be checkout-relative — to work in another repository, keep this task here and coordinate a separate linked task in that checkout", { fields: [{ path: "scope.paths", reason: "must be checkout-relative" }] });
|
|
11472
|
-
const roots = scopeRoots(checkout, normalizedScope);
|
|
11473
|
-
if (!roots.ok)
|
|
11474
|
-
return roots;
|
|
11475
|
-
const git = gitPaths(checkout);
|
|
11476
|
-
const names = new Set;
|
|
11477
|
-
let uncertain = git.uncertain;
|
|
11478
|
-
for (const target of roots.data) {
|
|
11479
|
-
const item = relative(checkout, target);
|
|
11480
|
-
if (item === ".git" || item === ".workit")
|
|
11481
|
-
continue;
|
|
11482
|
-
names.add(item);
|
|
11483
|
-
if (!fs7.existsSync(target))
|
|
11484
|
-
continue;
|
|
11485
|
-
let stat;
|
|
11486
|
-
try {
|
|
11487
|
-
stat = fs7.lstatSync(target);
|
|
11488
|
-
} catch {
|
|
11489
|
-
uncertain = true;
|
|
11490
|
-
continue;
|
|
11491
|
-
}
|
|
11492
|
-
if (!git.git && stat.isDirectory() && !stat.isSymbolicLink())
|
|
11493
|
-
uncertain = walk(checkout, target, names) || uncertain;
|
|
11494
|
-
}
|
|
11495
|
-
for (const item of git.paths)
|
|
11496
|
-
if (scopeMatches(item, normalizedScope))
|
|
11497
|
-
names.add(item);
|
|
11498
|
-
const files = [];
|
|
11499
|
-
let completeness = uncertain ? "uncertain" : "known";
|
|
11500
|
-
for (const item of [...names].sort()) {
|
|
11501
|
-
if (!scopeMatches(item, normalizedScope))
|
|
11502
|
-
continue;
|
|
11503
|
-
const target = path10.join(checkout, item);
|
|
11504
|
-
try {
|
|
11505
|
-
const stat = fs7.lstatSync(target);
|
|
11506
|
-
if (stat.isSymbolicLink()) {
|
|
11507
|
-
const link = fs7.readlinkSync(target);
|
|
11508
|
-
files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
|
|
11509
|
-
} else if (stat.isFile()) {
|
|
11510
|
-
files.push({
|
|
11511
|
-
path: item,
|
|
11512
|
-
kind: "file",
|
|
11513
|
-
digest: digestBytes3(fs7.readFileSync(target)),
|
|
11514
|
-
executable: (stat.mode & 73) !== 0
|
|
11515
|
-
});
|
|
11516
|
-
} else if (!stat.isDirectory()) {
|
|
11517
|
-
completeness = "uncertain";
|
|
11518
|
-
} else if (git.git && git.paths.includes(item)) {
|
|
11519
|
-
completeness = "uncertain";
|
|
11520
|
-
}
|
|
11521
|
-
} catch (error) {
|
|
11522
|
-
if (error?.code === "ENOENT") {
|
|
11523
|
-
files.push({ path: item, kind: "absent", digest: null, executable: null });
|
|
11524
|
-
} else
|
|
11525
|
-
completeness = "uncertain";
|
|
11526
|
-
}
|
|
11527
|
-
}
|
|
11528
|
-
const supplied = Array.isArray(environment) ? environment.map((value) => typeof value === "string" ? { name: value, value: process.env[value] ?? null } : { name: value.name, value: value.value ?? null }) : Object.keys(environment).map((name) => ({
|
|
11501
|
+
var stableUnique = (values) => [...new Set(values)].sort(compareCodeUnits2);
|
|
11502
|
+
var stableList = (values) => [...values].sort((left, right) => compareCodeUnits2(canonicalJson(left), canonicalJson(right)));
|
|
11503
|
+
var normalizedScope = (scope) => ({
|
|
11504
|
+
description: scope.description,
|
|
11505
|
+
paths: stableUnique(scope.paths),
|
|
11506
|
+
exclusions: stableUnique(scope.exclusions)
|
|
11507
|
+
});
|
|
11508
|
+
var normalizedRefList = (values) => stableList(values);
|
|
11509
|
+
var normalizeAssessment = (assessment) => ({
|
|
11510
|
+
facts: stableList(assessment.facts.map((fact) => ({ ...fact, refs: normalizedRefList(fact.refs) }))),
|
|
11511
|
+
signals: Object.fromEntries(Object.entries(assessment.signals).map(([name, signal]) => [
|
|
11529
11512
|
name,
|
|
11530
|
-
|
|
11531
|
-
|
|
11532
|
-
|
|
11533
|
-
|
|
11534
|
-
|
|
11535
|
-
|
|
11536
|
-
|
|
11537
|
-
|
|
11538
|
-
|
|
11539
|
-
|
|
11540
|
-
|
|
11541
|
-
|
|
11542
|
-
|
|
11543
|
-
|
|
11544
|
-
|
|
11545
|
-
|
|
11546
|
-
|
|
11547
|
-
|
|
11548
|
-
|
|
11513
|
+
{ ...signal, refs: normalizedRefList(signal.refs) }
|
|
11514
|
+
])),
|
|
11515
|
+
consequences: stableList(assessment.consequences.map((consequence) => ({
|
|
11516
|
+
...consequence,
|
|
11517
|
+
fact: { ...consequence.fact, refs: normalizedRefList(consequence.fact.refs) }
|
|
11518
|
+
}))),
|
|
11519
|
+
verification: stableList(assessment.verification.map((verification) => ({
|
|
11520
|
+
...verification,
|
|
11521
|
+
scope: normalizedScope(verification.scope),
|
|
11522
|
+
availableChecks: normalizedRefList(verification.availableChecks),
|
|
11523
|
+
gaps: stableUnique(verification.gaps)
|
|
11524
|
+
})))
|
|
11525
|
+
});
|
|
11526
|
+
var normalizeConstraint = (constraint) => ({
|
|
11527
|
+
...constraint,
|
|
11528
|
+
source: constraint.source,
|
|
11529
|
+
requires: stableList(constraint.requires.map((requirement) => ({
|
|
11530
|
+
...requirement,
|
|
11531
|
+
scope: normalizedScope(requirement.scope),
|
|
11532
|
+
refs: normalizedRefList(requirement.refs)
|
|
11533
|
+
})))
|
|
11534
|
+
});
|
|
11535
|
+
var normalizeDecision = (decision) => ({
|
|
11536
|
+
...decision,
|
|
11537
|
+
binding: {
|
|
11538
|
+
...decision.binding,
|
|
11539
|
+
scope: normalizedScope(decision.binding.scope),
|
|
11540
|
+
contentRefs: normalizedRefList(decision.binding.contentRefs)
|
|
11541
|
+
},
|
|
11542
|
+
requirementIds: stableUnique(decision.requirementIds)
|
|
11543
|
+
});
|
|
11544
|
+
var normalizeIntent = (intent) => ({
|
|
11545
|
+
...intent,
|
|
11546
|
+
scope: normalizedScope(intent.scope),
|
|
11547
|
+
authorityRefs: normalizedRefList(intent.authorityRefs)
|
|
11548
|
+
});
|
|
11549
|
+
var normalizePrior = (prior) => ({
|
|
11550
|
+
decisions: stableList(prior.decisions.map(normalizeDecision)),
|
|
11551
|
+
findings: stableList(prior.findings.map((finding) => ({
|
|
11552
|
+
...finding,
|
|
11553
|
+
scope: normalizedScope(finding.scope),
|
|
11554
|
+
refs: normalizedRefList(finding.refs),
|
|
11555
|
+
resolution: finding.resolution ? {
|
|
11556
|
+
...finding.resolution,
|
|
11557
|
+
evidenceIds: stableUnique(finding.resolution.evidenceIds),
|
|
11558
|
+
decisionIds: stableUnique(finding.resolution.decisionIds)
|
|
11559
|
+
} : null
|
|
11560
|
+
}))),
|
|
11561
|
+
requirements: stableList(prior.requirements.map((requirement) => ({
|
|
11562
|
+
...requirement,
|
|
11563
|
+
scope: normalizedScope(requirement.scope)
|
|
11564
|
+
})))
|
|
11565
|
+
});
|
|
11566
|
+
var invalidFields = (error) => ({
|
|
11567
|
+
fields: error.issues.map((issue) => ({
|
|
11568
|
+
path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
|
|
11569
|
+
reason: issue.message
|
|
11570
|
+
}))
|
|
11571
|
+
});
|
|
11572
|
+
function normalizeResolverInput(input) {
|
|
11573
|
+
const parsed = resolverInputSchema.safeParse(input);
|
|
11574
|
+
if (!parsed.success)
|
|
11575
|
+
return failure("invalid_input", "resolver input is invalid", invalidFields(parsed.error));
|
|
11576
|
+
const value = parsed.data;
|
|
11577
|
+
const signals = value.assessment.signals;
|
|
11578
|
+
if (signals.behaviorChange.value === true && signals.mechanicalLowRisk.value === true)
|
|
11579
|
+
return failure("invalid_input", "behavioral change cannot be wholly mechanical", {
|
|
11580
|
+
fields: [
|
|
11581
|
+
{
|
|
11582
|
+
path: "assessment.signals",
|
|
11583
|
+
reason: "contradictory behaviorChange and mechanicalLowRisk"
|
|
11584
|
+
}
|
|
11585
|
+
]
|
|
11586
|
+
});
|
|
11587
|
+
const ids = value.constraints.map((constraint) => constraint.id);
|
|
11588
|
+
if (new Set(ids).size !== ids.length)
|
|
11589
|
+
return failure("invalid_input", "constraint IDs must be unique", {
|
|
11590
|
+
fields: [{ path: "constraints", reason: "duplicate constraint id" }]
|
|
11591
|
+
});
|
|
11592
|
+
if (value.preferences?.fast && value.preferences.thorough)
|
|
11593
|
+
return failure("invalid_input", "fast and thorough preferences contradict", {
|
|
11594
|
+
fields: [{ path: "preferences", reason: "fast and thorough cannot both be enabled" }]
|
|
11595
|
+
});
|
|
11596
|
+
const normalized = {
|
|
11597
|
+
intent: normalizeIntent(value.intent),
|
|
11598
|
+
assessment: normalizeAssessment(value.assessment),
|
|
11599
|
+
constraints: stableList(value.constraints.map(normalizeConstraint)),
|
|
11600
|
+
prior: normalizePrior(value.prior),
|
|
11601
|
+
...value.preferences ? { preferences: { ...value.preferences } } : {},
|
|
11602
|
+
policyVersion: POLICY_VERSION
|
|
11549
11603
|
};
|
|
11550
|
-
|
|
11551
|
-
const parsed = candidateSchema.safeParse(candidate);
|
|
11552
|
-
return parsed.success ? success(null, null, parsed.data) : failure("invalid_input", "captured candidate is invalid");
|
|
11604
|
+
return success(null, null, normalized);
|
|
11553
11605
|
}
|
|
11554
|
-
var
|
|
11555
|
-
|
|
11556
|
-
|
|
11557
|
-
|
|
11558
|
-
|
|
11559
|
-
|
|
11560
|
-
|
|
11561
|
-
|
|
11562
|
-
|
|
11563
|
-
|
|
11564
|
-
|
|
11565
|
-
|
|
11566
|
-
|
|
11567
|
-
|
|
11568
|
-
|
|
11569
|
-
|
|
11570
|
-
|
|
11571
|
-
|
|
11572
|
-
|
|
11573
|
-
|
|
11574
|
-
|
|
11575
|
-
|
|
11576
|
-
|
|
11577
|
-
|
|
11578
|
-
|
|
11579
|
-
|
|
11580
|
-
|
|
11581
|
-
|
|
11582
|
-
|
|
11583
|
-
|
|
11584
|
-
|
|
11585
|
-
|
|
11586
|
-
|
|
11587
|
-
|
|
11588
|
-
|
|
11589
|
-
|
|
11590
|
-
|
|
11591
|
-
|
|
11592
|
-
|
|
11593
|
-
|
|
11594
|
-
|
|
11595
|
-
|
|
11596
|
-
|
|
11597
|
-
|
|
11598
|
-
|
|
11606
|
+
var requirement = (spec) => ({
|
|
11607
|
+
...spec,
|
|
11608
|
+
id: requirementId({
|
|
11609
|
+
ruleId: spec.ruleId,
|
|
11610
|
+
scope: spec.scope,
|
|
11611
|
+
satisfaction: spec.satisfaction,
|
|
11612
|
+
before: spec.before,
|
|
11613
|
+
dependentAction: spec.dependentAction
|
|
11614
|
+
})
|
|
11615
|
+
});
|
|
11616
|
+
var signalReason = (input, name) => input.assessment.signals[name].reason;
|
|
11617
|
+
function resolveRequirements(input) {
|
|
11618
|
+
const { signals } = input.assessment;
|
|
11619
|
+
const scope = input.intent.scope;
|
|
11620
|
+
const requirements = [];
|
|
11621
|
+
const hasUnknownSignal = Object.values(signals).some((signal) => signal.value === "unknown");
|
|
11622
|
+
const hasUnknownFact = [
|
|
11623
|
+
...input.assessment.facts,
|
|
11624
|
+
...input.assessment.consequences.map(({ fact }) => fact)
|
|
11625
|
+
].some((fact) => fact.basis === "unknown");
|
|
11626
|
+
if (hasUnknownSignal || hasUnknownFact)
|
|
11627
|
+
requirements.push(requirement({
|
|
11628
|
+
ruleId: "consequential-unknown",
|
|
11629
|
+
dimension: "investigation",
|
|
11630
|
+
scope,
|
|
11631
|
+
reason: `Resolve consequential uncertainty before the dependent action: ${[
|
|
11632
|
+
...Object.entries(signals).filter(([, signal]) => signal.value === "unknown").map(([name]) => name),
|
|
11633
|
+
...hasUnknownFact ? ["unknown fact"] : []
|
|
11634
|
+
].join(", ")}`,
|
|
11635
|
+
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
11636
|
+
before: "dependent_action",
|
|
11637
|
+
dependentAction: null,
|
|
11638
|
+
acceptanceAllowed: false
|
|
11639
|
+
}));
|
|
11640
|
+
if (signals.productChoiceOpen.value === true)
|
|
11641
|
+
requirements.push(requirement({
|
|
11642
|
+
ruleId: "product-decision",
|
|
11643
|
+
dimension: "decisions",
|
|
11644
|
+
scope,
|
|
11645
|
+
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
11646
|
+
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
11647
|
+
before: "dependent_action",
|
|
11648
|
+
dependentAction: null,
|
|
11649
|
+
acceptanceAllowed: false
|
|
11650
|
+
}));
|
|
11651
|
+
const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
|
|
11652
|
+
if (signals.behaviorChange.value === true)
|
|
11653
|
+
requirements.push(requirement({
|
|
11654
|
+
ruleId: "behavioral-verification",
|
|
11655
|
+
dimension: "testing",
|
|
11656
|
+
scope,
|
|
11657
|
+
reason: `Behavior, side effects, permissions, or data handling may change: ${signalReason(input, "behaviorChange")}`,
|
|
11658
|
+
satisfaction: "A useful behavioral check passes against the affected behavior and current candidate.",
|
|
11659
|
+
before: "close",
|
|
11660
|
+
dependentAction: null,
|
|
11661
|
+
acceptanceAllowed: false
|
|
11662
|
+
}), requirement({
|
|
11663
|
+
ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
|
|
11664
|
+
dimension: "review",
|
|
11665
|
+
scope,
|
|
11666
|
+
reason: independentBehaviorReview ? "Consequential behavior or the thorough preference requires fresh-context review." : "Bounded behavior changes use self-review unless consequential risks or trusted rules require more.",
|
|
11667
|
+
satisfaction: independentBehaviorReview ? "A separate review context examines the current candidate and records its result." : "The lead reviews the resulting diff and records the relevant scope and checks.",
|
|
11668
|
+
before: "close",
|
|
11669
|
+
dependentAction: null,
|
|
11670
|
+
acceptanceAllowed: !independentBehaviorReview
|
|
11671
|
+
}));
|
|
11672
|
+
else if (signals.mechanicalLowRisk.value === true)
|
|
11673
|
+
requirements.push(requirement({
|
|
11674
|
+
ruleId: "mechanical-existing-checks",
|
|
11675
|
+
dimension: "verification",
|
|
11676
|
+
scope,
|
|
11677
|
+
reason: `The change is demonstrably mechanical and low-risk: ${signalReason(input, "mechanicalLowRisk")}`,
|
|
11678
|
+
satisfaction: "Run the relevant existing checks for affected consumers; do not add tautological tests.",
|
|
11679
|
+
before: "close",
|
|
11680
|
+
dependentAction: null,
|
|
11681
|
+
acceptanceAllowed: true
|
|
11682
|
+
}), requirement({
|
|
11683
|
+
ruleId: input.preferences?.thorough ? "fresh-context-review" : "self-review",
|
|
11684
|
+
dimension: "review",
|
|
11685
|
+
scope,
|
|
11686
|
+
reason: input.preferences?.thorough ? "The thorough preference upgrades mechanical self-review to fresh-context review." : "Mechanical low-risk work uses self-review rather than mandatory fresh-context review.",
|
|
11687
|
+
satisfaction: input.preferences?.thorough ? "A separate review context examines the current candidate and records its result." : "The lead reviews the resulting diff and records the relevant scope and checks.",
|
|
11688
|
+
before: "close",
|
|
11689
|
+
dependentAction: null,
|
|
11690
|
+
acceptanceAllowed: !input.preferences?.thorough
|
|
11691
|
+
}));
|
|
11692
|
+
if (signals.behaviorChange.value === true || signals.mechanicalLowRisk.value === true)
|
|
11693
|
+
requirements.push(requirement({
|
|
11694
|
+
ruleId: "pre-pr-cleanup",
|
|
11695
|
+
dimension: "verification",
|
|
11696
|
+
scope,
|
|
11697
|
+
reason: "Implementation output must be deslopped before it is delivered.",
|
|
11698
|
+
satisfaction: "Run the workit-deslop pass and record passing check evidence, or record an approved limitation decision waiving cleanup.",
|
|
11699
|
+
before: "dependent_action",
|
|
11700
|
+
dependentAction: "hosting.pull_request",
|
|
11701
|
+
acceptanceAllowed: true
|
|
11702
|
+
}));
|
|
11703
|
+
if (signals.durableAgreementNeeded.value === true)
|
|
11704
|
+
requirements.push(requirement({
|
|
11705
|
+
ruleId: "durable-spec",
|
|
11706
|
+
dimension: "artifacts",
|
|
11707
|
+
scope,
|
|
11708
|
+
reason: `The work needs a durable agreement about intended behavior: ${signalReason(input, "durableAgreementNeeded")}`,
|
|
11709
|
+
satisfaction: "Record the agreed behavior in a durable specification before implementation writes.",
|
|
11710
|
+
before: "write",
|
|
11711
|
+
dependentAction: null,
|
|
11712
|
+
acceptanceAllowed: true
|
|
11713
|
+
}));
|
|
11714
|
+
if (signals.coordinationPlanNeeded.value === true)
|
|
11715
|
+
requirements.push(requirement({
|
|
11716
|
+
ruleId: "coordination-plan",
|
|
11717
|
+
dimension: "continuity",
|
|
11718
|
+
scope,
|
|
11719
|
+
reason: `Dependencies, sequencing, or resumption need durable coordination: ${signalReason(input, "coordinationPlanNeeded")}`,
|
|
11720
|
+
satisfaction: "Record useful sequencing, dependencies, and the next action in a compact plan.",
|
|
11721
|
+
before: "dependent_action",
|
|
11722
|
+
dependentAction: null,
|
|
11723
|
+
acceptanceAllowed: true
|
|
11724
|
+
}));
|
|
11725
|
+
if (signals.helperUseful.value === true && !input.preferences?.fast)
|
|
11726
|
+
requirements.push(requirement({
|
|
11727
|
+
ruleId: "helper-usefulness",
|
|
11728
|
+
dimension: "delegation",
|
|
11729
|
+
scope,
|
|
11730
|
+
reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
|
|
11731
|
+
satisfaction: "Use a bounded helper only for the independent objective and reconcile its result.",
|
|
11732
|
+
before: "dependent_action",
|
|
11733
|
+
dependentAction: null,
|
|
11734
|
+
acceptanceAllowed: true
|
|
11735
|
+
}));
|
|
11736
|
+
for (const constraint of input.constraints)
|
|
11737
|
+
for (const obligation of constraint.requires) {
|
|
11738
|
+
const dimension = obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge");
|
|
11739
|
+
requirements.push(requirement({
|
|
11740
|
+
ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
|
|
11741
|
+
kind: obligation.kind,
|
|
11742
|
+
scope: normalizedScope(obligation.scope),
|
|
11743
|
+
refs: normalizedRefList(obligation.refs),
|
|
11744
|
+
method: obligation.method,
|
|
11745
|
+
before: obligation.before,
|
|
11746
|
+
dependentAction: obligation.dependentAction
|
|
11747
|
+
})}`,
|
|
11748
|
+
dimension,
|
|
11749
|
+
scope: obligation.scope,
|
|
11750
|
+
reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
|
|
11751
|
+
satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
|
|
11752
|
+
before: obligation.before,
|
|
11753
|
+
dependentAction: obligation.dependentAction,
|
|
11754
|
+
acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
|
|
11755
|
+
}));
|
|
11599
11756
|
}
|
|
11600
|
-
|
|
11601
|
-
|
|
11602
|
-
|
|
11603
|
-
|
|
11604
|
-
|
|
11605
|
-
|
|
11606
|
-
const observed = task.candidates.find((item) => item.id === evidence.candidateId) ?? (current.id === evidence.candidateId ? current : undefined);
|
|
11607
|
-
if (!observed || observed.completeness === "uncertain" || current.completeness === "uncertain")
|
|
11608
|
-
return { evidenceId: entry.id, status: "stale", reason: "candidate is missing or uncertain" };
|
|
11609
|
-
if (observed.id === current.id)
|
|
11610
|
-
return { evidenceId: entry.id, status: evidence.result, reason: "candidate is unchanged" };
|
|
11611
|
-
const scopes = evidenceScopes(task, evidence.requirementIds);
|
|
11612
|
-
const changed = changedPaths(observed, current);
|
|
11613
|
-
const environmentChanged = JSON.stringify(observed.environment) !== JSON.stringify(current.environment);
|
|
11614
|
-
if (!scopes.length || environmentChanged || changed.some((item) => scopes.some((scope) => pathRelevant(item, scope))))
|
|
11615
|
-
return { evidenceId: entry.id, status: "stale", reason: "relevant candidate state changed" };
|
|
11616
|
-
return {
|
|
11617
|
-
evidenceId: entry.id,
|
|
11618
|
-
status: evidence.result,
|
|
11619
|
-
reason: "candidate changed outside evidence scope"
|
|
11620
|
-
};
|
|
11757
|
+
const seen = new Set;
|
|
11758
|
+
return requirements.filter((item) => {
|
|
11759
|
+
if (seen.has(item.id))
|
|
11760
|
+
return false;
|
|
11761
|
+
seen.add(item.id);
|
|
11762
|
+
return true;
|
|
11621
11763
|
});
|
|
11622
11764
|
}
|
|
11623
|
-
|
|
11624
|
-
|
|
11625
|
-
|
|
11626
|
-
|
|
11627
|
-
|
|
11628
|
-
|
|
11629
|
-
|
|
11630
|
-
|
|
11631
|
-
|
|
11632
|
-
const fallback = bound ? pin ?? currentCandidateId : null;
|
|
11633
|
-
return {
|
|
11634
|
-
beforeCandidateId: evidence.beforeCandidateId ?? fallback,
|
|
11635
|
-
candidateId: evidence.candidateId ?? fallback
|
|
11765
|
+
function resolvePolicy(input) {
|
|
11766
|
+
const normalized = normalizeResolverInput(input);
|
|
11767
|
+
if (!normalized.ok)
|
|
11768
|
+
return normalized;
|
|
11769
|
+
const requirements = resolveRequirements(normalized.data);
|
|
11770
|
+
const policy = {
|
|
11771
|
+
policyVersion: POLICY_VERSION,
|
|
11772
|
+
inputDigest: sha256(canonicalJson(normalized.data)),
|
|
11773
|
+
requirements
|
|
11636
11774
|
};
|
|
11637
|
-
|
|
11638
|
-
|
|
11639
|
-
|
|
11640
|
-
|
|
11641
|
-
var evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
|
|
11642
|
-
if (kind !== "passed")
|
|
11643
|
-
return false;
|
|
11644
|
-
if (dimension === "testing" || dimension === "verification")
|
|
11645
|
-
return evidenceKind === "check" || ruleId === "pre-pr-cleanup" && evidenceKind === "artifact";
|
|
11646
|
-
if (dimension === "review")
|
|
11647
|
-
return evidenceKind === "review";
|
|
11648
|
-
if (dimension === "investigation" || dimension === "challenge")
|
|
11649
|
-
return evidenceKind === "investigation";
|
|
11650
|
-
if (dimension === "artifacts" || dimension === "continuity")
|
|
11651
|
-
return evidenceKind === "artifact";
|
|
11652
|
-
return false;
|
|
11653
|
-
};
|
|
11654
|
-
var expectedKinds = (dimension, ruleId) => {
|
|
11655
|
-
if (dimension === "testing")
|
|
11656
|
-
return "check";
|
|
11657
|
-
if (dimension === "verification")
|
|
11658
|
-
return ruleId === "pre-pr-cleanup" ? "check or artifact" : "check";
|
|
11659
|
-
if (dimension === "review")
|
|
11660
|
-
return "review";
|
|
11661
|
-
if (dimension === "investigation" || dimension === "challenge")
|
|
11662
|
-
return "investigation";
|
|
11663
|
-
if (dimension === "artifacts" || dimension === "continuity")
|
|
11664
|
-
return "artifact";
|
|
11665
|
-
if (dimension === "decisions")
|
|
11666
|
-
return "decision";
|
|
11667
|
-
return "worker report";
|
|
11668
|
-
};
|
|
11669
|
-
function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller) {
|
|
11670
|
-
const evidence = evaluateEvidence(task, candidate);
|
|
11671
|
-
if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
|
|
11672
|
-
return task.policy.requirements.map((requirement) => ({
|
|
11673
|
-
requirementId: requirement.id,
|
|
11674
|
-
status: "unsatisfied",
|
|
11675
|
-
evidenceIds: [],
|
|
11676
|
-
decisionIds: [],
|
|
11677
|
-
reason: "stored policy version is unsupported; reassessment is required"
|
|
11678
|
-
}));
|
|
11679
|
-
return (task.policy?.requirements ?? []).map((requirement) => {
|
|
11680
|
-
const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
|
|
11681
|
-
const passed = related.filter(({ entry, evaluation }) => {
|
|
11682
|
-
if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
|
|
11683
|
-
return false;
|
|
11684
|
-
if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
|
|
11685
|
-
return false;
|
|
11686
|
-
if (requirement.dimension !== "review")
|
|
11687
|
-
return true;
|
|
11688
|
-
const reviewSession = sessionFromRef(entry.data.reviewContext);
|
|
11689
|
-
const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
|
|
11690
|
-
const sameEvidenceSession = related.some(({ entry: other }) => other.id !== entry.id && other.data.kind === "review" && other.data.candidateId !== null && other.data.candidateId === entry.data.candidateId && sameSession(reviewSession, other.provenance.session));
|
|
11691
|
-
return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
|
|
11692
|
-
});
|
|
11693
|
-
if (passed.length) {
|
|
11694
|
-
const currentId = candidate?.id ?? null;
|
|
11695
|
-
const withRed = requirement.dimension === "testing" ? passed.filter(({ entry }) => related.some((other) => other.entry.data.kind === "check" && other.evaluation.status === "failed" && other.entry.data.requirementIds.includes(requirement.id) && other.entry.recordedAt <= entry.recordedAt) || related.some((baseline) => baseline.entry.id !== entry.id && baseline.entry.data.kind === "check" && baseline.entry.data.result === "passed" && baseline.entry.data.candidateId !== null && currentId !== null && baseline.entry.data.candidateId !== currentId)) : passed;
|
|
11696
|
-
if (withRed.length)
|
|
11697
|
-
return {
|
|
11698
|
-
requirementId: requirement.id,
|
|
11699
|
-
status: "satisfied",
|
|
11700
|
-
evidenceIds: withRed.map(({ entry }) => entry.id),
|
|
11701
|
-
decisionIds: [],
|
|
11702
|
-
reason: "fresh applicable evidence passed"
|
|
11703
|
-
};
|
|
11704
|
-
if (requirement.dimension === "testing")
|
|
11705
|
-
return {
|
|
11706
|
-
requirementId: requirement.id,
|
|
11707
|
-
status: "unsatisfied",
|
|
11708
|
-
evidenceIds: [],
|
|
11709
|
-
decisionIds: [],
|
|
11710
|
-
reason: "testing requirement has GREEN evidence but no preceding RED"
|
|
11711
|
-
};
|
|
11712
|
-
}
|
|
11713
|
-
if (requirement.dimension === "delegation") {
|
|
11714
|
-
const dot = (scope) => scope.paths.length > 0 ? scope : { ...scope, paths: ["."] };
|
|
11715
|
-
const reconciled = task.workers.some((worker) => worker.data.report?.outcome === "completed" && (worker.data.assignment.requirementIds.includes(requirement.id) || scopeCovers2(dot(requirement.scope), dot(worker.data.assignment.scope))));
|
|
11716
|
-
if (reconciled)
|
|
11717
|
-
return {
|
|
11718
|
-
requirementId: requirement.id,
|
|
11719
|
-
status: "satisfied",
|
|
11720
|
-
evidenceIds: [],
|
|
11721
|
-
decisionIds: [],
|
|
11722
|
-
reason: "bounded helper completed and its result is recorded"
|
|
11723
|
-
};
|
|
11724
|
-
}
|
|
11725
|
-
const decisions = requirement.dimension === "decisions" ? applicableRequirementDecision(task, workspace, requirement, checkoutRoot) : [];
|
|
11726
|
-
if (decisions.length)
|
|
11727
|
-
return {
|
|
11728
|
-
requirementId: requirement.id,
|
|
11729
|
-
status: "satisfied",
|
|
11730
|
-
evidenceIds: [],
|
|
11731
|
-
decisionIds: decisions.map((decision) => decision.id),
|
|
11732
|
-
reason: "an applicable approved or stated decision satisfies the requirement"
|
|
11733
|
-
};
|
|
11734
|
-
const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
|
|
11735
|
-
if (limitations.length)
|
|
11736
|
-
return {
|
|
11737
|
-
requirementId: requirement.id,
|
|
11738
|
-
status: "accepted_limitation",
|
|
11739
|
-
evidenceIds: [],
|
|
11740
|
-
decisionIds: limitations.map((decision) => task.decisions.find((entry) => entry.data === decision).id),
|
|
11741
|
-
reason: "an applicable approved limitation permits the missing evidence"
|
|
11742
|
-
};
|
|
11743
|
-
const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
|
|
11744
|
-
if (!unavailable && related.length > 0 && !passed.length)
|
|
11745
|
-
return {
|
|
11746
|
-
requirementId: requirement.id,
|
|
11747
|
-
status: "unsatisfied",
|
|
11748
|
-
evidenceIds: related.map(({ entry }) => entry.id),
|
|
11749
|
-
decisionIds: [],
|
|
11750
|
-
reason: `no passing evidence (needs kind:${expectedKinds(requirement.dimension, requirement.ruleId)})`
|
|
11751
|
-
};
|
|
11752
|
-
return {
|
|
11753
|
-
requirementId: requirement.id,
|
|
11754
|
-
status: unavailable ? "unavailable" : "unsatisfied",
|
|
11755
|
-
evidenceIds: related.map(({ entry }) => entry.id),
|
|
11756
|
-
decisionIds: [],
|
|
11757
|
-
reason: unavailable ? "required capability is unavailable" : "no fresh passing evidence"
|
|
11758
|
-
};
|
|
11759
|
-
});
|
|
11775
|
+
const parsed = policySchema.safeParse(policy);
|
|
11776
|
+
if (!parsed.success)
|
|
11777
|
+
return failure("invalid_input", "resolved policy is invalid", invalidFields(parsed.error));
|
|
11778
|
+
return success(null, null, parsed.data);
|
|
11760
11779
|
}
|
|
11761
|
-
function
|
|
11762
|
-
if (
|
|
11763
|
-
|
|
11764
|
-
if (
|
|
11765
|
-
|
|
11766
|
-
const
|
|
11767
|
-
const
|
|
11768
|
-
|
|
11769
|
-
|
|
11770
|
-
if (
|
|
11771
|
-
|
|
11772
|
-
|
|
11773
|
-
|
|
11774
|
-
|
|
11775
|
-
|
|
11776
|
-
|
|
11777
|
-
|
|
11778
|
-
|
|
11779
|
-
|
|
11780
|
-
|
|
11781
|
-
|
|
11782
|
-
|
|
11783
|
-
|
|
11784
|
-
|
|
11785
|
-
|
|
11786
|
-
}
|
|
11787
|
-
if (entry.data.disposition === "deferred") {
|
|
11788
|
-
const valid = entry.data.resolution?.decisionIds.some((id) => {
|
|
11789
|
-
const decisionEntry = view.task.decisions.find((item) => item.id === id);
|
|
11790
|
-
const decision = decisionEntry?.data;
|
|
11791
|
-
return Boolean(decision && decisionEntry?.provenance.kind !== "imported" && decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.digest === decisionDigest(decision) && verifyDecisionContentAtRoot(view.workspace.root, decision.binding).ok && decision.binding.taskId === view.task.id && decision.binding.workspaceId === view.workspace.id && scopeCovers2(decision.binding.scope, entry.data.scope) && decision.requirementIds.some((requirementId) => view.task.policy?.requirements.some((requirement) => requirement.id === requirementId && requirement.acceptanceAllowed)));
|
|
11792
|
-
});
|
|
11793
|
-
if (!valid)
|
|
11794
|
-
return failure("requirements_unsatisfied", "deferred findings require an approved limitation decision that references an acceptanceAllowed requirement and covers the finding scope");
|
|
11795
|
-
}
|
|
11796
|
-
}
|
|
11797
|
-
}
|
|
11798
|
-
const blocking = evaluations.filter((item) => {
|
|
11799
|
-
if (item.status !== "unsatisfied" && item.status !== "unavailable")
|
|
11800
|
-
return false;
|
|
11801
|
-
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
11802
|
-
return !requirement || requirement.before === "close";
|
|
11803
|
-
});
|
|
11804
|
-
if (requestedOutcome !== "stopped" && blocking.length)
|
|
11805
|
-
return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
|
|
11806
|
-
requirementIds: blocking.map((item) => item.requirementId),
|
|
11807
|
-
requirements: blocking.map((item) => {
|
|
11808
|
-
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
11809
|
-
return {
|
|
11810
|
-
requirementId: item.requirementId,
|
|
11811
|
-
ruleId: requirement?.ruleId ?? "unknown",
|
|
11812
|
-
reason: item.reason,
|
|
11813
|
-
satisfaction: requirement?.satisfaction ?? "",
|
|
11814
|
-
dependentAction: requirement?.dependentAction ?? null
|
|
11815
|
-
};
|
|
11816
|
-
})
|
|
11817
|
-
});
|
|
11818
|
-
const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
|
|
11819
|
-
if (requestedOutcome === "verified" && accepted.length)
|
|
11820
|
-
return failure("requirements_unsatisfied", "accepted limitations cannot be reported as verified", {
|
|
11821
|
-
requirementIds: accepted.map((item) => item.requirementId)
|
|
11822
|
-
});
|
|
11823
|
-
const evidenceIds = [
|
|
11824
|
-
...new Set([
|
|
11825
|
-
...evaluations.flatMap((item) => item.evidenceIds),
|
|
11826
|
-
...view.task.findings.flatMap((item) => item.data.resolution?.evidenceIds ?? [])
|
|
11827
|
-
])
|
|
11828
|
-
];
|
|
11829
|
-
const decisionIds = [
|
|
11830
|
-
...new Set([
|
|
11831
|
-
...evaluations.flatMap((item) => item.decisionIds),
|
|
11832
|
-
...view.task.findings.flatMap((item) => item.data.resolution?.decisionIds ?? [])
|
|
11833
|
-
])
|
|
11834
|
-
];
|
|
11835
|
-
return success(null, null, {
|
|
11836
|
-
outcome: requestedOutcome === "stopped" ? "stopped" : accepted.length ? "accepted_limitations" : "verified",
|
|
11837
|
-
evidenceIds,
|
|
11838
|
-
decisionIds,
|
|
11839
|
-
requirementIds: evaluations.map((item) => item.requirementId)
|
|
11840
|
-
});
|
|
11780
|
+
function diffPolicy(previous, next, reason, now) {
|
|
11781
|
+
if (!policySchema.safeParse(previous).success && previous !== null)
|
|
11782
|
+
throw new TypeError("previous policy is invalid");
|
|
11783
|
+
if (!policySchema.safeParse(next).success)
|
|
11784
|
+
throw new TypeError("next policy is invalid");
|
|
11785
|
+
const oldIds = new Set(previous?.requirements.map((requirement) => requirement.id) ?? []);
|
|
11786
|
+
const newIds = new Set(next.requirements.map((requirement) => requirement.id));
|
|
11787
|
+
const added = [...newIds].filter((id) => !oldIds.has(id)).sort(compareCodeUnits2);
|
|
11788
|
+
const retired = [...oldIds].filter((id) => !newIds.has(id)).sort(compareCodeUnits2);
|
|
11789
|
+
if (added.length === 0 && retired.length === 0)
|
|
11790
|
+
return null;
|
|
11791
|
+
const normalizedReason = reason.trim();
|
|
11792
|
+
const changeReason = retired.length ? `${normalizedReason || "policy reassessed"} (retired: ${retired.join(",")})` : normalizedReason || "policy reassessed";
|
|
11793
|
+
const change = {
|
|
11794
|
+
recordedAt: now,
|
|
11795
|
+
fromInputDigest: previous?.inputDigest ?? null,
|
|
11796
|
+
toInputDigest: next.inputDigest,
|
|
11797
|
+
added,
|
|
11798
|
+
retired,
|
|
11799
|
+
reason: changeReason
|
|
11800
|
+
};
|
|
11801
|
+
const parsed = policyChangeSchema.safeParse(change);
|
|
11802
|
+
if (!parsed.success)
|
|
11803
|
+
throw new TypeError("policy change is invalid");
|
|
11804
|
+
return parsed.data;
|
|
11841
11805
|
}
|
|
11842
11806
|
|
|
11843
|
-
// packages/workit-core/src/core/
|
|
11844
|
-
|
|
11845
|
-
|
|
11846
|
-
|
|
11847
|
-
|
|
11848
|
-
|
|
11849
|
-
|
|
11850
|
-
|
|
11851
|
-
|
|
11852
|
-
|
|
11807
|
+
// packages/workit-core/src/core/task-evaluation.ts
|
|
11808
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
11809
|
+
import * as fs7 from "node:fs";
|
|
11810
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
11811
|
+
import path10 from "node:path";
|
|
11812
|
+
var digestBytes3 = (value) => createHash7("sha256").update(value).digest("hex");
|
|
11813
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`);
|
|
11814
|
+
var relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".";
|
|
11815
|
+
var canonicalPath = (value) => {
|
|
11816
|
+
if (typeof value !== "string" || !value)
|
|
11817
|
+
return null;
|
|
11818
|
+
if (value.includes("\\") || value.split("/").includes(".."))
|
|
11819
|
+
return null;
|
|
11820
|
+
const normalized = path10.posix.normalize(value);
|
|
11821
|
+
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
11822
|
+
return null;
|
|
11823
|
+
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
11853
11824
|
};
|
|
11854
|
-
var
|
|
11855
|
-
|
|
11856
|
-
const
|
|
11857
|
-
|
|
11858
|
-
|
|
11859
|
-
|
|
11860
|
-
|
|
11861
|
-
|
|
11862
|
-
|
|
11863
|
-
|
|
11864
|
-
|
|
11865
|
-
return {
|
|
11866
|
-
assurance: "unavailable",
|
|
11867
|
-
reasons: matches.filter((capability) => capability.assurance === "unavailable").map((capability) => capability.reason)
|
|
11868
|
-
};
|
|
11869
|
-
if (matches.some((capability) => capability.assurance === "enforced"))
|
|
11870
|
-
return { assurance: "enforced", reasons: matches.map((capability) => capability.reason) };
|
|
11871
|
-
return { assurance: "agent_guided", reasons: matches.map((capability) => capability.reason) };
|
|
11825
|
+
var canonicalScope = (scope) => {
|
|
11826
|
+
const paths = scope.paths.length ? scope.paths : ["."];
|
|
11827
|
+
const normalizedPaths = paths.map(canonicalPath);
|
|
11828
|
+
const normalizedExclusions = scope.exclusions.map(canonicalPath);
|
|
11829
|
+
if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
|
|
11830
|
+
return null;
|
|
11831
|
+
return {
|
|
11832
|
+
description: scope.description,
|
|
11833
|
+
paths: [...new Set(normalizedPaths)],
|
|
11834
|
+
exclusions: [...new Set(normalizedExclusions)]
|
|
11835
|
+
};
|
|
11872
11836
|
};
|
|
11873
|
-
|
|
11874
|
-
|
|
11875
|
-
|
|
11876
|
-
|
|
11877
|
-
|
|
11878
|
-
|
|
11879
|
-
|
|
11880
|
-
...new Set(requirements.map((requirement) => requirement.reason)),
|
|
11881
|
-
...capability.reasons
|
|
11882
|
-
];
|
|
11883
|
-
if (capability.assurance === "unavailable")
|
|
11884
|
-
reasons.unshift("required capability is unavailable");
|
|
11885
|
-
return [{ id, reason: reasons.join("; "), assurance: capability.assurance }];
|
|
11886
|
-
});
|
|
11887
|
-
}
|
|
11888
|
-
var invariantBootstrap = () => `
|
|
11889
|
-
Workit is optional coordination and repository-policy tooling around the host,
|
|
11890
|
-
not a permission system or a mandatory workflow. Native host allow/ask/deny,
|
|
11891
|
-
sandbox, plan/read-only mode, and organization rules remain authoritative.
|
|
11892
|
-
Ordinary investigation, questions, non-Git work, and routine reversible edits
|
|
11893
|
-
need zero Workit task, assessment, or writer calls. Use one compact tracked
|
|
11894
|
-
record only when handoff, dependent steps, concurrent actors, or meaningful
|
|
11895
|
-
decisions make continuity useful; similar titles alone never merge tasks.
|
|
11896
|
-
|
|
11897
|
-
For a routine user-authorized branch or commit, prefer native host Git/shell
|
|
11898
|
-
tools from the outset when managed coordination or outcome reconciliation is
|
|
11899
|
-
not needed. Inspect the actual target checkout and its configured conventions;
|
|
11900
|
-
use Workit's read-only context/policy tools when needed. Do not start a task,
|
|
11901
|
-
acquire a writer, or mint a decision receipt solely for a native Git action.
|
|
11902
|
-
Native permissions still apply. Never switch execution paths to evade a denial
|
|
11903
|
-
or repeat an uncertain managed effect. A local-commit endpoint does not imply
|
|
11904
|
-
PR readiness, a fresh review, or post-commit assessment/closure paperwork.
|
|
11905
|
-
|
|
11906
|
-
For tracked work, inspect its current state and use the shared operations for
|
|
11907
|
-
task, policy, evidence, finding, decision, worker, writer, and state changes.
|
|
11908
|
-
Start a record once for an explicit tracked objective; assess or reassess only
|
|
11909
|
-
when policy selection or changed evidence/constraints requires it. Omitted
|
|
11910
|
-
expectedRevision and expectedWorkspaceRevision use current values; explicit
|
|
11911
|
-
values are still concurrency-checked, so never copy revisions between calls.
|
|
11912
|
-
A solo edit does not need writer acquisition; use it when concurrent checkout
|
|
11913
|
-
writers need coordination. Record only observed facts and checks. Evidence can
|
|
11914
|
-
become stale when its bound candidate changes; reconcile findings against the
|
|
11915
|
-
current candidate before recording completion.
|
|
11916
|
-
|
|
11917
|
-
Workit validates domain policy against the actual action target, configured
|
|
11918
|
-
account, branch and commit conventions, protected refs, and current repository
|
|
11919
|
-
state. The task directory is coordination state, not a boundary on which
|
|
11920
|
-
repository may be changed. Use an action-time cwd target where a managed
|
|
11921
|
-
Git/hosting action supports it; a non-Git directory remains valid for OS work.
|
|
11922
|
-
GitHub/GitLab use the active gh/glab CLI identity; YouTrack uses its own token.
|
|
11923
|
-
Preserve uncertain external outcomes and reconcile repository/provider state
|
|
11924
|
-
before retrying. Internal reservations prevent duplicate or ambiguous effects;
|
|
11925
|
-
they are not permission tickets for every edit.
|
|
11926
|
-
|
|
11927
|
-
On OpenCode V1 and V2, use native host tools for external mutations. Workit
|
|
11928
|
-
provides read-only workit_context; it has no managed external-action executor.
|
|
11929
|
-
Do not create proposals or request Workit approvals merely to invoke native
|
|
11930
|
-
tools. Old uncertain managed effects still require evidence before any retry.
|
|
11931
|
-
|
|
11932
|
-
Use host-native authorization through the host's supported path. A native
|
|
11933
|
-
question receipt records an actual question interaction; it is not automatic
|
|
11934
|
-
host permission. Record a meaningful user choice once with provenance when
|
|
11935
|
-
future retrieval helps. Never fabricate a receipt, re-ask only to mint one, or
|
|
11936
|
-
let imported decisions grant authority. Never turn a host deny into allow or
|
|
11937
|
-
claim enforcement/evidence a host cannot provide. A precise request with settled
|
|
11938
|
-
constraints does not need an interview. Present genuine unresolved options with
|
|
11939
|
-
evidence and a recommendation, then continue toward the requested delivery
|
|
11940
|
-
endpoint: investigation, implementation, branch/commit/push, PR/MR-ready,
|
|
11941
|
-
merge, or release. Run
|
|
11942
|
-
applicable checks and safe repairs without repeated continuation questions;
|
|
11943
|
-
stop for missing host authority, a new consequential choice, a conflicting edit,
|
|
11944
|
-
or an unresolved blocker. A tracked record may close when actual evidence
|
|
11945
|
-
supports its outcome; no final human closure ceremony is required.
|
|
11946
|
-
|
|
11947
|
-
A completed native subagent run stops its bound worker by itself; if a worker
|
|
11948
|
-
strands in cancelling with its run verifiably over, repeat worker.cancel to
|
|
11949
|
-
confirm the stop. Preserve unresolved requirements, gaps, and uncertain workers.
|
|
11950
|
-
When context changes, distinguish a quick question, same-task adjustment, and
|
|
11951
|
-
separate request. Answer a quick question without pausing/resuming task state;
|
|
11952
|
-
park a concise checkpoint only when substantial work needs to continue later.
|
|
11953
|
-
For work spanning repositories, bind each unfinished item to its actual checkout,
|
|
11954
|
-
branch, requested deliverables, and delivery endpoint. Preserve held items with
|
|
11955
|
-
an explicit resume condition. Resolve competing plausible targets before a
|
|
11956
|
-
mutation; ask only when available context cannot settle that consequential choice.
|
|
11957
|
-
Before reporting delivery, reconcile every requested item and observe the actual
|
|
11958
|
-
target result; a local commit alone is not evidence of a requested remote push.
|
|
11959
|
-
|
|
11960
|
-
Skill routing: slash aliases /wk-* load on demand. Use workit-steer for a
|
|
11961
|
-
substantial interruption or change of direction, workit-deslop when relevant
|
|
11962
|
-
to a PR-ready endpoint, workit-green-run for failing CI, workit-blast-radius
|
|
11963
|
-
when impact is uncertain, and workit-challenge for genuinely open consequential
|
|
11964
|
-
choices. Load workit-plan when dependencies or handoff need durable next actions.
|
|
11965
|
-
Load the skill; never act from memory of it.
|
|
11966
|
-
`.trim();
|
|
11967
|
-
|
|
11968
|
-
// packages/workit-core/src/core/task-context.ts
|
|
11969
|
-
function reconcileResume(view, observations = []) {
|
|
11970
|
-
if (observations.length > 0)
|
|
11971
|
-
return failure("permission_denied", "worker observations require native host verification");
|
|
11972
|
-
const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
|
|
11973
|
-
if (!candidate.ok)
|
|
11974
|
-
return candidate;
|
|
11975
|
-
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
11976
|
-
const blockers = [...view.task.progress.blockers];
|
|
11977
|
-
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
11978
|
-
blockers.push({
|
|
11979
|
-
reason: "worker state requires reconciliation",
|
|
11980
|
-
dependentAction: "resume",
|
|
11981
|
-
refs: []
|
|
11982
|
-
});
|
|
11983
|
-
return success(null, null, {
|
|
11984
|
-
candidate: candidate.data,
|
|
11985
|
-
staleEvidenceIds,
|
|
11986
|
-
workerUpdates: [],
|
|
11987
|
-
blockers,
|
|
11988
|
-
reassessmentRequired: view.task.policy?.policyVersion !== POLICY_VERSION
|
|
11989
|
-
});
|
|
11990
|
-
}
|
|
11991
|
-
var COMPACT_MAX_BYTES = 4096;
|
|
11992
|
-
var COMPACT_MAX_ITEMS = 8;
|
|
11993
|
-
var COMPACT_TEXT_BYTES = 128;
|
|
11994
|
-
var sensitiveUrl = /\bhttps?:\/\/[^/\s:@]+:[^@\s]+@/gi;
|
|
11995
|
-
var sensitiveValue = /(?:token|secret|password|passwd|api[-_ ]?key|private[-_ ]?key|authorization)\s*[:=]\s*(?:bearer\s+)?\S+|\bbearer\s+\S+/gi;
|
|
11996
|
-
var privateKey = /-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/gi;
|
|
11997
|
-
var compactText = (value, limit) => value === null ? null : value.replace(privateKey, "<redacted-private-key>").replace(sensitiveUrl, "<redacted-url>").replace(sensitiveValue, (match) => {
|
|
11998
|
-
const separator = match.search(/[:=]/u);
|
|
11999
|
-
if (separator >= 0)
|
|
12000
|
-
return `${match.slice(0, separator)}=<redacted>`;
|
|
12001
|
-
const space = match.search(/\s/u);
|
|
12002
|
-
return `${match.slice(0, space)} <redacted>`;
|
|
12003
|
-
}).slice(0, limit);
|
|
12004
|
-
var compactBytes = (value) => new TextEncoder().encode(value).byteLength;
|
|
12005
|
-
var compactTimestamp = (value) => `${value.slice(0, 19)}.${value.slice(20, -1).padEnd(9, "0")}`;
|
|
12006
|
-
var compactDecisionChoice = (entry) => {
|
|
12007
|
-
const stated = entry.data.binding.statedChoice?.text;
|
|
12008
|
-
const approved = entry.data.response === "approved" ? entry.data.binding.approvedContent : "";
|
|
12009
|
-
const source = stated?.trim() || approved.trim() || null;
|
|
12010
|
-
return compactText(source, COMPACT_TEXT_BYTES);
|
|
11837
|
+
var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
|
|
11838
|
+
var scopeMatches = (value, scope) => {
|
|
11839
|
+
const normalized = canonicalScope(scope);
|
|
11840
|
+
if (!normalized)
|
|
11841
|
+
return false;
|
|
11842
|
+
const paths = normalized.paths;
|
|
11843
|
+
return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
|
|
12011
11844
|
};
|
|
12012
|
-
var
|
|
12013
|
-
|
|
12014
|
-
|
|
12015
|
-
|
|
12016
|
-
|
|
12017
|
-
|
|
12018
|
-
|
|
11845
|
+
var pathRelevant = (value, scope) => scopeMatches(value, scope);
|
|
11846
|
+
var compareCodeUnits3 = (left, right) => {
|
|
11847
|
+
const length = Math.min(left.length, right.length);
|
|
11848
|
+
for (let index = 0;index < length; index += 1) {
|
|
11849
|
+
const difference = left.charCodeAt(index) - right.charCodeAt(index);
|
|
11850
|
+
if (difference !== 0)
|
|
11851
|
+
return difference;
|
|
11852
|
+
}
|
|
11853
|
+
return left.length - right.length;
|
|
11854
|
+
};
|
|
11855
|
+
var gitPaths = (root) => {
|
|
11856
|
+
const result = spawnSync2("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
11857
|
+
cwd: root,
|
|
11858
|
+
encoding: "buffer"
|
|
11859
|
+
});
|
|
11860
|
+
if (result.status !== 0) {
|
|
11861
|
+
const stderr = result.stderr?.toString("utf8") ?? "";
|
|
12019
11862
|
return {
|
|
12020
|
-
|
|
12021
|
-
|
|
12022
|
-
|
|
11863
|
+
paths: [],
|
|
11864
|
+
uncertain: !/not a git repository/i.test(stderr),
|
|
11865
|
+
git: /not a git repository/i.test(stderr) ? false : true
|
|
12023
11866
|
};
|
|
12024
|
-
return { kind: ref.kind };
|
|
12025
|
-
};
|
|
12026
|
-
function compactTaskContext(view) {
|
|
12027
|
-
const decisions = view.task.decisions.slice().sort((left, right) => {
|
|
12028
|
-
const leftAt = compactTimestamp(left.recordedAt);
|
|
12029
|
-
const rightAt = compactTimestamp(right.recordedAt);
|
|
12030
|
-
if (leftAt !== rightAt)
|
|
12031
|
-
return leftAt > rightAt ? -1 : 1;
|
|
12032
|
-
return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
|
|
12033
|
-
}).slice(0, COMPACT_MAX_ITEMS).map((entry) => ({
|
|
12034
|
-
id: entry.id,
|
|
12035
|
-
purpose: entry.data.purpose,
|
|
12036
|
-
status: entry.data.response,
|
|
12037
|
-
digest: entry.data.digest,
|
|
12038
|
-
choice: compactDecisionChoice(entry) || `task.inspect taskId=${view.task.id} view=full; decision=${entry.id}`,
|
|
12039
|
-
references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference)
|
|
12040
|
-
}));
|
|
12041
|
-
const methods = view.task.policy ? selectMethods(view.task.policy, view.capabilities).map((method) => ({
|
|
12042
|
-
id: method.id,
|
|
12043
|
-
assurance: method.assurance,
|
|
12044
|
-
reason: compactText(method.reason, COMPACT_TEXT_BYTES) ?? ""
|
|
12045
|
-
})) : [];
|
|
12046
|
-
const gaps = Array.from(new Set([
|
|
12047
|
-
...view.evidence.filter((entry) => entry.status === "stale").map((entry) => `stale evidence: ${entry.evidenceId}`),
|
|
12048
|
-
...view.requirements.filter((entry) => entry.status !== "satisfied").map((entry) => entry.reason),
|
|
12049
|
-
...view.task.progress.blockers.map((entry) => entry.reason)
|
|
12050
|
-
])).sort().slice(0, COMPACT_MAX_ITEMS).map((gap) => compactText(gap, COMPACT_TEXT_BYTES));
|
|
12051
|
-
const context = {
|
|
12052
|
-
objective: compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "",
|
|
12053
|
-
status: view.task.status,
|
|
12054
|
-
decisions,
|
|
12055
|
-
methods,
|
|
12056
|
-
gaps,
|
|
12057
|
-
nextAction: compactText(view.task.progress.nextAction, COMPACT_TEXT_BYTES)
|
|
12058
|
-
};
|
|
12059
|
-
let encoded = JSON.stringify(context);
|
|
12060
|
-
while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.methods.length || context.gaps.length)) {
|
|
12061
|
-
if (context.gaps.length)
|
|
12062
|
-
context.gaps.pop();
|
|
12063
|
-
else if (context.decisions.length)
|
|
12064
|
-
context.decisions.pop();
|
|
12065
|
-
else
|
|
12066
|
-
context.methods.pop();
|
|
12067
|
-
encoded = JSON.stringify(context);
|
|
12068
11867
|
}
|
|
12069
|
-
|
|
12070
|
-
|
|
12071
|
-
|
|
12072
|
-
|
|
11868
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
|
|
11869
|
+
const stagedDeleted = spawnSync2("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
11870
|
+
if (stagedDeleted.status !== 0) {
|
|
11871
|
+
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
11872
|
+
if (!/not a git repository/i.test(stderr))
|
|
11873
|
+
return { paths, uncertain: true, git: true };
|
|
11874
|
+
} else if (stagedDeleted.stdout) {
|
|
11875
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")));
|
|
12073
11876
|
}
|
|
12074
|
-
return
|
|
12075
|
-
}
|
|
12076
|
-
var exportDigest = (bundle) => sha256({
|
|
12077
|
-
schemaVersion: bundle.schemaVersion,
|
|
12078
|
-
exportedAt: bundle.exportedAt,
|
|
12079
|
-
sourceWorkspaceId: bundle.sourceWorkspaceId,
|
|
12080
|
-
task: bundle.task
|
|
12081
|
-
});
|
|
12082
|
-
// packages/workit-core/src/core/route-intent.ts
|
|
12083
|
-
var shellBranchTarget = (command) => {
|
|
12084
|
-
if (!command.trim() || /[\r\n;&|<>`'"$()\\*?{}~!#]/u.test(command) || command.includes("[") || command.includes("]"))
|
|
12085
|
-
return null;
|
|
12086
|
-
const args = command.trim().split(/\s+/u);
|
|
12087
|
-
if (args[0] !== "git")
|
|
12088
|
-
return null;
|
|
12089
|
-
const option = args[2];
|
|
12090
|
-
const target = args[1] === "switch" && ["-c", "--create", "-C", "--force-create"].includes(option ?? "") && (args.length === 4 || args.length === 5) ? args[3] : args[1] === "checkout" && ["-b", "-B"].includes(option ?? "") && (args.length === 4 || args.length === 5) ? args[3] : args[1] === "branch" && (args.length === 3 || args.length === 4) ? args[2] : null;
|
|
12091
|
-
return target && !target.startsWith("-") ? target : null;
|
|
12092
|
-
};
|
|
12093
|
-
var shellBranchPolicyViolation = (root, command) => {
|
|
12094
|
-
const target = shellBranchTarget(command);
|
|
12095
|
-
return target === null ? null : validateBranchNameFor(root, target);
|
|
11877
|
+
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
12096
11878
|
};
|
|
12097
|
-
|
|
12098
|
-
|
|
12099
|
-
|
|
12100
|
-
|
|
12101
|
-
|
|
12102
|
-
|
|
12103
|
-
var externalActionSchema = discriminatedUnion("operation", [
|
|
12104
|
-
object({
|
|
12105
|
-
operation: literal("git.branch_setup"),
|
|
12106
|
-
payload: object({
|
|
12107
|
-
action: _enum(["setup", "reapply_stash"]).optional(),
|
|
12108
|
-
sdd_dir: string2().optional(),
|
|
12109
|
-
target_branch: string2().optional(),
|
|
12110
|
-
stash: _enum(["yes", "no"]).optional(),
|
|
12111
|
-
cwd: string2().min(1).optional()
|
|
12112
|
-
}).strict()
|
|
12113
|
-
}).strict(),
|
|
12114
|
-
object({
|
|
12115
|
-
operation: literal("git.commit"),
|
|
12116
|
-
payload: object({
|
|
12117
|
-
message: string2().min(1).optional(),
|
|
12118
|
-
plan_steps: array(chainStepSchema).min(1).max(32).optional(),
|
|
12119
|
-
plan_branch: string2().min(1).optional(),
|
|
12120
|
-
cwd: string2().min(1).optional()
|
|
12121
|
-
}).strict()
|
|
12122
|
-
}).strict(),
|
|
12123
|
-
object({
|
|
12124
|
-
operation: literal("git.push"),
|
|
12125
|
-
payload: object({ branch: string2().min(1).optional(), cwd: string2().min(1).optional() }).strict()
|
|
12126
|
-
}).strict(),
|
|
12127
|
-
object({
|
|
12128
|
-
operation: literal("hosting.pull_request"),
|
|
12129
|
-
payload: object({
|
|
12130
|
-
title: string2().min(1),
|
|
12131
|
-
body: string2().optional(),
|
|
12132
|
-
draft: boolean2().optional(),
|
|
12133
|
-
target_branch: string2().optional(),
|
|
12134
|
-
babysit: boolean2().optional(),
|
|
12135
|
-
cwd: string2().min(1).optional()
|
|
12136
|
-
}).strict()
|
|
12137
|
-
}).strict(),
|
|
12138
|
-
object({
|
|
12139
|
-
operation: literal("hosting.merge"),
|
|
12140
|
-
payload: object({
|
|
12141
|
-
target_branch: string2().min(1).optional(),
|
|
12142
|
-
source_branch: string2().min(1).optional(),
|
|
12143
|
-
cwd: string2().min(1).optional()
|
|
12144
|
-
}).strict()
|
|
12145
|
-
}).strict(),
|
|
12146
|
-
object({
|
|
12147
|
-
operation: literal("hosting.delete_branch"),
|
|
12148
|
-
payload: object({ branch: string2().min(1), cwd: string2().min(1).optional() }).strict()
|
|
12149
|
-
}).strict(),
|
|
12150
|
-
object({
|
|
12151
|
-
operation: literal("youtrack.update"),
|
|
12152
|
-
payload: object({
|
|
12153
|
-
issueId: string2().min(1),
|
|
12154
|
-
markdown: string2().min(1),
|
|
12155
|
-
minutes: number2().positive().optional()
|
|
12156
|
-
}).strict()
|
|
12157
|
-
}).strict(),
|
|
12158
|
-
object({
|
|
12159
|
-
operation: literal("youtrack.time"),
|
|
12160
|
-
payload: object({
|
|
12161
|
-
issueId: string2().min(1),
|
|
12162
|
-
minutes: number2().positive(),
|
|
12163
|
-
text: string2().optional(),
|
|
12164
|
-
dateMs: number2().finite().optional()
|
|
12165
|
-
}).strict()
|
|
12166
|
-
}).strict(),
|
|
12167
|
-
object({
|
|
12168
|
-
operation: literal("youtrack.meeting"),
|
|
12169
|
-
payload: object({
|
|
12170
|
-
issueId: string2().min(1),
|
|
12171
|
-
minutes: number2().positive(),
|
|
12172
|
-
text: string2().min(1)
|
|
12173
|
-
}).strict()
|
|
12174
|
-
}).strict(),
|
|
12175
|
-
object({
|
|
12176
|
-
operation: literal("changelog.apply"),
|
|
12177
|
-
payload: object({
|
|
12178
|
-
entries: union([
|
|
12179
|
-
record(string2(), array(string2())),
|
|
12180
|
-
array(object({ category: string2().min(1), text: string2().min(1) }).strict())
|
|
12181
|
-
]).optional(),
|
|
12182
|
-
path: string2().optional(),
|
|
12183
|
-
normalize_only: boolean2().optional()
|
|
12184
|
-
}).strict()
|
|
12185
|
-
}).strict(),
|
|
12186
|
-
object({
|
|
12187
|
-
operation: literal("context.read"),
|
|
12188
|
-
payload: object({
|
|
12189
|
-
kind: _enum([
|
|
12190
|
-
"git",
|
|
12191
|
-
"pr",
|
|
12192
|
-
"youtrack",
|
|
12193
|
-
"github_issue",
|
|
12194
|
-
"gitlab_issue",
|
|
12195
|
-
"changelog",
|
|
12196
|
-
"release",
|
|
12197
|
-
"affected"
|
|
12198
|
-
]),
|
|
12199
|
-
range: string2().optional(),
|
|
12200
|
-
issueId: string2().optional(),
|
|
12201
|
-
issueUrl: string2().optional(),
|
|
12202
|
-
issueRef: string2().optional(),
|
|
12203
|
-
mode: string2().optional(),
|
|
12204
|
-
specPath: string2().optional(),
|
|
12205
|
-
planPath: string2().optional(),
|
|
12206
|
-
cwd: string2().min(1).optional()
|
|
12207
|
-
}).strict()
|
|
12208
|
-
}).strict()
|
|
12209
|
-
]);
|
|
12210
|
-
// packages/workit-core/src/core/task-engine.ts
|
|
12211
|
-
import { realpathSync as realpathSync6 } from "node:fs";
|
|
12212
|
-
|
|
12213
|
-
// packages/workit-core/src/core/policy-resolver.ts
|
|
12214
|
-
var resolverInputSchema = object({
|
|
12215
|
-
intent: intentSchema,
|
|
12216
|
-
assessment: assessmentSchema,
|
|
12217
|
-
constraints: array(constraintSchema),
|
|
12218
|
-
prior: object({
|
|
12219
|
-
decisions: array(decisionSchema),
|
|
12220
|
-
findings: array(findingSchema),
|
|
12221
|
-
requirements: array(requirementSchema)
|
|
12222
|
-
}).strict(),
|
|
12223
|
-
preferences: object({ fast: boolean2().optional(), thorough: boolean2().optional() }).strict().optional()
|
|
12224
|
-
}).strict();
|
|
12225
|
-
var compareCodeUnits3 = (left, right) => {
|
|
12226
|
-
const length = Math.min(left.length, right.length);
|
|
12227
|
-
for (let index = 0;index < length; index += 1) {
|
|
12228
|
-
const difference = left.charCodeAt(index) - right.charCodeAt(index);
|
|
12229
|
-
if (difference !== 0)
|
|
12230
|
-
return difference;
|
|
11879
|
+
var walk = (root, directory, output) => {
|
|
11880
|
+
let names;
|
|
11881
|
+
try {
|
|
11882
|
+
names = fs7.readdirSync(directory);
|
|
11883
|
+
} catch {
|
|
11884
|
+
return true;
|
|
12231
11885
|
}
|
|
12232
|
-
|
|
11886
|
+
let uncertain = false;
|
|
11887
|
+
for (const name of names) {
|
|
11888
|
+
if (name === ".git" || name === ".workit")
|
|
11889
|
+
continue;
|
|
11890
|
+
const target = path10.join(directory, name);
|
|
11891
|
+
let stat;
|
|
11892
|
+
try {
|
|
11893
|
+
stat = fs7.lstatSync(target);
|
|
11894
|
+
} catch {
|
|
11895
|
+
uncertain = true;
|
|
11896
|
+
continue;
|
|
11897
|
+
}
|
|
11898
|
+
const item = relative(root, target);
|
|
11899
|
+
output.add(item);
|
|
11900
|
+
if (stat.isDirectory() && !stat.isSymbolicLink())
|
|
11901
|
+
uncertain = walk(root, target, output) || uncertain;
|
|
11902
|
+
}
|
|
11903
|
+
return uncertain;
|
|
12233
11904
|
};
|
|
12234
|
-
var
|
|
12235
|
-
|
|
12236
|
-
|
|
12237
|
-
|
|
12238
|
-
|
|
12239
|
-
|
|
12240
|
-
|
|
12241
|
-
|
|
12242
|
-
|
|
12243
|
-
|
|
12244
|
-
|
|
11905
|
+
var scopeRoots = (root, scope) => {
|
|
11906
|
+
const roots = [];
|
|
11907
|
+
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
11908
|
+
const target = path10.resolve(root, item);
|
|
11909
|
+
if (!inside(root, target))
|
|
11910
|
+
return failure("invalid_input", `candidate scope escapes checkout: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} is outside the checkout` }] });
|
|
11911
|
+
let ancestor = target;
|
|
11912
|
+
while (!fs7.existsSync(ancestor) && ancestor !== root)
|
|
11913
|
+
ancestor = path10.dirname(ancestor);
|
|
11914
|
+
try {
|
|
11915
|
+
if (!inside(root, fs7.realpathSync(ancestor)))
|
|
11916
|
+
return failure("invalid_input", `candidate scope escapes checkout through a symlink: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} resolves outside the checkout` }] });
|
|
11917
|
+
} catch {
|
|
11918
|
+
return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
11919
|
+
}
|
|
11920
|
+
roots.push(target);
|
|
11921
|
+
}
|
|
11922
|
+
return success(null, null, roots);
|
|
11923
|
+
};
|
|
11924
|
+
function captureCandidate(root, scope, environment = []) {
|
|
11925
|
+
let checkout;
|
|
11926
|
+
try {
|
|
11927
|
+
checkout = fs7.realpathSync(root);
|
|
11928
|
+
} catch {
|
|
11929
|
+
return failure("invalid_input", "candidate root does not exist");
|
|
11930
|
+
}
|
|
11931
|
+
const normalizedScope = canonicalScope(scope);
|
|
11932
|
+
if (!normalizedScope)
|
|
11933
|
+
return failure("invalid_input", "candidate scope is invalid: paths must be checkout-relative — to work in another repository, keep this task here and coordinate a separate linked task in that checkout", { fields: [{ path: "scope.paths", reason: "must be checkout-relative" }] });
|
|
11934
|
+
const roots = scopeRoots(checkout, normalizedScope);
|
|
11935
|
+
if (!roots.ok)
|
|
11936
|
+
return roots;
|
|
11937
|
+
const git = gitPaths(checkout);
|
|
11938
|
+
const names = new Set;
|
|
11939
|
+
let uncertain = git.uncertain;
|
|
11940
|
+
for (const target of roots.data) {
|
|
11941
|
+
const item = relative(checkout, target);
|
|
11942
|
+
if (item === ".git" || item === ".workit")
|
|
11943
|
+
continue;
|
|
11944
|
+
names.add(item);
|
|
11945
|
+
if (!fs7.existsSync(target))
|
|
11946
|
+
continue;
|
|
11947
|
+
let stat;
|
|
11948
|
+
try {
|
|
11949
|
+
stat = fs7.lstatSync(target);
|
|
11950
|
+
} catch {
|
|
11951
|
+
uncertain = true;
|
|
11952
|
+
continue;
|
|
11953
|
+
}
|
|
11954
|
+
if (!git.git && stat.isDirectory() && !stat.isSymbolicLink())
|
|
11955
|
+
uncertain = walk(checkout, target, names) || uncertain;
|
|
11956
|
+
}
|
|
11957
|
+
for (const item of git.paths)
|
|
11958
|
+
if (scopeMatches(item, normalizedScope))
|
|
11959
|
+
names.add(item);
|
|
11960
|
+
const files = [];
|
|
11961
|
+
let completeness = uncertain ? "uncertain" : "known";
|
|
11962
|
+
for (const item of [...names].sort()) {
|
|
11963
|
+
if (!scopeMatches(item, normalizedScope))
|
|
11964
|
+
continue;
|
|
11965
|
+
const target = path10.join(checkout, item);
|
|
11966
|
+
try {
|
|
11967
|
+
const stat = fs7.lstatSync(target);
|
|
11968
|
+
if (stat.isSymbolicLink()) {
|
|
11969
|
+
const link = fs7.readlinkSync(target);
|
|
11970
|
+
files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
|
|
11971
|
+
} else if (stat.isFile()) {
|
|
11972
|
+
files.push({
|
|
11973
|
+
path: item,
|
|
11974
|
+
kind: "file",
|
|
11975
|
+
digest: digestBytes3(fs7.readFileSync(target)),
|
|
11976
|
+
executable: (stat.mode & 73) !== 0
|
|
11977
|
+
});
|
|
11978
|
+
} else if (!stat.isDirectory()) {
|
|
11979
|
+
completeness = "uncertain";
|
|
11980
|
+
} else if (git.git && git.paths.includes(item)) {
|
|
11981
|
+
completeness = "uncertain";
|
|
11982
|
+
}
|
|
11983
|
+
} catch (error) {
|
|
11984
|
+
if (error?.code === "ENOENT") {
|
|
11985
|
+
files.push({ path: item, kind: "absent", digest: null, executable: null });
|
|
11986
|
+
} else
|
|
11987
|
+
completeness = "uncertain";
|
|
11988
|
+
}
|
|
11989
|
+
}
|
|
11990
|
+
const supplied = Array.isArray(environment) ? environment.map((value) => typeof value === "string" ? { name: value, value: process.env[value] ?? null } : { name: value.name, value: value.value ?? null }) : Object.keys(environment).map((name) => ({
|
|
12245
11991
|
name,
|
|
12246
|
-
|
|
12247
|
-
|
|
12248
|
-
|
|
12249
|
-
|
|
12250
|
-
|
|
12251
|
-
})))
|
|
12252
|
-
|
|
12253
|
-
|
|
12254
|
-
|
|
12255
|
-
|
|
12256
|
-
|
|
12257
|
-
|
|
12258
|
-
|
|
12259
|
-
|
|
12260
|
-
|
|
12261
|
-
|
|
12262
|
-
|
|
12263
|
-
|
|
12264
|
-
|
|
12265
|
-
refs: normalizedRefList(requirement.refs)
|
|
12266
|
-
})))
|
|
12267
|
-
});
|
|
12268
|
-
var normalizeDecision = (decision) => ({
|
|
12269
|
-
...decision,
|
|
12270
|
-
binding: {
|
|
12271
|
-
...decision.binding,
|
|
12272
|
-
scope: normalizedScope(decision.binding.scope),
|
|
12273
|
-
contentRefs: normalizedRefList(decision.binding.contentRefs)
|
|
12274
|
-
},
|
|
12275
|
-
requirementIds: stableUnique(decision.requirementIds)
|
|
12276
|
-
});
|
|
12277
|
-
var normalizeIntent = (intent) => ({
|
|
12278
|
-
...intent,
|
|
12279
|
-
scope: normalizedScope(intent.scope),
|
|
12280
|
-
authorityRefs: normalizedRefList(intent.authorityRefs)
|
|
12281
|
-
});
|
|
12282
|
-
var normalizePrior = (prior) => ({
|
|
12283
|
-
decisions: stableList(prior.decisions.map(normalizeDecision)),
|
|
12284
|
-
findings: stableList(prior.findings.map((finding) => ({
|
|
12285
|
-
...finding,
|
|
12286
|
-
scope: normalizedScope(finding.scope),
|
|
12287
|
-
refs: normalizedRefList(finding.refs),
|
|
12288
|
-
resolution: finding.resolution ? {
|
|
12289
|
-
...finding.resolution,
|
|
12290
|
-
evidenceIds: stableUnique(finding.resolution.evidenceIds),
|
|
12291
|
-
decisionIds: stableUnique(finding.resolution.decisionIds)
|
|
12292
|
-
} : null
|
|
12293
|
-
}))),
|
|
12294
|
-
requirements: stableList(prior.requirements.map((requirement) => ({
|
|
12295
|
-
...requirement,
|
|
12296
|
-
scope: normalizedScope(requirement.scope)
|
|
12297
|
-
})))
|
|
12298
|
-
});
|
|
12299
|
-
var invalidFields = (error) => ({
|
|
12300
|
-
fields: error.issues.map((issue) => ({
|
|
12301
|
-
path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
|
|
12302
|
-
reason: issue.message
|
|
12303
|
-
}))
|
|
12304
|
-
});
|
|
12305
|
-
function normalizeResolverInput(input) {
|
|
12306
|
-
const parsed = resolverInputSchema.safeParse(input);
|
|
12307
|
-
if (!parsed.success)
|
|
12308
|
-
return failure("invalid_input", "resolver input is invalid", invalidFields(parsed.error));
|
|
12309
|
-
const value = parsed.data;
|
|
12310
|
-
const signals = value.assessment.signals;
|
|
12311
|
-
if (signals.behaviorChange.value === true && signals.mechanicalLowRisk.value === true)
|
|
12312
|
-
return failure("invalid_input", "behavioral change cannot be wholly mechanical", {
|
|
12313
|
-
fields: [
|
|
12314
|
-
{
|
|
12315
|
-
path: "assessment.signals",
|
|
12316
|
-
reason: "contradictory behaviorChange and mechanicalLowRisk"
|
|
12317
|
-
}
|
|
12318
|
-
]
|
|
12319
|
-
});
|
|
12320
|
-
const ids = value.constraints.map((constraint) => constraint.id);
|
|
12321
|
-
if (new Set(ids).size !== ids.length)
|
|
12322
|
-
return failure("invalid_input", "constraint IDs must be unique", {
|
|
12323
|
-
fields: [{ path: "constraints", reason: "duplicate constraint id" }]
|
|
12324
|
-
});
|
|
12325
|
-
if (value.preferences?.fast && value.preferences.thorough)
|
|
12326
|
-
return failure("invalid_input", "fast and thorough preferences contradict", {
|
|
12327
|
-
fields: [{ path: "preferences", reason: "fast and thorough cannot both be enabled" }]
|
|
12328
|
-
});
|
|
12329
|
-
const normalized = {
|
|
12330
|
-
intent: normalizeIntent(value.intent),
|
|
12331
|
-
assessment: normalizeAssessment(value.assessment),
|
|
12332
|
-
constraints: stableList(value.constraints.map(normalizeConstraint)),
|
|
12333
|
-
prior: normalizePrior(value.prior),
|
|
12334
|
-
...value.preferences ? { preferences: { ...value.preferences } } : {},
|
|
12335
|
-
policyVersion: POLICY_VERSION
|
|
11992
|
+
value: environment[name] ?? null
|
|
11993
|
+
}));
|
|
11994
|
+
const namesByEnvironment = supplied.map(({ name }) => name);
|
|
11995
|
+
if (new Set(namesByEnvironment).size !== namesByEnvironment.length || namesByEnvironment.some((name) => typeof name !== "string" || !name))
|
|
11996
|
+
return failure("invalid_input", "candidate environment names must be unique and non-empty");
|
|
11997
|
+
const values = supplied.sort((left, right) => compareCodeUnits3(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
|
|
11998
|
+
if (values.some(({ value }) => value === null))
|
|
11999
|
+
completeness = "uncertain";
|
|
12000
|
+
const headResult = spawnSync2("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
|
|
12001
|
+
if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
|
|
12002
|
+
completeness = "uncertain";
|
|
12003
|
+
const head = headResult.status === 0 ? headResult.stdout.trim() : null;
|
|
12004
|
+
const partial = {
|
|
12005
|
+
id: "0".repeat(64),
|
|
12006
|
+
scope: normalizedScope,
|
|
12007
|
+
completeness,
|
|
12008
|
+
files,
|
|
12009
|
+
environment: values,
|
|
12010
|
+
head
|
|
12336
12011
|
};
|
|
12337
|
-
|
|
12012
|
+
const candidate = { ...partial, id: candidateDigest(partial) };
|
|
12013
|
+
const parsed = candidateSchema.safeParse(candidate);
|
|
12014
|
+
return parsed.success ? success(null, null, parsed.data) : failure("invalid_input", "captured candidate is invalid");
|
|
12338
12015
|
}
|
|
12339
|
-
var
|
|
12340
|
-
|
|
12341
|
-
|
|
12342
|
-
|
|
12343
|
-
|
|
12344
|
-
|
|
12345
|
-
|
|
12346
|
-
|
|
12347
|
-
|
|
12348
|
-
|
|
12349
|
-
|
|
12350
|
-
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
12354
|
-
|
|
12355
|
-
|
|
12356
|
-
|
|
12357
|
-
|
|
12358
|
-
|
|
12359
|
-
|
|
12360
|
-
|
|
12361
|
-
|
|
12362
|
-
|
|
12363
|
-
|
|
12364
|
-
|
|
12365
|
-
|
|
12366
|
-
|
|
12367
|
-
|
|
12368
|
-
|
|
12369
|
-
|
|
12370
|
-
|
|
12371
|
-
|
|
12372
|
-
|
|
12373
|
-
|
|
12374
|
-
|
|
12375
|
-
|
|
12376
|
-
|
|
12377
|
-
|
|
12378
|
-
|
|
12379
|
-
|
|
12380
|
-
|
|
12381
|
-
|
|
12382
|
-
|
|
12383
|
-
|
|
12384
|
-
|
|
12385
|
-
|
|
12386
|
-
|
|
12387
|
-
|
|
12388
|
-
|
|
12389
|
-
|
|
12390
|
-
|
|
12391
|
-
|
|
12392
|
-
|
|
12393
|
-
|
|
12394
|
-
|
|
12395
|
-
|
|
12396
|
-
|
|
12397
|
-
|
|
12398
|
-
|
|
12399
|
-
|
|
12400
|
-
|
|
12401
|
-
|
|
12402
|
-
|
|
12403
|
-
|
|
12404
|
-
|
|
12405
|
-
|
|
12406
|
-
|
|
12407
|
-
|
|
12408
|
-
|
|
12409
|
-
|
|
12410
|
-
|
|
12411
|
-
|
|
12412
|
-
|
|
12413
|
-
|
|
12414
|
-
|
|
12415
|
-
|
|
12416
|
-
|
|
12417
|
-
|
|
12418
|
-
|
|
12419
|
-
|
|
12420
|
-
|
|
12421
|
-
|
|
12422
|
-
|
|
12423
|
-
|
|
12424
|
-
|
|
12425
|
-
|
|
12426
|
-
|
|
12427
|
-
|
|
12428
|
-
|
|
12429
|
-
|
|
12430
|
-
|
|
12431
|
-
|
|
12432
|
-
|
|
12433
|
-
|
|
12434
|
-
|
|
12435
|
-
|
|
12436
|
-
|
|
12437
|
-
|
|
12438
|
-
|
|
12439
|
-
|
|
12440
|
-
|
|
12441
|
-
|
|
12442
|
-
|
|
12443
|
-
|
|
12444
|
-
|
|
12445
|
-
|
|
12446
|
-
|
|
12447
|
-
|
|
12448
|
-
|
|
12449
|
-
|
|
12450
|
-
|
|
12451
|
-
|
|
12452
|
-
|
|
12453
|
-
|
|
12454
|
-
|
|
12455
|
-
|
|
12456
|
-
|
|
12457
|
-
|
|
12458
|
-
|
|
12459
|
-
|
|
12460
|
-
|
|
12461
|
-
|
|
12462
|
-
|
|
12463
|
-
reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
|
|
12464
|
-
satisfaction: "Use a bounded helper only for the independent objective and reconcile its result.",
|
|
12465
|
-
before: "dependent_action",
|
|
12466
|
-
dependentAction: null,
|
|
12467
|
-
acceptanceAllowed: true
|
|
12016
|
+
var fileMap = (candidate) => new Map(candidate.files.map((file) => [file.path, file]));
|
|
12017
|
+
var changedPaths = (before, after) => {
|
|
12018
|
+
const paths = new Set([...before.files, ...after.files].map((file) => file.path));
|
|
12019
|
+
const left = fileMap(before);
|
|
12020
|
+
const right = fileMap(after);
|
|
12021
|
+
const changed = [...paths].filter((item) => JSON.stringify(left.get(item) ?? null) !== JSON.stringify(right.get(item) ?? null));
|
|
12022
|
+
if (before.head !== after.head)
|
|
12023
|
+
changed.push(".");
|
|
12024
|
+
if (JSON.stringify(before.environment) !== JSON.stringify(after.environment))
|
|
12025
|
+
changed.push(".");
|
|
12026
|
+
return changed;
|
|
12027
|
+
};
|
|
12028
|
+
var sessionFromRef = (value) => {
|
|
12029
|
+
if (typeof value !== "object" || value === null || value.kind !== "host")
|
|
12030
|
+
return null;
|
|
12031
|
+
return typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
|
|
12032
|
+
};
|
|
12033
|
+
var sameSession = (left, right) => {
|
|
12034
|
+
const other = sessionFromRef(right);
|
|
12035
|
+
return left !== null && other !== null && left.host === other.host && left.handle === other.handle;
|
|
12036
|
+
};
|
|
12037
|
+
var evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope);
|
|
12038
|
+
function evaluateEvidence(task, candidate) {
|
|
12039
|
+
const current = candidate ?? task.candidates.at(-1) ?? null;
|
|
12040
|
+
return task.evidence.map((entry) => {
|
|
12041
|
+
const evidence = entry.data;
|
|
12042
|
+
if (evidence.result === "failed")
|
|
12043
|
+
return {
|
|
12044
|
+
evidenceId: entry.id,
|
|
12045
|
+
status: "failed",
|
|
12046
|
+
reason: "failed evidence remains historical"
|
|
12047
|
+
};
|
|
12048
|
+
if (evidence.kind === "check" || evidence.kind === "review") {
|
|
12049
|
+
if (!evidence.beforeCandidateId || !evidence.candidateId)
|
|
12050
|
+
return {
|
|
12051
|
+
evidenceId: entry.id,
|
|
12052
|
+
status: "stale",
|
|
12053
|
+
reason: "check or review evidence lacks start and end candidates"
|
|
12054
|
+
};
|
|
12055
|
+
if (evidence.beforeCandidateId !== evidence.candidateId)
|
|
12056
|
+
return {
|
|
12057
|
+
evidenceId: entry.id,
|
|
12058
|
+
status: "stale",
|
|
12059
|
+
reason: "candidate changed during the check or review"
|
|
12060
|
+
};
|
|
12061
|
+
}
|
|
12062
|
+
if (!evidence.candidateId || !current)
|
|
12063
|
+
return {
|
|
12064
|
+
evidenceId: entry.id,
|
|
12065
|
+
status: evidence.result,
|
|
12066
|
+
reason: "evidence has no candidate binding"
|
|
12067
|
+
};
|
|
12068
|
+
const observed = task.candidates.find((item) => item.id === evidence.candidateId) ?? (current.id === evidence.candidateId ? current : undefined);
|
|
12069
|
+
if (!observed || observed.completeness === "uncertain" || current.completeness === "uncertain")
|
|
12070
|
+
return { evidenceId: entry.id, status: "stale", reason: "candidate is missing or uncertain" };
|
|
12071
|
+
if (observed.id === current.id)
|
|
12072
|
+
return { evidenceId: entry.id, status: evidence.result, reason: "candidate is unchanged" };
|
|
12073
|
+
const scopes = evidenceScopes(task, evidence.requirementIds);
|
|
12074
|
+
const changed = changedPaths(observed, current);
|
|
12075
|
+
const environmentChanged = JSON.stringify(observed.environment) !== JSON.stringify(current.environment);
|
|
12076
|
+
if (!scopes.length || environmentChanged || changed.some((item) => scopes.some((scope) => pathRelevant(item, scope))))
|
|
12077
|
+
return { evidenceId: entry.id, status: "stale", reason: "relevant candidate state changed" };
|
|
12078
|
+
return {
|
|
12079
|
+
evidenceId: entry.id,
|
|
12080
|
+
status: evidence.result,
|
|
12081
|
+
reason: "candidate changed outside evidence scope"
|
|
12082
|
+
};
|
|
12083
|
+
});
|
|
12084
|
+
}
|
|
12085
|
+
var scopeCovers2 = scopeCovers;
|
|
12086
|
+
var checkPin = (assignment, beforeCandidateId, candidateId) => {
|
|
12087
|
+
const pin = assignment.candidateId ?? null;
|
|
12088
|
+
if (pin === null)
|
|
12089
|
+
return true;
|
|
12090
|
+
return (beforeCandidateId ?? null) === pin || (candidateId ?? null) === pin;
|
|
12091
|
+
};
|
|
12092
|
+
var resolveBinding = (evidence, pin, currentCandidateId) => {
|
|
12093
|
+
const bound = evidence.kind === "check" || evidence.kind === "review";
|
|
12094
|
+
const fallback = bound ? pin ?? currentCandidateId : null;
|
|
12095
|
+
return {
|
|
12096
|
+
beforeCandidateId: evidence.beforeCandidateId ?? fallback,
|
|
12097
|
+
candidateId: evidence.candidateId ?? fallback
|
|
12098
|
+
};
|
|
12099
|
+
};
|
|
12100
|
+
var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId != null);
|
|
12101
|
+
var applicableDecision2 = (task, workspace, requirement, checkoutRoot) => task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data).filter((decision) => decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.binding.taskId === task.id && decision.binding.workspaceId === workspace.id && decision.requirementIds.includes(requirement.id) && decision.binding.scope && scopeCovers2(decision.binding.scope, requirement.scope) && decision.digest === decisionDigest(decision) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, decision.binding).ok : decision.binding.contentRefs.every((reference) => reference.kind !== "file")));
|
|
12102
|
+
var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && (data.response === "approved" || data.response === "stated") && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id }));
|
|
12103
|
+
var evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
|
|
12104
|
+
if (kind !== "passed")
|
|
12105
|
+
return false;
|
|
12106
|
+
if (dimension === "testing" || dimension === "verification")
|
|
12107
|
+
return evidenceKind === "check" || ruleId === "pre-pr-cleanup" && evidenceKind === "artifact";
|
|
12108
|
+
if (dimension === "review")
|
|
12109
|
+
return evidenceKind === "review";
|
|
12110
|
+
if (dimension === "investigation" || dimension === "challenge")
|
|
12111
|
+
return evidenceKind === "investigation";
|
|
12112
|
+
if (dimension === "artifacts" || dimension === "continuity")
|
|
12113
|
+
return evidenceKind === "artifact";
|
|
12114
|
+
return false;
|
|
12115
|
+
};
|
|
12116
|
+
var expectedKinds = (dimension, ruleId) => {
|
|
12117
|
+
if (dimension === "testing")
|
|
12118
|
+
return "check";
|
|
12119
|
+
if (dimension === "verification")
|
|
12120
|
+
return ruleId === "pre-pr-cleanup" ? "check or artifact" : "check";
|
|
12121
|
+
if (dimension === "review")
|
|
12122
|
+
return "review";
|
|
12123
|
+
if (dimension === "investigation" || dimension === "challenge")
|
|
12124
|
+
return "investigation";
|
|
12125
|
+
if (dimension === "artifacts" || dimension === "continuity")
|
|
12126
|
+
return "artifact";
|
|
12127
|
+
if (dimension === "decisions")
|
|
12128
|
+
return "decision";
|
|
12129
|
+
return "worker report";
|
|
12130
|
+
};
|
|
12131
|
+
function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller) {
|
|
12132
|
+
const evidence = evaluateEvidence(task, candidate);
|
|
12133
|
+
if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
|
|
12134
|
+
return task.policy.requirements.map((requirement) => ({
|
|
12135
|
+
requirementId: requirement.id,
|
|
12136
|
+
status: "unsatisfied",
|
|
12137
|
+
evidenceIds: [],
|
|
12138
|
+
decisionIds: [],
|
|
12139
|
+
reason: "stored policy version is unsupported; reassessment is required"
|
|
12468
12140
|
}));
|
|
12469
|
-
|
|
12470
|
-
|
|
12471
|
-
|
|
12472
|
-
|
|
12473
|
-
|
|
12474
|
-
|
|
12475
|
-
|
|
12476
|
-
|
|
12477
|
-
|
|
12478
|
-
|
|
12479
|
-
|
|
12480
|
-
|
|
12481
|
-
|
|
12482
|
-
|
|
12483
|
-
|
|
12484
|
-
|
|
12485
|
-
|
|
12486
|
-
|
|
12487
|
-
|
|
12488
|
-
|
|
12141
|
+
return (task.policy?.requirements ?? []).map((requirement) => {
|
|
12142
|
+
const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
|
|
12143
|
+
const passed = related.filter(({ entry, evaluation }) => {
|
|
12144
|
+
if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
|
|
12145
|
+
return false;
|
|
12146
|
+
if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
|
|
12147
|
+
return false;
|
|
12148
|
+
if (requirement.dimension !== "review")
|
|
12149
|
+
return true;
|
|
12150
|
+
const reviewSession = sessionFromRef(entry.data.reviewContext);
|
|
12151
|
+
const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
|
|
12152
|
+
const sameEvidenceSession = related.some(({ entry: other }) => other.id !== entry.id && other.data.kind === "review" && other.data.candidateId !== null && other.data.candidateId === entry.data.candidateId && sameSession(reviewSession, other.provenance.session));
|
|
12153
|
+
return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
|
|
12154
|
+
});
|
|
12155
|
+
if (passed.length) {
|
|
12156
|
+
const currentId = candidate?.id ?? null;
|
|
12157
|
+
const withRed = requirement.dimension === "testing" ? passed.filter(({ entry }) => related.some((other) => other.entry.data.kind === "check" && other.evaluation.status === "failed" && other.entry.data.requirementIds.includes(requirement.id) && other.entry.recordedAt <= entry.recordedAt) || related.some((baseline) => baseline.entry.id !== entry.id && baseline.entry.data.kind === "check" && baseline.entry.data.result === "passed" && baseline.entry.data.candidateId !== null && currentId !== null && baseline.entry.data.candidateId !== currentId)) : passed;
|
|
12158
|
+
if (withRed.length)
|
|
12159
|
+
return {
|
|
12160
|
+
requirementId: requirement.id,
|
|
12161
|
+
status: "satisfied",
|
|
12162
|
+
evidenceIds: withRed.map(({ entry }) => entry.id),
|
|
12163
|
+
decisionIds: [],
|
|
12164
|
+
reason: "fresh applicable evidence passed"
|
|
12165
|
+
};
|
|
12166
|
+
if (requirement.dimension === "testing")
|
|
12167
|
+
return {
|
|
12168
|
+
requirementId: requirement.id,
|
|
12169
|
+
status: "unsatisfied",
|
|
12170
|
+
evidenceIds: [],
|
|
12171
|
+
decisionIds: [],
|
|
12172
|
+
reason: "testing requirement has GREEN evidence but no preceding RED"
|
|
12173
|
+
};
|
|
12174
|
+
}
|
|
12175
|
+
if (requirement.dimension === "delegation") {
|
|
12176
|
+
const dot = (scope) => scope.paths.length > 0 ? scope : { ...scope, paths: ["."] };
|
|
12177
|
+
const reconciled = task.workers.some((worker) => worker.data.report?.outcome === "completed" && (worker.data.assignment.requirementIds.includes(requirement.id) || scopeCovers2(dot(requirement.scope), dot(worker.data.assignment.scope))));
|
|
12178
|
+
if (reconciled)
|
|
12179
|
+
return {
|
|
12180
|
+
requirementId: requirement.id,
|
|
12181
|
+
status: "satisfied",
|
|
12182
|
+
evidenceIds: [],
|
|
12183
|
+
decisionIds: [],
|
|
12184
|
+
reason: "bounded helper completed and its result is recorded"
|
|
12185
|
+
};
|
|
12186
|
+
}
|
|
12187
|
+
const decisions = requirement.dimension === "decisions" ? applicableRequirementDecision(task, workspace, requirement, checkoutRoot) : [];
|
|
12188
|
+
if (decisions.length)
|
|
12189
|
+
return {
|
|
12190
|
+
requirementId: requirement.id,
|
|
12191
|
+
status: "satisfied",
|
|
12192
|
+
evidenceIds: [],
|
|
12193
|
+
decisionIds: decisions.map((decision) => decision.id),
|
|
12194
|
+
reason: "an applicable approved or stated decision satisfies the requirement"
|
|
12195
|
+
};
|
|
12196
|
+
const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
|
|
12197
|
+
if (limitations.length)
|
|
12198
|
+
return {
|
|
12199
|
+
requirementId: requirement.id,
|
|
12200
|
+
status: "accepted_limitation",
|
|
12201
|
+
evidenceIds: [],
|
|
12202
|
+
decisionIds: limitations.map((decision) => task.decisions.find((entry) => entry.data === decision).id),
|
|
12203
|
+
reason: "an applicable approved limitation permits the missing evidence"
|
|
12204
|
+
};
|
|
12205
|
+
const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
|
|
12206
|
+
if (!unavailable && related.length > 0 && !passed.length)
|
|
12207
|
+
return {
|
|
12208
|
+
requirementId: requirement.id,
|
|
12209
|
+
status: "unsatisfied",
|
|
12210
|
+
evidenceIds: related.map(({ entry }) => entry.id),
|
|
12211
|
+
decisionIds: [],
|
|
12212
|
+
reason: `no passing evidence (needs kind:${expectedKinds(requirement.dimension, requirement.ruleId)})`
|
|
12213
|
+
};
|
|
12214
|
+
return {
|
|
12215
|
+
requirementId: requirement.id,
|
|
12216
|
+
status: unavailable ? "unavailable" : "unsatisfied",
|
|
12217
|
+
evidenceIds: related.map(({ entry }) => entry.id),
|
|
12218
|
+
decisionIds: [],
|
|
12219
|
+
reason: unavailable ? "required capability is unavailable" : "no fresh passing evidence"
|
|
12220
|
+
};
|
|
12221
|
+
});
|
|
12222
|
+
}
|
|
12223
|
+
function evaluateClosure(requestedOutcome, view) {
|
|
12224
|
+
if (requestedOutcome !== "stopped" && !view.task.policy)
|
|
12225
|
+
return failure("requirements_unsatisfied", "task has not been assessed");
|
|
12226
|
+
if (view.task.policy && view.task.policy.policyVersion !== POLICY_VERSION)
|
|
12227
|
+
return failure("requirements_unsatisfied", "stored policy version is unsupported; reassessment is required");
|
|
12228
|
+
const evaluations = view.requirements;
|
|
12229
|
+
const openFindings = view.task.findings.filter((entry) => entry.data.disposition === "open");
|
|
12230
|
+
if (requestedOutcome !== "stopped" && openFindings.length)
|
|
12231
|
+
return failure("requirements_unsatisfied", "open findings must be resolved before closure");
|
|
12232
|
+
if (requestedOutcome !== "stopped") {
|
|
12233
|
+
const evidenceById = new Map(view.evidence.map((entry) => [entry.evidenceId, entry.status]));
|
|
12234
|
+
for (const entry of view.task.findings) {
|
|
12235
|
+
if (entry.data.disposition === "fixed") {
|
|
12236
|
+
const verified = entry.data.resolution?.evidenceIds.some((id) => {
|
|
12237
|
+
const evidence = view.task.evidence.find((item) => item.id === id);
|
|
12238
|
+
if (!evidence)
|
|
12239
|
+
return false;
|
|
12240
|
+
return findingVerificationPasses(entry.data.candidateId, {
|
|
12241
|
+
kind: evidence.data.kind,
|
|
12242
|
+
candidateId: evidence.data.candidateId,
|
|
12243
|
+
status: evidenceById.get(id) ?? "missing"
|
|
12244
|
+
});
|
|
12245
|
+
});
|
|
12246
|
+
if (!verified)
|
|
12247
|
+
return failure("requirements_unsatisfied", "fixed findings require current verification");
|
|
12248
|
+
}
|
|
12249
|
+
if (entry.data.disposition === "deferred") {
|
|
12250
|
+
const valid = entry.data.resolution?.decisionIds.some((id) => {
|
|
12251
|
+
const decisionEntry = view.task.decisions.find((item) => item.id === id);
|
|
12252
|
+
const decision = decisionEntry?.data;
|
|
12253
|
+
return Boolean(decision && decisionEntry?.provenance.kind !== "imported" && decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.digest === decisionDigest(decision) && verifyDecisionContentAtRoot(view.workspace.root, decision.binding).ok && decision.binding.taskId === view.task.id && decision.binding.workspaceId === view.workspace.id && scopeCovers2(decision.binding.scope, entry.data.scope) && decision.requirementIds.some((requirementId) => view.task.policy?.requirements.some((requirement) => requirement.id === requirementId && requirement.acceptanceAllowed)));
|
|
12254
|
+
});
|
|
12255
|
+
if (!valid)
|
|
12256
|
+
return failure("requirements_unsatisfied", "deferred findings require an approved limitation decision that references an acceptanceAllowed requirement and covers the finding scope");
|
|
12257
|
+
}
|
|
12489
12258
|
}
|
|
12490
|
-
|
|
12491
|
-
|
|
12492
|
-
if (
|
|
12259
|
+
}
|
|
12260
|
+
const blocking = evaluations.filter((item) => {
|
|
12261
|
+
if (item.status !== "unsatisfied" && item.status !== "unavailable")
|
|
12493
12262
|
return false;
|
|
12494
|
-
|
|
12495
|
-
return
|
|
12263
|
+
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
12264
|
+
return !requirement || requirement.before === "close";
|
|
12265
|
+
});
|
|
12266
|
+
if (requestedOutcome !== "stopped" && blocking.length)
|
|
12267
|
+
return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
|
|
12268
|
+
requirementIds: blocking.map((item) => item.requirementId),
|
|
12269
|
+
requirements: blocking.map((item) => {
|
|
12270
|
+
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
12271
|
+
return {
|
|
12272
|
+
requirementId: item.requirementId,
|
|
12273
|
+
ruleId: requirement?.ruleId ?? "unknown",
|
|
12274
|
+
reason: item.reason,
|
|
12275
|
+
satisfaction: requirement?.satisfaction ?? "",
|
|
12276
|
+
dependentAction: requirement?.dependentAction ?? null
|
|
12277
|
+
};
|
|
12278
|
+
})
|
|
12279
|
+
});
|
|
12280
|
+
const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
|
|
12281
|
+
if (requestedOutcome === "verified" && accepted.length)
|
|
12282
|
+
return failure("requirements_unsatisfied", "accepted limitations cannot be reported as verified", {
|
|
12283
|
+
requirementIds: accepted.map((item) => item.requirementId)
|
|
12284
|
+
});
|
|
12285
|
+
const evidenceIds = [
|
|
12286
|
+
...new Set([
|
|
12287
|
+
...evaluations.flatMap((item) => item.evidenceIds),
|
|
12288
|
+
...view.task.findings.flatMap((item) => item.data.resolution?.evidenceIds ?? [])
|
|
12289
|
+
])
|
|
12290
|
+
];
|
|
12291
|
+
const decisionIds = [
|
|
12292
|
+
...new Set([
|
|
12293
|
+
...evaluations.flatMap((item) => item.decisionIds),
|
|
12294
|
+
...view.task.findings.flatMap((item) => item.data.resolution?.decisionIds ?? [])
|
|
12295
|
+
])
|
|
12296
|
+
];
|
|
12297
|
+
return success(null, null, {
|
|
12298
|
+
outcome: requestedOutcome === "stopped" ? "stopped" : accepted.length ? "accepted_limitations" : "verified",
|
|
12299
|
+
evidenceIds,
|
|
12300
|
+
decisionIds,
|
|
12301
|
+
requirementIds: evaluations.map((item) => item.requirementId)
|
|
12496
12302
|
});
|
|
12497
12303
|
}
|
|
12498
|
-
|
|
12499
|
-
|
|
12500
|
-
|
|
12501
|
-
|
|
12502
|
-
|
|
12503
|
-
|
|
12504
|
-
|
|
12505
|
-
|
|
12506
|
-
|
|
12507
|
-
}
|
|
12508
|
-
|
|
12509
|
-
|
|
12510
|
-
|
|
12511
|
-
|
|
12304
|
+
|
|
12305
|
+
// packages/workit-core/src/core/methods.ts
|
|
12306
|
+
var METHODS = {
|
|
12307
|
+
"workit-challenge": { dimensions: ["challenge", "decisions"] },
|
|
12308
|
+
"workit-behavioral-tdd": { dimensions: ["testing"], ruleIds: ["mechanical-existing-checks"] },
|
|
12309
|
+
"workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review", "self-review"] },
|
|
12310
|
+
"workit-plan": { dimensions: ["artifacts", "continuity"] },
|
|
12311
|
+
"workit-implement": { dimensions: ["delegation"] },
|
|
12312
|
+
"workit-debug": {},
|
|
12313
|
+
"workit-handoff": {},
|
|
12314
|
+
"workit-deslop": { ruleIds: ["pre-pr-cleanup"] }
|
|
12315
|
+
};
|
|
12316
|
+
var methodMatches = (definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true || definition.ruleIds?.includes(requirement.ruleId) === true;
|
|
12317
|
+
var assuranceFor = (id, definition, capabilities) => {
|
|
12318
|
+
const keys = new Set([
|
|
12319
|
+
id,
|
|
12320
|
+
...definition.dimensions ?? [],
|
|
12321
|
+
...definition.ruleIds ?? []
|
|
12322
|
+
]);
|
|
12323
|
+
const matches = capabilities.filter((capability) => keys.has(capability.name) || keys.has(capability.surface));
|
|
12324
|
+
if (id === "workit-review" && matches.length === 0)
|
|
12325
|
+
return { assurance: "unavailable", reasons: ["independent review capability is unavailable"] };
|
|
12326
|
+
if (matches.some((capability) => capability.assurance === "unavailable"))
|
|
12327
|
+
return {
|
|
12328
|
+
assurance: "unavailable",
|
|
12329
|
+
reasons: matches.filter((capability) => capability.assurance === "unavailable").map((capability) => capability.reason)
|
|
12330
|
+
};
|
|
12331
|
+
if (matches.some((capability) => capability.assurance === "enforced"))
|
|
12332
|
+
return { assurance: "enforced", reasons: matches.map((capability) => capability.reason) };
|
|
12333
|
+
return { assurance: "agent_guided", reasons: matches.map((capability) => capability.reason) };
|
|
12334
|
+
};
|
|
12335
|
+
function selectMethods(policy, capabilities) {
|
|
12336
|
+
return Object.entries(METHODS).flatMap(([id, definition]) => {
|
|
12337
|
+
const requirements = policy.requirements.filter((requirement) => methodMatches(definition, requirement));
|
|
12338
|
+
if (requirements.length === 0)
|
|
12339
|
+
return [];
|
|
12340
|
+
const capability = assuranceFor(id, definition, capabilities);
|
|
12341
|
+
const reasons = [
|
|
12342
|
+
...new Set(requirements.map((requirement) => requirement.reason)),
|
|
12343
|
+
...capability.reasons
|
|
12344
|
+
];
|
|
12345
|
+
if (capability.assurance === "unavailable")
|
|
12346
|
+
reasons.unshift("required capability is unavailable");
|
|
12347
|
+
return [{ id, reason: reasons.join("; "), assurance: capability.assurance }];
|
|
12348
|
+
});
|
|
12349
|
+
}
|
|
12350
|
+
var invariantBootstrap = () => `
|
|
12351
|
+
Workit is optional coordination and repository-policy tooling around the host,
|
|
12352
|
+
not a permission system or a mandatory workflow. Native host allow/ask/deny,
|
|
12353
|
+
sandbox, plan/read-only mode, and organization rules remain authoritative.
|
|
12354
|
+
Ordinary investigation, questions, non-Git work, and routine reversible edits
|
|
12355
|
+
need zero Workit task, assessment, or writer calls. Use one compact tracked
|
|
12356
|
+
record only when handoff, dependent steps, concurrent actors, or meaningful
|
|
12357
|
+
decisions make continuity useful; similar titles alone never merge tasks.
|
|
12358
|
+
|
|
12359
|
+
For a routine user-authorized branch or commit, prefer native host Git/shell
|
|
12360
|
+
tools from the outset when managed coordination or outcome reconciliation is
|
|
12361
|
+
not needed. Inspect the actual target checkout and its configured conventions;
|
|
12362
|
+
use Workit's read-only context/policy tools when needed. Do not start a task,
|
|
12363
|
+
acquire a writer, or mint a decision receipt solely for a native Git action.
|
|
12364
|
+
Native permissions still apply. Never switch execution paths to evade a denial
|
|
12365
|
+
or repeat an uncertain managed effect. A local-commit endpoint does not imply
|
|
12366
|
+
PR readiness, a fresh review, or post-commit assessment/closure paperwork.
|
|
12367
|
+
|
|
12368
|
+
For tracked work, inspect its current state and use the shared operations for
|
|
12369
|
+
task, policy, evidence, finding, decision, worker, writer, and state changes.
|
|
12370
|
+
Start a record once for an explicit tracked objective; assess or reassess only
|
|
12371
|
+
when policy selection or changed evidence/constraints requires it. Omitted
|
|
12372
|
+
expectedRevision and expectedWorkspaceRevision use current values; explicit
|
|
12373
|
+
values are still concurrency-checked, so never copy revisions between calls.
|
|
12374
|
+
A solo edit does not need writer acquisition; use it when concurrent checkout
|
|
12375
|
+
writers need coordination. Record only observed facts and checks. Evidence can
|
|
12376
|
+
become stale when its bound candidate changes; reconcile findings against the
|
|
12377
|
+
current candidate before recording completion.
|
|
12378
|
+
|
|
12379
|
+
Workit validates domain policy against the actual action target, configured
|
|
12380
|
+
account, branch and commit conventions, protected refs, and current repository
|
|
12381
|
+
state. The task directory is coordination state, not a boundary on which
|
|
12382
|
+
repository may be changed. Use an action-time cwd target where a managed
|
|
12383
|
+
Git/hosting action supports it; a non-Git directory remains valid for OS work.
|
|
12384
|
+
GitHub/GitLab use the active gh/glab CLI identity; YouTrack uses its own token.
|
|
12385
|
+
Preserve uncertain external outcomes and reconcile repository/provider state
|
|
12386
|
+
before retrying. Internal reservations prevent duplicate or ambiguous effects;
|
|
12387
|
+
they are not permission tickets for every edit.
|
|
12388
|
+
|
|
12389
|
+
On OpenCode V1 and V2, use native host tools for external mutations. Workit
|
|
12390
|
+
provides read-only workit_context; it has no managed external-action executor.
|
|
12391
|
+
Do not create proposals or request Workit approvals merely to invoke native
|
|
12392
|
+
tools. Old uncertain managed effects still require evidence before any retry.
|
|
12393
|
+
|
|
12394
|
+
Use host-native authorization through the host's supported path. A native
|
|
12395
|
+
question receipt records an actual question interaction; it is not automatic
|
|
12396
|
+
host permission. Record a meaningful user choice once with provenance when
|
|
12397
|
+
future retrieval helps. Never fabricate a receipt, re-ask only to mint one, or
|
|
12398
|
+
let imported decisions grant authority. Never turn a host deny into allow or
|
|
12399
|
+
claim enforcement/evidence a host cannot provide. A precise request with settled
|
|
12400
|
+
constraints does not need an interview. Present genuine unresolved options with
|
|
12401
|
+
evidence and a recommendation, then continue toward the requested delivery
|
|
12402
|
+
endpoint: investigation, implementation, branch/commit/push, PR/MR-ready,
|
|
12403
|
+
merge, or release. Run
|
|
12404
|
+
applicable checks and safe repairs without repeated continuation questions;
|
|
12405
|
+
stop for missing host authority, a new consequential choice, a conflicting edit,
|
|
12406
|
+
or an unresolved blocker. A tracked record may close when actual evidence
|
|
12407
|
+
supports its outcome; no final human closure ceremony is required.
|
|
12408
|
+
|
|
12409
|
+
A completed native subagent run stops its bound worker by itself; if a worker
|
|
12410
|
+
strands in cancelling with its run verifiably over, repeat worker.cancel to
|
|
12411
|
+
confirm the stop. Preserve unresolved requirements, gaps, and uncertain workers.
|
|
12412
|
+
When context changes, distinguish a quick question, same-task adjustment, and
|
|
12413
|
+
separate request. Answer a quick question without pausing/resuming task state;
|
|
12414
|
+
park a concise checkpoint only when substantial work needs to continue later.
|
|
12415
|
+
For work spanning repositories, bind each unfinished item to its actual checkout,
|
|
12416
|
+
branch, requested deliverables, and delivery endpoint. Preserve held items with
|
|
12417
|
+
an explicit resume condition. Resolve competing plausible targets before a
|
|
12418
|
+
mutation; ask only when available context cannot settle that consequential choice.
|
|
12419
|
+
Before reporting delivery, reconcile every requested item and observe the actual
|
|
12420
|
+
target result; a local commit alone is not evidence of a requested remote push.
|
|
12421
|
+
|
|
12422
|
+
Skill routing: slash aliases /wk-* load on demand. Use workit-steer for a
|
|
12423
|
+
substantial interruption or change of direction, workit-deslop when relevant
|
|
12424
|
+
to a PR-ready endpoint, workit-green-run for failing CI, workit-blast-radius
|
|
12425
|
+
when impact is uncertain, and workit-challenge for genuinely open consequential
|
|
12426
|
+
choices. Load workit-plan when dependencies or handoff need durable next actions.
|
|
12427
|
+
Load the skill; never act from memory of it.
|
|
12428
|
+
`.trim();
|
|
12429
|
+
|
|
12430
|
+
// packages/workit-core/src/core/task-context.ts
|
|
12431
|
+
function reconcileResume(view, observations = []) {
|
|
12432
|
+
if (observations.length > 0)
|
|
12433
|
+
return failure("permission_denied", "worker observations require native host verification");
|
|
12434
|
+
const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
|
|
12435
|
+
if (!candidate.ok)
|
|
12436
|
+
return candidate;
|
|
12437
|
+
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
12438
|
+
const blockers = [...view.task.progress.blockers];
|
|
12439
|
+
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
12440
|
+
blockers.push({
|
|
12441
|
+
reason: "worker state requires reconciliation",
|
|
12442
|
+
dependentAction: "resume",
|
|
12443
|
+
refs: []
|
|
12444
|
+
});
|
|
12445
|
+
return success(null, null, {
|
|
12446
|
+
candidate: candidate.data,
|
|
12447
|
+
staleEvidenceIds,
|
|
12448
|
+
workerUpdates: [],
|
|
12449
|
+
blockers,
|
|
12450
|
+
reassessmentRequired: view.task.policy?.policyVersion !== POLICY_VERSION
|
|
12451
|
+
});
|
|
12512
12452
|
}
|
|
12513
|
-
|
|
12514
|
-
|
|
12515
|
-
|
|
12516
|
-
|
|
12517
|
-
|
|
12518
|
-
|
|
12519
|
-
|
|
12520
|
-
const
|
|
12521
|
-
|
|
12522
|
-
|
|
12523
|
-
|
|
12524
|
-
|
|
12525
|
-
|
|
12526
|
-
|
|
12527
|
-
|
|
12528
|
-
|
|
12529
|
-
|
|
12530
|
-
|
|
12531
|
-
|
|
12532
|
-
|
|
12453
|
+
var COMPACT_MAX_BYTES = 4096;
|
|
12454
|
+
var COMPACT_MAX_ITEMS = 8;
|
|
12455
|
+
var COMPACT_TEXT_BYTES = 128;
|
|
12456
|
+
var sensitiveUrl = /\bhttps?:\/\/[^/\s:@]+:[^@\s]+@/gi;
|
|
12457
|
+
var sensitiveValue = /(?:token|secret|password|passwd|api[-_ ]?key|private[-_ ]?key|authorization)\s*[:=]\s*(?:bearer\s+)?\S+|\bbearer\s+\S+/gi;
|
|
12458
|
+
var privateKey = /-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/gi;
|
|
12459
|
+
var compactText = (value, limit) => value === null ? null : value.replace(privateKey, "<redacted-private-key>").replace(sensitiveUrl, "<redacted-url>").replace(sensitiveValue, (match) => {
|
|
12460
|
+
const separator = match.search(/[:=]/u);
|
|
12461
|
+
if (separator >= 0)
|
|
12462
|
+
return `${match.slice(0, separator)}=<redacted>`;
|
|
12463
|
+
const space = match.search(/\s/u);
|
|
12464
|
+
return `${match.slice(0, space)} <redacted>`;
|
|
12465
|
+
}).slice(0, limit);
|
|
12466
|
+
var compactBytes = (value) => new TextEncoder().encode(value).byteLength;
|
|
12467
|
+
var compactTimestamp = (value) => `${value.slice(0, 19)}.${value.slice(20, -1).padEnd(9, "0")}`;
|
|
12468
|
+
var compactDecisionChoice = (entry) => {
|
|
12469
|
+
const stated = entry.data.binding.statedChoice?.text;
|
|
12470
|
+
const approved = entry.data.response === "approved" ? entry.data.binding.approvedContent : "";
|
|
12471
|
+
const source = stated?.trim() || approved.trim() || null;
|
|
12472
|
+
return compactText(source, COMPACT_TEXT_BYTES);
|
|
12473
|
+
};
|
|
12474
|
+
var compactReference = (ref) => {
|
|
12475
|
+
if (ref.kind === "file")
|
|
12476
|
+
return {
|
|
12477
|
+
kind: "file",
|
|
12478
|
+
path: compactText(ref.path, COMPACT_TEXT_BYTES) ?? ""
|
|
12479
|
+
};
|
|
12480
|
+
if (ref.kind === "record")
|
|
12481
|
+
return {
|
|
12482
|
+
kind: "record",
|
|
12483
|
+
collection: ref.collection,
|
|
12484
|
+
id: ref.id
|
|
12485
|
+
};
|
|
12486
|
+
return { kind: ref.kind };
|
|
12487
|
+
};
|
|
12488
|
+
function compactTaskContext(view) {
|
|
12489
|
+
const decisions = view.task.decisions.slice().sort((left, right) => {
|
|
12490
|
+
const leftAt = compactTimestamp(left.recordedAt);
|
|
12491
|
+
const rightAt = compactTimestamp(right.recordedAt);
|
|
12492
|
+
if (leftAt !== rightAt)
|
|
12493
|
+
return leftAt > rightAt ? -1 : 1;
|
|
12494
|
+
return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
|
|
12495
|
+
}).slice(0, COMPACT_MAX_ITEMS).map((entry) => ({
|
|
12496
|
+
id: entry.id,
|
|
12497
|
+
purpose: entry.data.purpose,
|
|
12498
|
+
status: entry.data.response,
|
|
12499
|
+
digest: entry.data.digest,
|
|
12500
|
+
choice: compactDecisionChoice(entry) || `task.inspect taskId=${view.task.id} view=full; decision=${entry.id}`,
|
|
12501
|
+
references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference)
|
|
12502
|
+
}));
|
|
12503
|
+
const methods = view.task.policy ? selectMethods(view.task.policy, view.capabilities).map((method) => ({
|
|
12504
|
+
id: method.id,
|
|
12505
|
+
assurance: method.assurance,
|
|
12506
|
+
reason: compactText(method.reason, COMPACT_TEXT_BYTES) ?? ""
|
|
12507
|
+
})) : [];
|
|
12508
|
+
const gaps = Array.from(new Set([
|
|
12509
|
+
...view.evidence.filter((entry) => entry.status === "stale").map((entry) => `stale evidence: ${entry.evidenceId}`),
|
|
12510
|
+
...view.requirements.filter((entry) => entry.status !== "satisfied").map((entry) => entry.reason),
|
|
12511
|
+
...view.task.progress.blockers.map((entry) => entry.reason)
|
|
12512
|
+
])).sort().slice(0, COMPACT_MAX_ITEMS).map((gap) => compactText(gap, COMPACT_TEXT_BYTES));
|
|
12513
|
+
const context = {
|
|
12514
|
+
objective: compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "",
|
|
12515
|
+
status: view.task.status,
|
|
12516
|
+
decisions,
|
|
12517
|
+
methods,
|
|
12518
|
+
gaps,
|
|
12519
|
+
nextAction: compactText(view.task.progress.nextAction, COMPACT_TEXT_BYTES)
|
|
12533
12520
|
};
|
|
12534
|
-
|
|
12535
|
-
|
|
12536
|
-
|
|
12537
|
-
|
|
12521
|
+
let encoded = JSON.stringify(context);
|
|
12522
|
+
while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.methods.length || context.gaps.length)) {
|
|
12523
|
+
if (context.gaps.length)
|
|
12524
|
+
context.gaps.pop();
|
|
12525
|
+
else if (context.decisions.length)
|
|
12526
|
+
context.decisions.pop();
|
|
12527
|
+
else
|
|
12528
|
+
context.methods.pop();
|
|
12529
|
+
encoded = JSON.stringify(context);
|
|
12530
|
+
}
|
|
12531
|
+
if (compactBytes(encoded) > COMPACT_MAX_BYTES) {
|
|
12532
|
+
context.objective = compactText(context.objective, 64) ?? "";
|
|
12533
|
+
context.nextAction = compactText(context.nextAction, 64);
|
|
12534
|
+
encoded = JSON.stringify(context);
|
|
12535
|
+
}
|
|
12536
|
+
return encoded;
|
|
12538
12537
|
}
|
|
12538
|
+
var exportDigest = (bundle) => sha256({
|
|
12539
|
+
schemaVersion: bundle.schemaVersion,
|
|
12540
|
+
exportedAt: bundle.exportedAt,
|
|
12541
|
+
sourceWorkspaceId: bundle.sourceWorkspaceId,
|
|
12542
|
+
task: bundle.task
|
|
12543
|
+
});
|
|
12539
12544
|
|
|
12540
12545
|
// packages/workit-core/src/core/task-engine.ts
|
|
12541
12546
|
var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
|
|
@@ -14223,14 +14228,29 @@ class WorkitCore {
|
|
|
14223
14228
|
writer: evaluationWorkspace.writer
|
|
14224
14229
|
});
|
|
14225
14230
|
}
|
|
14226
|
-
|
|
14231
|
+
compactContext(taskId) {
|
|
14232
|
+
const root = this.contextRootError();
|
|
14233
|
+
if (!root.ok)
|
|
14234
|
+
return root;
|
|
14235
|
+
const task = this.store.readTask(taskId);
|
|
14236
|
+
if (!task.ok)
|
|
14237
|
+
return task;
|
|
14238
|
+
const helper = this.helperTaskGuard(task.data, true);
|
|
14239
|
+
if (!helper.ok)
|
|
14240
|
+
return helper;
|
|
14241
|
+
const view = this.view(task.data, false);
|
|
14242
|
+
if (!view.ok)
|
|
14243
|
+
return view;
|
|
14244
|
+
return success(view.revision, view.workspaceRevision, compactTaskContext(view.data));
|
|
14245
|
+
}
|
|
14246
|
+
view(task, capture = true) {
|
|
14227
14247
|
const workspace = this.store.readWorkspace();
|
|
14228
14248
|
if (!workspace.ok)
|
|
14229
14249
|
return workspace;
|
|
14230
14250
|
if (!workspace.data)
|
|
14231
14251
|
return failure("not_found", "workspace not found");
|
|
14232
14252
|
const historical = task.status === "closed" ? task.candidates.at(-1) : undefined;
|
|
14233
|
-
const current = historical ? success(null, null, historical) : captureCandidate(this.store.root, task.intent.data.scope, environment());
|
|
14253
|
+
const current = historical ? success(null, null, historical) : capture ? captureCandidate(this.store.root, task.intent.data.scope, environment()) : success(null, null, task.candidates.at(-1) ?? null);
|
|
14234
14254
|
if (!current.ok)
|
|
14235
14255
|
return current;
|
|
14236
14256
|
const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
|
|
@@ -14502,6 +14522,137 @@ class WorkitCore {
|
|
|
14502
14522
|
return this.summary(changed.data.task);
|
|
14503
14523
|
}
|
|
14504
14524
|
}
|
|
14525
|
+
|
|
14526
|
+
// packages/workit-core/src/core/session-context.ts
|
|
14527
|
+
var cache = new Map;
|
|
14528
|
+
// packages/workit-core/src/core/route-intent.ts
|
|
14529
|
+
var shellBranchTarget = (command) => {
|
|
14530
|
+
if (!command.trim() || /[\r\n;&|<>`'"$()\\*?{}~!#]/u.test(command) || command.includes("[") || command.includes("]"))
|
|
14531
|
+
return null;
|
|
14532
|
+
const args = command.trim().split(/\s+/u);
|
|
14533
|
+
if (args[0] !== "git")
|
|
14534
|
+
return null;
|
|
14535
|
+
const option = args[2];
|
|
14536
|
+
const target = args[1] === "switch" && ["-c", "--create", "-C", "--force-create"].includes(option ?? "") && (args.length === 4 || args.length === 5) ? args[3] : args[1] === "checkout" && ["-b", "-B"].includes(option ?? "") && (args.length === 4 || args.length === 5) ? args[3] : args[1] === "branch" && (args.length === 3 || args.length === 4) ? args[2] : null;
|
|
14537
|
+
return target && !target.startsWith("-") ? target : null;
|
|
14538
|
+
};
|
|
14539
|
+
var shellBranchPolicyViolation = (root, command) => {
|
|
14540
|
+
const target = shellBranchTarget(command);
|
|
14541
|
+
return target === null ? null : validateBranchNameFor(root, target);
|
|
14542
|
+
};
|
|
14543
|
+
// packages/workit-core/src/core/external-action.ts
|
|
14544
|
+
var chainStepSchema = union([
|
|
14545
|
+
string2().min(1),
|
|
14546
|
+
object({ branch: string2().min(1) }).strict(),
|
|
14547
|
+
object({ pr: literal(true) }).strict()
|
|
14548
|
+
]);
|
|
14549
|
+
var externalActionSchema = discriminatedUnion("operation", [
|
|
14550
|
+
object({
|
|
14551
|
+
operation: literal("git.branch_setup"),
|
|
14552
|
+
payload: object({
|
|
14553
|
+
action: _enum(["setup", "reapply_stash"]).optional(),
|
|
14554
|
+
sdd_dir: string2().optional(),
|
|
14555
|
+
target_branch: string2().optional(),
|
|
14556
|
+
stash: _enum(["yes", "no"]).optional(),
|
|
14557
|
+
cwd: string2().min(1).optional()
|
|
14558
|
+
}).strict()
|
|
14559
|
+
}).strict(),
|
|
14560
|
+
object({
|
|
14561
|
+
operation: literal("git.commit"),
|
|
14562
|
+
payload: object({
|
|
14563
|
+
message: string2().min(1).optional(),
|
|
14564
|
+
plan_steps: array(chainStepSchema).min(1).max(32).optional(),
|
|
14565
|
+
plan_branch: string2().min(1).optional(),
|
|
14566
|
+
cwd: string2().min(1).optional()
|
|
14567
|
+
}).strict()
|
|
14568
|
+
}).strict(),
|
|
14569
|
+
object({
|
|
14570
|
+
operation: literal("git.push"),
|
|
14571
|
+
payload: object({ branch: string2().min(1).optional(), cwd: string2().min(1).optional() }).strict()
|
|
14572
|
+
}).strict(),
|
|
14573
|
+
object({
|
|
14574
|
+
operation: literal("hosting.pull_request"),
|
|
14575
|
+
payload: object({
|
|
14576
|
+
title: string2().min(1),
|
|
14577
|
+
body: string2().optional(),
|
|
14578
|
+
draft: boolean2().optional(),
|
|
14579
|
+
target_branch: string2().optional(),
|
|
14580
|
+
babysit: boolean2().optional(),
|
|
14581
|
+
cwd: string2().min(1).optional()
|
|
14582
|
+
}).strict()
|
|
14583
|
+
}).strict(),
|
|
14584
|
+
object({
|
|
14585
|
+
operation: literal("hosting.merge"),
|
|
14586
|
+
payload: object({
|
|
14587
|
+
target_branch: string2().min(1).optional(),
|
|
14588
|
+
source_branch: string2().min(1).optional(),
|
|
14589
|
+
cwd: string2().min(1).optional()
|
|
14590
|
+
}).strict()
|
|
14591
|
+
}).strict(),
|
|
14592
|
+
object({
|
|
14593
|
+
operation: literal("hosting.delete_branch"),
|
|
14594
|
+
payload: object({ branch: string2().min(1), cwd: string2().min(1).optional() }).strict()
|
|
14595
|
+
}).strict(),
|
|
14596
|
+
object({
|
|
14597
|
+
operation: literal("youtrack.update"),
|
|
14598
|
+
payload: object({
|
|
14599
|
+
issueId: string2().min(1),
|
|
14600
|
+
markdown: string2().min(1),
|
|
14601
|
+
minutes: number2().positive().optional()
|
|
14602
|
+
}).strict()
|
|
14603
|
+
}).strict(),
|
|
14604
|
+
object({
|
|
14605
|
+
operation: literal("youtrack.time"),
|
|
14606
|
+
payload: object({
|
|
14607
|
+
issueId: string2().min(1),
|
|
14608
|
+
minutes: number2().positive(),
|
|
14609
|
+
text: string2().optional(),
|
|
14610
|
+
dateMs: number2().finite().optional()
|
|
14611
|
+
}).strict()
|
|
14612
|
+
}).strict(),
|
|
14613
|
+
object({
|
|
14614
|
+
operation: literal("youtrack.meeting"),
|
|
14615
|
+
payload: object({
|
|
14616
|
+
issueId: string2().min(1),
|
|
14617
|
+
minutes: number2().positive(),
|
|
14618
|
+
text: string2().min(1)
|
|
14619
|
+
}).strict()
|
|
14620
|
+
}).strict(),
|
|
14621
|
+
object({
|
|
14622
|
+
operation: literal("changelog.apply"),
|
|
14623
|
+
payload: object({
|
|
14624
|
+
entries: union([
|
|
14625
|
+
record(string2(), array(string2())),
|
|
14626
|
+
array(object({ category: string2().min(1), text: string2().min(1) }).strict())
|
|
14627
|
+
]).optional(),
|
|
14628
|
+
path: string2().optional(),
|
|
14629
|
+
normalize_only: boolean2().optional()
|
|
14630
|
+
}).strict()
|
|
14631
|
+
}).strict(),
|
|
14632
|
+
object({
|
|
14633
|
+
operation: literal("context.read"),
|
|
14634
|
+
payload: object({
|
|
14635
|
+
kind: _enum([
|
|
14636
|
+
"git",
|
|
14637
|
+
"pr",
|
|
14638
|
+
"youtrack",
|
|
14639
|
+
"github_issue",
|
|
14640
|
+
"gitlab_issue",
|
|
14641
|
+
"changelog",
|
|
14642
|
+
"release",
|
|
14643
|
+
"affected"
|
|
14644
|
+
]),
|
|
14645
|
+
range: string2().optional(),
|
|
14646
|
+
issueId: string2().optional(),
|
|
14647
|
+
issueUrl: string2().optional(),
|
|
14648
|
+
issueRef: string2().optional(),
|
|
14649
|
+
mode: string2().optional(),
|
|
14650
|
+
specPath: string2().optional(),
|
|
14651
|
+
planPath: string2().optional(),
|
|
14652
|
+
cwd: string2().min(1).optional()
|
|
14653
|
+
}).strict()
|
|
14654
|
+
}).strict()
|
|
14655
|
+
]);
|
|
14505
14656
|
// packages/workit-codex/hooks/workit-hook.ts
|
|
14506
14657
|
var ref = (host, handle) => ({ kind: "host", host, handle });
|
|
14507
14658
|
function detectCodexSurface(env) {
|
|
@@ -14705,7 +14856,7 @@ var activeTask = (store) => {
|
|
|
14705
14856
|
return { ok: true, task: active[0], workspace: workspace.data };
|
|
14706
14857
|
};
|
|
14707
14858
|
var historyOfferSessions = new Set;
|
|
14708
|
-
var
|
|
14859
|
+
var unfinishedTaskOffer2 = (input, excludedTaskId) => {
|
|
14709
14860
|
if (input.source !== "startup" || historyOfferSessions.has(input.session_id))
|
|
14710
14861
|
return null;
|
|
14711
14862
|
historyOfferSessions.add(input.session_id);
|
|
@@ -14740,21 +14891,16 @@ var sessionContext = (input) => {
|
|
|
14740
14891
|
capabilities: codexCapabilities(detectCodexSurface(process.env), { sessionStart: true }),
|
|
14741
14892
|
constraints: [],
|
|
14742
14893
|
now: new Date().toISOString().replace(/\.\d{3}Z$/, "Z")
|
|
14743
|
-
}).task
|
|
14744
|
-
schemaVersion: 1,
|
|
14745
|
-
action: "inspect",
|
|
14746
|
-
taskId: state.task.id,
|
|
14747
|
-
view: "full"
|
|
14748
|
-
});
|
|
14894
|
+
}).compactContext(state.task.id);
|
|
14749
14895
|
if (view.ok)
|
|
14750
14896
|
compact = `
|
|
14751
|
-
<workit-task-context>${
|
|
14897
|
+
<workit-task-context>${view.data}</workit-task-context>`;
|
|
14752
14898
|
}
|
|
14753
14899
|
} catch {
|
|
14754
14900
|
compact = `
|
|
14755
14901
|
[workit diagnostic: task state unavailable]`;
|
|
14756
14902
|
}
|
|
14757
|
-
const offer =
|
|
14903
|
+
const offer = unfinishedTaskOffer2(input, currentTaskId);
|
|
14758
14904
|
return `<workit-contract>
|
|
14759
14905
|
${invariantBootstrap()}${compact}${offer ? `
|
|
14760
14906
|
${offer}` : ""}
|