@brainervirus/workit-cursor 2.1.1 → 2.1.3

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.
@@ -8956,6 +8956,57 @@ class TaskStore {
8956
8956
  }
8957
8957
  return success(null, null, tasks);
8958
8958
  }
8959
+ listTaskIndex() {
8960
+ if (!fs4.existsSync(this.tasksDir))
8961
+ return success(null, null, []);
8962
+ let names;
8963
+ try {
8964
+ names = fs4.readdirSync(this.tasksDir).filter((name) => name.endsWith(".json"));
8965
+ } catch (error) {
8966
+ return failure("storage_error", `unable to list tasks: ${String(error)}`, {
8967
+ path: this.tasksDir
8968
+ });
8969
+ }
8970
+ const workspace = this.readWorkspace();
8971
+ if (!workspace.ok)
8972
+ return workspace;
8973
+ if (!workspace.data)
8974
+ return failure("recovery_required", "task workspace binding is invalid", {
8975
+ path: this.workspacePath
8976
+ });
8977
+ const stored = this.readIndex(workspace.data.id);
8978
+ const entries = [];
8979
+ let changed = Object.keys(stored).length !== names.length;
8980
+ for (const name of names.sort()) {
8981
+ const file = path4.join(this.tasksDir, name);
8982
+ const signature = fileSignature(file);
8983
+ if (signature === null) {
8984
+ changed = true;
8985
+ continue;
8986
+ }
8987
+ const cached = stored[name.slice(0, -5)];
8988
+ if (cached && cached.file === signature && !racySignature(signature)) {
8989
+ entries.push(cached);
8990
+ continue;
8991
+ }
8992
+ const item = this.readRecord(file, taskRecordSchema);
8993
+ if (!item.exists)
8994
+ continue;
8995
+ if (!item.result.ok)
8996
+ return item.result;
8997
+ if (!validId(name.slice(0, -5)) || item.result.data.id !== name.slice(0, -5))
8998
+ return failure("recovery_required", "task filename and record ID differ", { path: name });
8999
+ if (item.result.data.workspaceId !== workspace.data.id)
9000
+ return failure("recovery_required", "task workspace binding is invalid", { path: name });
9001
+ const entry = indexEntry(item.result.data, signature);
9002
+ if (!cached || canonicalJson(cached) !== canonicalJson(entry))
9003
+ changed = true;
9004
+ entries.push(entry);
9005
+ }
9006
+ if (changed)
9007
+ this.writeIndex(workspace.data.id, entries);
9008
+ return success(null, null, entries);
9009
+ }
8959
9010
  readWorkspace() {
8960
9011
  const item = this.readRecord(this.workspacePath, workspaceRecordSchema);
8961
9012
  if (!item.exists)
@@ -9635,6 +9686,8 @@ class TaskStore {
9635
9686
  fs4.renameSync(temporary, file);
9636
9687
  temporary = undefined;
9637
9688
  this.fsyncDirectory(path4.dirname(file));
9689
+ if (path4.dirname(file) === this.tasksDir)
9690
+ this.indexTaskWrite(file, value);
9638
9691
  return success(null, null, value);
9639
9692
  } catch (error) {
9640
9693
  return failure("storage_error", `snapshot replacement failed: ${String(error)}`, {
@@ -9647,6 +9700,45 @@ class TaskStore {
9647
9700
  } catch {}
9648
9701
  }
9649
9702
  }
9703
+ readIndex(workspaceId) {
9704
+ try {
9705
+ const value = JSON.parse(fs4.readFileSync(this.indexPath, "utf8"));
9706
+ if (!isObject2(value) || value.version !== INDEX_VERSION || value.workspaceId !== workspaceId || !isObject2(value.tasks))
9707
+ return {};
9708
+ const tasks = {};
9709
+ for (const [id, entry] of Object.entries(value.tasks))
9710
+ 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))
9711
+ tasks[id] = entry;
9712
+ return tasks;
9713
+ } catch {
9714
+ return {};
9715
+ }
9716
+ }
9717
+ writeIndex(workspaceId, entries) {
9718
+ const temporary = `${this.indexPath}.${process.pid}.${randomUUID2()}.tmp`;
9719
+ try {
9720
+ fs4.writeFileSync(temporary, JSON.stringify({
9721
+ version: INDEX_VERSION,
9722
+ workspaceId,
9723
+ tasks: Object.fromEntries(entries.map((entry) => [entry.id, entry]))
9724
+ }), { mode: 384, flag: "wx" });
9725
+ fs4.renameSync(temporary, this.indexPath);
9726
+ } catch {
9727
+ try {
9728
+ fs4.unlinkSync(temporary);
9729
+ } catch {}
9730
+ }
9731
+ }
9732
+ indexTaskWrite(file, task) {
9733
+ try {
9734
+ const signature = fileSignature(file);
9735
+ if (signature === null)
9736
+ return;
9737
+ const tasks = this.readIndex(task.workspaceId);
9738
+ tasks[task.id] = indexEntry(task, signature);
9739
+ this.writeIndex(task.workspaceId, Object.values(tasks));
9740
+ } catch {}
9741
+ }
9650
9742
  saveRecovery(file, bytes) {
9651
9743
  const target = path4.basename(file) === "workspace.json" ? "workspace" : "task";
9652
9744
  const id = target === "task" ? path4.basename(file, ".json") : "workspace";
@@ -9805,6 +9897,9 @@ class TaskStore {
9805
9897
  get workspacePath() {
9806
9898
  return path4.join(this.workitDir, "workspace.json");
9807
9899
  }
9900
+ get indexPath() {
9901
+ return path4.join(this.workitDir, "index.json");
9902
+ }
9808
9903
  get lockPath() {
9809
9904
  return path4.join(this.workitDir, "metadata.lock");
9810
9905
  }
@@ -9812,7 +9907,40 @@ class TaskStore {
9812
9907
  return path4.join(this.workitDir, ".gitignore");
9813
9908
  }
9814
9909
  }
9815
- var processEvidenceSchema, metadataLockSchema, now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"), cachedRuntimeVersion = null, runtimeVersion = () => {
9910
+ var processEvidenceSchema, metadataLockSchema, INDEX_VERSION = 1, hostSession = (value) => isObject2(value) && value.kind === "host" && typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null, indexEntry = (task, file) => {
9911
+ const sessions = [];
9912
+ const intent = hostSession(task.intent.provenance.session);
9913
+ if (intent)
9914
+ sessions.push({ ...intent, workerId: null });
9915
+ for (const worker of task.workers) {
9916
+ const session = hostSession(worker.data.session);
9917
+ if (session)
9918
+ sessions.push({ ...session, workerId: worker.id });
9919
+ }
9920
+ return {
9921
+ id: task.id,
9922
+ revision: task.revision,
9923
+ status: task.status,
9924
+ createdAt: task.createdAt,
9925
+ updatedAt: task.updatedAt,
9926
+ objective: task.intent.data.objective,
9927
+ source: { host: task.intent.provenance.host, kind: task.intent.provenance.kind },
9928
+ progress: { summary: task.progress.summary, nextAction: task.progress.nextAction },
9929
+ sessions,
9930
+ file
9931
+ };
9932
+ }, fileSignature = (file) => {
9933
+ try {
9934
+ const stat = fs4.statSync(file, { bigint: true });
9935
+ return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
9936
+ } catch {
9937
+ return null;
9938
+ }
9939
+ }, RACY_WINDOW_NS = 2000000000n, racySignature = (signature) => {
9940
+ const [mtime = 0n, ctime = 0n] = signature.split(":").slice(3, 5).map(BigInt);
9941
+ const changed = mtime > ctime ? mtime : ctime;
9942
+ return BigInt(Date.now()) * 1000000n - changed < RACY_WINDOW_NS;
9943
+ }, now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"), cachedRuntimeVersion = null, runtimeVersion = () => {
9816
9944
  if (cachedRuntimeVersion === null) {
9817
9945
  try {
9818
9946
  const pkg = JSON.parse(fs4.readFileSync(path4.join(packageRoot(), "package.json"), "utf8"));
@@ -9892,62 +10020,6 @@ var init_task_store = __esm(() => {
9892
10020
  externalActionLockRoots = new AsyncLocalStorage;
9893
10021
  });
9894
10022
 
9895
- // packages/workit-core/src/core/workers.ts
9896
- function assertProductWriteAllowed(input) {
9897
- if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
9898
- return failure("invalid_input", "invalid product write path");
9899
- if (!input.store)
9900
- return failure("permission_denied", "write authorization requires core state");
9901
- const task = input.store.readTask(input.task.id);
9902
- if (!task.ok)
9903
- return task;
9904
- const workspace = input.store.readWorkspace();
9905
- if (!workspace.ok)
9906
- return workspace;
9907
- if (!workspace.data)
9908
- return failure("not_found", "workspace not found");
9909
- const workspaceRecord = workspace.data;
9910
- if (task.data.status !== "active")
9911
- return failure("invalid_transition", "paused or closed tasks cannot own product writes");
9912
- if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
9913
- return failure("recovery_required", "task and workspace bindings are invalid");
9914
- const writer = workspaceRecord.writer;
9915
- if (!writer)
9916
- return failure("permission_denied", "checkout has no writer owner");
9917
- if (writer.state === "uncertain")
9918
- return failure("recovery_required", "checkout writer requires recovery", {
9919
- owner: writer.owner
9920
- });
9921
- const caller = callerValue(input.caller);
9922
- const workerId = workerIdOf(input.caller);
9923
- const owner = writer.owner;
9924
- if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
9925
- return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
9926
- const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
9927
- if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
9928
- return failure("permission_denied", "only implementers can own product writes");
9929
- 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))))
9930
- return failure("recovery_required", "worker session is not an active writer");
9931
- return success(null, workspaceRecord.revision, owner);
9932
- }
9933
- var same = (left, right) => {
9934
- try {
9935
- return canonicalJson(left) === canonicalJson(right);
9936
- } catch {
9937
- return false;
9938
- }
9939
- }, UNCERTAIN_WORKER_STATES, isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state), workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state), callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor }, validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
9940
- const code = char.charCodeAt(0);
9941
- return code < 32 || code === 127;
9942
- }) && !value.split(/[\\/]/).includes(".."), callerValue = (value) => ({ host: value.host, actor: value.actor }), workerIdOf = (value) => value.workerId ?? null, currentWriterOwnsTask = (task, workspace, caller) => {
9943
- const owner = workspace.writer?.owner;
9944
- return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
9945
- };
9946
- var init_workers = __esm(() => {
9947
- init_task_contract();
9948
- UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
9949
- });
9950
-
9951
10023
  // packages/workit-core/src/core/boundary.ts
9952
10024
  var EVENT, errorDetail = (err) => {
9953
10025
  if (err instanceof Error) {
@@ -10058,7 +10130,6 @@ var diagnosticLogger, PRESETS, mergePreset = (preset, input = {}, current = {
10058
10130
  config: {
10059
10131
  locale,
10060
10132
  localeOptions: Array.isArray(input.localeOptions) ? input.localeOptions : DEFAULTS.localeOptions,
10061
- timezone: input.timezone ?? DEFAULTS.timezone,
10062
10133
  branchPolicy: mergePreset(preset, {
10063
10134
  allowed: Array.isArray(input.branchPolicy?.allowed) ? input.branchPolicy.allowed : undefined,
10064
10135
  protectedNames: Array.isArray(input.branchPolicy?.protected) ? input.branchPolicy.protected : undefined
@@ -10117,7 +10188,6 @@ var init_config = __esm(() => {
10117
10188
  DEFAULTS = {
10118
10189
  locale: "en",
10119
10190
  localeOptions: ["en", "es-CL", "es-MX", "es-AR", "pt-BR"],
10120
- timezone: "America/Santiago",
10121
10191
  branchPolicy: {
10122
10192
  preset: "gitflow",
10123
10193
  allowed: [...PRESETS.gitflow.allowed],
@@ -10693,6 +10763,62 @@ var init_branch = __esm(() => {
10693
10763
  init_vcs_config();
10694
10764
  });
10695
10765
 
10766
+ // packages/workit-core/src/core/workers.ts
10767
+ function assertProductWriteAllowed(input) {
10768
+ if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
10769
+ return failure("invalid_input", "invalid product write path");
10770
+ if (!input.store)
10771
+ return failure("permission_denied", "write authorization requires core state");
10772
+ const task = input.store.readTask(input.task.id);
10773
+ if (!task.ok)
10774
+ return task;
10775
+ const workspace = input.store.readWorkspace();
10776
+ if (!workspace.ok)
10777
+ return workspace;
10778
+ if (!workspace.data)
10779
+ return failure("not_found", "workspace not found");
10780
+ const workspaceRecord = workspace.data;
10781
+ if (task.data.status !== "active")
10782
+ return failure("invalid_transition", "paused or closed tasks cannot own product writes");
10783
+ if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
10784
+ return failure("recovery_required", "task and workspace bindings are invalid");
10785
+ const writer = workspaceRecord.writer;
10786
+ if (!writer)
10787
+ return failure("permission_denied", "checkout has no writer owner");
10788
+ if (writer.state === "uncertain")
10789
+ return failure("recovery_required", "checkout writer requires recovery", {
10790
+ owner: writer.owner
10791
+ });
10792
+ const caller = callerValue(input.caller);
10793
+ const workerId = workerIdOf(input.caller);
10794
+ const owner = writer.owner;
10795
+ if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
10796
+ return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
10797
+ const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
10798
+ if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
10799
+ return failure("permission_denied", "only implementers can own product writes");
10800
+ 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))))
10801
+ return failure("recovery_required", "worker session is not an active writer");
10802
+ return success(null, workspaceRecord.revision, owner);
10803
+ }
10804
+ var same = (left, right) => {
10805
+ try {
10806
+ return canonicalJson(left) === canonicalJson(right);
10807
+ } catch {
10808
+ return false;
10809
+ }
10810
+ }, UNCERTAIN_WORKER_STATES, isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state), workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state), callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor }, validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
10811
+ const code = char.charCodeAt(0);
10812
+ return code < 32 || code === 127;
10813
+ }) && !value.split(/[\\/]/).includes(".."), callerValue = (value) => ({ host: value.host, actor: value.actor }), workerIdOf = (value) => value.workerId ?? null, currentWriterOwnsTask = (task, workspace, caller) => {
10814
+ const owner = workspace.writer?.owner;
10815
+ return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
10816
+ };
10817
+ var init_workers = __esm(() => {
10818
+ init_task_contract();
10819
+ UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
10820
+ });
10821
+
10696
10822
  // packages/workit-core/src/core/auto-approval.ts
10697
10823
  import { createHash as createHash4 } from "node:crypto";
10698
10824
  import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
@@ -11372,350 +11498,231 @@ var init_authority = __esm(() => {
11372
11498
  verifyDecisionContent = verifyContentRefs;
11373
11499
  });
11374
11500
 
11375
- // packages/workit-core/src/core/task-evaluation.ts
11376
- import { createHash as createHash6 } from "node:crypto";
11377
- import * as fs7 from "node:fs";
11378
- import { spawnSync as spawnSync2 } from "node:child_process";
11379
- import path10 from "node:path";
11380
- function captureCandidate(root, scope, environment = []) {
11381
- let checkout;
11382
- try {
11383
- checkout = fs7.realpathSync(root);
11384
- } catch {
11385
- return failure("invalid_input", "candidate root does not exist");
11386
- }
11387
- const normalizedScope = canonicalScope(scope);
11388
- if (!normalizedScope)
11389
- 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" }] });
11390
- const roots = scopeRoots(checkout, normalizedScope);
11391
- if (!roots.ok)
11392
- return roots;
11393
- const git = gitPaths(checkout);
11394
- const names = new Set;
11395
- let uncertain = git.uncertain;
11396
- for (const target of roots.data) {
11397
- const item = relative(checkout, target);
11398
- if (item === ".git" || item === ".workit")
11399
- continue;
11400
- names.add(item);
11401
- if (!fs7.existsSync(target))
11402
- continue;
11403
- let stat;
11404
- try {
11405
- stat = fs7.lstatSync(target);
11406
- } catch {
11407
- uncertain = true;
11408
- continue;
11409
- }
11410
- if (!git.git && stat.isDirectory() && !stat.isSymbolicLink())
11411
- uncertain = walk(checkout, target, names) || uncertain;
11412
- }
11413
- for (const item of git.paths)
11414
- if (scopeMatches(item, normalizedScope))
11415
- names.add(item);
11416
- const files = [];
11417
- let completeness = uncertain ? "uncertain" : "known";
11418
- for (const item of [...names].sort()) {
11419
- if (!scopeMatches(item, normalizedScope))
11420
- continue;
11421
- const target = path10.join(checkout, item);
11422
- try {
11423
- const stat = fs7.lstatSync(target);
11424
- if (stat.isSymbolicLink()) {
11425
- const link = fs7.readlinkSync(target);
11426
- files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
11427
- } else if (stat.isFile()) {
11428
- files.push({
11429
- path: item,
11430
- kind: "file",
11431
- digest: digestBytes3(fs7.readFileSync(target)),
11432
- executable: (stat.mode & 73) !== 0
11433
- });
11434
- } else if (!stat.isDirectory()) {
11435
- completeness = "uncertain";
11436
- } else if (git.git && git.paths.includes(item)) {
11437
- completeness = "uncertain";
11438
- }
11439
- } catch (error) {
11440
- if (error?.code === "ENOENT") {
11441
- files.push({ path: item, kind: "absent", digest: null, executable: null });
11442
- } else
11443
- completeness = "uncertain";
11444
- }
11445
- }
11446
- 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) => ({
11447
- name,
11448
- value: environment[name] ?? null
11449
- }));
11450
- const namesByEnvironment = supplied.map(({ name }) => name);
11451
- if (new Set(namesByEnvironment).size !== namesByEnvironment.length || namesByEnvironment.some((name) => typeof name !== "string" || !name))
11452
- return failure("invalid_input", "candidate environment names must be unique and non-empty");
11453
- const values = supplied.sort((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
11454
- if (values.some(({ value }) => value === null))
11455
- completeness = "uncertain";
11456
- const headResult = spawnSync2("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
11457
- if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
11458
- completeness = "uncertain";
11459
- const head = headResult.status === 0 ? headResult.stdout.trim() : null;
11460
- const partial = {
11461
- id: "0".repeat(64),
11462
- scope: normalizedScope,
11463
- completeness,
11464
- files,
11465
- environment: values,
11466
- head
11501
+ // packages/workit-core/src/core/policy-resolver.ts
11502
+ function normalizeResolverInput(input) {
11503
+ const parsed = resolverInputSchema.safeParse(input);
11504
+ if (!parsed.success)
11505
+ return failure("invalid_input", "resolver input is invalid", invalidFields(parsed.error));
11506
+ const value = parsed.data;
11507
+ const signals = value.assessment.signals;
11508
+ if (signals.behaviorChange.value === true && signals.mechanicalLowRisk.value === true)
11509
+ return failure("invalid_input", "behavioral change cannot be wholly mechanical", {
11510
+ fields: [
11511
+ {
11512
+ path: "assessment.signals",
11513
+ reason: "contradictory behaviorChange and mechanicalLowRisk"
11514
+ }
11515
+ ]
11516
+ });
11517
+ const ids = value.constraints.map((constraint) => constraint.id);
11518
+ if (new Set(ids).size !== ids.length)
11519
+ return failure("invalid_input", "constraint IDs must be unique", {
11520
+ fields: [{ path: "constraints", reason: "duplicate constraint id" }]
11521
+ });
11522
+ if (value.preferences?.fast && value.preferences.thorough)
11523
+ return failure("invalid_input", "fast and thorough preferences contradict", {
11524
+ fields: [{ path: "preferences", reason: "fast and thorough cannot both be enabled" }]
11525
+ });
11526
+ const normalized = {
11527
+ intent: normalizeIntent(value.intent),
11528
+ assessment: normalizeAssessment(value.assessment),
11529
+ constraints: stableList(value.constraints.map(normalizeConstraint)),
11530
+ prior: normalizePrior(value.prior),
11531
+ ...value.preferences ? { preferences: { ...value.preferences } } : {},
11532
+ policyVersion: POLICY_VERSION
11467
11533
  };
11468
- const candidate = { ...partial, id: candidateDigest(partial) };
11469
- const parsed = candidateSchema.safeParse(candidate);
11470
- return parsed.success ? success(null, null, parsed.data) : failure("invalid_input", "captured candidate is invalid");
11534
+ return success(null, null, normalized);
11471
11535
  }
11472
- function evaluateEvidence(task, candidate) {
11473
- const current = candidate ?? task.candidates.at(-1) ?? null;
11474
- return task.evidence.map((entry) => {
11475
- const evidence = entry.data;
11476
- if (evidence.result === "failed")
11477
- return {
11478
- evidenceId: entry.id,
11479
- status: "failed",
11480
- reason: "failed evidence remains historical"
11481
- };
11482
- if (evidence.kind === "check" || evidence.kind === "review") {
11483
- if (!evidence.beforeCandidateId || !evidence.candidateId)
11484
- return {
11485
- evidenceId: entry.id,
11486
- status: "stale",
11487
- reason: "check or review evidence lacks start and end candidates"
11488
- };
11489
- if (evidence.beforeCandidateId !== evidence.candidateId)
11490
- return {
11491
- evidenceId: entry.id,
11492
- status: "stale",
11493
- reason: "candidate changed during the check or review"
11494
- };
11536
+ function resolveRequirements(input) {
11537
+ const { signals } = input.assessment;
11538
+ const scope = input.intent.scope;
11539
+ const requirements = [];
11540
+ const hasUnknownSignal = Object.values(signals).some((signal) => signal.value === "unknown");
11541
+ const hasUnknownFact = [
11542
+ ...input.assessment.facts,
11543
+ ...input.assessment.consequences.map(({ fact }) => fact)
11544
+ ].some((fact) => fact.basis === "unknown");
11545
+ if (hasUnknownSignal || hasUnknownFact)
11546
+ requirements.push(requirement({
11547
+ ruleId: "consequential-unknown",
11548
+ dimension: "investigation",
11549
+ scope,
11550
+ reason: `Resolve consequential uncertainty before the dependent action: ${[
11551
+ ...Object.entries(signals).filter(([, signal]) => signal.value === "unknown").map(([name]) => name),
11552
+ ...hasUnknownFact ? ["unknown fact"] : []
11553
+ ].join(", ")}`,
11554
+ satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
11555
+ before: "dependent_action",
11556
+ dependentAction: null,
11557
+ acceptanceAllowed: false
11558
+ }));
11559
+ if (signals.productChoiceOpen.value === true)
11560
+ requirements.push(requirement({
11561
+ ruleId: "product-decision",
11562
+ dimension: "decisions",
11563
+ scope,
11564
+ reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
11565
+ satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
11566
+ before: "dependent_action",
11567
+ dependentAction: null,
11568
+ acceptanceAllowed: false
11569
+ }));
11570
+ const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
11571
+ if (signals.behaviorChange.value === true)
11572
+ requirements.push(requirement({
11573
+ ruleId: "behavioral-verification",
11574
+ dimension: "testing",
11575
+ scope,
11576
+ reason: `Behavior, side effects, permissions, or data handling may change: ${signalReason(input, "behaviorChange")}`,
11577
+ satisfaction: "A useful behavioral check passes against the affected behavior and current candidate.",
11578
+ before: "close",
11579
+ dependentAction: null,
11580
+ acceptanceAllowed: false
11581
+ }), requirement({
11582
+ ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
11583
+ dimension: "review",
11584
+ scope,
11585
+ 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.",
11586
+ 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.",
11587
+ before: "close",
11588
+ dependentAction: null,
11589
+ acceptanceAllowed: !independentBehaviorReview
11590
+ }));
11591
+ else if (signals.mechanicalLowRisk.value === true)
11592
+ requirements.push(requirement({
11593
+ ruleId: "mechanical-existing-checks",
11594
+ dimension: "verification",
11595
+ scope,
11596
+ reason: `The change is demonstrably mechanical and low-risk: ${signalReason(input, "mechanicalLowRisk")}`,
11597
+ satisfaction: "Run the relevant existing checks for affected consumers; do not add tautological tests.",
11598
+ before: "close",
11599
+ dependentAction: null,
11600
+ acceptanceAllowed: true
11601
+ }), requirement({
11602
+ ruleId: input.preferences?.thorough ? "fresh-context-review" : "self-review",
11603
+ dimension: "review",
11604
+ scope,
11605
+ 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.",
11606
+ 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.",
11607
+ before: "close",
11608
+ dependentAction: null,
11609
+ acceptanceAllowed: !input.preferences?.thorough
11610
+ }));
11611
+ if (signals.behaviorChange.value === true || signals.mechanicalLowRisk.value === true)
11612
+ requirements.push(requirement({
11613
+ ruleId: "pre-pr-cleanup",
11614
+ dimension: "verification",
11615
+ scope,
11616
+ reason: "Implementation output must be deslopped before it is delivered.",
11617
+ satisfaction: "Run the workit-deslop pass and record passing check evidence, or record an approved limitation decision waiving cleanup.",
11618
+ before: "dependent_action",
11619
+ dependentAction: "hosting.pull_request",
11620
+ acceptanceAllowed: true
11621
+ }));
11622
+ if (signals.durableAgreementNeeded.value === true)
11623
+ requirements.push(requirement({
11624
+ ruleId: "durable-spec",
11625
+ dimension: "artifacts",
11626
+ scope,
11627
+ reason: `The work needs a durable agreement about intended behavior: ${signalReason(input, "durableAgreementNeeded")}`,
11628
+ satisfaction: "Record the agreed behavior in a durable specification before implementation writes.",
11629
+ before: "write",
11630
+ dependentAction: null,
11631
+ acceptanceAllowed: true
11632
+ }));
11633
+ if (signals.coordinationPlanNeeded.value === true)
11634
+ requirements.push(requirement({
11635
+ ruleId: "coordination-plan",
11636
+ dimension: "continuity",
11637
+ scope,
11638
+ reason: `Dependencies, sequencing, or resumption need durable coordination: ${signalReason(input, "coordinationPlanNeeded")}`,
11639
+ satisfaction: "Record useful sequencing, dependencies, and the next action in a compact plan.",
11640
+ before: "dependent_action",
11641
+ dependentAction: null,
11642
+ acceptanceAllowed: true
11643
+ }));
11644
+ if (signals.helperUseful.value === true && !input.preferences?.fast)
11645
+ requirements.push(requirement({
11646
+ ruleId: "helper-usefulness",
11647
+ dimension: "delegation",
11648
+ scope,
11649
+ reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
11650
+ satisfaction: "Use a bounded helper only for the independent objective and reconcile its result.",
11651
+ before: "dependent_action",
11652
+ dependentAction: null,
11653
+ acceptanceAllowed: true
11654
+ }));
11655
+ for (const constraint of input.constraints)
11656
+ for (const obligation of constraint.requires) {
11657
+ const dimension = obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge");
11658
+ requirements.push(requirement({
11659
+ ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
11660
+ kind: obligation.kind,
11661
+ scope: normalizedScope(obligation.scope),
11662
+ refs: normalizedRefList(obligation.refs),
11663
+ method: obligation.method,
11664
+ before: obligation.before,
11665
+ dependentAction: obligation.dependentAction
11666
+ })}`,
11667
+ dimension,
11668
+ scope: obligation.scope,
11669
+ reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
11670
+ satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
11671
+ before: obligation.before,
11672
+ dependentAction: obligation.dependentAction,
11673
+ acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
11674
+ }));
11495
11675
  }
11496
- if (!evidence.candidateId || !current)
11497
- return {
11498
- evidenceId: entry.id,
11499
- status: evidence.result,
11500
- reason: "evidence has no candidate binding"
11501
- };
11502
- const observed = task.candidates.find((item) => item.id === evidence.candidateId) ?? (current.id === evidence.candidateId ? current : undefined);
11503
- if (!observed || observed.completeness === "uncertain" || current.completeness === "uncertain")
11504
- return { evidenceId: entry.id, status: "stale", reason: "candidate is missing or uncertain" };
11505
- if (observed.id === current.id)
11506
- return { evidenceId: entry.id, status: evidence.result, reason: "candidate is unchanged" };
11507
- const scopes = evidenceScopes(task, evidence.requirementIds);
11508
- const changed = changedPaths(observed, current);
11509
- const environmentChanged = JSON.stringify(observed.environment) !== JSON.stringify(current.environment);
11510
- if (!scopes.length || environmentChanged || changed.some((item) => scopes.some((scope) => pathRelevant(item, scope))))
11511
- return { evidenceId: entry.id, status: "stale", reason: "relevant candidate state changed" };
11512
- return {
11513
- evidenceId: entry.id,
11514
- status: evidence.result,
11515
- reason: "candidate changed outside evidence scope"
11516
- };
11676
+ const seen = new Set;
11677
+ return requirements.filter((item) => {
11678
+ if (seen.has(item.id))
11679
+ return false;
11680
+ seen.add(item.id);
11681
+ return true;
11517
11682
  });
11518
11683
  }
11519
- function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller) {
11520
- const evidence = evaluateEvidence(task, candidate);
11521
- if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
11522
- return task.policy.requirements.map((requirement) => ({
11523
- requirementId: requirement.id,
11524
- status: "unsatisfied",
11525
- evidenceIds: [],
11526
- decisionIds: [],
11527
- reason: "stored policy version is unsupported; reassessment is required"
11528
- }));
11529
- return (task.policy?.requirements ?? []).map((requirement) => {
11530
- const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
11531
- const passed = related.filter(({ entry, evaluation }) => {
11532
- if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
11533
- return false;
11534
- if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
11535
- return false;
11536
- if (requirement.dimension !== "review")
11537
- return true;
11538
- const reviewSession = sessionFromRef(entry.data.reviewContext);
11539
- const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
11540
- 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));
11541
- return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
11542
- });
11543
- if (passed.length) {
11544
- const currentId = candidate?.id ?? null;
11545
- 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;
11546
- if (withRed.length)
11547
- return {
11548
- requirementId: requirement.id,
11549
- status: "satisfied",
11550
- evidenceIds: withRed.map(({ entry }) => entry.id),
11551
- decisionIds: [],
11552
- reason: "fresh applicable evidence passed"
11553
- };
11554
- if (requirement.dimension === "testing")
11555
- return {
11556
- requirementId: requirement.id,
11557
- status: "unsatisfied",
11558
- evidenceIds: [],
11559
- decisionIds: [],
11560
- reason: "testing requirement has GREEN evidence but no preceding RED"
11561
- };
11562
- }
11563
- if (requirement.dimension === "delegation") {
11564
- const dot = (scope) => scope.paths.length > 0 ? scope : { ...scope, paths: ["."] };
11565
- 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))));
11566
- if (reconciled)
11567
- return {
11568
- requirementId: requirement.id,
11569
- status: "satisfied",
11570
- evidenceIds: [],
11571
- decisionIds: [],
11572
- reason: "bounded helper completed and its result is recorded"
11573
- };
11574
- }
11575
- const decisions = requirement.dimension === "decisions" ? applicableRequirementDecision(task, workspace, requirement, checkoutRoot) : [];
11576
- if (decisions.length)
11577
- return {
11578
- requirementId: requirement.id,
11579
- status: "satisfied",
11580
- evidenceIds: [],
11581
- decisionIds: decisions.map((decision) => decision.id),
11582
- reason: "an applicable approved or stated decision satisfies the requirement"
11583
- };
11584
- const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
11585
- if (limitations.length)
11586
- return {
11587
- requirementId: requirement.id,
11588
- status: "accepted_limitation",
11589
- evidenceIds: [],
11590
- decisionIds: limitations.map((decision) => task.decisions.find((entry) => entry.data === decision).id),
11591
- reason: "an applicable approved limitation permits the missing evidence"
11592
- };
11593
- const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
11594
- if (!unavailable && related.length > 0 && !passed.length)
11595
- return {
11596
- requirementId: requirement.id,
11597
- status: "unsatisfied",
11598
- evidenceIds: related.map(({ entry }) => entry.id),
11599
- decisionIds: [],
11600
- reason: `no passing evidence (needs kind:${expectedKinds(requirement.dimension, requirement.ruleId)})`
11601
- };
11602
- return {
11603
- requirementId: requirement.id,
11604
- status: unavailable ? "unavailable" : "unsatisfied",
11605
- evidenceIds: related.map(({ entry }) => entry.id),
11606
- decisionIds: [],
11607
- reason: unavailable ? "required capability is unavailable" : "no fresh passing evidence"
11608
- };
11609
- });
11610
- }
11611
- function evaluateClosure(requestedOutcome, view) {
11612
- if (requestedOutcome !== "stopped" && !view.task.policy)
11613
- return failure("requirements_unsatisfied", "task has not been assessed");
11614
- if (view.task.policy && view.task.policy.policyVersion !== POLICY_VERSION)
11615
- return failure("requirements_unsatisfied", "stored policy version is unsupported; reassessment is required");
11616
- const evaluations = view.requirements;
11617
- const openFindings = view.task.findings.filter((entry) => entry.data.disposition === "open");
11618
- if (requestedOutcome !== "stopped" && openFindings.length)
11619
- return failure("requirements_unsatisfied", "open findings must be resolved before closure");
11620
- if (requestedOutcome !== "stopped") {
11621
- const evidenceById = new Map(view.evidence.map((entry) => [entry.evidenceId, entry.status]));
11622
- for (const entry of view.task.findings) {
11623
- if (entry.data.disposition === "fixed") {
11624
- const verified = entry.data.resolution?.evidenceIds.some((id) => {
11625
- const evidence = view.task.evidence.find((item) => item.id === id);
11626
- if (!evidence)
11627
- return false;
11628
- return findingVerificationPasses(entry.data.candidateId, {
11629
- kind: evidence.data.kind,
11630
- candidateId: evidence.data.candidateId,
11631
- status: evidenceById.get(id) ?? "missing"
11632
- });
11633
- });
11634
- if (!verified)
11635
- return failure("requirements_unsatisfied", "fixed findings require current verification");
11636
- }
11637
- if (entry.data.disposition === "deferred") {
11638
- const valid = entry.data.resolution?.decisionIds.some((id) => {
11639
- const decisionEntry = view.task.decisions.find((item) => item.id === id);
11640
- const decision = decisionEntry?.data;
11641
- 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)));
11642
- });
11643
- if (!valid)
11644
- return failure("requirements_unsatisfied", "deferred findings require an approved limitation decision that references an acceptanceAllowed requirement and covers the finding scope");
11645
- }
11646
- }
11647
- }
11648
- const blocking = evaluations.filter((item) => {
11649
- if (item.status !== "unsatisfied" && item.status !== "unavailable")
11650
- return false;
11651
- const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
11652
- return !requirement || requirement.before === "close";
11653
- });
11654
- if (requestedOutcome !== "stopped" && blocking.length)
11655
- return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
11656
- requirementIds: blocking.map((item) => item.requirementId),
11657
- requirements: blocking.map((item) => {
11658
- const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
11659
- return {
11660
- requirementId: item.requirementId,
11661
- ruleId: requirement?.ruleId ?? "unknown",
11662
- reason: item.reason,
11663
- satisfaction: requirement?.satisfaction ?? "",
11664
- dependentAction: requirement?.dependentAction ?? null
11665
- };
11666
- })
11667
- });
11668
- const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
11669
- if (requestedOutcome === "verified" && accepted.length)
11670
- return failure("requirements_unsatisfied", "accepted limitations cannot be reported as verified", {
11671
- requirementIds: accepted.map((item) => item.requirementId)
11672
- });
11673
- const evidenceIds = [
11674
- ...new Set([
11675
- ...evaluations.flatMap((item) => item.evidenceIds),
11676
- ...view.task.findings.flatMap((item) => item.data.resolution?.evidenceIds ?? [])
11677
- ])
11678
- ];
11679
- const decisionIds = [
11680
- ...new Set([
11681
- ...evaluations.flatMap((item) => item.decisionIds),
11682
- ...view.task.findings.flatMap((item) => item.data.resolution?.decisionIds ?? [])
11683
- ])
11684
- ];
11685
- return success(null, null, {
11686
- outcome: requestedOutcome === "stopped" ? "stopped" : accepted.length ? "accepted_limitations" : "verified",
11687
- evidenceIds,
11688
- decisionIds,
11689
- requirementIds: evaluations.map((item) => item.requirementId)
11690
- });
11684
+ function resolvePolicy(input) {
11685
+ const normalized = normalizeResolverInput(input);
11686
+ if (!normalized.ok)
11687
+ return normalized;
11688
+ const requirements = resolveRequirements(normalized.data);
11689
+ const policy = {
11690
+ policyVersion: POLICY_VERSION,
11691
+ inputDigest: sha256(canonicalJson(normalized.data)),
11692
+ requirements
11693
+ };
11694
+ const parsed = policySchema.safeParse(policy);
11695
+ if (!parsed.success)
11696
+ return failure("invalid_input", "resolved policy is invalid", invalidFields(parsed.error));
11697
+ return success(null, null, parsed.data);
11691
11698
  }
11692
- var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex"), inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`), relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".", canonicalPath = (value) => {
11693
- if (typeof value !== "string" || !value)
11694
- return null;
11695
- if (value.includes("\\") || value.split("/").includes(".."))
11696
- return null;
11697
- const normalized = path10.posix.normalize(value);
11698
- if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
11699
- return null;
11700
- return normalized === "." ? "." : normalized.replace(/\/$/, "");
11701
- }, canonicalScope = (scope) => {
11702
- const paths = scope.paths.length ? scope.paths : ["."];
11703
- const normalizedPaths = paths.map(canonicalPath);
11704
- const normalizedExclusions = scope.exclusions.map(canonicalPath);
11705
- if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
11699
+ function diffPolicy(previous, next, reason, now) {
11700
+ if (!policySchema.safeParse(previous).success && previous !== null)
11701
+ throw new TypeError("previous policy is invalid");
11702
+ if (!policySchema.safeParse(next).success)
11703
+ throw new TypeError("next policy is invalid");
11704
+ const oldIds = new Set(previous?.requirements.map((requirement) => requirement.id) ?? []);
11705
+ const newIds = new Set(next.requirements.map((requirement) => requirement.id));
11706
+ const added = [...newIds].filter((id) => !oldIds.has(id)).sort(compareCodeUnits2);
11707
+ const retired = [...oldIds].filter((id) => !newIds.has(id)).sort(compareCodeUnits2);
11708
+ if (added.length === 0 && retired.length === 0)
11706
11709
  return null;
11707
- return {
11708
- description: scope.description,
11709
- paths: [...new Set(normalizedPaths)],
11710
- exclusions: [...new Set(normalizedExclusions)]
11710
+ const normalizedReason = reason.trim();
11711
+ const changeReason = retired.length ? `${normalizedReason || "policy reassessed"} (retired: ${retired.join(",")})` : normalizedReason || "policy reassessed";
11712
+ const change = {
11713
+ recordedAt: now,
11714
+ fromInputDigest: previous?.inputDigest ?? null,
11715
+ toInputDigest: next.inputDigest,
11716
+ added,
11717
+ retired,
11718
+ reason: changeReason
11711
11719
  };
11712
- }, excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`)), scopeMatches = (value, scope) => {
11713
- const normalized = canonicalScope(scope);
11714
- if (!normalized)
11715
- return false;
11716
- const paths = normalized.paths;
11717
- return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
11718
- }, pathRelevant = (value, scope) => scopeMatches(value, scope), compareCodeUnits2 = (left, right) => {
11720
+ const parsed = policyChangeSchema.safeParse(change);
11721
+ if (!parsed.success)
11722
+ throw new TypeError("policy change is invalid");
11723
+ return parsed.data;
11724
+ }
11725
+ var resolverInputSchema, compareCodeUnits2 = (left, right) => {
11719
11726
  const length = Math.min(left.length, right.length);
11720
11727
  for (let index = 0;index < length; index += 1) {
11721
11728
  const difference = left.charCodeAt(index) - right.charCodeAt(index);
@@ -11723,818 +11730,808 @@ var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex"),
11723
11730
  return difference;
11724
11731
  }
11725
11732
  return left.length - right.length;
11726
- }, gitPaths = (root) => {
11727
- const result = spawnSync2("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
11728
- cwd: root,
11729
- encoding: "buffer"
11730
- });
11731
- if (result.status !== 0) {
11732
- const stderr = result.stderr?.toString("utf8") ?? "";
11733
- return {
11734
- paths: [],
11735
- uncertain: !/not a git repository/i.test(stderr),
11736
- git: /not a git repository/i.test(stderr) ? false : true
11737
- };
11738
- }
11739
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
11740
- const stagedDeleted = spawnSync2("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
11741
- if (stagedDeleted.status !== 0) {
11742
- const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
11743
- if (!/not a git repository/i.test(stderr))
11744
- return { paths, uncertain: true, git: true };
11745
- } else if (stagedDeleted.stdout) {
11746
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")));
11747
- }
11748
- return { paths: [...new Set(paths)], uncertain: false, git: true };
11749
- }, walk = (root, directory, output) => {
11750
- let names;
11751
- try {
11752
- names = fs7.readdirSync(directory);
11753
- } catch {
11754
- return true;
11755
- }
11756
- let uncertain = false;
11757
- for (const name of names) {
11758
- if (name === ".git" || name === ".workit")
11759
- continue;
11760
- const target = path10.join(directory, name);
11761
- let stat;
11762
- try {
11763
- stat = fs7.lstatSync(target);
11764
- } catch {
11765
- uncertain = true;
11766
- continue;
11767
- }
11768
- const item = relative(root, target);
11769
- output.add(item);
11770
- if (stat.isDirectory() && !stat.isSymbolicLink())
11771
- uncertain = walk(root, target, output) || uncertain;
11772
- }
11773
- return uncertain;
11774
- }, scopeRoots = (root, scope) => {
11775
- const roots = [];
11776
- for (const item of scope.paths.length ? scope.paths : ["."]) {
11777
- const target = path10.resolve(root, item);
11778
- if (!inside(root, target))
11779
- 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` }] });
11780
- let ancestor = target;
11781
- while (!fs7.existsSync(ancestor) && ancestor !== root)
11782
- ancestor = path10.dirname(ancestor);
11733
+ }, stableUnique = (values) => [...new Set(values)].sort(compareCodeUnits2), stableList = (values) => [...values].sort((left, right) => compareCodeUnits2(canonicalJson(left), canonicalJson(right))), normalizedScope = (scope) => ({
11734
+ description: scope.description,
11735
+ paths: stableUnique(scope.paths),
11736
+ exclusions: stableUnique(scope.exclusions)
11737
+ }), normalizedRefList = (values) => stableList(values), normalizeAssessment = (assessment) => ({
11738
+ facts: stableList(assessment.facts.map((fact) => ({ ...fact, refs: normalizedRefList(fact.refs) }))),
11739
+ signals: Object.fromEntries(Object.entries(assessment.signals).map(([name, signal]) => [
11740
+ name,
11741
+ { ...signal, refs: normalizedRefList(signal.refs) }
11742
+ ])),
11743
+ consequences: stableList(assessment.consequences.map((consequence) => ({
11744
+ ...consequence,
11745
+ fact: { ...consequence.fact, refs: normalizedRefList(consequence.fact.refs) }
11746
+ }))),
11747
+ verification: stableList(assessment.verification.map((verification) => ({
11748
+ ...verification,
11749
+ scope: normalizedScope(verification.scope),
11750
+ availableChecks: normalizedRefList(verification.availableChecks),
11751
+ gaps: stableUnique(verification.gaps)
11752
+ })))
11753
+ }), normalizeConstraint = (constraint) => ({
11754
+ ...constraint,
11755
+ source: constraint.source,
11756
+ requires: stableList(constraint.requires.map((requirement) => ({
11757
+ ...requirement,
11758
+ scope: normalizedScope(requirement.scope),
11759
+ refs: normalizedRefList(requirement.refs)
11760
+ })))
11761
+ }), normalizeDecision = (decision) => ({
11762
+ ...decision,
11763
+ binding: {
11764
+ ...decision.binding,
11765
+ scope: normalizedScope(decision.binding.scope),
11766
+ contentRefs: normalizedRefList(decision.binding.contentRefs)
11767
+ },
11768
+ requirementIds: stableUnique(decision.requirementIds)
11769
+ }), normalizeIntent = (intent) => ({
11770
+ ...intent,
11771
+ scope: normalizedScope(intent.scope),
11772
+ authorityRefs: normalizedRefList(intent.authorityRefs)
11773
+ }), normalizePrior = (prior) => ({
11774
+ decisions: stableList(prior.decisions.map(normalizeDecision)),
11775
+ findings: stableList(prior.findings.map((finding) => ({
11776
+ ...finding,
11777
+ scope: normalizedScope(finding.scope),
11778
+ refs: normalizedRefList(finding.refs),
11779
+ resolution: finding.resolution ? {
11780
+ ...finding.resolution,
11781
+ evidenceIds: stableUnique(finding.resolution.evidenceIds),
11782
+ decisionIds: stableUnique(finding.resolution.decisionIds)
11783
+ } : null
11784
+ }))),
11785
+ requirements: stableList(prior.requirements.map((requirement) => ({
11786
+ ...requirement,
11787
+ scope: normalizedScope(requirement.scope)
11788
+ })))
11789
+ }), invalidFields = (error) => ({
11790
+ fields: error.issues.map((issue) => ({
11791
+ path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
11792
+ reason: issue.message
11793
+ }))
11794
+ }), requirement = (spec) => ({
11795
+ ...spec,
11796
+ id: requirementId({
11797
+ ruleId: spec.ruleId,
11798
+ scope: spec.scope,
11799
+ satisfaction: spec.satisfaction,
11800
+ before: spec.before,
11801
+ dependentAction: spec.dependentAction
11802
+ })
11803
+ }), signalReason = (input, name) => input.assessment.signals[name].reason;
11804
+ var init_policy_resolver = __esm(() => {
11805
+ init_zod();
11806
+ init_task_contract();
11807
+ resolverInputSchema = object({
11808
+ intent: intentSchema,
11809
+ assessment: assessmentSchema,
11810
+ constraints: array(constraintSchema),
11811
+ prior: object({
11812
+ decisions: array(decisionSchema),
11813
+ findings: array(findingSchema),
11814
+ requirements: array(requirementSchema)
11815
+ }).strict(),
11816
+ preferences: object({ fast: boolean2().optional(), thorough: boolean2().optional() }).strict().optional()
11817
+ }).strict();
11818
+ });
11819
+
11820
+ // packages/workit-core/src/core/task-evaluation.ts
11821
+ import { createHash as createHash6 } from "node:crypto";
11822
+ import * as fs7 from "node:fs";
11823
+ import { spawnSync as spawnSync2 } from "node:child_process";
11824
+ import path10 from "node:path";
11825
+ function captureCandidate(root, scope, environment = []) {
11826
+ let checkout;
11827
+ try {
11828
+ checkout = fs7.realpathSync(root);
11829
+ } catch {
11830
+ return failure("invalid_input", "candidate root does not exist");
11831
+ }
11832
+ const normalizedScope = canonicalScope(scope);
11833
+ if (!normalizedScope)
11834
+ 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" }] });
11835
+ const roots = scopeRoots(checkout, normalizedScope);
11836
+ if (!roots.ok)
11837
+ return roots;
11838
+ const git = gitPaths(checkout);
11839
+ const names = new Set;
11840
+ let uncertain = git.uncertain;
11841
+ for (const target of roots.data) {
11842
+ const item = relative(checkout, target);
11843
+ if (item === ".git" || item === ".workit")
11844
+ continue;
11845
+ names.add(item);
11846
+ if (!fs7.existsSync(target))
11847
+ continue;
11848
+ let stat;
11783
11849
  try {
11784
- if (!inside(root, fs7.realpathSync(ancestor)))
11785
- 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` }] });
11850
+ stat = fs7.lstatSync(target);
11786
11851
  } catch {
11787
- return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
11852
+ uncertain = true;
11853
+ continue;
11788
11854
  }
11789
- roots.push(target);
11855
+ if (!git.git && stat.isDirectory() && !stat.isSymbolicLink())
11856
+ uncertain = walk(checkout, target, names) || uncertain;
11790
11857
  }
11791
- return success(null, null, roots);
11792
- }, fileMap = (candidate) => new Map(candidate.files.map((file) => [file.path, file])), changedPaths = (before, after) => {
11793
- const paths = new Set([...before.files, ...after.files].map((file) => file.path));
11794
- const left = fileMap(before);
11795
- const right = fileMap(after);
11796
- const changed = [...paths].filter((item) => JSON.stringify(left.get(item) ?? null) !== JSON.stringify(right.get(item) ?? null));
11797
- if (before.head !== after.head)
11798
- changed.push(".");
11799
- if (JSON.stringify(before.environment) !== JSON.stringify(after.environment))
11800
- changed.push(".");
11801
- return changed;
11802
- }, sessionFromRef = (value) => {
11803
- if (typeof value !== "object" || value === null || value.kind !== "host")
11804
- return null;
11805
- return typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
11806
- }, sameSession = (left, right) => {
11807
- const other = sessionFromRef(right);
11808
- return left !== null && other !== null && left.host === other.host && left.handle === other.handle;
11809
- }, evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope), scopeCovers2, checkPin = (assignment, beforeCandidateId, candidateId) => {
11810
- const pin = assignment.candidateId ?? null;
11811
- if (pin === null)
11812
- return true;
11813
- return (beforeCandidateId ?? null) === pin || (candidateId ?? null) === pin;
11814
- }, resolveBinding = (evidence, pin, currentCandidateId) => {
11815
- const bound = evidence.kind === "check" || evidence.kind === "review";
11816
- const fallback = bound ? pin ?? currentCandidateId : null;
11817
- return {
11818
- beforeCandidateId: evidence.beforeCandidateId ?? fallback,
11819
- candidateId: evidence.candidateId ?? fallback
11858
+ for (const item of git.paths)
11859
+ if (scopeMatches(item, normalizedScope))
11860
+ names.add(item);
11861
+ const files = [];
11862
+ let completeness = uncertain ? "uncertain" : "known";
11863
+ for (const item of [...names].sort()) {
11864
+ if (!scopeMatches(item, normalizedScope))
11865
+ continue;
11866
+ const target = path10.join(checkout, item);
11867
+ try {
11868
+ const stat = fs7.lstatSync(target);
11869
+ if (stat.isSymbolicLink()) {
11870
+ const link = fs7.readlinkSync(target);
11871
+ files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
11872
+ } else if (stat.isFile()) {
11873
+ files.push({
11874
+ path: item,
11875
+ kind: "file",
11876
+ digest: digestBytes3(fs7.readFileSync(target)),
11877
+ executable: (stat.mode & 73) !== 0
11878
+ });
11879
+ } else if (!stat.isDirectory()) {
11880
+ completeness = "uncertain";
11881
+ } else if (git.git && git.paths.includes(item)) {
11882
+ completeness = "uncertain";
11883
+ }
11884
+ } catch (error) {
11885
+ if (error?.code === "ENOENT") {
11886
+ files.push({ path: item, kind: "absent", digest: null, executable: null });
11887
+ } else
11888
+ completeness = "uncertain";
11889
+ }
11890
+ }
11891
+ 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) => ({
11892
+ name,
11893
+ value: environment[name] ?? null
11894
+ }));
11895
+ const namesByEnvironment = supplied.map(({ name }) => name);
11896
+ if (new Set(namesByEnvironment).size !== namesByEnvironment.length || namesByEnvironment.some((name) => typeof name !== "string" || !name))
11897
+ return failure("invalid_input", "candidate environment names must be unique and non-empty");
11898
+ const values = supplied.sort((left, right) => compareCodeUnits3(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
11899
+ if (values.some(({ value }) => value === null))
11900
+ completeness = "uncertain";
11901
+ const headResult = spawnSync2("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
11902
+ if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
11903
+ completeness = "uncertain";
11904
+ const head = headResult.status === 0 ? headResult.stdout.trim() : null;
11905
+ const partial = {
11906
+ id: "0".repeat(64),
11907
+ scope: normalizedScope,
11908
+ completeness,
11909
+ files,
11910
+ environment: values,
11911
+ head
11820
11912
  };
11821
- }, findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId != null), 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"))), 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 })), evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
11822
- if (kind !== "passed")
11823
- return false;
11824
- if (dimension === "testing" || dimension === "verification")
11825
- return evidenceKind === "check" || ruleId === "pre-pr-cleanup" && evidenceKind === "artifact";
11826
- if (dimension === "review")
11827
- return evidenceKind === "review";
11828
- if (dimension === "investigation" || dimension === "challenge")
11829
- return evidenceKind === "investigation";
11830
- if (dimension === "artifacts" || dimension === "continuity")
11831
- return evidenceKind === "artifact";
11832
- return false;
11833
- }, expectedKinds = (dimension, ruleId) => {
11834
- if (dimension === "testing")
11835
- return "check";
11836
- if (dimension === "verification")
11837
- return ruleId === "pre-pr-cleanup" ? "check or artifact" : "check";
11838
- if (dimension === "review")
11839
- return "review";
11840
- if (dimension === "investigation" || dimension === "challenge")
11841
- return "investigation";
11842
- if (dimension === "artifacts" || dimension === "continuity")
11843
- return "artifact";
11844
- if (dimension === "decisions")
11845
- return "decision";
11846
- return "worker report";
11847
- };
11848
- var init_task_evaluation = __esm(() => {
11849
- init_task_contract();
11850
- init_authority();
11851
- scopeCovers2 = scopeCovers;
11852
- });
11853
-
11854
- // packages/workit-core/src/core/methods.ts
11855
- function selectMethods(policy, capabilities) {
11856
- return Object.entries(METHODS).flatMap(([id, definition]) => {
11857
- const requirements = policy.requirements.filter((requirement) => methodMatches(definition, requirement));
11858
- if (requirements.length === 0)
11859
- return [];
11860
- const capability = assuranceFor(id, definition, capabilities);
11861
- const reasons = [
11862
- ...new Set(requirements.map((requirement) => requirement.reason)),
11863
- ...capability.reasons
11864
- ];
11865
- if (capability.assurance === "unavailable")
11866
- reasons.unshift("required capability is unavailable");
11867
- return [{ id, reason: reasons.join("; "), assurance: capability.assurance }];
11868
- });
11913
+ const candidate = { ...partial, id: candidateDigest(partial) };
11914
+ const parsed = candidateSchema.safeParse(candidate);
11915
+ return parsed.success ? success(null, null, parsed.data) : failure("invalid_input", "captured candidate is invalid");
11869
11916
  }
11870
- var METHODS, methodMatches = (definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true || definition.ruleIds?.includes(requirement.ruleId) === true, assuranceFor = (id, definition, capabilities) => {
11871
- const keys = new Set([
11872
- id,
11873
- ...definition.dimensions ?? [],
11874
- ...definition.ruleIds ?? []
11875
- ]);
11876
- const matches = capabilities.filter((capability) => keys.has(capability.name) || keys.has(capability.surface));
11877
- if (id === "workit-review" && matches.length === 0)
11878
- return { assurance: "unavailable", reasons: ["independent review capability is unavailable"] };
11879
- if (matches.some((capability) => capability.assurance === "unavailable"))
11917
+ function evaluateEvidence(task, candidate) {
11918
+ const current = candidate ?? task.candidates.at(-1) ?? null;
11919
+ return task.evidence.map((entry) => {
11920
+ const evidence = entry.data;
11921
+ if (evidence.result === "failed")
11922
+ return {
11923
+ evidenceId: entry.id,
11924
+ status: "failed",
11925
+ reason: "failed evidence remains historical"
11926
+ };
11927
+ if (evidence.kind === "check" || evidence.kind === "review") {
11928
+ if (!evidence.beforeCandidateId || !evidence.candidateId)
11929
+ return {
11930
+ evidenceId: entry.id,
11931
+ status: "stale",
11932
+ reason: "check or review evidence lacks start and end candidates"
11933
+ };
11934
+ if (evidence.beforeCandidateId !== evidence.candidateId)
11935
+ return {
11936
+ evidenceId: entry.id,
11937
+ status: "stale",
11938
+ reason: "candidate changed during the check or review"
11939
+ };
11940
+ }
11941
+ if (!evidence.candidateId || !current)
11942
+ return {
11943
+ evidenceId: entry.id,
11944
+ status: evidence.result,
11945
+ reason: "evidence has no candidate binding"
11946
+ };
11947
+ const observed = task.candidates.find((item) => item.id === evidence.candidateId) ?? (current.id === evidence.candidateId ? current : undefined);
11948
+ if (!observed || observed.completeness === "uncertain" || current.completeness === "uncertain")
11949
+ return { evidenceId: entry.id, status: "stale", reason: "candidate is missing or uncertain" };
11950
+ if (observed.id === current.id)
11951
+ return { evidenceId: entry.id, status: evidence.result, reason: "candidate is unchanged" };
11952
+ const scopes = evidenceScopes(task, evidence.requirementIds);
11953
+ const changed = changedPaths(observed, current);
11954
+ const environmentChanged = JSON.stringify(observed.environment) !== JSON.stringify(current.environment);
11955
+ if (!scopes.length || environmentChanged || changed.some((item) => scopes.some((scope) => pathRelevant(item, scope))))
11956
+ return { evidenceId: entry.id, status: "stale", reason: "relevant candidate state changed" };
11880
11957
  return {
11881
- assurance: "unavailable",
11882
- reasons: matches.filter((capability) => capability.assurance === "unavailable").map((capability) => capability.reason)
11958
+ evidenceId: entry.id,
11959
+ status: evidence.result,
11960
+ reason: "candidate changed outside evidence scope"
11883
11961
  };
11884
- if (matches.some((capability) => capability.assurance === "enforced"))
11885
- return { assurance: "enforced", reasons: matches.map((capability) => capability.reason) };
11886
- return { assurance: "agent_guided", reasons: matches.map((capability) => capability.reason) };
11887
- }, invariantBootstrap = () => `
11888
- Workit is optional coordination and repository-policy tooling around the host,
11889
- not a permission system or a mandatory workflow. Native host allow/ask/deny,
11890
- sandbox, plan/read-only mode, and organization rules remain authoritative.
11891
- Ordinary investigation, questions, non-Git work, and routine reversible edits
11892
- need zero Workit task, assessment, or writer calls. Use one compact tracked
11893
- record only when handoff, dependent steps, concurrent actors, or meaningful
11894
- decisions make continuity useful; similar titles alone never merge tasks.
11895
-
11896
- For a routine user-authorized branch or commit, prefer native host Git/shell
11897
- tools from the outset when managed coordination or outcome reconciliation is
11898
- not needed. Inspect the actual target checkout and its configured conventions;
11899
- use Workit's read-only context/policy tools when needed. Do not start a task,
11900
- acquire a writer, or mint a decision receipt solely for a native Git action.
11901
- Native permissions still apply. Never switch execution paths to evade a denial
11902
- or repeat an uncertain managed effect. A local-commit endpoint does not imply
11903
- PR readiness, a fresh review, or post-commit assessment/closure paperwork.
11904
-
11905
- For tracked work, inspect its current state and use the shared operations for
11906
- task, policy, evidence, finding, decision, worker, writer, and state changes.
11907
- Start a record once for an explicit tracked objective; assess or reassess only
11908
- when policy selection or changed evidence/constraints requires it. Omitted
11909
- expectedRevision and expectedWorkspaceRevision use current values; explicit
11910
- values are still concurrency-checked, so never copy revisions between calls.
11911
- A solo edit does not need writer acquisition; use it when concurrent checkout
11912
- writers need coordination. Record only observed facts and checks. Evidence can
11913
- become stale when its bound candidate changes; reconcile findings against the
11914
- current candidate before recording completion.
11915
-
11916
- Workit validates domain policy against the actual action target, configured
11917
- account, branch and commit conventions, protected refs, and current repository
11918
- state. The task directory is coordination state, not a boundary on which
11919
- repository may be changed. Use an action-time cwd target where a managed
11920
- Git/hosting action supports it; a non-Git directory remains valid for OS work.
11921
- GitHub/GitLab use the active gh/glab CLI identity; YouTrack uses its own token.
11922
- Preserve uncertain external outcomes and reconcile repository/provider state
11923
- before retrying. Internal reservations prevent duplicate or ambiguous effects;
11924
- they are not permission tickets for every edit.
11925
-
11926
- On OpenCode V1 and V2, use native host tools for external mutations. Workit
11927
- provides read-only workit_context; it has no managed external-action executor.
11928
- Do not create proposals or request Workit approvals merely to invoke native
11929
- tools. Old uncertain managed effects still require evidence before any retry.
11930
-
11931
- Use host-native authorization through the host's supported path. A native
11932
- question receipt records an actual question interaction; it is not automatic
11933
- host permission. Record a meaningful user choice once with provenance when
11934
- future retrieval helps. Never fabricate a receipt, re-ask only to mint one, or
11935
- let imported decisions grant authority. Never turn a host deny into allow or
11936
- claim enforcement/evidence a host cannot provide. A precise request with settled
11937
- constraints does not need an interview. Present genuine unresolved options with
11938
- evidence and a recommendation, then continue toward the requested delivery
11939
- endpoint: investigation, implementation, branch/commit/push, PR/MR-ready,
11940
- merge, or release. Run
11941
- applicable checks and safe repairs without repeated continuation questions;
11942
- stop for missing host authority, a new consequential choice, a conflicting edit,
11943
- or an unresolved blocker. A tracked record may close when actual evidence
11944
- supports its outcome; no final human closure ceremony is required.
11945
-
11946
- A completed native subagent run stops its bound worker by itself; if a worker
11947
- strands in cancelling with its run verifiably over, repeat worker.cancel to
11948
- confirm the stop. Preserve unresolved requirements, gaps, and uncertain workers.
11949
- When context changes, distinguish a quick question, same-task adjustment, and
11950
- separate request. Answer a quick question without pausing/resuming task state;
11951
- park a concise checkpoint only when substantial work needs to continue later.
11952
- For work spanning repositories, bind each unfinished item to its actual checkout,
11953
- branch, requested deliverables, and delivery endpoint. Preserve held items with
11954
- an explicit resume condition. Resolve competing plausible targets before a
11955
- mutation; ask only when available context cannot settle that consequential choice.
11956
- Before reporting delivery, reconcile every requested item and observe the actual
11957
- target result; a local commit alone is not evidence of a requested remote push.
11958
-
11959
- Skill routing: slash aliases /wk-* load on demand. Use workit-steer for a
11960
- substantial interruption or change of direction, workit-deslop when relevant
11961
- to a PR-ready endpoint, workit-green-run for failing CI, workit-blast-radius
11962
- when impact is uncertain, and workit-challenge for genuinely open consequential
11963
- choices. Load workit-plan when dependencies or handoff need durable next actions.
11964
- Load the skill; never act from memory of it.
11965
- `.trim();
11966
- var init_methods = __esm(() => {
11967
- METHODS = {
11968
- "workit-challenge": { dimensions: ["challenge", "decisions"] },
11969
- "workit-behavioral-tdd": { dimensions: ["testing"], ruleIds: ["mechanical-existing-checks"] },
11970
- "workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review", "self-review"] },
11971
- "workit-plan": { dimensions: ["artifacts", "continuity"] },
11972
- "workit-implement": { dimensions: ["delegation"] },
11973
- "workit-debug": {},
11974
- "workit-handoff": {},
11975
- "workit-deslop": { ruleIds: ["pre-pr-cleanup"] }
11976
- };
11977
- });
11978
-
11979
- // packages/workit-core/src/core/task-context.ts
11980
- function reconcileResume(view, observations = []) {
11981
- if (observations.length > 0)
11982
- return failure("permission_denied", "worker observations require native host verification");
11983
- const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
11984
- if (!candidate.ok)
11985
- return candidate;
11986
- const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
11987
- const blockers = [...view.task.progress.blockers];
11988
- if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
11989
- blockers.push({
11990
- reason: "worker state requires reconciliation",
11991
- dependentAction: "resume",
11992
- refs: []
11993
- });
11994
- return success(null, null, {
11995
- candidate: candidate.data,
11996
- staleEvidenceIds,
11997
- workerUpdates: [],
11998
- blockers,
11999
- reassessmentRequired: view.task.policy?.policyVersion !== POLICY_VERSION
12000
11962
  });
12001
11963
  }
12002
- function compactTaskContext(view) {
12003
- const decisions = view.task.decisions.slice().sort((left, right) => {
12004
- const leftAt = compactTimestamp(left.recordedAt);
12005
- const rightAt = compactTimestamp(right.recordedAt);
12006
- if (leftAt !== rightAt)
12007
- return leftAt > rightAt ? -1 : 1;
12008
- return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
12009
- }).slice(0, COMPACT_MAX_ITEMS).map((entry) => ({
12010
- id: entry.id,
12011
- purpose: entry.data.purpose,
12012
- status: entry.data.response,
12013
- digest: entry.data.digest,
12014
- choice: compactDecisionChoice(entry) || `task.inspect taskId=${view.task.id} view=full; decision=${entry.id}`,
12015
- references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference)
12016
- }));
12017
- const methods = view.task.policy ? selectMethods(view.task.policy, view.capabilities).map((method) => ({
12018
- id: method.id,
12019
- assurance: method.assurance,
12020
- reason: compactText(method.reason, COMPACT_TEXT_BYTES) ?? ""
12021
- })) : [];
12022
- const gaps = Array.from(new Set([
12023
- ...view.evidence.filter((entry) => entry.status === "stale").map((entry) => `stale evidence: ${entry.evidenceId}`),
12024
- ...view.requirements.filter((entry) => entry.status !== "satisfied").map((entry) => entry.reason),
12025
- ...view.task.progress.blockers.map((entry) => entry.reason)
12026
- ])).sort().slice(0, COMPACT_MAX_ITEMS).map((gap) => compactText(gap, COMPACT_TEXT_BYTES));
12027
- const context = {
12028
- objective: compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "",
12029
- status: view.task.status,
12030
- decisions,
12031
- methods,
12032
- gaps,
12033
- nextAction: compactText(view.task.progress.nextAction, COMPACT_TEXT_BYTES)
12034
- };
12035
- let encoded = JSON.stringify(context);
12036
- while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.methods.length || context.gaps.length)) {
12037
- if (context.gaps.length)
12038
- context.gaps.pop();
12039
- else if (context.decisions.length)
12040
- context.decisions.pop();
12041
- else
12042
- context.methods.pop();
12043
- encoded = JSON.stringify(context);
12044
- }
12045
- if (compactBytes(encoded) > COMPACT_MAX_BYTES) {
12046
- context.objective = compactText(context.objective, 64) ?? "";
12047
- context.nextAction = compactText(context.nextAction, 64);
12048
- encoded = JSON.stringify(context);
12049
- }
12050
- return encoded;
12051
- }
12052
- var COMPACT_MAX_BYTES = 4096, COMPACT_MAX_ITEMS = 8, COMPACT_TEXT_BYTES = 128, sensitiveUrl, sensitiveValue, privateKey, compactText = (value, limit) => value === null ? null : value.replace(privateKey, "<redacted-private-key>").replace(sensitiveUrl, "<redacted-url>").replace(sensitiveValue, (match) => {
12053
- const separator = match.search(/[:=]/u);
12054
- if (separator >= 0)
12055
- return `${match.slice(0, separator)}=<redacted>`;
12056
- const space = match.search(/\s/u);
12057
- return `${match.slice(0, space)} <redacted>`;
12058
- }).slice(0, limit), compactBytes = (value) => new TextEncoder().encode(value).byteLength, compactTimestamp = (value) => `${value.slice(0, 19)}.${value.slice(20, -1).padEnd(9, "0")}`, compactDecisionChoice = (entry) => {
12059
- const stated = entry.data.binding.statedChoice?.text;
12060
- const approved = entry.data.response === "approved" ? entry.data.binding.approvedContent : "";
12061
- const source = stated?.trim() || approved.trim() || null;
12062
- return compactText(source, COMPACT_TEXT_BYTES);
12063
- }, compactReference = (ref) => {
12064
- if (ref.kind === "file")
12065
- return {
12066
- kind: "file",
12067
- path: compactText(ref.path, COMPACT_TEXT_BYTES) ?? ""
12068
- };
12069
- if (ref.kind === "record")
12070
- return {
12071
- kind: "record",
12072
- collection: ref.collection,
12073
- id: ref.id
12074
- };
12075
- return { kind: ref.kind };
12076
- }, exportDigest = (bundle) => sha256({
12077
- schemaVersion: bundle.schemaVersion,
12078
- exportedAt: bundle.exportedAt,
12079
- sourceWorkspaceId: bundle.sourceWorkspaceId,
12080
- task: bundle.task
12081
- });
12082
- var init_task_context = __esm(() => {
12083
- init_workers();
12084
- init_task_contract();
12085
- init_task_evaluation();
12086
- init_methods();
12087
- sensitiveUrl = /\bhttps?:\/\/[^/\s:@]+:[^@\s]+@/gi;
12088
- sensitiveValue = /(?:token|secret|password|passwd|api[-_ ]?key|private[-_ ]?key|authorization)\s*[:=]\s*(?:bearer\s+)?\S+|\bbearer\s+\S+/gi;
12089
- privateKey = /-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/gi;
12090
- });
12091
-
12092
- // packages/workit-core/src/core/route-intent.ts
12093
- var init_route_intent = __esm(() => {
12094
- init_branch();
12095
- });
12096
-
12097
- // packages/workit-core/src/core/runtime-identity.ts
12098
- var init_runtime_identity = () => {};
12099
-
12100
- // packages/workit-core/src/core/external-action.ts
12101
- var chainStepSchema, externalActionSchema;
12102
- var init_external_action = __esm(() => {
12103
- init_task_contract();
12104
- init_task_store();
12105
- init_authority();
12106
- init_zod();
12107
- chainStepSchema = union([
12108
- string2().min(1),
12109
- object({ branch: string2().min(1) }).strict(),
12110
- object({ pr: literal(true) }).strict()
12111
- ]);
12112
- externalActionSchema = discriminatedUnion("operation", [
12113
- object({
12114
- operation: literal("git.branch_setup"),
12115
- payload: object({
12116
- action: _enum(["setup", "reapply_stash"]).optional(),
12117
- sdd_dir: string2().optional(),
12118
- target_branch: string2().optional(),
12119
- stash: _enum(["yes", "no"]).optional(),
12120
- cwd: string2().min(1).optional()
12121
- }).strict()
12122
- }).strict(),
12123
- object({
12124
- operation: literal("git.commit"),
12125
- payload: object({
12126
- message: string2().min(1).optional(),
12127
- plan_steps: array(chainStepSchema).min(1).max(32).optional(),
12128
- plan_branch: string2().min(1).optional(),
12129
- cwd: string2().min(1).optional()
12130
- }).strict()
12131
- }).strict(),
12132
- object({
12133
- operation: literal("git.push"),
12134
- payload: object({ branch: string2().min(1).optional(), cwd: string2().min(1).optional() }).strict()
12135
- }).strict(),
12136
- object({
12137
- operation: literal("hosting.pull_request"),
12138
- payload: object({
12139
- title: string2().min(1),
12140
- body: string2().optional(),
12141
- draft: boolean2().optional(),
12142
- target_branch: string2().optional(),
12143
- babysit: boolean2().optional(),
12144
- cwd: string2().min(1).optional()
12145
- }).strict()
12146
- }).strict(),
12147
- object({
12148
- operation: literal("hosting.merge"),
12149
- payload: object({
12150
- target_branch: string2().min(1).optional(),
12151
- source_branch: string2().min(1).optional(),
12152
- cwd: string2().min(1).optional()
12153
- }).strict()
12154
- }).strict(),
12155
- object({
12156
- operation: literal("hosting.delete_branch"),
12157
- payload: object({ branch: string2().min(1), cwd: string2().min(1).optional() }).strict()
12158
- }).strict(),
12159
- object({
12160
- operation: literal("youtrack.update"),
12161
- payload: object({
12162
- issueId: string2().min(1),
12163
- markdown: string2().min(1),
12164
- minutes: number2().positive().optional()
12165
- }).strict()
12166
- }).strict(),
12167
- object({
12168
- operation: literal("youtrack.time"),
12169
- payload: object({
12170
- issueId: string2().min(1),
12171
- minutes: number2().positive(),
12172
- text: string2().optional(),
12173
- dateMs: number2().finite().optional()
12174
- }).strict()
12175
- }).strict(),
12176
- object({
12177
- operation: literal("youtrack.meeting"),
12178
- payload: object({
12179
- issueId: string2().min(1),
12180
- minutes: number2().positive(),
12181
- text: string2().min(1)
12182
- }).strict()
12183
- }).strict(),
12184
- object({
12185
- operation: literal("changelog.apply"),
12186
- payload: object({
12187
- entries: union([
12188
- record(string2(), array(string2())),
12189
- array(object({ category: string2().min(1), text: string2().min(1) }).strict())
12190
- ]).optional(),
12191
- path: string2().optional(),
12192
- normalize_only: boolean2().optional()
12193
- }).strict()
12194
- }).strict(),
12195
- object({
12196
- operation: literal("context.read"),
12197
- payload: object({
12198
- kind: _enum([
12199
- "git",
12200
- "pr",
12201
- "youtrack",
12202
- "github_issue",
12203
- "gitlab_issue",
12204
- "changelog",
12205
- "release",
12206
- "affected"
12207
- ]),
12208
- range: string2().optional(),
12209
- issueId: string2().optional(),
12210
- issueUrl: string2().optional(),
12211
- issueRef: string2().optional(),
12212
- mode: string2().optional(),
12213
- specPath: string2().optional(),
12214
- planPath: string2().optional(),
12215
- cwd: string2().min(1).optional()
12216
- }).strict()
12217
- }).strict()
12218
- ]);
12219
- });
12220
-
12221
- // packages/workit-core/src/core/policy-resolver.ts
12222
- function normalizeResolverInput(input) {
12223
- const parsed = resolverInputSchema.safeParse(input);
12224
- if (!parsed.success)
12225
- return failure("invalid_input", "resolver input is invalid", invalidFields(parsed.error));
12226
- const value = parsed.data;
12227
- const signals = value.assessment.signals;
12228
- if (signals.behaviorChange.value === true && signals.mechanicalLowRisk.value === true)
12229
- return failure("invalid_input", "behavioral change cannot be wholly mechanical", {
12230
- fields: [
12231
- {
12232
- path: "assessment.signals",
12233
- reason: "contradictory behaviorChange and mechanicalLowRisk"
12234
- }
12235
- ]
12236
- });
12237
- const ids = value.constraints.map((constraint) => constraint.id);
12238
- if (new Set(ids).size !== ids.length)
12239
- return failure("invalid_input", "constraint IDs must be unique", {
12240
- fields: [{ path: "constraints", reason: "duplicate constraint id" }]
12241
- });
12242
- if (value.preferences?.fast && value.preferences.thorough)
12243
- return failure("invalid_input", "fast and thorough preferences contradict", {
12244
- fields: [{ path: "preferences", reason: "fast and thorough cannot both be enabled" }]
12245
- });
12246
- const normalized = {
12247
- intent: normalizeIntent(value.intent),
12248
- assessment: normalizeAssessment(value.assessment),
12249
- constraints: stableList(value.constraints.map(normalizeConstraint)),
12250
- prior: normalizePrior(value.prior),
12251
- ...value.preferences ? { preferences: { ...value.preferences } } : {},
12252
- policyVersion: POLICY_VERSION
12253
- };
12254
- return success(null, null, normalized);
12255
- }
12256
- function resolveRequirements(input) {
12257
- const { signals } = input.assessment;
12258
- const scope = input.intent.scope;
12259
- const requirements = [];
12260
- const hasUnknownSignal = Object.values(signals).some((signal) => signal.value === "unknown");
12261
- const hasUnknownFact = [
12262
- ...input.assessment.facts,
12263
- ...input.assessment.consequences.map(({ fact }) => fact)
12264
- ].some((fact) => fact.basis === "unknown");
12265
- if (hasUnknownSignal || hasUnknownFact)
12266
- requirements.push(requirement({
12267
- ruleId: "consequential-unknown",
12268
- dimension: "investigation",
12269
- scope,
12270
- reason: `Resolve consequential uncertainty before the dependent action: ${[
12271
- ...Object.entries(signals).filter(([, signal]) => signal.value === "unknown").map(([name]) => name),
12272
- ...hasUnknownFact ? ["unknown fact"] : []
12273
- ].join(", ")}`,
12274
- satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
12275
- before: "dependent_action",
12276
- dependentAction: null,
12277
- acceptanceAllowed: false
12278
- }));
12279
- if (signals.productChoiceOpen.value === true)
12280
- requirements.push(requirement({
12281
- ruleId: "product-decision",
12282
- dimension: "decisions",
12283
- scope,
12284
- reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
12285
- satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
12286
- before: "dependent_action",
12287
- dependentAction: null,
12288
- acceptanceAllowed: false
12289
- }));
12290
- const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
12291
- if (signals.behaviorChange.value === true)
12292
- requirements.push(requirement({
12293
- ruleId: "behavioral-verification",
12294
- dimension: "testing",
12295
- scope,
12296
- reason: `Behavior, side effects, permissions, or data handling may change: ${signalReason(input, "behaviorChange")}`,
12297
- satisfaction: "A useful behavioral check passes against the affected behavior and current candidate.",
12298
- before: "close",
12299
- dependentAction: null,
12300
- acceptanceAllowed: false
12301
- }), requirement({
12302
- ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
12303
- dimension: "review",
12304
- scope,
12305
- 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.",
12306
- 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.",
12307
- before: "close",
12308
- dependentAction: null,
12309
- acceptanceAllowed: !independentBehaviorReview
12310
- }));
12311
- else if (signals.mechanicalLowRisk.value === true)
12312
- requirements.push(requirement({
12313
- ruleId: "mechanical-existing-checks",
12314
- dimension: "verification",
12315
- scope,
12316
- reason: `The change is demonstrably mechanical and low-risk: ${signalReason(input, "mechanicalLowRisk")}`,
12317
- satisfaction: "Run the relevant existing checks for affected consumers; do not add tautological tests.",
12318
- before: "close",
12319
- dependentAction: null,
12320
- acceptanceAllowed: true
12321
- }), requirement({
12322
- ruleId: input.preferences?.thorough ? "fresh-context-review" : "self-review",
12323
- dimension: "review",
12324
- scope,
12325
- 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.",
12326
- 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.",
12327
- before: "close",
12328
- dependentAction: null,
12329
- acceptanceAllowed: !input.preferences?.thorough
12330
- }));
12331
- if (signals.behaviorChange.value === true || signals.mechanicalLowRisk.value === true)
12332
- requirements.push(requirement({
12333
- ruleId: "pre-pr-cleanup",
12334
- dimension: "verification",
12335
- scope,
12336
- reason: "Implementation output must be deslopped before it is delivered.",
12337
- satisfaction: "Run the workit-deslop pass and record passing check evidence, or record an approved limitation decision waiving cleanup.",
12338
- before: "dependent_action",
12339
- dependentAction: "hosting.pull_request",
12340
- acceptanceAllowed: true
12341
- }));
12342
- if (signals.durableAgreementNeeded.value === true)
12343
- requirements.push(requirement({
12344
- ruleId: "durable-spec",
12345
- dimension: "artifacts",
12346
- scope,
12347
- reason: `The work needs a durable agreement about intended behavior: ${signalReason(input, "durableAgreementNeeded")}`,
12348
- satisfaction: "Record the agreed behavior in a durable specification before implementation writes.",
12349
- before: "write",
12350
- dependentAction: null,
12351
- acceptanceAllowed: true
12352
- }));
12353
- if (signals.coordinationPlanNeeded.value === true)
12354
- requirements.push(requirement({
12355
- ruleId: "coordination-plan",
12356
- dimension: "continuity",
12357
- scope,
12358
- reason: `Dependencies, sequencing, or resumption need durable coordination: ${signalReason(input, "coordinationPlanNeeded")}`,
12359
- satisfaction: "Record useful sequencing, dependencies, and the next action in a compact plan.",
12360
- before: "dependent_action",
12361
- dependentAction: null,
12362
- acceptanceAllowed: true
12363
- }));
12364
- if (signals.helperUseful.value === true && !input.preferences?.fast)
12365
- requirements.push(requirement({
12366
- ruleId: "helper-usefulness",
12367
- dimension: "delegation",
12368
- scope,
12369
- reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
12370
- satisfaction: "Use a bounded helper only for the independent objective and reconcile its result.",
12371
- before: "dependent_action",
12372
- dependentAction: null,
12373
- acceptanceAllowed: true
11964
+ function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller) {
11965
+ const evidence = evaluateEvidence(task, candidate);
11966
+ if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
11967
+ return task.policy.requirements.map((requirement) => ({
11968
+ requirementId: requirement.id,
11969
+ status: "unsatisfied",
11970
+ evidenceIds: [],
11971
+ decisionIds: [],
11972
+ reason: "stored policy version is unsupported; reassessment is required"
12374
11973
  }));
12375
- for (const constraint of input.constraints)
12376
- for (const obligation of constraint.requires) {
12377
- const dimension = obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge");
12378
- requirements.push(requirement({
12379
- ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
12380
- kind: obligation.kind,
12381
- scope: normalizedScope(obligation.scope),
12382
- refs: normalizedRefList(obligation.refs),
12383
- method: obligation.method,
12384
- before: obligation.before,
12385
- dependentAction: obligation.dependentAction
12386
- })}`,
12387
- dimension,
12388
- scope: obligation.scope,
12389
- reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
12390
- satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
12391
- before: obligation.before,
12392
- dependentAction: obligation.dependentAction,
12393
- acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
12394
- }));
11974
+ return (task.policy?.requirements ?? []).map((requirement) => {
11975
+ const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
11976
+ const passed = related.filter(({ entry, evaluation }) => {
11977
+ if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
11978
+ return false;
11979
+ if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
11980
+ return false;
11981
+ if (requirement.dimension !== "review")
11982
+ return true;
11983
+ const reviewSession = sessionFromRef(entry.data.reviewContext);
11984
+ const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
11985
+ 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));
11986
+ return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
11987
+ });
11988
+ if (passed.length) {
11989
+ const currentId = candidate?.id ?? null;
11990
+ 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;
11991
+ if (withRed.length)
11992
+ return {
11993
+ requirementId: requirement.id,
11994
+ status: "satisfied",
11995
+ evidenceIds: withRed.map(({ entry }) => entry.id),
11996
+ decisionIds: [],
11997
+ reason: "fresh applicable evidence passed"
11998
+ };
11999
+ if (requirement.dimension === "testing")
12000
+ return {
12001
+ requirementId: requirement.id,
12002
+ status: "unsatisfied",
12003
+ evidenceIds: [],
12004
+ decisionIds: [],
12005
+ reason: "testing requirement has GREEN evidence but no preceding RED"
12006
+ };
12007
+ }
12008
+ if (requirement.dimension === "delegation") {
12009
+ const dot = (scope) => scope.paths.length > 0 ? scope : { ...scope, paths: ["."] };
12010
+ 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))));
12011
+ if (reconciled)
12012
+ return {
12013
+ requirementId: requirement.id,
12014
+ status: "satisfied",
12015
+ evidenceIds: [],
12016
+ decisionIds: [],
12017
+ reason: "bounded helper completed and its result is recorded"
12018
+ };
12019
+ }
12020
+ const decisions = requirement.dimension === "decisions" ? applicableRequirementDecision(task, workspace, requirement, checkoutRoot) : [];
12021
+ if (decisions.length)
12022
+ return {
12023
+ requirementId: requirement.id,
12024
+ status: "satisfied",
12025
+ evidenceIds: [],
12026
+ decisionIds: decisions.map((decision) => decision.id),
12027
+ reason: "an applicable approved or stated decision satisfies the requirement"
12028
+ };
12029
+ const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
12030
+ if (limitations.length)
12031
+ return {
12032
+ requirementId: requirement.id,
12033
+ status: "accepted_limitation",
12034
+ evidenceIds: [],
12035
+ decisionIds: limitations.map((decision) => task.decisions.find((entry) => entry.data === decision).id),
12036
+ reason: "an applicable approved limitation permits the missing evidence"
12037
+ };
12038
+ const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
12039
+ if (!unavailable && related.length > 0 && !passed.length)
12040
+ return {
12041
+ requirementId: requirement.id,
12042
+ status: "unsatisfied",
12043
+ evidenceIds: related.map(({ entry }) => entry.id),
12044
+ decisionIds: [],
12045
+ reason: `no passing evidence (needs kind:${expectedKinds(requirement.dimension, requirement.ruleId)})`
12046
+ };
12047
+ return {
12048
+ requirementId: requirement.id,
12049
+ status: unavailable ? "unavailable" : "unsatisfied",
12050
+ evidenceIds: related.map(({ entry }) => entry.id),
12051
+ decisionIds: [],
12052
+ reason: unavailable ? "required capability is unavailable" : "no fresh passing evidence"
12053
+ };
12054
+ });
12055
+ }
12056
+ function evaluateClosure(requestedOutcome, view) {
12057
+ if (requestedOutcome !== "stopped" && !view.task.policy)
12058
+ return failure("requirements_unsatisfied", "task has not been assessed");
12059
+ if (view.task.policy && view.task.policy.policyVersion !== POLICY_VERSION)
12060
+ return failure("requirements_unsatisfied", "stored policy version is unsupported; reassessment is required");
12061
+ const evaluations = view.requirements;
12062
+ const openFindings = view.task.findings.filter((entry) => entry.data.disposition === "open");
12063
+ if (requestedOutcome !== "stopped" && openFindings.length)
12064
+ return failure("requirements_unsatisfied", "open findings must be resolved before closure");
12065
+ if (requestedOutcome !== "stopped") {
12066
+ const evidenceById = new Map(view.evidence.map((entry) => [entry.evidenceId, entry.status]));
12067
+ for (const entry of view.task.findings) {
12068
+ if (entry.data.disposition === "fixed") {
12069
+ const verified = entry.data.resolution?.evidenceIds.some((id) => {
12070
+ const evidence = view.task.evidence.find((item) => item.id === id);
12071
+ if (!evidence)
12072
+ return false;
12073
+ return findingVerificationPasses(entry.data.candidateId, {
12074
+ kind: evidence.data.kind,
12075
+ candidateId: evidence.data.candidateId,
12076
+ status: evidenceById.get(id) ?? "missing"
12077
+ });
12078
+ });
12079
+ if (!verified)
12080
+ return failure("requirements_unsatisfied", "fixed findings require current verification");
12081
+ }
12082
+ if (entry.data.disposition === "deferred") {
12083
+ const valid = entry.data.resolution?.decisionIds.some((id) => {
12084
+ const decisionEntry = view.task.decisions.find((item) => item.id === id);
12085
+ const decision = decisionEntry?.data;
12086
+ 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)));
12087
+ });
12088
+ if (!valid)
12089
+ return failure("requirements_unsatisfied", "deferred findings require an approved limitation decision that references an acceptanceAllowed requirement and covers the finding scope");
12090
+ }
12395
12091
  }
12396
- const seen = new Set;
12397
- return requirements.filter((item) => {
12398
- if (seen.has(item.id))
12092
+ }
12093
+ const blocking = evaluations.filter((item) => {
12094
+ if (item.status !== "unsatisfied" && item.status !== "unavailable")
12399
12095
  return false;
12400
- seen.add(item.id);
12096
+ const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
12097
+ return !requirement || requirement.before === "close";
12098
+ });
12099
+ if (requestedOutcome !== "stopped" && blocking.length)
12100
+ return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
12101
+ requirementIds: blocking.map((item) => item.requirementId),
12102
+ requirements: blocking.map((item) => {
12103
+ const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
12104
+ return {
12105
+ requirementId: item.requirementId,
12106
+ ruleId: requirement?.ruleId ?? "unknown",
12107
+ reason: item.reason,
12108
+ satisfaction: requirement?.satisfaction ?? "",
12109
+ dependentAction: requirement?.dependentAction ?? null
12110
+ };
12111
+ })
12112
+ });
12113
+ const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
12114
+ if (requestedOutcome === "verified" && accepted.length)
12115
+ return failure("requirements_unsatisfied", "accepted limitations cannot be reported as verified", {
12116
+ requirementIds: accepted.map((item) => item.requirementId)
12117
+ });
12118
+ const evidenceIds = [
12119
+ ...new Set([
12120
+ ...evaluations.flatMap((item) => item.evidenceIds),
12121
+ ...view.task.findings.flatMap((item) => item.data.resolution?.evidenceIds ?? [])
12122
+ ])
12123
+ ];
12124
+ const decisionIds = [
12125
+ ...new Set([
12126
+ ...evaluations.flatMap((item) => item.decisionIds),
12127
+ ...view.task.findings.flatMap((item) => item.data.resolution?.decisionIds ?? [])
12128
+ ])
12129
+ ];
12130
+ return success(null, null, {
12131
+ outcome: requestedOutcome === "stopped" ? "stopped" : accepted.length ? "accepted_limitations" : "verified",
12132
+ evidenceIds,
12133
+ decisionIds,
12134
+ requirementIds: evaluations.map((item) => item.requirementId)
12135
+ });
12136
+ }
12137
+ var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex"), inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`), relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".", canonicalPath = (value) => {
12138
+ if (typeof value !== "string" || !value)
12139
+ return null;
12140
+ if (value.includes("\\") || value.split("/").includes(".."))
12141
+ return null;
12142
+ const normalized = path10.posix.normalize(value);
12143
+ if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
12144
+ return null;
12145
+ return normalized === "." ? "." : normalized.replace(/\/$/, "");
12146
+ }, canonicalScope = (scope) => {
12147
+ const paths = scope.paths.length ? scope.paths : ["."];
12148
+ const normalizedPaths = paths.map(canonicalPath);
12149
+ const normalizedExclusions = scope.exclusions.map(canonicalPath);
12150
+ if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
12151
+ return null;
12152
+ return {
12153
+ description: scope.description,
12154
+ paths: [...new Set(normalizedPaths)],
12155
+ exclusions: [...new Set(normalizedExclusions)]
12156
+ };
12157
+ }, excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`)), scopeMatches = (value, scope) => {
12158
+ const normalized = canonicalScope(scope);
12159
+ if (!normalized)
12160
+ return false;
12161
+ const paths = normalized.paths;
12162
+ return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
12163
+ }, pathRelevant = (value, scope) => scopeMatches(value, scope), compareCodeUnits3 = (left, right) => {
12164
+ const length = Math.min(left.length, right.length);
12165
+ for (let index = 0;index < length; index += 1) {
12166
+ const difference = left.charCodeAt(index) - right.charCodeAt(index);
12167
+ if (difference !== 0)
12168
+ return difference;
12169
+ }
12170
+ return left.length - right.length;
12171
+ }, gitPaths = (root) => {
12172
+ const result = spawnSync2("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
12173
+ cwd: root,
12174
+ encoding: "buffer"
12175
+ });
12176
+ if (result.status !== 0) {
12177
+ const stderr = result.stderr?.toString("utf8") ?? "";
12178
+ return {
12179
+ paths: [],
12180
+ uncertain: !/not a git repository/i.test(stderr),
12181
+ git: /not a git repository/i.test(stderr) ? false : true
12182
+ };
12183
+ }
12184
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
12185
+ const stagedDeleted = spawnSync2("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
12186
+ if (stagedDeleted.status !== 0) {
12187
+ const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
12188
+ if (!/not a git repository/i.test(stderr))
12189
+ return { paths, uncertain: true, git: true };
12190
+ } else if (stagedDeleted.stdout) {
12191
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")));
12192
+ }
12193
+ return { paths: [...new Set(paths)], uncertain: false, git: true };
12194
+ }, walk = (root, directory, output) => {
12195
+ let names;
12196
+ try {
12197
+ names = fs7.readdirSync(directory);
12198
+ } catch {
12199
+ return true;
12200
+ }
12201
+ let uncertain = false;
12202
+ for (const name of names) {
12203
+ if (name === ".git" || name === ".workit")
12204
+ continue;
12205
+ const target = path10.join(directory, name);
12206
+ let stat;
12207
+ try {
12208
+ stat = fs7.lstatSync(target);
12209
+ } catch {
12210
+ uncertain = true;
12211
+ continue;
12212
+ }
12213
+ const item = relative(root, target);
12214
+ output.add(item);
12215
+ if (stat.isDirectory() && !stat.isSymbolicLink())
12216
+ uncertain = walk(root, target, output) || uncertain;
12217
+ }
12218
+ return uncertain;
12219
+ }, scopeRoots = (root, scope) => {
12220
+ const roots = [];
12221
+ for (const item of scope.paths.length ? scope.paths : ["."]) {
12222
+ const target = path10.resolve(root, item);
12223
+ if (!inside(root, target))
12224
+ 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` }] });
12225
+ let ancestor = target;
12226
+ while (!fs7.existsSync(ancestor) && ancestor !== root)
12227
+ ancestor = path10.dirname(ancestor);
12228
+ try {
12229
+ if (!inside(root, fs7.realpathSync(ancestor)))
12230
+ 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` }] });
12231
+ } catch {
12232
+ return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
12233
+ }
12234
+ roots.push(target);
12235
+ }
12236
+ return success(null, null, roots);
12237
+ }, fileMap = (candidate) => new Map(candidate.files.map((file) => [file.path, file])), changedPaths = (before, after) => {
12238
+ const paths = new Set([...before.files, ...after.files].map((file) => file.path));
12239
+ const left = fileMap(before);
12240
+ const right = fileMap(after);
12241
+ const changed = [...paths].filter((item) => JSON.stringify(left.get(item) ?? null) !== JSON.stringify(right.get(item) ?? null));
12242
+ if (before.head !== after.head)
12243
+ changed.push(".");
12244
+ if (JSON.stringify(before.environment) !== JSON.stringify(after.environment))
12245
+ changed.push(".");
12246
+ return changed;
12247
+ }, sessionFromRef = (value) => {
12248
+ if (typeof value !== "object" || value === null || value.kind !== "host")
12249
+ return null;
12250
+ return typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
12251
+ }, sameSession = (left, right) => {
12252
+ const other = sessionFromRef(right);
12253
+ return left !== null && other !== null && left.host === other.host && left.handle === other.handle;
12254
+ }, evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope), scopeCovers2, checkPin = (assignment, beforeCandidateId, candidateId) => {
12255
+ const pin = assignment.candidateId ?? null;
12256
+ if (pin === null)
12401
12257
  return true;
12258
+ return (beforeCandidateId ?? null) === pin || (candidateId ?? null) === pin;
12259
+ }, resolveBinding = (evidence, pin, currentCandidateId) => {
12260
+ const bound = evidence.kind === "check" || evidence.kind === "review";
12261
+ const fallback = bound ? pin ?? currentCandidateId : null;
12262
+ return {
12263
+ beforeCandidateId: evidence.beforeCandidateId ?? fallback,
12264
+ candidateId: evidence.candidateId ?? fallback
12265
+ };
12266
+ }, findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId != null), 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"))), 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 })), evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
12267
+ if (kind !== "passed")
12268
+ return false;
12269
+ if (dimension === "testing" || dimension === "verification")
12270
+ return evidenceKind === "check" || ruleId === "pre-pr-cleanup" && evidenceKind === "artifact";
12271
+ if (dimension === "review")
12272
+ return evidenceKind === "review";
12273
+ if (dimension === "investigation" || dimension === "challenge")
12274
+ return evidenceKind === "investigation";
12275
+ if (dimension === "artifacts" || dimension === "continuity")
12276
+ return evidenceKind === "artifact";
12277
+ return false;
12278
+ }, expectedKinds = (dimension, ruleId) => {
12279
+ if (dimension === "testing")
12280
+ return "check";
12281
+ if (dimension === "verification")
12282
+ return ruleId === "pre-pr-cleanup" ? "check or artifact" : "check";
12283
+ if (dimension === "review")
12284
+ return "review";
12285
+ if (dimension === "investigation" || dimension === "challenge")
12286
+ return "investigation";
12287
+ if (dimension === "artifacts" || dimension === "continuity")
12288
+ return "artifact";
12289
+ if (dimension === "decisions")
12290
+ return "decision";
12291
+ return "worker report";
12292
+ };
12293
+ var init_task_evaluation = __esm(() => {
12294
+ init_task_contract();
12295
+ init_authority();
12296
+ scopeCovers2 = scopeCovers;
12297
+ });
12298
+
12299
+ // packages/workit-core/src/core/methods.ts
12300
+ function selectMethods(policy, capabilities) {
12301
+ return Object.entries(METHODS).flatMap(([id, definition]) => {
12302
+ const requirements = policy.requirements.filter((requirement) => methodMatches(definition, requirement));
12303
+ if (requirements.length === 0)
12304
+ return [];
12305
+ const capability = assuranceFor(id, definition, capabilities);
12306
+ const reasons = [
12307
+ ...new Set(requirements.map((requirement) => requirement.reason)),
12308
+ ...capability.reasons
12309
+ ];
12310
+ if (capability.assurance === "unavailable")
12311
+ reasons.unshift("required capability is unavailable");
12312
+ return [{ id, reason: reasons.join("; "), assurance: capability.assurance }];
12402
12313
  });
12403
12314
  }
12404
- function resolvePolicy(input) {
12405
- const normalized = normalizeResolverInput(input);
12406
- if (!normalized.ok)
12407
- return normalized;
12408
- const requirements = resolveRequirements(normalized.data);
12409
- const policy = {
12410
- policyVersion: POLICY_VERSION,
12411
- inputDigest: sha256(canonicalJson(normalized.data)),
12412
- requirements
12413
- };
12414
- const parsed = policySchema.safeParse(policy);
12415
- if (!parsed.success)
12416
- return failure("invalid_input", "resolved policy is invalid", invalidFields(parsed.error));
12417
- return success(null, null, parsed.data);
12418
- }
12419
- function diffPolicy(previous, next, reason, now) {
12420
- if (!policySchema.safeParse(previous).success && previous !== null)
12421
- throw new TypeError("previous policy is invalid");
12422
- if (!policySchema.safeParse(next).success)
12423
- throw new TypeError("next policy is invalid");
12424
- const oldIds = new Set(previous?.requirements.map((requirement) => requirement.id) ?? []);
12425
- const newIds = new Set(next.requirements.map((requirement) => requirement.id));
12426
- const added = [...newIds].filter((id) => !oldIds.has(id)).sort(compareCodeUnits3);
12427
- const retired = [...oldIds].filter((id) => !newIds.has(id)).sort(compareCodeUnits3);
12428
- if (added.length === 0 && retired.length === 0)
12429
- return null;
12430
- const normalizedReason = reason.trim();
12431
- const changeReason = retired.length ? `${normalizedReason || "policy reassessed"} (retired: ${retired.join(",")})` : normalizedReason || "policy reassessed";
12432
- const change = {
12433
- recordedAt: now,
12434
- fromInputDigest: previous?.inputDigest ?? null,
12435
- toInputDigest: next.inputDigest,
12436
- added,
12437
- retired,
12438
- reason: changeReason
12315
+ var METHODS, methodMatches = (definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true || definition.ruleIds?.includes(requirement.ruleId) === true, assuranceFor = (id, definition, capabilities) => {
12316
+ const keys = new Set([
12317
+ id,
12318
+ ...definition.dimensions ?? [],
12319
+ ...definition.ruleIds ?? []
12320
+ ]);
12321
+ const matches = capabilities.filter((capability) => keys.has(capability.name) || keys.has(capability.surface));
12322
+ if (id === "workit-review" && matches.length === 0)
12323
+ return { assurance: "unavailable", reasons: ["independent review capability is unavailable"] };
12324
+ if (matches.some((capability) => capability.assurance === "unavailable"))
12325
+ return {
12326
+ assurance: "unavailable",
12327
+ reasons: matches.filter((capability) => capability.assurance === "unavailable").map((capability) => capability.reason)
12328
+ };
12329
+ if (matches.some((capability) => capability.assurance === "enforced"))
12330
+ return { assurance: "enforced", reasons: matches.map((capability) => capability.reason) };
12331
+ return { assurance: "agent_guided", reasons: matches.map((capability) => capability.reason) };
12332
+ }, invariantBootstrap = () => `
12333
+ Workit is optional coordination and repository-policy tooling around the host,
12334
+ not a permission system or a mandatory workflow. Native host allow/ask/deny,
12335
+ sandbox, plan/read-only mode, and organization rules remain authoritative.
12336
+ Ordinary investigation, questions, non-Git work, and routine reversible edits
12337
+ need zero Workit task, assessment, or writer calls. Use one compact tracked
12338
+ record only when handoff, dependent steps, concurrent actors, or meaningful
12339
+ decisions make continuity useful; similar titles alone never merge tasks.
12340
+
12341
+ For a routine user-authorized branch or commit, prefer native host Git/shell
12342
+ tools from the outset when managed coordination or outcome reconciliation is
12343
+ not needed. Inspect the actual target checkout and its configured conventions;
12344
+ use Workit's read-only context/policy tools when needed. Do not start a task,
12345
+ acquire a writer, or mint a decision receipt solely for a native Git action.
12346
+ Native permissions still apply. Never switch execution paths to evade a denial
12347
+ or repeat an uncertain managed effect. A local-commit endpoint does not imply
12348
+ PR readiness, a fresh review, or post-commit assessment/closure paperwork.
12349
+
12350
+ For tracked work, inspect its current state and use the shared operations for
12351
+ task, policy, evidence, finding, decision, worker, writer, and state changes.
12352
+ Start a record once for an explicit tracked objective; assess or reassess only
12353
+ when policy selection or changed evidence/constraints requires it. Omitted
12354
+ expectedRevision and expectedWorkspaceRevision use current values; explicit
12355
+ values are still concurrency-checked, so never copy revisions between calls.
12356
+ A solo edit does not need writer acquisition; use it when concurrent checkout
12357
+ writers need coordination. Record only observed facts and checks. Evidence can
12358
+ become stale when its bound candidate changes; reconcile findings against the
12359
+ current candidate before recording completion.
12360
+
12361
+ Workit validates domain policy against the actual action target, configured
12362
+ account, branch and commit conventions, protected refs, and current repository
12363
+ state. The task directory is coordination state, not a boundary on which
12364
+ repository may be changed. Use an action-time cwd target where a managed
12365
+ Git/hosting action supports it; a non-Git directory remains valid for OS work.
12366
+ GitHub/GitLab use the active gh/glab CLI identity; YouTrack uses its own token.
12367
+ Preserve uncertain external outcomes and reconcile repository/provider state
12368
+ before retrying. Internal reservations prevent duplicate or ambiguous effects;
12369
+ they are not permission tickets for every edit.
12370
+
12371
+ On OpenCode V1 and V2, use native host tools for external mutations. Workit
12372
+ provides read-only workit_context; it has no managed external-action executor.
12373
+ Do not create proposals or request Workit approvals merely to invoke native
12374
+ tools. Old uncertain managed effects still require evidence before any retry.
12375
+
12376
+ Use host-native authorization through the host's supported path. A native
12377
+ question receipt records an actual question interaction; it is not automatic
12378
+ host permission. Record a meaningful user choice once with provenance when
12379
+ future retrieval helps. Never fabricate a receipt, re-ask only to mint one, or
12380
+ let imported decisions grant authority. Never turn a host deny into allow or
12381
+ claim enforcement/evidence a host cannot provide. A precise request with settled
12382
+ constraints does not need an interview. Present genuine unresolved options with
12383
+ evidence and a recommendation, then continue toward the requested delivery
12384
+ endpoint: investigation, implementation, branch/commit/push, PR/MR-ready,
12385
+ merge, or release. Run
12386
+ applicable checks and safe repairs without repeated continuation questions;
12387
+ stop for missing host authority, a new consequential choice, a conflicting edit,
12388
+ or an unresolved blocker. A tracked record may close when actual evidence
12389
+ supports its outcome; no final human closure ceremony is required.
12390
+
12391
+ A completed native subagent run stops its bound worker by itself; if a worker
12392
+ strands in cancelling with its run verifiably over, repeat worker.cancel to
12393
+ confirm the stop. Preserve unresolved requirements, gaps, and uncertain workers.
12394
+ When context changes, distinguish a quick question, same-task adjustment, and
12395
+ separate request. Answer a quick question without pausing/resuming task state;
12396
+ park a concise checkpoint only when substantial work needs to continue later.
12397
+ For work spanning repositories, bind each unfinished item to its actual checkout,
12398
+ branch, requested deliverables, and delivery endpoint. Preserve held items with
12399
+ an explicit resume condition. Resolve competing plausible targets before a
12400
+ mutation; ask only when available context cannot settle that consequential choice.
12401
+ Before reporting delivery, reconcile every requested item and observe the actual
12402
+ target result; a local commit alone is not evidence of a requested remote push.
12403
+
12404
+ Skill routing: slash aliases /wk-* load on demand. Use workit-steer for a
12405
+ substantial interruption or change of direction, workit-deslop when relevant
12406
+ to a PR-ready endpoint, workit-green-run for failing CI, workit-blast-radius
12407
+ when impact is uncertain, and workit-challenge for genuinely open consequential
12408
+ choices. Load workit-plan when dependencies or handoff need durable next actions.
12409
+ Load the skill; never act from memory of it.
12410
+ `.trim();
12411
+ var init_methods = __esm(() => {
12412
+ METHODS = {
12413
+ "workit-challenge": { dimensions: ["challenge", "decisions"] },
12414
+ "workit-behavioral-tdd": { dimensions: ["testing"], ruleIds: ["mechanical-existing-checks"] },
12415
+ "workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review", "self-review"] },
12416
+ "workit-plan": { dimensions: ["artifacts", "continuity"] },
12417
+ "workit-implement": { dimensions: ["delegation"] },
12418
+ "workit-debug": {},
12419
+ "workit-handoff": {},
12420
+ "workit-deslop": { ruleIds: ["pre-pr-cleanup"] }
12439
12421
  };
12440
- const parsed = policyChangeSchema.safeParse(change);
12441
- if (!parsed.success)
12442
- throw new TypeError("policy change is invalid");
12443
- return parsed.data;
12444
- }
12445
- var resolverInputSchema, compareCodeUnits3 = (left, right) => {
12446
- const length = Math.min(left.length, right.length);
12447
- for (let index = 0;index < length; index += 1) {
12448
- const difference = left.charCodeAt(index) - right.charCodeAt(index);
12449
- if (difference !== 0)
12450
- return difference;
12451
- }
12452
- return left.length - right.length;
12453
- }, stableUnique = (values) => [...new Set(values)].sort(compareCodeUnits3), stableList = (values) => [...values].sort((left, right) => compareCodeUnits3(canonicalJson(left), canonicalJson(right))), normalizedScope = (scope) => ({
12454
- description: scope.description,
12455
- paths: stableUnique(scope.paths),
12456
- exclusions: stableUnique(scope.exclusions)
12457
- }), normalizedRefList = (values) => stableList(values), normalizeAssessment = (assessment) => ({
12458
- facts: stableList(assessment.facts.map((fact) => ({ ...fact, refs: normalizedRefList(fact.refs) }))),
12459
- signals: Object.fromEntries(Object.entries(assessment.signals).map(([name, signal]) => [
12460
- name,
12461
- { ...signal, refs: normalizedRefList(signal.refs) }
12462
- ])),
12463
- consequences: stableList(assessment.consequences.map((consequence) => ({
12464
- ...consequence,
12465
- fact: { ...consequence.fact, refs: normalizedRefList(consequence.fact.refs) }
12466
- }))),
12467
- verification: stableList(assessment.verification.map((verification) => ({
12468
- ...verification,
12469
- scope: normalizedScope(verification.scope),
12470
- availableChecks: normalizedRefList(verification.availableChecks),
12471
- gaps: stableUnique(verification.gaps)
12472
- })))
12473
- }), normalizeConstraint = (constraint) => ({
12474
- ...constraint,
12475
- source: constraint.source,
12476
- requires: stableList(constraint.requires.map((requirement) => ({
12477
- ...requirement,
12478
- scope: normalizedScope(requirement.scope),
12479
- refs: normalizedRefList(requirement.refs)
12480
- })))
12481
- }), normalizeDecision = (decision) => ({
12482
- ...decision,
12483
- binding: {
12484
- ...decision.binding,
12485
- scope: normalizedScope(decision.binding.scope),
12486
- contentRefs: normalizedRefList(decision.binding.contentRefs)
12487
- },
12488
- requirementIds: stableUnique(decision.requirementIds)
12489
- }), normalizeIntent = (intent) => ({
12490
- ...intent,
12491
- scope: normalizedScope(intent.scope),
12492
- authorityRefs: normalizedRefList(intent.authorityRefs)
12493
- }), normalizePrior = (prior) => ({
12494
- decisions: stableList(prior.decisions.map(normalizeDecision)),
12495
- findings: stableList(prior.findings.map((finding) => ({
12496
- ...finding,
12497
- scope: normalizedScope(finding.scope),
12498
- refs: normalizedRefList(finding.refs),
12499
- resolution: finding.resolution ? {
12500
- ...finding.resolution,
12501
- evidenceIds: stableUnique(finding.resolution.evidenceIds),
12502
- decisionIds: stableUnique(finding.resolution.decisionIds)
12503
- } : null
12504
- }))),
12505
- requirements: stableList(prior.requirements.map((requirement) => ({
12506
- ...requirement,
12507
- scope: normalizedScope(requirement.scope)
12508
- })))
12509
- }), invalidFields = (error) => ({
12510
- fields: error.issues.map((issue) => ({
12511
- path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
12512
- reason: issue.message
12513
- }))
12514
- }), requirement = (spec) => ({
12515
- ...spec,
12516
- id: requirementId({
12517
- ruleId: spec.ruleId,
12518
- scope: spec.scope,
12519
- satisfaction: spec.satisfaction,
12520
- before: spec.before,
12521
- dependentAction: spec.dependentAction
12522
- })
12523
- }), signalReason = (input, name) => input.assessment.signals[name].reason;
12524
- var init_policy_resolver = __esm(() => {
12525
- init_zod();
12422
+ });
12423
+
12424
+ // packages/workit-core/src/core/task-context.ts
12425
+ function reconcileResume(view, observations = []) {
12426
+ if (observations.length > 0)
12427
+ return failure("permission_denied", "worker observations require native host verification");
12428
+ const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
12429
+ if (!candidate.ok)
12430
+ return candidate;
12431
+ const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
12432
+ const blockers = [...view.task.progress.blockers];
12433
+ if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
12434
+ blockers.push({
12435
+ reason: "worker state requires reconciliation",
12436
+ dependentAction: "resume",
12437
+ refs: []
12438
+ });
12439
+ return success(null, null, {
12440
+ candidate: candidate.data,
12441
+ staleEvidenceIds,
12442
+ workerUpdates: [],
12443
+ blockers,
12444
+ reassessmentRequired: view.task.policy?.policyVersion !== POLICY_VERSION
12445
+ });
12446
+ }
12447
+ function compactTaskContext(view) {
12448
+ const decisions = view.task.decisions.slice().sort((left, right) => {
12449
+ const leftAt = compactTimestamp(left.recordedAt);
12450
+ const rightAt = compactTimestamp(right.recordedAt);
12451
+ if (leftAt !== rightAt)
12452
+ return leftAt > rightAt ? -1 : 1;
12453
+ return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
12454
+ }).slice(0, COMPACT_MAX_ITEMS).map((entry) => ({
12455
+ id: entry.id,
12456
+ purpose: entry.data.purpose,
12457
+ status: entry.data.response,
12458
+ digest: entry.data.digest,
12459
+ choice: compactDecisionChoice(entry) || `task.inspect taskId=${view.task.id} view=full; decision=${entry.id}`,
12460
+ references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference)
12461
+ }));
12462
+ const methods = view.task.policy ? selectMethods(view.task.policy, view.capabilities).map((method) => ({
12463
+ id: method.id,
12464
+ assurance: method.assurance,
12465
+ reason: compactText(method.reason, COMPACT_TEXT_BYTES) ?? ""
12466
+ })) : [];
12467
+ const gaps = Array.from(new Set([
12468
+ ...view.evidence.filter((entry) => entry.status === "stale").map((entry) => `stale evidence: ${entry.evidenceId}`),
12469
+ ...view.requirements.filter((entry) => entry.status !== "satisfied").map((entry) => entry.reason),
12470
+ ...view.task.progress.blockers.map((entry) => entry.reason)
12471
+ ])).sort().slice(0, COMPACT_MAX_ITEMS).map((gap) => compactText(gap, COMPACT_TEXT_BYTES));
12472
+ const context = {
12473
+ objective: compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "",
12474
+ status: view.task.status,
12475
+ decisions,
12476
+ methods,
12477
+ gaps,
12478
+ nextAction: compactText(view.task.progress.nextAction, COMPACT_TEXT_BYTES)
12479
+ };
12480
+ let encoded = JSON.stringify(context);
12481
+ while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.methods.length || context.gaps.length)) {
12482
+ if (context.gaps.length)
12483
+ context.gaps.pop();
12484
+ else if (context.decisions.length)
12485
+ context.decisions.pop();
12486
+ else
12487
+ context.methods.pop();
12488
+ encoded = JSON.stringify(context);
12489
+ }
12490
+ if (compactBytes(encoded) > COMPACT_MAX_BYTES) {
12491
+ context.objective = compactText(context.objective, 64) ?? "";
12492
+ context.nextAction = compactText(context.nextAction, 64);
12493
+ encoded = JSON.stringify(context);
12494
+ }
12495
+ return encoded;
12496
+ }
12497
+ var COMPACT_MAX_BYTES = 4096, COMPACT_MAX_ITEMS = 8, COMPACT_TEXT_BYTES = 128, sensitiveUrl, sensitiveValue, privateKey, compactText = (value, limit) => value === null ? null : value.replace(privateKey, "<redacted-private-key>").replace(sensitiveUrl, "<redacted-url>").replace(sensitiveValue, (match) => {
12498
+ const separator = match.search(/[:=]/u);
12499
+ if (separator >= 0)
12500
+ return `${match.slice(0, separator)}=<redacted>`;
12501
+ const space = match.search(/\s/u);
12502
+ return `${match.slice(0, space)} <redacted>`;
12503
+ }).slice(0, limit), compactBytes = (value) => new TextEncoder().encode(value).byteLength, compactTimestamp = (value) => `${value.slice(0, 19)}.${value.slice(20, -1).padEnd(9, "0")}`, compactDecisionChoice = (entry) => {
12504
+ const stated = entry.data.binding.statedChoice?.text;
12505
+ const approved = entry.data.response === "approved" ? entry.data.binding.approvedContent : "";
12506
+ const source = stated?.trim() || approved.trim() || null;
12507
+ return compactText(source, COMPACT_TEXT_BYTES);
12508
+ }, compactReference = (ref) => {
12509
+ if (ref.kind === "file")
12510
+ return {
12511
+ kind: "file",
12512
+ path: compactText(ref.path, COMPACT_TEXT_BYTES) ?? ""
12513
+ };
12514
+ if (ref.kind === "record")
12515
+ return {
12516
+ kind: "record",
12517
+ collection: ref.collection,
12518
+ id: ref.id
12519
+ };
12520
+ return { kind: ref.kind };
12521
+ }, exportDigest = (bundle) => sha256({
12522
+ schemaVersion: bundle.schemaVersion,
12523
+ exportedAt: bundle.exportedAt,
12524
+ sourceWorkspaceId: bundle.sourceWorkspaceId,
12525
+ task: bundle.task
12526
+ });
12527
+ var init_task_context = __esm(() => {
12528
+ init_workers();
12526
12529
  init_task_contract();
12527
- resolverInputSchema = object({
12528
- intent: intentSchema,
12529
- assessment: assessmentSchema,
12530
- constraints: array(constraintSchema),
12531
- prior: object({
12532
- decisions: array(decisionSchema),
12533
- findings: array(findingSchema),
12534
- requirements: array(requirementSchema)
12535
- }).strict(),
12536
- preferences: object({ fast: boolean2().optional(), thorough: boolean2().optional() }).strict().optional()
12537
- }).strict();
12530
+ init_task_evaluation();
12531
+ init_methods();
12532
+ sensitiveUrl = /\bhttps?:\/\/[^/\s:@]+:[^@\s]+@/gi;
12533
+ sensitiveValue = /(?:token|secret|password|passwd|api[-_ ]?key|private[-_ ]?key|authorization)\s*[:=]\s*(?:bearer\s+)?\S+|\bbearer\s+\S+/gi;
12534
+ privateKey = /-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/gi;
12538
12535
  });
12539
12536
 
12540
12537
  // packages/workit-core/src/core/task-engine.ts
@@ -13840,14 +13837,29 @@ class WorkitCore {
13840
13837
  writer: evaluationWorkspace.writer
13841
13838
  });
13842
13839
  }
13843
- view(task) {
13840
+ compactContext(taskId) {
13841
+ const root = this.contextRootError();
13842
+ if (!root.ok)
13843
+ return root;
13844
+ const task = this.store.readTask(taskId);
13845
+ if (!task.ok)
13846
+ return task;
13847
+ const helper = this.helperTaskGuard(task.data, true);
13848
+ if (!helper.ok)
13849
+ return helper;
13850
+ const view = this.view(task.data, false);
13851
+ if (!view.ok)
13852
+ return view;
13853
+ return success(view.revision, view.workspaceRevision, compactTaskContext(view.data));
13854
+ }
13855
+ view(task, capture = true) {
13844
13856
  const workspace = this.store.readWorkspace();
13845
13857
  if (!workspace.ok)
13846
13858
  return workspace;
13847
13859
  if (!workspace.data)
13848
13860
  return failure("not_found", "workspace not found");
13849
13861
  const historical = task.status === "closed" ? task.candidates.at(-1) : undefined;
13850
- const current = historical ? success(null, null, historical) : captureCandidate(this.store.root, task.intent.data.scope, environment());
13862
+ 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);
13851
13863
  if (!current.ok)
13852
13864
  return current;
13853
13865
  const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
@@ -14493,10 +14505,149 @@ var init_task_engine = __esm(() => {
14493
14505
  dispatchReservations = new WeakMap;
14494
14506
  });
14495
14507
 
14508
+ // packages/workit-core/src/core/session-context.ts
14509
+ var cache;
14510
+ var init_session_context = __esm(() => {
14511
+ init_task_contract();
14512
+ init_task_engine();
14513
+ init_task_store();
14514
+ cache = new Map;
14515
+ });
14516
+
14517
+ // packages/workit-core/src/core/route-intent.ts
14518
+ var init_route_intent = __esm(() => {
14519
+ init_branch();
14520
+ });
14521
+
14522
+ // packages/workit-core/src/core/runtime-identity.ts
14523
+ var init_runtime_identity = () => {};
14524
+
14525
+ // packages/workit-core/src/core/external-action.ts
14526
+ var chainStepSchema, externalActionSchema;
14527
+ var init_external_action = __esm(() => {
14528
+ init_task_contract();
14529
+ init_task_store();
14530
+ init_authority();
14531
+ init_zod();
14532
+ chainStepSchema = union([
14533
+ string2().min(1),
14534
+ object({ branch: string2().min(1) }).strict(),
14535
+ object({ pr: literal(true) }).strict()
14536
+ ]);
14537
+ externalActionSchema = discriminatedUnion("operation", [
14538
+ object({
14539
+ operation: literal("git.branch_setup"),
14540
+ payload: object({
14541
+ action: _enum(["setup", "reapply_stash"]).optional(),
14542
+ sdd_dir: string2().optional(),
14543
+ target_branch: string2().optional(),
14544
+ stash: _enum(["yes", "no"]).optional(),
14545
+ cwd: string2().min(1).optional()
14546
+ }).strict()
14547
+ }).strict(),
14548
+ object({
14549
+ operation: literal("git.commit"),
14550
+ payload: object({
14551
+ message: string2().min(1).optional(),
14552
+ plan_steps: array(chainStepSchema).min(1).max(32).optional(),
14553
+ plan_branch: string2().min(1).optional(),
14554
+ cwd: string2().min(1).optional()
14555
+ }).strict()
14556
+ }).strict(),
14557
+ object({
14558
+ operation: literal("git.push"),
14559
+ payload: object({ branch: string2().min(1).optional(), cwd: string2().min(1).optional() }).strict()
14560
+ }).strict(),
14561
+ object({
14562
+ operation: literal("hosting.pull_request"),
14563
+ payload: object({
14564
+ title: string2().min(1),
14565
+ body: string2().optional(),
14566
+ draft: boolean2().optional(),
14567
+ target_branch: string2().optional(),
14568
+ babysit: boolean2().optional(),
14569
+ cwd: string2().min(1).optional()
14570
+ }).strict()
14571
+ }).strict(),
14572
+ object({
14573
+ operation: literal("hosting.merge"),
14574
+ payload: object({
14575
+ target_branch: string2().min(1).optional(),
14576
+ source_branch: string2().min(1).optional(),
14577
+ cwd: string2().min(1).optional()
14578
+ }).strict()
14579
+ }).strict(),
14580
+ object({
14581
+ operation: literal("hosting.delete_branch"),
14582
+ payload: object({ branch: string2().min(1), cwd: string2().min(1).optional() }).strict()
14583
+ }).strict(),
14584
+ object({
14585
+ operation: literal("youtrack.update"),
14586
+ payload: object({
14587
+ issueId: string2().min(1),
14588
+ markdown: string2().min(1),
14589
+ minutes: number2().positive().optional()
14590
+ }).strict()
14591
+ }).strict(),
14592
+ object({
14593
+ operation: literal("youtrack.time"),
14594
+ payload: object({
14595
+ issueId: string2().min(1),
14596
+ minutes: number2().positive(),
14597
+ text: string2().optional(),
14598
+ dateMs: number2().finite().optional()
14599
+ }).strict()
14600
+ }).strict(),
14601
+ object({
14602
+ operation: literal("youtrack.meeting"),
14603
+ payload: object({
14604
+ issueId: string2().min(1),
14605
+ minutes: number2().positive(),
14606
+ text: string2().min(1)
14607
+ }).strict()
14608
+ }).strict(),
14609
+ object({
14610
+ operation: literal("changelog.apply"),
14611
+ payload: object({
14612
+ entries: union([
14613
+ record(string2(), array(string2())),
14614
+ array(object({ category: string2().min(1), text: string2().min(1) }).strict())
14615
+ ]).optional(),
14616
+ path: string2().optional(),
14617
+ normalize_only: boolean2().optional()
14618
+ }).strict()
14619
+ }).strict(),
14620
+ object({
14621
+ operation: literal("context.read"),
14622
+ payload: object({
14623
+ kind: _enum([
14624
+ "git",
14625
+ "pr",
14626
+ "youtrack",
14627
+ "github_issue",
14628
+ "gitlab_issue",
14629
+ "changelog",
14630
+ "release",
14631
+ "affected"
14632
+ ]),
14633
+ range: string2().optional(),
14634
+ issueId: string2().optional(),
14635
+ issueUrl: string2().optional(),
14636
+ issueRef: string2().optional(),
14637
+ mode: string2().optional(),
14638
+ specPath: string2().optional(),
14639
+ planPath: string2().optional(),
14640
+ cwd: string2().min(1).optional()
14641
+ }).strict()
14642
+ }).strict()
14643
+ ]);
14644
+ });
14645
+
14496
14646
  // packages/workit-core/src/core.ts
14497
14647
  var init_core3 = __esm(() => {
14498
14648
  init_task_contract();
14499
14649
  init_task_store();
14650
+ init_session_context();
14500
14651
  init_task_context();
14501
14652
  init_methods();
14502
14653
  init_route_intent();
@@ -14627,7 +14778,7 @@ var hostRef = (handle) => ({ kind: "host", host: "cursor", handle }), cursorCapa
14627
14778
  reason: tasks.length ? "task state is ambiguous" : "no active task"
14628
14779
  };
14629
14780
  return { ok: true, workspace: workspace.data, task: tasks[0] };
14630
- }, historyOfferSessions, unfinishedTaskOffer = (root, session, excludedTaskId) => {
14781
+ }, historyOfferSessions, unfinishedTaskOffer2 = (root, session, excludedTaskId) => {
14631
14782
  try {
14632
14783
  const listed = new TaskStore(root).listTasks();
14633
14784
  if (!listed.ok)
@@ -14731,18 +14882,13 @@ ${tasks.map((task) => `- ${task.id} [${task.status}; source ${task.intent.proven
14731
14882
  let compact = "";
14732
14883
  const store = new TaskStore(root);
14733
14884
  const state = activeTask(store);
14734
- const offer = historyOfferSessions.has(actor) ? null : unfinishedTaskOffer(root, actor, state.ok ? state.task.id : undefined);
14885
+ const offer = historyOfferSessions.has(actor) ? null : unfinishedTaskOffer2(root, actor, state.ok ? state.task.id : undefined);
14735
14886
  historyOfferSessions.add(actor);
14736
14887
  if (state.ok) {
14737
- const view = new WorkitCore(store, contextFor(root, actor, null, { sessionStart: true })).task({
14738
- schemaVersion: 1,
14739
- action: "inspect",
14740
- taskId: state.task.id,
14741
- view: "full"
14742
- });
14743
- if (view.ok)
14888
+ const context = new WorkitCore(store, contextFor(root, actor, null, { sessionStart: true })).compactContext(state.task.id);
14889
+ if (context.ok)
14744
14890
  compact = `
14745
- <workit-task-context>${compactTaskContext(view.data)}</workit-task-context>`;
14891
+ <workit-task-context>${context.data}</workit-task-context>`;
14746
14892
  }
14747
14893
  return {
14748
14894
  additional_context: `<workit-contract>