@brainervirus/workit-codex 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.
@@ -15731,6 +15731,45 @@ var metadataLockSchema = object({
15731
15731
  nonce: string2().min(1),
15732
15732
  externalAction: literal(true).optional()
15733
15733
  }).strict();
15734
+ var INDEX_VERSION = 1;
15735
+ var hostSession = (value) => isObject2(value) && value.kind === "host" && typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
15736
+ var indexEntry = (task, file) => {
15737
+ const sessions = [];
15738
+ const intent = hostSession(task.intent.provenance.session);
15739
+ if (intent)
15740
+ sessions.push({ ...intent, workerId: null });
15741
+ for (const worker of task.workers) {
15742
+ const session = hostSession(worker.data.session);
15743
+ if (session)
15744
+ sessions.push({ ...session, workerId: worker.id });
15745
+ }
15746
+ return {
15747
+ id: task.id,
15748
+ revision: task.revision,
15749
+ status: task.status,
15750
+ createdAt: task.createdAt,
15751
+ updatedAt: task.updatedAt,
15752
+ objective: task.intent.data.objective,
15753
+ source: { host: task.intent.provenance.host, kind: task.intent.provenance.kind },
15754
+ progress: { summary: task.progress.summary, nextAction: task.progress.nextAction },
15755
+ sessions,
15756
+ file
15757
+ };
15758
+ };
15759
+ var fileSignature = (file) => {
15760
+ try {
15761
+ const stat = fs4.statSync(file, { bigint: true });
15762
+ return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
15763
+ } catch {
15764
+ return null;
15765
+ }
15766
+ };
15767
+ var RACY_WINDOW_NS = 2000000000n;
15768
+ var racySignature = (signature) => {
15769
+ const [mtime = 0n, ctime = 0n] = signature.split(":").slice(3, 5).map(BigInt);
15770
+ const changed = mtime > ctime ? mtime : ctime;
15771
+ return BigInt(Date.now()) * 1000000n - changed < RACY_WINDOW_NS;
15772
+ };
15734
15773
  var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
15735
15774
  var cachedRuntimeVersion = null;
15736
15775
  var runtimeVersion = () => {
@@ -15859,6 +15898,57 @@ class TaskStore {
15859
15898
  }
15860
15899
  return success(null, null, tasks);
15861
15900
  }
15901
+ listTaskIndex() {
15902
+ if (!fs4.existsSync(this.tasksDir))
15903
+ return success(null, null, []);
15904
+ let names;
15905
+ try {
15906
+ names = fs4.readdirSync(this.tasksDir).filter((name) => name.endsWith(".json"));
15907
+ } catch (error) {
15908
+ return failure("storage_error", `unable to list tasks: ${String(error)}`, {
15909
+ path: this.tasksDir
15910
+ });
15911
+ }
15912
+ const workspace = this.readWorkspace();
15913
+ if (!workspace.ok)
15914
+ return workspace;
15915
+ if (!workspace.data)
15916
+ return failure("recovery_required", "task workspace binding is invalid", {
15917
+ path: this.workspacePath
15918
+ });
15919
+ const stored = this.readIndex(workspace.data.id);
15920
+ const entries = [];
15921
+ let changed = Object.keys(stored).length !== names.length;
15922
+ for (const name of names.sort()) {
15923
+ const file = path4.join(this.tasksDir, name);
15924
+ const signature = fileSignature(file);
15925
+ if (signature === null) {
15926
+ changed = true;
15927
+ continue;
15928
+ }
15929
+ const cached = stored[name.slice(0, -5)];
15930
+ if (cached && cached.file === signature && !racySignature(signature)) {
15931
+ entries.push(cached);
15932
+ continue;
15933
+ }
15934
+ const item = this.readRecord(file, taskRecordSchema);
15935
+ if (!item.exists)
15936
+ continue;
15937
+ if (!item.result.ok)
15938
+ return item.result;
15939
+ if (!validId(name.slice(0, -5)) || item.result.data.id !== name.slice(0, -5))
15940
+ return failure("recovery_required", "task filename and record ID differ", { path: name });
15941
+ if (item.result.data.workspaceId !== workspace.data.id)
15942
+ return failure("recovery_required", "task workspace binding is invalid", { path: name });
15943
+ const entry = indexEntry(item.result.data, signature);
15944
+ if (!cached || canonicalJson(cached) !== canonicalJson(entry))
15945
+ changed = true;
15946
+ entries.push(entry);
15947
+ }
15948
+ if (changed)
15949
+ this.writeIndex(workspace.data.id, entries);
15950
+ return success(null, null, entries);
15951
+ }
15862
15952
  readWorkspace() {
15863
15953
  const item = this.readRecord(this.workspacePath, workspaceRecordSchema);
15864
15954
  if (!item.exists)
@@ -16538,6 +16628,8 @@ class TaskStore {
16538
16628
  fs4.renameSync(temporary, file);
16539
16629
  temporary = undefined;
16540
16630
  this.fsyncDirectory(path4.dirname(file));
16631
+ if (path4.dirname(file) === this.tasksDir)
16632
+ this.indexTaskWrite(file, value);
16541
16633
  return success(null, null, value);
16542
16634
  } catch (error) {
16543
16635
  return failure("storage_error", `snapshot replacement failed: ${String(error)}`, {
@@ -16550,6 +16642,45 @@ class TaskStore {
16550
16642
  } catch {}
16551
16643
  }
16552
16644
  }
16645
+ readIndex(workspaceId) {
16646
+ try {
16647
+ const value = JSON.parse(fs4.readFileSync(this.indexPath, "utf8"));
16648
+ if (!isObject2(value) || value.version !== INDEX_VERSION || value.workspaceId !== workspaceId || !isObject2(value.tasks))
16649
+ return {};
16650
+ const tasks = {};
16651
+ for (const [id, entry] of Object.entries(value.tasks))
16652
+ 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))
16653
+ tasks[id] = entry;
16654
+ return tasks;
16655
+ } catch {
16656
+ return {};
16657
+ }
16658
+ }
16659
+ writeIndex(workspaceId, entries) {
16660
+ const temporary = `${this.indexPath}.${process.pid}.${randomUUID2()}.tmp`;
16661
+ try {
16662
+ fs4.writeFileSync(temporary, JSON.stringify({
16663
+ version: INDEX_VERSION,
16664
+ workspaceId,
16665
+ tasks: Object.fromEntries(entries.map((entry) => [entry.id, entry]))
16666
+ }), { mode: 384, flag: "wx" });
16667
+ fs4.renameSync(temporary, this.indexPath);
16668
+ } catch {
16669
+ try {
16670
+ fs4.unlinkSync(temporary);
16671
+ } catch {}
16672
+ }
16673
+ }
16674
+ indexTaskWrite(file, task) {
16675
+ try {
16676
+ const signature = fileSignature(file);
16677
+ if (signature === null)
16678
+ return;
16679
+ const tasks = this.readIndex(task.workspaceId);
16680
+ tasks[task.id] = indexEntry(task, signature);
16681
+ this.writeIndex(task.workspaceId, Object.values(tasks));
16682
+ } catch {}
16683
+ }
16553
16684
  saveRecovery(file, bytes) {
16554
16685
  const target = path4.basename(file) === "workspace.json" ? "workspace" : "task";
16555
16686
  const id = target === "task" ? path4.basename(file, ".json") : "workspace";
@@ -16708,6 +16839,9 @@ class TaskStore {
16708
16839
  get workspacePath() {
16709
16840
  return path4.join(this.workitDir, "workspace.json");
16710
16841
  }
16842
+ get indexPath() {
16843
+ return path4.join(this.workitDir, "index.json");
16844
+ }
16711
16845
  get lockPath() {
16712
16846
  return path4.join(this.workitDir, "metadata.lock");
16713
16847
  }
@@ -16715,71 +16849,8 @@ class TaskStore {
16715
16849
  return path4.join(this.workitDir, ".gitignore");
16716
16850
  }
16717
16851
  }
16718
- // packages/workit-core/src/core/workers.ts
16719
- var same = (left, right) => {
16720
- try {
16721
- return canonicalJson(left) === canonicalJson(right);
16722
- } catch {
16723
- return false;
16724
- }
16725
- };
16726
- var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
16727
- var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
16728
- var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
16729
- var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
16730
- var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
16731
- const code = char.charCodeAt(0);
16732
- return code < 32 || code === 127;
16733
- }) && !value.split(/[\\/]/).includes("..");
16734
- var callerValue = (value) => ({ host: value.host, actor: value.actor });
16735
- var workerIdOf = (value) => value.workerId ?? null;
16736
- var currentWriterOwnsTask = (task, workspace, caller) => {
16737
- const owner = workspace.writer?.owner;
16738
- return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
16739
- };
16740
- function assertProductWriteAllowed(input) {
16741
- if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
16742
- return failure("invalid_input", "invalid product write path");
16743
- if (!input.store)
16744
- return failure("permission_denied", "write authorization requires core state");
16745
- const task = input.store.readTask(input.task.id);
16746
- if (!task.ok)
16747
- return task;
16748
- const workspace = input.store.readWorkspace();
16749
- if (!workspace.ok)
16750
- return workspace;
16751
- if (!workspace.data)
16752
- return failure("not_found", "workspace not found");
16753
- const workspaceRecord = workspace.data;
16754
- if (task.data.status !== "active")
16755
- return failure("invalid_transition", "paused or closed tasks cannot own product writes");
16756
- if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
16757
- return failure("recovery_required", "task and workspace bindings are invalid");
16758
- const writer = workspaceRecord.writer;
16759
- if (!writer)
16760
- return failure("permission_denied", "checkout has no writer owner");
16761
- if (writer.state === "uncertain")
16762
- return failure("recovery_required", "checkout writer requires recovery", {
16763
- owner: writer.owner
16764
- });
16765
- const caller = callerValue(input.caller);
16766
- const workerId = workerIdOf(input.caller);
16767
- const owner = writer.owner;
16768
- if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
16769
- return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
16770
- const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
16771
- if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
16772
- return failure("permission_denied", "only implementers can own product writes");
16773
- 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))))
16774
- return failure("recovery_required", "worker session is not an active writer");
16775
- return success(null, workspaceRecord.revision, owner);
16776
- }
16777
-
16778
- // packages/workit-core/src/core/task-evaluation.ts
16779
- import { createHash as createHash6 } from "node:crypto";
16780
- import * as fs7 from "node:fs";
16781
- import { spawnSync as spawnSync2 } from "node:child_process";
16782
- import path10 from "node:path";
16852
+ // packages/workit-core/src/core/task-engine.ts
16853
+ import { realpathSync as realpathSync6 } from "node:fs";
16783
16854
 
16784
16855
  // packages/workit-core/src/core/authority.ts
16785
16856
  import { createHash as createHash5 } from "node:crypto";
@@ -16913,7 +16984,6 @@ var LOCALE_RE = /^[a-z]{2,3}(-(?:[A-Z]{2}|[0-9]{3}))?$/;
16913
16984
  var DEFAULTS = {
16914
16985
  locale: "en",
16915
16986
  localeOptions: ["en", "es-CL", "es-MX", "es-AR", "pt-BR"],
16916
- timezone: "America/Santiago",
16917
16987
  branchPolicy: {
16918
16988
  preset: "gitflow",
16919
16989
  allowed: [...PRESETS.gitflow.allowed],
@@ -16951,7 +17021,6 @@ var parseConfigResult = (raw, file) => {
16951
17021
  config: {
16952
17022
  locale,
16953
17023
  localeOptions: Array.isArray(input.localeOptions) ? input.localeOptions : DEFAULTS.localeOptions,
16954
- timezone: input.timezone ?? DEFAULTS.timezone,
16955
17024
  branchPolicy: mergePreset(preset, {
16956
17025
  allowed: Array.isArray(input.branchPolicy?.allowed) ? input.branchPolicy.allowed : undefined,
16957
17026
  protectedNames: Array.isArray(input.branchPolicy?.protected) ? input.branchPolicy.protected : undefined
@@ -17786,6 +17855,66 @@ var verifyPushIdentity = (root, provider, account) => {
17786
17855
  return { ok: true };
17787
17856
  };
17788
17857
 
17858
+ // packages/workit-core/src/core/workers.ts
17859
+ var same = (left, right) => {
17860
+ try {
17861
+ return canonicalJson(left) === canonicalJson(right);
17862
+ } catch {
17863
+ return false;
17864
+ }
17865
+ };
17866
+ var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
17867
+ var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
17868
+ var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
17869
+ var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
17870
+ var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
17871
+ const code = char.charCodeAt(0);
17872
+ return code < 32 || code === 127;
17873
+ }) && !value.split(/[\\/]/).includes("..");
17874
+ var callerValue = (value) => ({ host: value.host, actor: value.actor });
17875
+ var workerIdOf = (value) => value.workerId ?? null;
17876
+ var currentWriterOwnsTask = (task, workspace, caller) => {
17877
+ const owner = workspace.writer?.owner;
17878
+ return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
17879
+ };
17880
+ function assertProductWriteAllowed(input) {
17881
+ if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
17882
+ return failure("invalid_input", "invalid product write path");
17883
+ if (!input.store)
17884
+ return failure("permission_denied", "write authorization requires core state");
17885
+ const task = input.store.readTask(input.task.id);
17886
+ if (!task.ok)
17887
+ return task;
17888
+ const workspace = input.store.readWorkspace();
17889
+ if (!workspace.ok)
17890
+ return workspace;
17891
+ if (!workspace.data)
17892
+ return failure("not_found", "workspace not found");
17893
+ const workspaceRecord = workspace.data;
17894
+ if (task.data.status !== "active")
17895
+ return failure("invalid_transition", "paused or closed tasks cannot own product writes");
17896
+ if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
17897
+ return failure("recovery_required", "task and workspace bindings are invalid");
17898
+ const writer = workspaceRecord.writer;
17899
+ if (!writer)
17900
+ return failure("permission_denied", "checkout has no writer owner");
17901
+ if (writer.state === "uncertain")
17902
+ return failure("recovery_required", "checkout writer requires recovery", {
17903
+ owner: writer.owner
17904
+ });
17905
+ const caller = callerValue(input.caller);
17906
+ const workerId = workerIdOf(input.caller);
17907
+ const owner = writer.owner;
17908
+ if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
17909
+ return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
17910
+ const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
17911
+ if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
17912
+ return failure("permission_denied", "only implementers can own product writes");
17913
+ 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))))
17914
+ return failure("recovery_required", "worker session is not an active writer");
17915
+ return success(null, workspaceRecord.revision, owner);
17916
+ }
17917
+
17789
17918
  // packages/workit-core/src/core/auto-approval.ts
17790
17919
  var AUTO_CLASSES = ["branch", "commit", "push", "pr", "merge"];
17791
17920
  var OPERATION_CLASS = {
@@ -18476,41 +18605,18 @@ function reconcileAction(input) {
18476
18605
  var bindingCovers = scopeCovers;
18477
18606
  var verifyDecisionContent = verifyContentRefs;
18478
18607
 
18479
- // packages/workit-core/src/core/task-evaluation.ts
18480
- var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex");
18481
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`);
18482
- var relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".";
18483
- var canonicalPath = (value) => {
18484
- if (typeof value !== "string" || !value)
18485
- return null;
18486
- if (value.includes("\\") || value.split("/").includes(".."))
18487
- return null;
18488
- const normalized = path10.posix.normalize(value);
18489
- if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
18490
- return null;
18491
- return normalized === "." ? "." : normalized.replace(/\/$/, "");
18492
- };
18493
- var canonicalScope = (scope) => {
18494
- const paths = scope.paths.length ? scope.paths : ["."];
18495
- const normalizedPaths = paths.map(canonicalPath);
18496
- const normalizedExclusions = scope.exclusions.map(canonicalPath);
18497
- if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
18498
- return null;
18499
- return {
18500
- description: scope.description,
18501
- paths: [...new Set(normalizedPaths)],
18502
- exclusions: [...new Set(normalizedExclusions)]
18503
- };
18504
- };
18505
- var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
18506
- var scopeMatches = (value, scope) => {
18507
- const normalized = canonicalScope(scope);
18508
- if (!normalized)
18509
- return false;
18510
- const paths = normalized.paths;
18511
- return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
18512
- };
18513
- var pathRelevant = (value, scope) => scopeMatches(value, scope);
18608
+ // packages/workit-core/src/core/policy-resolver.ts
18609
+ var resolverInputSchema = object({
18610
+ intent: intentSchema,
18611
+ assessment: assessmentSchema,
18612
+ constraints: array(constraintSchema),
18613
+ prior: object({
18614
+ decisions: array(decisionSchema),
18615
+ findings: array(findingSchema),
18616
+ requirements: array(requirementSchema)
18617
+ }).strict(),
18618
+ preferences: object({ fast: boolean2().optional(), thorough: boolean2().optional() }).strict().optional()
18619
+ }).strict();
18514
18620
  var compareCodeUnits2 = (left, right) => {
18515
18621
  const length = Math.min(left.length, right.length);
18516
18622
  for (let index = 0;index < length; index += 1) {
@@ -18520,927 +18626,970 @@ var compareCodeUnits2 = (left, right) => {
18520
18626
  }
18521
18627
  return left.length - right.length;
18522
18628
  };
18523
- var gitPaths = (root) => {
18524
- const result = spawnSync2("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
18525
- cwd: root,
18526
- encoding: "buffer"
18527
- });
18528
- if (result.status !== 0) {
18529
- const stderr = result.stderr?.toString("utf8") ?? "";
18530
- return {
18531
- paths: [],
18532
- uncertain: !/not a git repository/i.test(stderr),
18533
- git: /not a git repository/i.test(stderr) ? false : true
18534
- };
18535
- }
18536
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
18537
- const stagedDeleted = spawnSync2("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
18538
- if (stagedDeleted.status !== 0) {
18539
- const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
18540
- if (!/not a git repository/i.test(stderr))
18541
- return { paths, uncertain: true, git: true };
18542
- } else if (stagedDeleted.stdout) {
18543
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")));
18544
- }
18545
- return { paths: [...new Set(paths)], uncertain: false, git: true };
18546
- };
18547
- var walk = (root, directory, output) => {
18548
- let names;
18549
- try {
18550
- names = fs7.readdirSync(directory);
18551
- } catch {
18552
- return true;
18553
- }
18554
- let uncertain = false;
18555
- for (const name of names) {
18556
- if (name === ".git" || name === ".workit")
18557
- continue;
18558
- const target = path10.join(directory, name);
18559
- let stat;
18560
- try {
18561
- stat = fs7.lstatSync(target);
18562
- } catch {
18563
- uncertain = true;
18564
- continue;
18565
- }
18566
- const item = relative(root, target);
18567
- output.add(item);
18568
- if (stat.isDirectory() && !stat.isSymbolicLink())
18569
- uncertain = walk(root, target, output) || uncertain;
18570
- }
18571
- return uncertain;
18572
- };
18573
- var scopeRoots = (root, scope) => {
18574
- const roots = [];
18575
- for (const item of scope.paths.length ? scope.paths : ["."]) {
18576
- const target = path10.resolve(root, item);
18577
- if (!inside(root, target))
18578
- 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` }] });
18579
- let ancestor = target;
18580
- while (!fs7.existsSync(ancestor) && ancestor !== root)
18581
- ancestor = path10.dirname(ancestor);
18582
- try {
18583
- if (!inside(root, fs7.realpathSync(ancestor)))
18584
- 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` }] });
18585
- } catch {
18586
- return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
18587
- }
18588
- roots.push(target);
18589
- }
18590
- return success(null, null, roots);
18591
- };
18592
- function captureCandidate(root, scope, environment = []) {
18593
- let checkout;
18594
- try {
18595
- checkout = fs7.realpathSync(root);
18596
- } catch {
18597
- return failure("invalid_input", "candidate root does not exist");
18598
- }
18599
- const normalizedScope = canonicalScope(scope);
18600
- if (!normalizedScope)
18601
- 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" }] });
18602
- const roots = scopeRoots(checkout, normalizedScope);
18603
- if (!roots.ok)
18604
- return roots;
18605
- const git = gitPaths(checkout);
18606
- const names = new Set;
18607
- let uncertain = git.uncertain;
18608
- for (const target of roots.data) {
18609
- const item = relative(checkout, target);
18610
- if (item === ".git" || item === ".workit")
18611
- continue;
18612
- names.add(item);
18613
- if (!fs7.existsSync(target))
18614
- continue;
18615
- let stat;
18616
- try {
18617
- stat = fs7.lstatSync(target);
18618
- } catch {
18619
- uncertain = true;
18620
- continue;
18621
- }
18622
- if (!git.git && stat.isDirectory() && !stat.isSymbolicLink())
18623
- uncertain = walk(checkout, target, names) || uncertain;
18624
- }
18625
- for (const item of git.paths)
18626
- if (scopeMatches(item, normalizedScope))
18627
- names.add(item);
18628
- const files = [];
18629
- let completeness = uncertain ? "uncertain" : "known";
18630
- for (const item of [...names].sort()) {
18631
- if (!scopeMatches(item, normalizedScope))
18632
- continue;
18633
- const target = path10.join(checkout, item);
18634
- try {
18635
- const stat = fs7.lstatSync(target);
18636
- if (stat.isSymbolicLink()) {
18637
- const link = fs7.readlinkSync(target);
18638
- files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
18639
- } else if (stat.isFile()) {
18640
- files.push({
18641
- path: item,
18642
- kind: "file",
18643
- digest: digestBytes3(fs7.readFileSync(target)),
18644
- executable: (stat.mode & 73) !== 0
18645
- });
18646
- } else if (!stat.isDirectory()) {
18647
- completeness = "uncertain";
18648
- } else if (git.git && git.paths.includes(item)) {
18649
- completeness = "uncertain";
18650
- }
18651
- } catch (error) {
18652
- if (error?.code === "ENOENT") {
18653
- files.push({ path: item, kind: "absent", digest: null, executable: null });
18654
- } else
18655
- completeness = "uncertain";
18656
- }
18657
- }
18658
- 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) => ({
18629
+ var stableUnique = (values) => [...new Set(values)].sort(compareCodeUnits2);
18630
+ var stableList = (values) => [...values].sort((left, right) => compareCodeUnits2(canonicalJson(left), canonicalJson(right)));
18631
+ var normalizedScope = (scope) => ({
18632
+ description: scope.description,
18633
+ paths: stableUnique(scope.paths),
18634
+ exclusions: stableUnique(scope.exclusions)
18635
+ });
18636
+ var normalizedRefList = (values) => stableList(values);
18637
+ var normalizeAssessment = (assessment) => ({
18638
+ facts: stableList(assessment.facts.map((fact) => ({ ...fact, refs: normalizedRefList(fact.refs) }))),
18639
+ signals: Object.fromEntries(Object.entries(assessment.signals).map(([name, signal]) => [
18659
18640
  name,
18660
- value: environment[name] ?? null
18661
- }));
18662
- const namesByEnvironment = supplied.map(({ name }) => name);
18663
- if (new Set(namesByEnvironment).size !== namesByEnvironment.length || namesByEnvironment.some((name) => typeof name !== "string" || !name))
18664
- return failure("invalid_input", "candidate environment names must be unique and non-empty");
18665
- const values = supplied.sort((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
18666
- if (values.some(({ value }) => value === null))
18667
- completeness = "uncertain";
18668
- const headResult = spawnSync2("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
18669
- if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
18670
- completeness = "uncertain";
18671
- const head = headResult.status === 0 ? headResult.stdout.trim() : null;
18672
- const partial = {
18673
- id: "0".repeat(64),
18674
- scope: normalizedScope,
18675
- completeness,
18676
- files,
18677
- environment: values,
18678
- head
18641
+ { ...signal, refs: normalizedRefList(signal.refs) }
18642
+ ])),
18643
+ consequences: stableList(assessment.consequences.map((consequence) => ({
18644
+ ...consequence,
18645
+ fact: { ...consequence.fact, refs: normalizedRefList(consequence.fact.refs) }
18646
+ }))),
18647
+ verification: stableList(assessment.verification.map((verification) => ({
18648
+ ...verification,
18649
+ scope: normalizedScope(verification.scope),
18650
+ availableChecks: normalizedRefList(verification.availableChecks),
18651
+ gaps: stableUnique(verification.gaps)
18652
+ })))
18653
+ });
18654
+ var normalizeConstraint = (constraint) => ({
18655
+ ...constraint,
18656
+ source: constraint.source,
18657
+ requires: stableList(constraint.requires.map((requirement) => ({
18658
+ ...requirement,
18659
+ scope: normalizedScope(requirement.scope),
18660
+ refs: normalizedRefList(requirement.refs)
18661
+ })))
18662
+ });
18663
+ var normalizeDecision = (decision) => ({
18664
+ ...decision,
18665
+ binding: {
18666
+ ...decision.binding,
18667
+ scope: normalizedScope(decision.binding.scope),
18668
+ contentRefs: normalizedRefList(decision.binding.contentRefs)
18669
+ },
18670
+ requirementIds: stableUnique(decision.requirementIds)
18671
+ });
18672
+ var normalizeIntent = (intent) => ({
18673
+ ...intent,
18674
+ scope: normalizedScope(intent.scope),
18675
+ authorityRefs: normalizedRefList(intent.authorityRefs)
18676
+ });
18677
+ var normalizePrior = (prior) => ({
18678
+ decisions: stableList(prior.decisions.map(normalizeDecision)),
18679
+ findings: stableList(prior.findings.map((finding) => ({
18680
+ ...finding,
18681
+ scope: normalizedScope(finding.scope),
18682
+ refs: normalizedRefList(finding.refs),
18683
+ resolution: finding.resolution ? {
18684
+ ...finding.resolution,
18685
+ evidenceIds: stableUnique(finding.resolution.evidenceIds),
18686
+ decisionIds: stableUnique(finding.resolution.decisionIds)
18687
+ } : null
18688
+ }))),
18689
+ requirements: stableList(prior.requirements.map((requirement) => ({
18690
+ ...requirement,
18691
+ scope: normalizedScope(requirement.scope)
18692
+ })))
18693
+ });
18694
+ var invalidFields = (error) => ({
18695
+ fields: error.issues.map((issue) => ({
18696
+ path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
18697
+ reason: issue.message
18698
+ }))
18699
+ });
18700
+ function normalizeResolverInput(input) {
18701
+ const parsed = resolverInputSchema.safeParse(input);
18702
+ if (!parsed.success)
18703
+ return failure("invalid_input", "resolver input is invalid", invalidFields(parsed.error));
18704
+ const value = parsed.data;
18705
+ const signals = value.assessment.signals;
18706
+ if (signals.behaviorChange.value === true && signals.mechanicalLowRisk.value === true)
18707
+ return failure("invalid_input", "behavioral change cannot be wholly mechanical", {
18708
+ fields: [
18709
+ {
18710
+ path: "assessment.signals",
18711
+ reason: "contradictory behaviorChange and mechanicalLowRisk"
18712
+ }
18713
+ ]
18714
+ });
18715
+ const ids = value.constraints.map((constraint) => constraint.id);
18716
+ if (new Set(ids).size !== ids.length)
18717
+ return failure("invalid_input", "constraint IDs must be unique", {
18718
+ fields: [{ path: "constraints", reason: "duplicate constraint id" }]
18719
+ });
18720
+ if (value.preferences?.fast && value.preferences.thorough)
18721
+ return failure("invalid_input", "fast and thorough preferences contradict", {
18722
+ fields: [{ path: "preferences", reason: "fast and thorough cannot both be enabled" }]
18723
+ });
18724
+ const normalized = {
18725
+ intent: normalizeIntent(value.intent),
18726
+ assessment: normalizeAssessment(value.assessment),
18727
+ constraints: stableList(value.constraints.map(normalizeConstraint)),
18728
+ prior: normalizePrior(value.prior),
18729
+ ...value.preferences ? { preferences: { ...value.preferences } } : {},
18730
+ policyVersion: POLICY_VERSION
18679
18731
  };
18680
- const candidate = { ...partial, id: candidateDigest(partial) };
18681
- const parsed = candidateSchema.safeParse(candidate);
18682
- return parsed.success ? success(null, null, parsed.data) : failure("invalid_input", "captured candidate is invalid");
18732
+ return success(null, null, normalized);
18683
18733
  }
18684
- var fileMap = (candidate) => new Map(candidate.files.map((file) => [file.path, file]));
18685
- var changedPaths = (before, after) => {
18686
- const paths = new Set([...before.files, ...after.files].map((file) => file.path));
18687
- const left = fileMap(before);
18688
- const right = fileMap(after);
18689
- const changed = [...paths].filter((item) => JSON.stringify(left.get(item) ?? null) !== JSON.stringify(right.get(item) ?? null));
18690
- if (before.head !== after.head)
18691
- changed.push(".");
18692
- if (JSON.stringify(before.environment) !== JSON.stringify(after.environment))
18693
- changed.push(".");
18694
- return changed;
18695
- };
18696
- var sessionFromRef = (value) => {
18697
- if (typeof value !== "object" || value === null || value.kind !== "host")
18698
- return null;
18699
- return typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
18700
- };
18701
- var sameSession = (left, right) => {
18702
- const other = sessionFromRef(right);
18703
- return left !== null && other !== null && left.host === other.host && left.handle === other.handle;
18704
- };
18705
- var evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope);
18706
- function evaluateEvidence(task, candidate) {
18707
- const current = candidate ?? task.candidates.at(-1) ?? null;
18708
- return task.evidence.map((entry) => {
18709
- const evidence = entry.data;
18710
- if (evidence.result === "failed")
18711
- return {
18712
- evidenceId: entry.id,
18713
- status: "failed",
18714
- reason: "failed evidence remains historical"
18715
- };
18716
- if (evidence.kind === "check" || evidence.kind === "review") {
18717
- if (!evidence.beforeCandidateId || !evidence.candidateId)
18718
- return {
18719
- evidenceId: entry.id,
18720
- status: "stale",
18721
- reason: "check or review evidence lacks start and end candidates"
18722
- };
18723
- if (evidence.beforeCandidateId !== evidence.candidateId)
18724
- return {
18725
- evidenceId: entry.id,
18726
- status: "stale",
18727
- reason: "candidate changed during the check or review"
18728
- };
18734
+ var requirement = (spec) => ({
18735
+ ...spec,
18736
+ id: requirementId({
18737
+ ruleId: spec.ruleId,
18738
+ scope: spec.scope,
18739
+ satisfaction: spec.satisfaction,
18740
+ before: spec.before,
18741
+ dependentAction: spec.dependentAction
18742
+ })
18743
+ });
18744
+ var signalReason = (input, name) => input.assessment.signals[name].reason;
18745
+ function resolveRequirements(input) {
18746
+ const { signals } = input.assessment;
18747
+ const scope = input.intent.scope;
18748
+ const requirements = [];
18749
+ const hasUnknownSignal = Object.values(signals).some((signal) => signal.value === "unknown");
18750
+ const hasUnknownFact = [
18751
+ ...input.assessment.facts,
18752
+ ...input.assessment.consequences.map(({ fact }) => fact)
18753
+ ].some((fact) => fact.basis === "unknown");
18754
+ if (hasUnknownSignal || hasUnknownFact)
18755
+ requirements.push(requirement({
18756
+ ruleId: "consequential-unknown",
18757
+ dimension: "investigation",
18758
+ scope,
18759
+ reason: `Resolve consequential uncertainty before the dependent action: ${[
18760
+ ...Object.entries(signals).filter(([, signal]) => signal.value === "unknown").map(([name]) => name),
18761
+ ...hasUnknownFact ? ["unknown fact"] : []
18762
+ ].join(", ")}`,
18763
+ satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
18764
+ before: "dependent_action",
18765
+ dependentAction: null,
18766
+ acceptanceAllowed: false
18767
+ }));
18768
+ if (signals.productChoiceOpen.value === true)
18769
+ requirements.push(requirement({
18770
+ ruleId: "product-decision",
18771
+ dimension: "decisions",
18772
+ scope,
18773
+ reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
18774
+ satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
18775
+ before: "dependent_action",
18776
+ dependentAction: null,
18777
+ acceptanceAllowed: false
18778
+ }));
18779
+ const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
18780
+ if (signals.behaviorChange.value === true)
18781
+ requirements.push(requirement({
18782
+ ruleId: "behavioral-verification",
18783
+ dimension: "testing",
18784
+ scope,
18785
+ reason: `Behavior, side effects, permissions, or data handling may change: ${signalReason(input, "behaviorChange")}`,
18786
+ satisfaction: "A useful behavioral check passes against the affected behavior and current candidate.",
18787
+ before: "close",
18788
+ dependentAction: null,
18789
+ acceptanceAllowed: false
18790
+ }), requirement({
18791
+ ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
18792
+ dimension: "review",
18793
+ scope,
18794
+ 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.",
18795
+ 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.",
18796
+ before: "close",
18797
+ dependentAction: null,
18798
+ acceptanceAllowed: !independentBehaviorReview
18799
+ }));
18800
+ else if (signals.mechanicalLowRisk.value === true)
18801
+ requirements.push(requirement({
18802
+ ruleId: "mechanical-existing-checks",
18803
+ dimension: "verification",
18804
+ scope,
18805
+ reason: `The change is demonstrably mechanical and low-risk: ${signalReason(input, "mechanicalLowRisk")}`,
18806
+ satisfaction: "Run the relevant existing checks for affected consumers; do not add tautological tests.",
18807
+ before: "close",
18808
+ dependentAction: null,
18809
+ acceptanceAllowed: true
18810
+ }), requirement({
18811
+ ruleId: input.preferences?.thorough ? "fresh-context-review" : "self-review",
18812
+ dimension: "review",
18813
+ scope,
18814
+ 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.",
18815
+ 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.",
18816
+ before: "close",
18817
+ dependentAction: null,
18818
+ acceptanceAllowed: !input.preferences?.thorough
18819
+ }));
18820
+ if (signals.behaviorChange.value === true || signals.mechanicalLowRisk.value === true)
18821
+ requirements.push(requirement({
18822
+ ruleId: "pre-pr-cleanup",
18823
+ dimension: "verification",
18824
+ scope,
18825
+ reason: "Implementation output must be deslopped before it is delivered.",
18826
+ satisfaction: "Run the workit-deslop pass and record passing check evidence, or record an approved limitation decision waiving cleanup.",
18827
+ before: "dependent_action",
18828
+ dependentAction: "hosting.pull_request",
18829
+ acceptanceAllowed: true
18830
+ }));
18831
+ if (signals.durableAgreementNeeded.value === true)
18832
+ requirements.push(requirement({
18833
+ ruleId: "durable-spec",
18834
+ dimension: "artifacts",
18835
+ scope,
18836
+ reason: `The work needs a durable agreement about intended behavior: ${signalReason(input, "durableAgreementNeeded")}`,
18837
+ satisfaction: "Record the agreed behavior in a durable specification before implementation writes.",
18838
+ before: "write",
18839
+ dependentAction: null,
18840
+ acceptanceAllowed: true
18841
+ }));
18842
+ if (signals.coordinationPlanNeeded.value === true)
18843
+ requirements.push(requirement({
18844
+ ruleId: "coordination-plan",
18845
+ dimension: "continuity",
18846
+ scope,
18847
+ reason: `Dependencies, sequencing, or resumption need durable coordination: ${signalReason(input, "coordinationPlanNeeded")}`,
18848
+ satisfaction: "Record useful sequencing, dependencies, and the next action in a compact plan.",
18849
+ before: "dependent_action",
18850
+ dependentAction: null,
18851
+ acceptanceAllowed: true
18852
+ }));
18853
+ if (signals.helperUseful.value === true && !input.preferences?.fast)
18854
+ requirements.push(requirement({
18855
+ ruleId: "helper-usefulness",
18856
+ dimension: "delegation",
18857
+ scope,
18858
+ reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
18859
+ satisfaction: "Use a bounded helper only for the independent objective and reconcile its result.",
18860
+ before: "dependent_action",
18861
+ dependentAction: null,
18862
+ acceptanceAllowed: true
18863
+ }));
18864
+ for (const constraint of input.constraints)
18865
+ for (const obligation of constraint.requires) {
18866
+ const dimension = obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge");
18867
+ requirements.push(requirement({
18868
+ ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
18869
+ kind: obligation.kind,
18870
+ scope: normalizedScope(obligation.scope),
18871
+ refs: normalizedRefList(obligation.refs),
18872
+ method: obligation.method,
18873
+ before: obligation.before,
18874
+ dependentAction: obligation.dependentAction
18875
+ })}`,
18876
+ dimension,
18877
+ scope: obligation.scope,
18878
+ reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
18879
+ satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
18880
+ before: obligation.before,
18881
+ dependentAction: obligation.dependentAction,
18882
+ acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
18883
+ }));
18729
18884
  }
18730
- if (!evidence.candidateId || !current)
18731
- return {
18732
- evidenceId: entry.id,
18733
- status: evidence.result,
18734
- reason: "evidence has no candidate binding"
18735
- };
18736
- const observed = task.candidates.find((item) => item.id === evidence.candidateId) ?? (current.id === evidence.candidateId ? current : undefined);
18737
- if (!observed || observed.completeness === "uncertain" || current.completeness === "uncertain")
18738
- return { evidenceId: entry.id, status: "stale", reason: "candidate is missing or uncertain" };
18739
- if (observed.id === current.id)
18740
- return { evidenceId: entry.id, status: evidence.result, reason: "candidate is unchanged" };
18741
- const scopes = evidenceScopes(task, evidence.requirementIds);
18742
- const changed = changedPaths(observed, current);
18743
- const environmentChanged = JSON.stringify(observed.environment) !== JSON.stringify(current.environment);
18744
- if (!scopes.length || environmentChanged || changed.some((item) => scopes.some((scope) => pathRelevant(item, scope))))
18745
- return { evidenceId: entry.id, status: "stale", reason: "relevant candidate state changed" };
18746
- return {
18747
- evidenceId: entry.id,
18748
- status: evidence.result,
18749
- reason: "candidate changed outside evidence scope"
18750
- };
18885
+ const seen = new Set;
18886
+ return requirements.filter((item) => {
18887
+ if (seen.has(item.id))
18888
+ return false;
18889
+ seen.add(item.id);
18890
+ return true;
18751
18891
  });
18752
18892
  }
18753
- var scopeCovers2 = scopeCovers;
18754
- var checkPin = (assignment, beforeCandidateId, candidateId) => {
18755
- const pin = assignment.candidateId ?? null;
18756
- if (pin === null)
18757
- return true;
18758
- return (beforeCandidateId ?? null) === pin || (candidateId ?? null) === pin;
18759
- };
18760
- var resolveBinding = (evidence, pin, currentCandidateId) => {
18761
- const bound = evidence.kind === "check" || evidence.kind === "review";
18762
- const fallback = bound ? pin ?? currentCandidateId : null;
18763
- return {
18764
- beforeCandidateId: evidence.beforeCandidateId ?? fallback,
18765
- candidateId: evidence.candidateId ?? fallback
18893
+ function resolvePolicy(input) {
18894
+ const normalized = normalizeResolverInput(input);
18895
+ if (!normalized.ok)
18896
+ return normalized;
18897
+ const requirements = resolveRequirements(normalized.data);
18898
+ const policy = {
18899
+ policyVersion: POLICY_VERSION,
18900
+ inputDigest: sha256(canonicalJson(normalized.data)),
18901
+ requirements
18766
18902
  };
18767
- };
18768
- var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId != null);
18769
- var applicableDecision2 = (task, workspace, requirement, checkoutRoot) => task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data).filter((decision) => decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.binding.taskId === task.id && decision.binding.workspaceId === workspace.id && decision.requirementIds.includes(requirement.id) && decision.binding.scope && scopeCovers2(decision.binding.scope, requirement.scope) && decision.digest === decisionDigest(decision) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, decision.binding).ok : decision.binding.contentRefs.every((reference) => reference.kind !== "file")));
18770
- var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && (data.response === "approved" || data.response === "stated") && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id }));
18771
- var evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
18772
- if (kind !== "passed")
18773
- return false;
18774
- if (dimension === "testing" || dimension === "verification")
18775
- return evidenceKind === "check" || ruleId === "pre-pr-cleanup" && evidenceKind === "artifact";
18776
- if (dimension === "review")
18777
- return evidenceKind === "review";
18778
- if (dimension === "investigation" || dimension === "challenge")
18779
- return evidenceKind === "investigation";
18780
- if (dimension === "artifacts" || dimension === "continuity")
18781
- return evidenceKind === "artifact";
18782
- return false;
18783
- };
18784
- var expectedKinds = (dimension, ruleId) => {
18785
- if (dimension === "testing")
18786
- return "check";
18787
- if (dimension === "verification")
18788
- return ruleId === "pre-pr-cleanup" ? "check or artifact" : "check";
18789
- if (dimension === "review")
18790
- return "review";
18791
- if (dimension === "investigation" || dimension === "challenge")
18792
- return "investigation";
18793
- if (dimension === "artifacts" || dimension === "continuity")
18794
- return "artifact";
18795
- if (dimension === "decisions")
18796
- return "decision";
18797
- return "worker report";
18798
- };
18799
- function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller) {
18800
- const evidence = evaluateEvidence(task, candidate);
18801
- if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
18802
- return task.policy.requirements.map((requirement) => ({
18803
- requirementId: requirement.id,
18804
- status: "unsatisfied",
18805
- evidenceIds: [],
18806
- decisionIds: [],
18807
- reason: "stored policy version is unsupported; reassessment is required"
18808
- }));
18809
- return (task.policy?.requirements ?? []).map((requirement) => {
18810
- const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
18811
- const passed = related.filter(({ entry, evaluation }) => {
18812
- if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
18813
- return false;
18814
- if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
18815
- return false;
18816
- if (requirement.dimension !== "review")
18817
- return true;
18818
- const reviewSession = sessionFromRef(entry.data.reviewContext);
18819
- const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
18820
- 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));
18821
- return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
18822
- });
18823
- if (passed.length) {
18824
- const currentId = candidate?.id ?? null;
18825
- 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;
18826
- if (withRed.length)
18827
- return {
18828
- requirementId: requirement.id,
18829
- status: "satisfied",
18830
- evidenceIds: withRed.map(({ entry }) => entry.id),
18831
- decisionIds: [],
18832
- reason: "fresh applicable evidence passed"
18833
- };
18834
- if (requirement.dimension === "testing")
18835
- return {
18836
- requirementId: requirement.id,
18837
- status: "unsatisfied",
18838
- evidenceIds: [],
18839
- decisionIds: [],
18840
- reason: "testing requirement has GREEN evidence but no preceding RED"
18841
- };
18842
- }
18843
- if (requirement.dimension === "delegation") {
18844
- const dot = (scope) => scope.paths.length > 0 ? scope : { ...scope, paths: ["."] };
18845
- 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))));
18846
- if (reconciled)
18847
- return {
18848
- requirementId: requirement.id,
18849
- status: "satisfied",
18850
- evidenceIds: [],
18851
- decisionIds: [],
18852
- reason: "bounded helper completed and its result is recorded"
18853
- };
18854
- }
18855
- const decisions = requirement.dimension === "decisions" ? applicableRequirementDecision(task, workspace, requirement, checkoutRoot) : [];
18856
- if (decisions.length)
18857
- return {
18858
- requirementId: requirement.id,
18859
- status: "satisfied",
18860
- evidenceIds: [],
18861
- decisionIds: decisions.map((decision) => decision.id),
18862
- reason: "an applicable approved or stated decision satisfies the requirement"
18863
- };
18864
- const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
18865
- if (limitations.length)
18866
- return {
18867
- requirementId: requirement.id,
18868
- status: "accepted_limitation",
18869
- evidenceIds: [],
18870
- decisionIds: limitations.map((decision) => task.decisions.find((entry) => entry.data === decision).id),
18871
- reason: "an applicable approved limitation permits the missing evidence"
18872
- };
18873
- const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
18874
- if (!unavailable && related.length > 0 && !passed.length)
18875
- return {
18876
- requirementId: requirement.id,
18877
- status: "unsatisfied",
18878
- evidenceIds: related.map(({ entry }) => entry.id),
18879
- decisionIds: [],
18880
- reason: `no passing evidence (needs kind:${expectedKinds(requirement.dimension, requirement.ruleId)})`
18881
- };
18903
+ const parsed = policySchema.safeParse(policy);
18904
+ if (!parsed.success)
18905
+ return failure("invalid_input", "resolved policy is invalid", invalidFields(parsed.error));
18906
+ return success(null, null, parsed.data);
18907
+ }
18908
+ function diffPolicy(previous, next, reason, now) {
18909
+ if (!policySchema.safeParse(previous).success && previous !== null)
18910
+ throw new TypeError("previous policy is invalid");
18911
+ if (!policySchema.safeParse(next).success)
18912
+ throw new TypeError("next policy is invalid");
18913
+ const oldIds = new Set(previous?.requirements.map((requirement) => requirement.id) ?? []);
18914
+ const newIds = new Set(next.requirements.map((requirement) => requirement.id));
18915
+ const added = [...newIds].filter((id) => !oldIds.has(id)).sort(compareCodeUnits2);
18916
+ const retired = [...oldIds].filter((id) => !newIds.has(id)).sort(compareCodeUnits2);
18917
+ if (added.length === 0 && retired.length === 0)
18918
+ return null;
18919
+ const normalizedReason = reason.trim();
18920
+ const changeReason = retired.length ? `${normalizedReason || "policy reassessed"} (retired: ${retired.join(",")})` : normalizedReason || "policy reassessed";
18921
+ const change = {
18922
+ recordedAt: now,
18923
+ fromInputDigest: previous?.inputDigest ?? null,
18924
+ toInputDigest: next.inputDigest,
18925
+ added,
18926
+ retired,
18927
+ reason: changeReason
18928
+ };
18929
+ const parsed = policyChangeSchema.safeParse(change);
18930
+ if (!parsed.success)
18931
+ throw new TypeError("policy change is invalid");
18932
+ return parsed.data;
18933
+ }
18934
+
18935
+ // packages/workit-core/src/core/task-evaluation.ts
18936
+ import { createHash as createHash6 } from "node:crypto";
18937
+ import * as fs7 from "node:fs";
18938
+ import { spawnSync as spawnSync2 } from "node:child_process";
18939
+ import path10 from "node:path";
18940
+ var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex");
18941
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`);
18942
+ var relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".";
18943
+ var canonicalPath = (value) => {
18944
+ if (typeof value !== "string" || !value)
18945
+ return null;
18946
+ if (value.includes("\\") || value.split("/").includes(".."))
18947
+ return null;
18948
+ const normalized = path10.posix.normalize(value);
18949
+ if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
18950
+ return null;
18951
+ return normalized === "." ? "." : normalized.replace(/\/$/, "");
18952
+ };
18953
+ var canonicalScope = (scope) => {
18954
+ const paths = scope.paths.length ? scope.paths : ["."];
18955
+ const normalizedPaths = paths.map(canonicalPath);
18956
+ const normalizedExclusions = scope.exclusions.map(canonicalPath);
18957
+ if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
18958
+ return null;
18959
+ return {
18960
+ description: scope.description,
18961
+ paths: [...new Set(normalizedPaths)],
18962
+ exclusions: [...new Set(normalizedExclusions)]
18963
+ };
18964
+ };
18965
+ var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
18966
+ var scopeMatches = (value, scope) => {
18967
+ const normalized = canonicalScope(scope);
18968
+ if (!normalized)
18969
+ return false;
18970
+ const paths = normalized.paths;
18971
+ return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
18972
+ };
18973
+ var pathRelevant = (value, scope) => scopeMatches(value, scope);
18974
+ var compareCodeUnits3 = (left, right) => {
18975
+ const length = Math.min(left.length, right.length);
18976
+ for (let index = 0;index < length; index += 1) {
18977
+ const difference = left.charCodeAt(index) - right.charCodeAt(index);
18978
+ if (difference !== 0)
18979
+ return difference;
18980
+ }
18981
+ return left.length - right.length;
18982
+ };
18983
+ var gitPaths = (root) => {
18984
+ const result = spawnSync2("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
18985
+ cwd: root,
18986
+ encoding: "buffer"
18987
+ });
18988
+ if (result.status !== 0) {
18989
+ const stderr = result.stderr?.toString("utf8") ?? "";
18882
18990
  return {
18883
- requirementId: requirement.id,
18884
- status: unavailable ? "unavailable" : "unsatisfied",
18885
- evidenceIds: related.map(({ entry }) => entry.id),
18886
- decisionIds: [],
18887
- reason: unavailable ? "required capability is unavailable" : "no fresh passing evidence"
18991
+ paths: [],
18992
+ uncertain: !/not a git repository/i.test(stderr),
18993
+ git: /not a git repository/i.test(stderr) ? false : true
18888
18994
  };
18889
- });
18890
- }
18891
- function evaluateClosure(requestedOutcome, view) {
18892
- if (requestedOutcome !== "stopped" && !view.task.policy)
18893
- return failure("requirements_unsatisfied", "task has not been assessed");
18894
- if (view.task.policy && view.task.policy.policyVersion !== POLICY_VERSION)
18895
- return failure("requirements_unsatisfied", "stored policy version is unsupported; reassessment is required");
18896
- const evaluations = view.requirements;
18897
- const openFindings = view.task.findings.filter((entry) => entry.data.disposition === "open");
18898
- if (requestedOutcome !== "stopped" && openFindings.length)
18899
- return failure("requirements_unsatisfied", "open findings must be resolved before closure");
18900
- if (requestedOutcome !== "stopped") {
18901
- const evidenceById = new Map(view.evidence.map((entry) => [entry.evidenceId, entry.status]));
18902
- for (const entry of view.task.findings) {
18903
- if (entry.data.disposition === "fixed") {
18904
- const verified = entry.data.resolution?.evidenceIds.some((id) => {
18905
- const evidence = view.task.evidence.find((item) => item.id === id);
18906
- if (!evidence)
18907
- return false;
18908
- return findingVerificationPasses(entry.data.candidateId, {
18909
- kind: evidence.data.kind,
18910
- candidateId: evidence.data.candidateId,
18911
- status: evidenceById.get(id) ?? "missing"
18912
- });
18913
- });
18914
- if (!verified)
18915
- return failure("requirements_unsatisfied", "fixed findings require current verification");
18916
- }
18917
- if (entry.data.disposition === "deferred") {
18918
- const valid = entry.data.resolution?.decisionIds.some((id) => {
18919
- const decisionEntry = view.task.decisions.find((item) => item.id === id);
18920
- const decision = decisionEntry?.data;
18921
- 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)));
18922
- });
18923
- if (!valid)
18924
- return failure("requirements_unsatisfied", "deferred findings require an approved limitation decision that references an acceptanceAllowed requirement and covers the finding scope");
18925
- }
18995
+ }
18996
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
18997
+ const stagedDeleted = spawnSync2("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
18998
+ if (stagedDeleted.status !== 0) {
18999
+ const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
19000
+ if (!/not a git repository/i.test(stderr))
19001
+ return { paths, uncertain: true, git: true };
19002
+ } else if (stagedDeleted.stdout) {
19003
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")));
19004
+ }
19005
+ return { paths: [...new Set(paths)], uncertain: false, git: true };
19006
+ };
19007
+ var walk = (root, directory, output) => {
19008
+ let names;
19009
+ try {
19010
+ names = fs7.readdirSync(directory);
19011
+ } catch {
19012
+ return true;
19013
+ }
19014
+ let uncertain = false;
19015
+ for (const name of names) {
19016
+ if (name === ".git" || name === ".workit")
19017
+ continue;
19018
+ const target = path10.join(directory, name);
19019
+ let stat;
19020
+ try {
19021
+ stat = fs7.lstatSync(target);
19022
+ } catch {
19023
+ uncertain = true;
19024
+ continue;
18926
19025
  }
19026
+ const item = relative(root, target);
19027
+ output.add(item);
19028
+ if (stat.isDirectory() && !stat.isSymbolicLink())
19029
+ uncertain = walk(root, target, output) || uncertain;
18927
19030
  }
18928
- const blocking = evaluations.filter((item) => {
18929
- if (item.status !== "unsatisfied" && item.status !== "unavailable")
18930
- return false;
18931
- const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
18932
- return !requirement || requirement.before === "close";
18933
- });
18934
- if (requestedOutcome !== "stopped" && blocking.length)
18935
- return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
18936
- requirementIds: blocking.map((item) => item.requirementId),
18937
- requirements: blocking.map((item) => {
18938
- const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
18939
- return {
18940
- requirementId: item.requirementId,
18941
- ruleId: requirement?.ruleId ?? "unknown",
18942
- reason: item.reason,
18943
- satisfaction: requirement?.satisfaction ?? "",
18944
- dependentAction: requirement?.dependentAction ?? null
18945
- };
18946
- })
18947
- });
18948
- const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
18949
- if (requestedOutcome === "verified" && accepted.length)
18950
- return failure("requirements_unsatisfied", "accepted limitations cannot be reported as verified", {
18951
- requirementIds: accepted.map((item) => item.requirementId)
18952
- });
18953
- const evidenceIds = [
18954
- ...new Set([
18955
- ...evaluations.flatMap((item) => item.evidenceIds),
18956
- ...view.task.findings.flatMap((item) => item.data.resolution?.evidenceIds ?? [])
18957
- ])
18958
- ];
18959
- const decisionIds = [
18960
- ...new Set([
18961
- ...evaluations.flatMap((item) => item.decisionIds),
18962
- ...view.task.findings.flatMap((item) => item.data.resolution?.decisionIds ?? [])
18963
- ])
18964
- ];
18965
- return success(null, null, {
18966
- outcome: requestedOutcome === "stopped" ? "stopped" : accepted.length ? "accepted_limitations" : "verified",
18967
- evidenceIds,
18968
- decisionIds,
18969
- requirementIds: evaluations.map((item) => item.requirementId)
18970
- });
18971
- }
18972
-
18973
- // packages/workit-core/src/core/task-context.ts
18974
- function reconcileResume(view, observations = []) {
18975
- if (observations.length > 0)
18976
- return failure("permission_denied", "worker observations require native host verification");
18977
- const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
18978
- if (!candidate.ok)
18979
- return candidate;
18980
- const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
18981
- const blockers = [...view.task.progress.blockers];
18982
- if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
18983
- blockers.push({
18984
- reason: "worker state requires reconciliation",
18985
- dependentAction: "resume",
18986
- refs: []
18987
- });
18988
- return success(null, null, {
18989
- candidate: candidate.data,
18990
- staleEvidenceIds,
18991
- workerUpdates: [],
18992
- blockers,
18993
- reassessmentRequired: view.task.policy?.policyVersion !== POLICY_VERSION
18994
- });
18995
- }
18996
- var exportDigest = (bundle) => sha256({
18997
- schemaVersion: bundle.schemaVersion,
18998
- exportedAt: bundle.exportedAt,
18999
- sourceWorkspaceId: bundle.sourceWorkspaceId,
19000
- task: bundle.task
19001
- });
19002
- // packages/workit-core/src/core/external-action.ts
19003
- var chainStepSchema = union([
19004
- string2().min(1),
19005
- object({ branch: string2().min(1) }).strict(),
19006
- object({ pr: literal(true) }).strict()
19007
- ]);
19008
- var externalActionSchema = discriminatedUnion("operation", [
19009
- object({
19010
- operation: literal("git.branch_setup"),
19011
- payload: object({
19012
- action: _enum(["setup", "reapply_stash"]).optional(),
19013
- sdd_dir: string2().optional(),
19014
- target_branch: string2().optional(),
19015
- stash: _enum(["yes", "no"]).optional(),
19016
- cwd: string2().min(1).optional()
19017
- }).strict()
19018
- }).strict(),
19019
- object({
19020
- operation: literal("git.commit"),
19021
- payload: object({
19022
- message: string2().min(1).optional(),
19023
- plan_steps: array(chainStepSchema).min(1).max(32).optional(),
19024
- plan_branch: string2().min(1).optional(),
19025
- cwd: string2().min(1).optional()
19026
- }).strict()
19027
- }).strict(),
19028
- object({
19029
- operation: literal("git.push"),
19030
- payload: object({ branch: string2().min(1).optional(), cwd: string2().min(1).optional() }).strict()
19031
- }).strict(),
19032
- object({
19033
- operation: literal("hosting.pull_request"),
19034
- payload: object({
19035
- title: string2().min(1),
19036
- body: string2().optional(),
19037
- draft: boolean2().optional(),
19038
- target_branch: string2().optional(),
19039
- babysit: boolean2().optional(),
19040
- cwd: string2().min(1).optional()
19041
- }).strict()
19042
- }).strict(),
19043
- object({
19044
- operation: literal("hosting.merge"),
19045
- payload: object({
19046
- target_branch: string2().min(1).optional(),
19047
- source_branch: string2().min(1).optional(),
19048
- cwd: string2().min(1).optional()
19049
- }).strict()
19050
- }).strict(),
19051
- object({
19052
- operation: literal("hosting.delete_branch"),
19053
- payload: object({ branch: string2().min(1), cwd: string2().min(1).optional() }).strict()
19054
- }).strict(),
19055
- object({
19056
- operation: literal("youtrack.update"),
19057
- payload: object({
19058
- issueId: string2().min(1),
19059
- markdown: string2().min(1),
19060
- minutes: number2().positive().optional()
19061
- }).strict()
19062
- }).strict(),
19063
- object({
19064
- operation: literal("youtrack.time"),
19065
- payload: object({
19066
- issueId: string2().min(1),
19067
- minutes: number2().positive(),
19068
- text: string2().optional(),
19069
- dateMs: number2().finite().optional()
19070
- }).strict()
19071
- }).strict(),
19072
- object({
19073
- operation: literal("youtrack.meeting"),
19074
- payload: object({
19075
- issueId: string2().min(1),
19076
- minutes: number2().positive(),
19077
- text: string2().min(1)
19078
- }).strict()
19079
- }).strict(),
19080
- object({
19081
- operation: literal("changelog.apply"),
19082
- payload: object({
19083
- entries: union([
19084
- record(string2(), array(string2())),
19085
- array(object({ category: string2().min(1), text: string2().min(1) }).strict())
19086
- ]).optional(),
19087
- path: string2().optional(),
19088
- normalize_only: boolean2().optional()
19089
- }).strict()
19090
- }).strict(),
19091
- object({
19092
- operation: literal("context.read"),
19093
- payload: object({
19094
- kind: _enum([
19095
- "git",
19096
- "pr",
19097
- "youtrack",
19098
- "github_issue",
19099
- "gitlab_issue",
19100
- "changelog",
19101
- "release",
19102
- "affected"
19103
- ]),
19104
- range: string2().optional(),
19105
- issueId: string2().optional(),
19106
- issueUrl: string2().optional(),
19107
- issueRef: string2().optional(),
19108
- mode: string2().optional(),
19109
- specPath: string2().optional(),
19110
- planPath: string2().optional(),
19111
- cwd: string2().min(1).optional()
19112
- }).strict()
19113
- }).strict()
19114
- ]);
19115
- // packages/workit-core/src/core/task-engine.ts
19116
- import { realpathSync as realpathSync6 } from "node:fs";
19117
-
19118
- // packages/workit-core/src/core/policy-resolver.ts
19119
- var resolverInputSchema = object({
19120
- intent: intentSchema,
19121
- assessment: assessmentSchema,
19122
- constraints: array(constraintSchema),
19123
- prior: object({
19124
- decisions: array(decisionSchema),
19125
- findings: array(findingSchema),
19126
- requirements: array(requirementSchema)
19127
- }).strict(),
19128
- preferences: object({ fast: boolean2().optional(), thorough: boolean2().optional() }).strict().optional()
19129
- }).strict();
19130
- var compareCodeUnits3 = (left, right) => {
19131
- const length = Math.min(left.length, right.length);
19132
- for (let index = 0;index < length; index += 1) {
19133
- const difference = left.charCodeAt(index) - right.charCodeAt(index);
19134
- if (difference !== 0)
19135
- return difference;
19136
- }
19137
- return left.length - right.length;
19031
+ return uncertain;
19138
19032
  };
19139
- var stableUnique = (values) => [...new Set(values)].sort(compareCodeUnits3);
19140
- var stableList = (values) => [...values].sort((left, right) => compareCodeUnits3(canonicalJson(left), canonicalJson(right)));
19141
- var normalizedScope = (scope) => ({
19142
- description: scope.description,
19143
- paths: stableUnique(scope.paths),
19144
- exclusions: stableUnique(scope.exclusions)
19145
- });
19146
- var normalizedRefList = (values) => stableList(values);
19147
- var normalizeAssessment = (assessment) => ({
19148
- facts: stableList(assessment.facts.map((fact) => ({ ...fact, refs: normalizedRefList(fact.refs) }))),
19149
- signals: Object.fromEntries(Object.entries(assessment.signals).map(([name, signal]) => [
19150
- name,
19151
- { ...signal, refs: normalizedRefList(signal.refs) }
19152
- ])),
19153
- consequences: stableList(assessment.consequences.map((consequence) => ({
19154
- ...consequence,
19155
- fact: { ...consequence.fact, refs: normalizedRefList(consequence.fact.refs) }
19156
- }))),
19157
- verification: stableList(assessment.verification.map((verification) => ({
19158
- ...verification,
19159
- scope: normalizedScope(verification.scope),
19160
- availableChecks: normalizedRefList(verification.availableChecks),
19161
- gaps: stableUnique(verification.gaps)
19162
- })))
19163
- });
19164
- var normalizeConstraint = (constraint) => ({
19165
- ...constraint,
19166
- source: constraint.source,
19167
- requires: stableList(constraint.requires.map((requirement) => ({
19168
- ...requirement,
19169
- scope: normalizedScope(requirement.scope),
19170
- refs: normalizedRefList(requirement.refs)
19171
- })))
19172
- });
19173
- var normalizeDecision = (decision) => ({
19174
- ...decision,
19175
- binding: {
19176
- ...decision.binding,
19177
- scope: normalizedScope(decision.binding.scope),
19178
- contentRefs: normalizedRefList(decision.binding.contentRefs)
19179
- },
19180
- requirementIds: stableUnique(decision.requirementIds)
19181
- });
19182
- var normalizeIntent = (intent) => ({
19183
- ...intent,
19184
- scope: normalizedScope(intent.scope),
19185
- authorityRefs: normalizedRefList(intent.authorityRefs)
19186
- });
19187
- var normalizePrior = (prior) => ({
19188
- decisions: stableList(prior.decisions.map(normalizeDecision)),
19189
- findings: stableList(prior.findings.map((finding) => ({
19190
- ...finding,
19191
- scope: normalizedScope(finding.scope),
19192
- refs: normalizedRefList(finding.refs),
19193
- resolution: finding.resolution ? {
19194
- ...finding.resolution,
19195
- evidenceIds: stableUnique(finding.resolution.evidenceIds),
19196
- decisionIds: stableUnique(finding.resolution.decisionIds)
19197
- } : null
19198
- }))),
19199
- requirements: stableList(prior.requirements.map((requirement) => ({
19200
- ...requirement,
19201
- scope: normalizedScope(requirement.scope)
19202
- })))
19203
- });
19204
- var invalidFields = (error) => ({
19205
- fields: error.issues.map((issue) => ({
19206
- path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
19207
- reason: issue.message
19208
- }))
19209
- });
19210
- function normalizeResolverInput(input) {
19211
- const parsed = resolverInputSchema.safeParse(input);
19212
- if (!parsed.success)
19213
- return failure("invalid_input", "resolver input is invalid", invalidFields(parsed.error));
19214
- const value = parsed.data;
19215
- const signals = value.assessment.signals;
19216
- if (signals.behaviorChange.value === true && signals.mechanicalLowRisk.value === true)
19217
- return failure("invalid_input", "behavioral change cannot be wholly mechanical", {
19218
- fields: [
19219
- {
19220
- path: "assessment.signals",
19221
- reason: "contradictory behaviorChange and mechanicalLowRisk"
19222
- }
19223
- ]
19224
- });
19225
- const ids = value.constraints.map((constraint) => constraint.id);
19226
- if (new Set(ids).size !== ids.length)
19227
- return failure("invalid_input", "constraint IDs must be unique", {
19228
- fields: [{ path: "constraints", reason: "duplicate constraint id" }]
19229
- });
19230
- if (value.preferences?.fast && value.preferences.thorough)
19231
- return failure("invalid_input", "fast and thorough preferences contradict", {
19232
- fields: [{ path: "preferences", reason: "fast and thorough cannot both be enabled" }]
19233
- });
19234
- const normalized = {
19235
- intent: normalizeIntent(value.intent),
19236
- assessment: normalizeAssessment(value.assessment),
19237
- constraints: stableList(value.constraints.map(normalizeConstraint)),
19238
- prior: normalizePrior(value.prior),
19239
- ...value.preferences ? { preferences: { ...value.preferences } } : {},
19240
- policyVersion: POLICY_VERSION
19033
+ var scopeRoots = (root, scope) => {
19034
+ const roots = [];
19035
+ for (const item of scope.paths.length ? scope.paths : ["."]) {
19036
+ const target = path10.resolve(root, item);
19037
+ if (!inside(root, target))
19038
+ 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` }] });
19039
+ let ancestor = target;
19040
+ while (!fs7.existsSync(ancestor) && ancestor !== root)
19041
+ ancestor = path10.dirname(ancestor);
19042
+ try {
19043
+ if (!inside(root, fs7.realpathSync(ancestor)))
19044
+ 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` }] });
19045
+ } catch {
19046
+ return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
19047
+ }
19048
+ roots.push(target);
19049
+ }
19050
+ return success(null, null, roots);
19051
+ };
19052
+ function captureCandidate(root, scope, environment = []) {
19053
+ let checkout;
19054
+ try {
19055
+ checkout = fs7.realpathSync(root);
19056
+ } catch {
19057
+ return failure("invalid_input", "candidate root does not exist");
19058
+ }
19059
+ const normalizedScope = canonicalScope(scope);
19060
+ if (!normalizedScope)
19061
+ 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" }] });
19062
+ const roots = scopeRoots(checkout, normalizedScope);
19063
+ if (!roots.ok)
19064
+ return roots;
19065
+ const git = gitPaths(checkout);
19066
+ const names = new Set;
19067
+ let uncertain = git.uncertain;
19068
+ for (const target of roots.data) {
19069
+ const item = relative(checkout, target);
19070
+ if (item === ".git" || item === ".workit")
19071
+ continue;
19072
+ names.add(item);
19073
+ if (!fs7.existsSync(target))
19074
+ continue;
19075
+ let stat;
19076
+ try {
19077
+ stat = fs7.lstatSync(target);
19078
+ } catch {
19079
+ uncertain = true;
19080
+ continue;
19081
+ }
19082
+ if (!git.git && stat.isDirectory() && !stat.isSymbolicLink())
19083
+ uncertain = walk(checkout, target, names) || uncertain;
19084
+ }
19085
+ for (const item of git.paths)
19086
+ if (scopeMatches(item, normalizedScope))
19087
+ names.add(item);
19088
+ const files = [];
19089
+ let completeness = uncertain ? "uncertain" : "known";
19090
+ for (const item of [...names].sort()) {
19091
+ if (!scopeMatches(item, normalizedScope))
19092
+ continue;
19093
+ const target = path10.join(checkout, item);
19094
+ try {
19095
+ const stat = fs7.lstatSync(target);
19096
+ if (stat.isSymbolicLink()) {
19097
+ const link = fs7.readlinkSync(target);
19098
+ files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
19099
+ } else if (stat.isFile()) {
19100
+ files.push({
19101
+ path: item,
19102
+ kind: "file",
19103
+ digest: digestBytes3(fs7.readFileSync(target)),
19104
+ executable: (stat.mode & 73) !== 0
19105
+ });
19106
+ } else if (!stat.isDirectory()) {
19107
+ completeness = "uncertain";
19108
+ } else if (git.git && git.paths.includes(item)) {
19109
+ completeness = "uncertain";
19110
+ }
19111
+ } catch (error) {
19112
+ if (error?.code === "ENOENT") {
19113
+ files.push({ path: item, kind: "absent", digest: null, executable: null });
19114
+ } else
19115
+ completeness = "uncertain";
19116
+ }
19117
+ }
19118
+ 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) => ({
19119
+ name,
19120
+ value: environment[name] ?? null
19121
+ }));
19122
+ const namesByEnvironment = supplied.map(({ name }) => name);
19123
+ if (new Set(namesByEnvironment).size !== namesByEnvironment.length || namesByEnvironment.some((name) => typeof name !== "string" || !name))
19124
+ return failure("invalid_input", "candidate environment names must be unique and non-empty");
19125
+ const values = supplied.sort((left, right) => compareCodeUnits3(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
19126
+ if (values.some(({ value }) => value === null))
19127
+ completeness = "uncertain";
19128
+ const headResult = spawnSync2("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
19129
+ if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
19130
+ completeness = "uncertain";
19131
+ const head = headResult.status === 0 ? headResult.stdout.trim() : null;
19132
+ const partial = {
19133
+ id: "0".repeat(64),
19134
+ scope: normalizedScope,
19135
+ completeness,
19136
+ files,
19137
+ environment: values,
19138
+ head
19241
19139
  };
19242
- return success(null, null, normalized);
19140
+ const candidate = { ...partial, id: candidateDigest(partial) };
19141
+ const parsed = candidateSchema.safeParse(candidate);
19142
+ return parsed.success ? success(null, null, parsed.data) : failure("invalid_input", "captured candidate is invalid");
19243
19143
  }
19244
- var requirement = (spec) => ({
19245
- ...spec,
19246
- id: requirementId({
19247
- ruleId: spec.ruleId,
19248
- scope: spec.scope,
19249
- satisfaction: spec.satisfaction,
19250
- before: spec.before,
19251
- dependentAction: spec.dependentAction
19252
- })
19253
- });
19254
- var signalReason = (input, name) => input.assessment.signals[name].reason;
19255
- function resolveRequirements(input) {
19256
- const { signals } = input.assessment;
19257
- const scope = input.intent.scope;
19258
- const requirements = [];
19259
- const hasUnknownSignal = Object.values(signals).some((signal) => signal.value === "unknown");
19260
- const hasUnknownFact = [
19261
- ...input.assessment.facts,
19262
- ...input.assessment.consequences.map(({ fact }) => fact)
19263
- ].some((fact) => fact.basis === "unknown");
19264
- if (hasUnknownSignal || hasUnknownFact)
19265
- requirements.push(requirement({
19266
- ruleId: "consequential-unknown",
19267
- dimension: "investigation",
19268
- scope,
19269
- reason: `Resolve consequential uncertainty before the dependent action: ${[
19270
- ...Object.entries(signals).filter(([, signal]) => signal.value === "unknown").map(([name]) => name),
19271
- ...hasUnknownFact ? ["unknown fact"] : []
19272
- ].join(", ")}`,
19273
- satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
19274
- before: "dependent_action",
19275
- dependentAction: null,
19276
- acceptanceAllowed: false
19277
- }));
19278
- if (signals.productChoiceOpen.value === true)
19279
- requirements.push(requirement({
19280
- ruleId: "product-decision",
19281
- dimension: "decisions",
19282
- scope,
19283
- reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
19284
- satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
19285
- before: "dependent_action",
19286
- dependentAction: null,
19287
- acceptanceAllowed: false
19288
- }));
19289
- const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
19290
- if (signals.behaviorChange.value === true)
19291
- requirements.push(requirement({
19292
- ruleId: "behavioral-verification",
19293
- dimension: "testing",
19294
- scope,
19295
- reason: `Behavior, side effects, permissions, or data handling may change: ${signalReason(input, "behaviorChange")}`,
19296
- satisfaction: "A useful behavioral check passes against the affected behavior and current candidate.",
19297
- before: "close",
19298
- dependentAction: null,
19299
- acceptanceAllowed: false
19300
- }), requirement({
19301
- ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
19302
- dimension: "review",
19303
- scope,
19304
- 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.",
19305
- 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.",
19306
- before: "close",
19307
- dependentAction: null,
19308
- acceptanceAllowed: !independentBehaviorReview
19309
- }));
19310
- else if (signals.mechanicalLowRisk.value === true)
19311
- requirements.push(requirement({
19312
- ruleId: "mechanical-existing-checks",
19313
- dimension: "verification",
19314
- scope,
19315
- reason: `The change is demonstrably mechanical and low-risk: ${signalReason(input, "mechanicalLowRisk")}`,
19316
- satisfaction: "Run the relevant existing checks for affected consumers; do not add tautological tests.",
19317
- before: "close",
19318
- dependentAction: null,
19319
- acceptanceAllowed: true
19320
- }), requirement({
19321
- ruleId: input.preferences?.thorough ? "fresh-context-review" : "self-review",
19322
- dimension: "review",
19323
- scope,
19324
- 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.",
19325
- 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.",
19326
- before: "close",
19327
- dependentAction: null,
19328
- acceptanceAllowed: !input.preferences?.thorough
19329
- }));
19330
- if (signals.behaviorChange.value === true || signals.mechanicalLowRisk.value === true)
19331
- requirements.push(requirement({
19332
- ruleId: "pre-pr-cleanup",
19333
- dimension: "verification",
19334
- scope,
19335
- reason: "Implementation output must be deslopped before it is delivered.",
19336
- satisfaction: "Run the workit-deslop pass and record passing check evidence, or record an approved limitation decision waiving cleanup.",
19337
- before: "dependent_action",
19338
- dependentAction: "hosting.pull_request",
19339
- acceptanceAllowed: true
19340
- }));
19341
- if (signals.durableAgreementNeeded.value === true)
19342
- requirements.push(requirement({
19343
- ruleId: "durable-spec",
19344
- dimension: "artifacts",
19345
- scope,
19346
- reason: `The work needs a durable agreement about intended behavior: ${signalReason(input, "durableAgreementNeeded")}`,
19347
- satisfaction: "Record the agreed behavior in a durable specification before implementation writes.",
19348
- before: "write",
19349
- dependentAction: null,
19350
- acceptanceAllowed: true
19351
- }));
19352
- if (signals.coordinationPlanNeeded.value === true)
19353
- requirements.push(requirement({
19354
- ruleId: "coordination-plan",
19355
- dimension: "continuity",
19356
- scope,
19357
- reason: `Dependencies, sequencing, or resumption need durable coordination: ${signalReason(input, "coordinationPlanNeeded")}`,
19358
- satisfaction: "Record useful sequencing, dependencies, and the next action in a compact plan.",
19359
- before: "dependent_action",
19360
- dependentAction: null,
19361
- acceptanceAllowed: true
19362
- }));
19363
- if (signals.helperUseful.value === true && !input.preferences?.fast)
19364
- requirements.push(requirement({
19365
- ruleId: "helper-usefulness",
19366
- dimension: "delegation",
19367
- scope,
19368
- reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
19369
- satisfaction: "Use a bounded helper only for the independent objective and reconcile its result.",
19370
- before: "dependent_action",
19371
- dependentAction: null,
19372
- acceptanceAllowed: true
19144
+ var fileMap = (candidate) => new Map(candidate.files.map((file) => [file.path, file]));
19145
+ var changedPaths = (before, after) => {
19146
+ const paths = new Set([...before.files, ...after.files].map((file) => file.path));
19147
+ const left = fileMap(before);
19148
+ const right = fileMap(after);
19149
+ const changed = [...paths].filter((item) => JSON.stringify(left.get(item) ?? null) !== JSON.stringify(right.get(item) ?? null));
19150
+ if (before.head !== after.head)
19151
+ changed.push(".");
19152
+ if (JSON.stringify(before.environment) !== JSON.stringify(after.environment))
19153
+ changed.push(".");
19154
+ return changed;
19155
+ };
19156
+ var sessionFromRef = (value) => {
19157
+ if (typeof value !== "object" || value === null || value.kind !== "host")
19158
+ return null;
19159
+ return typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
19160
+ };
19161
+ var sameSession = (left, right) => {
19162
+ const other = sessionFromRef(right);
19163
+ return left !== null && other !== null && left.host === other.host && left.handle === other.handle;
19164
+ };
19165
+ var evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope);
19166
+ function evaluateEvidence(task, candidate) {
19167
+ const current = candidate ?? task.candidates.at(-1) ?? null;
19168
+ return task.evidence.map((entry) => {
19169
+ const evidence = entry.data;
19170
+ if (evidence.result === "failed")
19171
+ return {
19172
+ evidenceId: entry.id,
19173
+ status: "failed",
19174
+ reason: "failed evidence remains historical"
19175
+ };
19176
+ if (evidence.kind === "check" || evidence.kind === "review") {
19177
+ if (!evidence.beforeCandidateId || !evidence.candidateId)
19178
+ return {
19179
+ evidenceId: entry.id,
19180
+ status: "stale",
19181
+ reason: "check or review evidence lacks start and end candidates"
19182
+ };
19183
+ if (evidence.beforeCandidateId !== evidence.candidateId)
19184
+ return {
19185
+ evidenceId: entry.id,
19186
+ status: "stale",
19187
+ reason: "candidate changed during the check or review"
19188
+ };
19189
+ }
19190
+ if (!evidence.candidateId || !current)
19191
+ return {
19192
+ evidenceId: entry.id,
19193
+ status: evidence.result,
19194
+ reason: "evidence has no candidate binding"
19195
+ };
19196
+ const observed = task.candidates.find((item) => item.id === evidence.candidateId) ?? (current.id === evidence.candidateId ? current : undefined);
19197
+ if (!observed || observed.completeness === "uncertain" || current.completeness === "uncertain")
19198
+ return { evidenceId: entry.id, status: "stale", reason: "candidate is missing or uncertain" };
19199
+ if (observed.id === current.id)
19200
+ return { evidenceId: entry.id, status: evidence.result, reason: "candidate is unchanged" };
19201
+ const scopes = evidenceScopes(task, evidence.requirementIds);
19202
+ const changed = changedPaths(observed, current);
19203
+ const environmentChanged = JSON.stringify(observed.environment) !== JSON.stringify(current.environment);
19204
+ if (!scopes.length || environmentChanged || changed.some((item) => scopes.some((scope) => pathRelevant(item, scope))))
19205
+ return { evidenceId: entry.id, status: "stale", reason: "relevant candidate state changed" };
19206
+ return {
19207
+ evidenceId: entry.id,
19208
+ status: evidence.result,
19209
+ reason: "candidate changed outside evidence scope"
19210
+ };
19211
+ });
19212
+ }
19213
+ var scopeCovers2 = scopeCovers;
19214
+ var checkPin = (assignment, beforeCandidateId, candidateId) => {
19215
+ const pin = assignment.candidateId ?? null;
19216
+ if (pin === null)
19217
+ return true;
19218
+ return (beforeCandidateId ?? null) === pin || (candidateId ?? null) === pin;
19219
+ };
19220
+ var resolveBinding = (evidence, pin, currentCandidateId) => {
19221
+ const bound = evidence.kind === "check" || evidence.kind === "review";
19222
+ const fallback = bound ? pin ?? currentCandidateId : null;
19223
+ return {
19224
+ beforeCandidateId: evidence.beforeCandidateId ?? fallback,
19225
+ candidateId: evidence.candidateId ?? fallback
19226
+ };
19227
+ };
19228
+ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId != null);
19229
+ var applicableDecision2 = (task, workspace, requirement, checkoutRoot) => task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data).filter((decision) => decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.binding.taskId === task.id && decision.binding.workspaceId === workspace.id && decision.requirementIds.includes(requirement.id) && decision.binding.scope && scopeCovers2(decision.binding.scope, requirement.scope) && decision.digest === decisionDigest(decision) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, decision.binding).ok : decision.binding.contentRefs.every((reference) => reference.kind !== "file")));
19230
+ var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && (data.response === "approved" || data.response === "stated") && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id }));
19231
+ var evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
19232
+ if (kind !== "passed")
19233
+ return false;
19234
+ if (dimension === "testing" || dimension === "verification")
19235
+ return evidenceKind === "check" || ruleId === "pre-pr-cleanup" && evidenceKind === "artifact";
19236
+ if (dimension === "review")
19237
+ return evidenceKind === "review";
19238
+ if (dimension === "investigation" || dimension === "challenge")
19239
+ return evidenceKind === "investigation";
19240
+ if (dimension === "artifacts" || dimension === "continuity")
19241
+ return evidenceKind === "artifact";
19242
+ return false;
19243
+ };
19244
+ var expectedKinds = (dimension, ruleId) => {
19245
+ if (dimension === "testing")
19246
+ return "check";
19247
+ if (dimension === "verification")
19248
+ return ruleId === "pre-pr-cleanup" ? "check or artifact" : "check";
19249
+ if (dimension === "review")
19250
+ return "review";
19251
+ if (dimension === "investigation" || dimension === "challenge")
19252
+ return "investigation";
19253
+ if (dimension === "artifacts" || dimension === "continuity")
19254
+ return "artifact";
19255
+ if (dimension === "decisions")
19256
+ return "decision";
19257
+ return "worker report";
19258
+ };
19259
+ function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller) {
19260
+ const evidence = evaluateEvidence(task, candidate);
19261
+ if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
19262
+ return task.policy.requirements.map((requirement) => ({
19263
+ requirementId: requirement.id,
19264
+ status: "unsatisfied",
19265
+ evidenceIds: [],
19266
+ decisionIds: [],
19267
+ reason: "stored policy version is unsupported; reassessment is required"
19373
19268
  }));
19374
- for (const constraint of input.constraints)
19375
- for (const obligation of constraint.requires) {
19376
- const dimension = obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge");
19377
- requirements.push(requirement({
19378
- ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
19379
- kind: obligation.kind,
19380
- scope: normalizedScope(obligation.scope),
19381
- refs: normalizedRefList(obligation.refs),
19382
- method: obligation.method,
19383
- before: obligation.before,
19384
- dependentAction: obligation.dependentAction
19385
- })}`,
19386
- dimension,
19387
- scope: obligation.scope,
19388
- reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
19389
- satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
19390
- before: obligation.before,
19391
- dependentAction: obligation.dependentAction,
19392
- acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
19393
- }));
19269
+ return (task.policy?.requirements ?? []).map((requirement) => {
19270
+ const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
19271
+ const passed = related.filter(({ entry, evaluation }) => {
19272
+ if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
19273
+ return false;
19274
+ if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
19275
+ return false;
19276
+ if (requirement.dimension !== "review")
19277
+ return true;
19278
+ const reviewSession = sessionFromRef(entry.data.reviewContext);
19279
+ const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
19280
+ 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));
19281
+ return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
19282
+ });
19283
+ if (passed.length) {
19284
+ const currentId = candidate?.id ?? null;
19285
+ 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;
19286
+ if (withRed.length)
19287
+ return {
19288
+ requirementId: requirement.id,
19289
+ status: "satisfied",
19290
+ evidenceIds: withRed.map(({ entry }) => entry.id),
19291
+ decisionIds: [],
19292
+ reason: "fresh applicable evidence passed"
19293
+ };
19294
+ if (requirement.dimension === "testing")
19295
+ return {
19296
+ requirementId: requirement.id,
19297
+ status: "unsatisfied",
19298
+ evidenceIds: [],
19299
+ decisionIds: [],
19300
+ reason: "testing requirement has GREEN evidence but no preceding RED"
19301
+ };
19394
19302
  }
19395
- const seen = new Set;
19396
- return requirements.filter((item) => {
19397
- if (seen.has(item.id))
19303
+ if (requirement.dimension === "delegation") {
19304
+ const dot = (scope) => scope.paths.length > 0 ? scope : { ...scope, paths: ["."] };
19305
+ 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))));
19306
+ if (reconciled)
19307
+ return {
19308
+ requirementId: requirement.id,
19309
+ status: "satisfied",
19310
+ evidenceIds: [],
19311
+ decisionIds: [],
19312
+ reason: "bounded helper completed and its result is recorded"
19313
+ };
19314
+ }
19315
+ const decisions = requirement.dimension === "decisions" ? applicableRequirementDecision(task, workspace, requirement, checkoutRoot) : [];
19316
+ if (decisions.length)
19317
+ return {
19318
+ requirementId: requirement.id,
19319
+ status: "satisfied",
19320
+ evidenceIds: [],
19321
+ decisionIds: decisions.map((decision) => decision.id),
19322
+ reason: "an applicable approved or stated decision satisfies the requirement"
19323
+ };
19324
+ const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
19325
+ if (limitations.length)
19326
+ return {
19327
+ requirementId: requirement.id,
19328
+ status: "accepted_limitation",
19329
+ evidenceIds: [],
19330
+ decisionIds: limitations.map((decision) => task.decisions.find((entry) => entry.data === decision).id),
19331
+ reason: "an applicable approved limitation permits the missing evidence"
19332
+ };
19333
+ const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
19334
+ if (!unavailable && related.length > 0 && !passed.length)
19335
+ return {
19336
+ requirementId: requirement.id,
19337
+ status: "unsatisfied",
19338
+ evidenceIds: related.map(({ entry }) => entry.id),
19339
+ decisionIds: [],
19340
+ reason: `no passing evidence (needs kind:${expectedKinds(requirement.dimension, requirement.ruleId)})`
19341
+ };
19342
+ return {
19343
+ requirementId: requirement.id,
19344
+ status: unavailable ? "unavailable" : "unsatisfied",
19345
+ evidenceIds: related.map(({ entry }) => entry.id),
19346
+ decisionIds: [],
19347
+ reason: unavailable ? "required capability is unavailable" : "no fresh passing evidence"
19348
+ };
19349
+ });
19350
+ }
19351
+ function evaluateClosure(requestedOutcome, view) {
19352
+ if (requestedOutcome !== "stopped" && !view.task.policy)
19353
+ return failure("requirements_unsatisfied", "task has not been assessed");
19354
+ if (view.task.policy && view.task.policy.policyVersion !== POLICY_VERSION)
19355
+ return failure("requirements_unsatisfied", "stored policy version is unsupported; reassessment is required");
19356
+ const evaluations = view.requirements;
19357
+ const openFindings = view.task.findings.filter((entry) => entry.data.disposition === "open");
19358
+ if (requestedOutcome !== "stopped" && openFindings.length)
19359
+ return failure("requirements_unsatisfied", "open findings must be resolved before closure");
19360
+ if (requestedOutcome !== "stopped") {
19361
+ const evidenceById = new Map(view.evidence.map((entry) => [entry.evidenceId, entry.status]));
19362
+ for (const entry of view.task.findings) {
19363
+ if (entry.data.disposition === "fixed") {
19364
+ const verified = entry.data.resolution?.evidenceIds.some((id) => {
19365
+ const evidence = view.task.evidence.find((item) => item.id === id);
19366
+ if (!evidence)
19367
+ return false;
19368
+ return findingVerificationPasses(entry.data.candidateId, {
19369
+ kind: evidence.data.kind,
19370
+ candidateId: evidence.data.candidateId,
19371
+ status: evidenceById.get(id) ?? "missing"
19372
+ });
19373
+ });
19374
+ if (!verified)
19375
+ return failure("requirements_unsatisfied", "fixed findings require current verification");
19376
+ }
19377
+ if (entry.data.disposition === "deferred") {
19378
+ const valid = entry.data.resolution?.decisionIds.some((id) => {
19379
+ const decisionEntry = view.task.decisions.find((item) => item.id === id);
19380
+ const decision = decisionEntry?.data;
19381
+ 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)));
19382
+ });
19383
+ if (!valid)
19384
+ return failure("requirements_unsatisfied", "deferred findings require an approved limitation decision that references an acceptanceAllowed requirement and covers the finding scope");
19385
+ }
19386
+ }
19387
+ }
19388
+ const blocking = evaluations.filter((item) => {
19389
+ if (item.status !== "unsatisfied" && item.status !== "unavailable")
19398
19390
  return false;
19399
- seen.add(item.id);
19400
- return true;
19391
+ const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
19392
+ return !requirement || requirement.before === "close";
19393
+ });
19394
+ if (requestedOutcome !== "stopped" && blocking.length)
19395
+ return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
19396
+ requirementIds: blocking.map((item) => item.requirementId),
19397
+ requirements: blocking.map((item) => {
19398
+ const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
19399
+ return {
19400
+ requirementId: item.requirementId,
19401
+ ruleId: requirement?.ruleId ?? "unknown",
19402
+ reason: item.reason,
19403
+ satisfaction: requirement?.satisfaction ?? "",
19404
+ dependentAction: requirement?.dependentAction ?? null
19405
+ };
19406
+ })
19407
+ });
19408
+ const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
19409
+ if (requestedOutcome === "verified" && accepted.length)
19410
+ return failure("requirements_unsatisfied", "accepted limitations cannot be reported as verified", {
19411
+ requirementIds: accepted.map((item) => item.requirementId)
19412
+ });
19413
+ const evidenceIds = [
19414
+ ...new Set([
19415
+ ...evaluations.flatMap((item) => item.evidenceIds),
19416
+ ...view.task.findings.flatMap((item) => item.data.resolution?.evidenceIds ?? [])
19417
+ ])
19418
+ ];
19419
+ const decisionIds = [
19420
+ ...new Set([
19421
+ ...evaluations.flatMap((item) => item.decisionIds),
19422
+ ...view.task.findings.flatMap((item) => item.data.resolution?.decisionIds ?? [])
19423
+ ])
19424
+ ];
19425
+ return success(null, null, {
19426
+ outcome: requestedOutcome === "stopped" ? "stopped" : accepted.length ? "accepted_limitations" : "verified",
19427
+ evidenceIds,
19428
+ decisionIds,
19429
+ requirementIds: evaluations.map((item) => item.requirementId)
19401
19430
  });
19402
19431
  }
19403
- function resolvePolicy(input) {
19404
- const normalized = normalizeResolverInput(input);
19405
- if (!normalized.ok)
19406
- return normalized;
19407
- const requirements = resolveRequirements(normalized.data);
19408
- const policy = {
19409
- policyVersion: POLICY_VERSION,
19410
- inputDigest: sha256(canonicalJson(normalized.data)),
19411
- requirements
19412
- };
19413
- const parsed = policySchema.safeParse(policy);
19414
- if (!parsed.success)
19415
- return failure("invalid_input", "resolved policy is invalid", invalidFields(parsed.error));
19416
- return success(null, null, parsed.data);
19432
+
19433
+ // packages/workit-core/src/core/methods.ts
19434
+ var METHODS = {
19435
+ "workit-challenge": { dimensions: ["challenge", "decisions"] },
19436
+ "workit-behavioral-tdd": { dimensions: ["testing"], ruleIds: ["mechanical-existing-checks"] },
19437
+ "workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review", "self-review"] },
19438
+ "workit-plan": { dimensions: ["artifacts", "continuity"] },
19439
+ "workit-implement": { dimensions: ["delegation"] },
19440
+ "workit-debug": {},
19441
+ "workit-handoff": {},
19442
+ "workit-deslop": { ruleIds: ["pre-pr-cleanup"] }
19443
+ };
19444
+ var methodMatches = (definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true || definition.ruleIds?.includes(requirement.ruleId) === true;
19445
+ var assuranceFor = (id, definition, capabilities) => {
19446
+ const keys = new Set([
19447
+ id,
19448
+ ...definition.dimensions ?? [],
19449
+ ...definition.ruleIds ?? []
19450
+ ]);
19451
+ const matches = capabilities.filter((capability) => keys.has(capability.name) || keys.has(capability.surface));
19452
+ if (id === "workit-review" && matches.length === 0)
19453
+ return { assurance: "unavailable", reasons: ["independent review capability is unavailable"] };
19454
+ if (matches.some((capability) => capability.assurance === "unavailable"))
19455
+ return {
19456
+ assurance: "unavailable",
19457
+ reasons: matches.filter((capability) => capability.assurance === "unavailable").map((capability) => capability.reason)
19458
+ };
19459
+ if (matches.some((capability) => capability.assurance === "enforced"))
19460
+ return { assurance: "enforced", reasons: matches.map((capability) => capability.reason) };
19461
+ return { assurance: "agent_guided", reasons: matches.map((capability) => capability.reason) };
19462
+ };
19463
+ function selectMethods(policy, capabilities) {
19464
+ return Object.entries(METHODS).flatMap(([id, definition]) => {
19465
+ const requirements = policy.requirements.filter((requirement) => methodMatches(definition, requirement));
19466
+ if (requirements.length === 0)
19467
+ return [];
19468
+ const capability = assuranceFor(id, definition, capabilities);
19469
+ const reasons = [
19470
+ ...new Set(requirements.map((requirement) => requirement.reason)),
19471
+ ...capability.reasons
19472
+ ];
19473
+ if (capability.assurance === "unavailable")
19474
+ reasons.unshift("required capability is unavailable");
19475
+ return [{ id, reason: reasons.join("; "), assurance: capability.assurance }];
19476
+ });
19417
19477
  }
19418
- function diffPolicy(previous, next, reason, now) {
19419
- if (!policySchema.safeParse(previous).success && previous !== null)
19420
- throw new TypeError("previous policy is invalid");
19421
- if (!policySchema.safeParse(next).success)
19422
- throw new TypeError("next policy is invalid");
19423
- const oldIds = new Set(previous?.requirements.map((requirement) => requirement.id) ?? []);
19424
- const newIds = new Set(next.requirements.map((requirement) => requirement.id));
19425
- const added = [...newIds].filter((id) => !oldIds.has(id)).sort(compareCodeUnits3);
19426
- const retired = [...oldIds].filter((id) => !newIds.has(id)).sort(compareCodeUnits3);
19427
- if (added.length === 0 && retired.length === 0)
19428
- return null;
19429
- const normalizedReason = reason.trim();
19430
- const changeReason = retired.length ? `${normalizedReason || "policy reassessed"} (retired: ${retired.join(",")})` : normalizedReason || "policy reassessed";
19431
- const change = {
19432
- recordedAt: now,
19433
- fromInputDigest: previous?.inputDigest ?? null,
19434
- toInputDigest: next.inputDigest,
19435
- added,
19436
- retired,
19437
- reason: changeReason
19438
- };
19439
- const parsed = policyChangeSchema.safeParse(change);
19440
- if (!parsed.success)
19441
- throw new TypeError("policy change is invalid");
19442
- return parsed.data;
19478
+
19479
+ // packages/workit-core/src/core/task-context.ts
19480
+ function reconcileResume(view, observations = []) {
19481
+ if (observations.length > 0)
19482
+ return failure("permission_denied", "worker observations require native host verification");
19483
+ const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
19484
+ if (!candidate.ok)
19485
+ return candidate;
19486
+ const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
19487
+ const blockers = [...view.task.progress.blockers];
19488
+ if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
19489
+ blockers.push({
19490
+ reason: "worker state requires reconciliation",
19491
+ dependentAction: "resume",
19492
+ refs: []
19493
+ });
19494
+ return success(null, null, {
19495
+ candidate: candidate.data,
19496
+ staleEvidenceIds,
19497
+ workerUpdates: [],
19498
+ blockers,
19499
+ reassessmentRequired: view.task.policy?.policyVersion !== POLICY_VERSION
19500
+ });
19443
19501
  }
19502
+ var COMPACT_MAX_BYTES = 4096;
19503
+ var COMPACT_MAX_ITEMS = 8;
19504
+ var COMPACT_TEXT_BYTES = 128;
19505
+ var sensitiveUrl = /\bhttps?:\/\/[^/\s:@]+:[^@\s]+@/gi;
19506
+ var sensitiveValue = /(?:token|secret|password|passwd|api[-_ ]?key|private[-_ ]?key|authorization)\s*[:=]\s*(?:bearer\s+)?\S+|\bbearer\s+\S+/gi;
19507
+ var privateKey = /-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/gi;
19508
+ var compactText = (value, limit) => value === null ? null : value.replace(privateKey, "<redacted-private-key>").replace(sensitiveUrl, "<redacted-url>").replace(sensitiveValue, (match) => {
19509
+ const separator = match.search(/[:=]/u);
19510
+ if (separator >= 0)
19511
+ return `${match.slice(0, separator)}=<redacted>`;
19512
+ const space = match.search(/\s/u);
19513
+ return `${match.slice(0, space)} <redacted>`;
19514
+ }).slice(0, limit);
19515
+ var compactBytes = (value) => new TextEncoder().encode(value).byteLength;
19516
+ var compactTimestamp = (value) => `${value.slice(0, 19)}.${value.slice(20, -1).padEnd(9, "0")}`;
19517
+ var compactDecisionChoice = (entry) => {
19518
+ const stated = entry.data.binding.statedChoice?.text;
19519
+ const approved = entry.data.response === "approved" ? entry.data.binding.approvedContent : "";
19520
+ const source = stated?.trim() || approved.trim() || null;
19521
+ return compactText(source, COMPACT_TEXT_BYTES);
19522
+ };
19523
+ var compactReference = (ref) => {
19524
+ if (ref.kind === "file")
19525
+ return {
19526
+ kind: "file",
19527
+ path: compactText(ref.path, COMPACT_TEXT_BYTES) ?? ""
19528
+ };
19529
+ if (ref.kind === "record")
19530
+ return {
19531
+ kind: "record",
19532
+ collection: ref.collection,
19533
+ id: ref.id
19534
+ };
19535
+ return { kind: ref.kind };
19536
+ };
19537
+ function compactTaskContext(view) {
19538
+ const decisions = view.task.decisions.slice().sort((left, right) => {
19539
+ const leftAt = compactTimestamp(left.recordedAt);
19540
+ const rightAt = compactTimestamp(right.recordedAt);
19541
+ if (leftAt !== rightAt)
19542
+ return leftAt > rightAt ? -1 : 1;
19543
+ return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
19544
+ }).slice(0, COMPACT_MAX_ITEMS).map((entry) => ({
19545
+ id: entry.id,
19546
+ purpose: entry.data.purpose,
19547
+ status: entry.data.response,
19548
+ digest: entry.data.digest,
19549
+ choice: compactDecisionChoice(entry) || `task.inspect taskId=${view.task.id} view=full; decision=${entry.id}`,
19550
+ references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference)
19551
+ }));
19552
+ const methods = view.task.policy ? selectMethods(view.task.policy, view.capabilities).map((method) => ({
19553
+ id: method.id,
19554
+ assurance: method.assurance,
19555
+ reason: compactText(method.reason, COMPACT_TEXT_BYTES) ?? ""
19556
+ })) : [];
19557
+ const gaps = Array.from(new Set([
19558
+ ...view.evidence.filter((entry) => entry.status === "stale").map((entry) => `stale evidence: ${entry.evidenceId}`),
19559
+ ...view.requirements.filter((entry) => entry.status !== "satisfied").map((entry) => entry.reason),
19560
+ ...view.task.progress.blockers.map((entry) => entry.reason)
19561
+ ])).sort().slice(0, COMPACT_MAX_ITEMS).map((gap) => compactText(gap, COMPACT_TEXT_BYTES));
19562
+ const context = {
19563
+ objective: compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "",
19564
+ status: view.task.status,
19565
+ decisions,
19566
+ methods,
19567
+ gaps,
19568
+ nextAction: compactText(view.task.progress.nextAction, COMPACT_TEXT_BYTES)
19569
+ };
19570
+ let encoded = JSON.stringify(context);
19571
+ while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.methods.length || context.gaps.length)) {
19572
+ if (context.gaps.length)
19573
+ context.gaps.pop();
19574
+ else if (context.decisions.length)
19575
+ context.decisions.pop();
19576
+ else
19577
+ context.methods.pop();
19578
+ encoded = JSON.stringify(context);
19579
+ }
19580
+ if (compactBytes(encoded) > COMPACT_MAX_BYTES) {
19581
+ context.objective = compactText(context.objective, 64) ?? "";
19582
+ context.nextAction = compactText(context.nextAction, 64);
19583
+ encoded = JSON.stringify(context);
19584
+ }
19585
+ return encoded;
19586
+ }
19587
+ var exportDigest = (bundle) => sha256({
19588
+ schemaVersion: bundle.schemaVersion,
19589
+ exportedAt: bundle.exportedAt,
19590
+ sourceWorkspaceId: bundle.sourceWorkspaceId,
19591
+ task: bundle.task
19592
+ });
19444
19593
 
19445
19594
  // packages/workit-core/src/core/task-engine.ts
19446
19595
  var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
@@ -21128,14 +21277,29 @@ class WorkitCore {
21128
21277
  writer: evaluationWorkspace.writer
21129
21278
  });
21130
21279
  }
21131
- view(task) {
21280
+ compactContext(taskId) {
21281
+ const root = this.contextRootError();
21282
+ if (!root.ok)
21283
+ return root;
21284
+ const task = this.store.readTask(taskId);
21285
+ if (!task.ok)
21286
+ return task;
21287
+ const helper = this.helperTaskGuard(task.data, true);
21288
+ if (!helper.ok)
21289
+ return helper;
21290
+ const view = this.view(task.data, false);
21291
+ if (!view.ok)
21292
+ return view;
21293
+ return success(view.revision, view.workspaceRevision, compactTaskContext(view.data));
21294
+ }
21295
+ view(task, capture = true) {
21132
21296
  const workspace = this.store.readWorkspace();
21133
21297
  if (!workspace.ok)
21134
21298
  return workspace;
21135
21299
  if (!workspace.data)
21136
21300
  return failure("not_found", "workspace not found");
21137
21301
  const historical = task.status === "closed" ? task.candidates.at(-1) : undefined;
21138
- const current = historical ? success(null, null, historical) : captureCandidate(this.store.root, task.intent.data.scope, environment());
21302
+ 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);
21139
21303
  if (!current.ok)
21140
21304
  return current;
21141
21305
  const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
@@ -21407,6 +21571,122 @@ class WorkitCore {
21407
21571
  return this.summary(changed.data.task);
21408
21572
  }
21409
21573
  }
21574
+
21575
+ // packages/workit-core/src/core/session-context.ts
21576
+ var cache = new Map;
21577
+ // packages/workit-core/src/core/external-action.ts
21578
+ var chainStepSchema = union([
21579
+ string2().min(1),
21580
+ object({ branch: string2().min(1) }).strict(),
21581
+ object({ pr: literal(true) }).strict()
21582
+ ]);
21583
+ var externalActionSchema = discriminatedUnion("operation", [
21584
+ object({
21585
+ operation: literal("git.branch_setup"),
21586
+ payload: object({
21587
+ action: _enum(["setup", "reapply_stash"]).optional(),
21588
+ sdd_dir: string2().optional(),
21589
+ target_branch: string2().optional(),
21590
+ stash: _enum(["yes", "no"]).optional(),
21591
+ cwd: string2().min(1).optional()
21592
+ }).strict()
21593
+ }).strict(),
21594
+ object({
21595
+ operation: literal("git.commit"),
21596
+ payload: object({
21597
+ message: string2().min(1).optional(),
21598
+ plan_steps: array(chainStepSchema).min(1).max(32).optional(),
21599
+ plan_branch: string2().min(1).optional(),
21600
+ cwd: string2().min(1).optional()
21601
+ }).strict()
21602
+ }).strict(),
21603
+ object({
21604
+ operation: literal("git.push"),
21605
+ payload: object({ branch: string2().min(1).optional(), cwd: string2().min(1).optional() }).strict()
21606
+ }).strict(),
21607
+ object({
21608
+ operation: literal("hosting.pull_request"),
21609
+ payload: object({
21610
+ title: string2().min(1),
21611
+ body: string2().optional(),
21612
+ draft: boolean2().optional(),
21613
+ target_branch: string2().optional(),
21614
+ babysit: boolean2().optional(),
21615
+ cwd: string2().min(1).optional()
21616
+ }).strict()
21617
+ }).strict(),
21618
+ object({
21619
+ operation: literal("hosting.merge"),
21620
+ payload: object({
21621
+ target_branch: string2().min(1).optional(),
21622
+ source_branch: string2().min(1).optional(),
21623
+ cwd: string2().min(1).optional()
21624
+ }).strict()
21625
+ }).strict(),
21626
+ object({
21627
+ operation: literal("hosting.delete_branch"),
21628
+ payload: object({ branch: string2().min(1), cwd: string2().min(1).optional() }).strict()
21629
+ }).strict(),
21630
+ object({
21631
+ operation: literal("youtrack.update"),
21632
+ payload: object({
21633
+ issueId: string2().min(1),
21634
+ markdown: string2().min(1),
21635
+ minutes: number2().positive().optional()
21636
+ }).strict()
21637
+ }).strict(),
21638
+ object({
21639
+ operation: literal("youtrack.time"),
21640
+ payload: object({
21641
+ issueId: string2().min(1),
21642
+ minutes: number2().positive(),
21643
+ text: string2().optional(),
21644
+ dateMs: number2().finite().optional()
21645
+ }).strict()
21646
+ }).strict(),
21647
+ object({
21648
+ operation: literal("youtrack.meeting"),
21649
+ payload: object({
21650
+ issueId: string2().min(1),
21651
+ minutes: number2().positive(),
21652
+ text: string2().min(1)
21653
+ }).strict()
21654
+ }).strict(),
21655
+ object({
21656
+ operation: literal("changelog.apply"),
21657
+ payload: object({
21658
+ entries: union([
21659
+ record(string2(), array(string2())),
21660
+ array(object({ category: string2().min(1), text: string2().min(1) }).strict())
21661
+ ]).optional(),
21662
+ path: string2().optional(),
21663
+ normalize_only: boolean2().optional()
21664
+ }).strict()
21665
+ }).strict(),
21666
+ object({
21667
+ operation: literal("context.read"),
21668
+ payload: object({
21669
+ kind: _enum([
21670
+ "git",
21671
+ "pr",
21672
+ "youtrack",
21673
+ "github_issue",
21674
+ "gitlab_issue",
21675
+ "changelog",
21676
+ "release",
21677
+ "affected"
21678
+ ]),
21679
+ range: string2().optional(),
21680
+ issueId: string2().optional(),
21681
+ issueUrl: string2().optional(),
21682
+ issueRef: string2().optional(),
21683
+ mode: string2().optional(),
21684
+ specPath: string2().optional(),
21685
+ planPath: string2().optional(),
21686
+ cwd: string2().min(1).optional()
21687
+ }).strict()
21688
+ }).strict()
21689
+ ]);
21410
21690
  // packages/workit-core/src/core.ts
21411
21691
  var revision2 = /^[A-Za-z0-9@][A-Za-z0-9@._/~^{}-]*$/;
21412
21692
  function gitRevisionParts(value) {
@@ -28039,56 +28319,19 @@ var readYouTrackContextConfig = () => {
28039
28319
  return { error: "YouTrack configuration is unavailable" };
28040
28320
  return { data: readOnlyYouTrackConfig(parsed) };
28041
28321
  };
28042
- function tzParts(date, tz) {
28043
- const parts = new Intl.DateTimeFormat("en-GB", {
28044
- timeZone: tz,
28045
- year: "numeric",
28046
- month: "2-digit",
28047
- day: "2-digit",
28048
- hour: "2-digit",
28049
- minute: "2-digit",
28050
- hourCycle: "h23"
28051
- }).formatToParts(date);
28052
- const map = Object.fromEntries(parts.map((p) => [p.type, p.value]));
28053
- return { y: map.year, m: map.month, d: map.day, hour: map.hour, minute: map.minute };
28054
- }
28055
- function youTrackGreeting(configOverride) {
28056
- const cfgPath = configOverride ?? youTrackConfigPath();
28057
- let parsed;
28058
- try {
28059
- parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
28060
- } catch {
28061
- return {
28062
- stdout: "",
28063
- exitCode: 1,
28064
- stderr: fs9.existsSync(cfgPath) ? `${cfgPath} is not valid JSON` : `missing youtrack.json: ${cfgPath}`
28065
- };
28066
- }
28067
- if (!isConfigObject(parsed)) {
28068
- return { stdout: "", exitCode: 1, stderr: `${cfgPath} is not a JSON object` };
28069
- }
28070
- const config = parsed;
28071
- try {
28072
- const tz = String(config.timezone ?? "America/Santiago");
28073
- const now = new Date;
28074
- const { y, m, d, hour, minute } = tzParts(now, tz);
28075
- const cutoff = String(config.greetingCutoff ?? "12:00").split(":");
28076
- const cutoffHour = Number(cutoff[0]);
28077
- const cutoffMinute = Number(cutoff[1] ?? 0);
28078
- const greetings = config.greetings ?? {};
28079
- const isMorning = Number(hour) < cutoffHour || Number(hour) === cutoffHour && Number(minute) < cutoffMinute;
28080
- const greeting = isMorning ? greetings.morning ?? "buenos días" : greetings.afternoon ?? "buenas tardes";
28081
- const mention = String(config.defaultMention ?? "Alejandra.Flores");
28082
- return { stdout: `@${mention} Hola, ${greeting}.
28083
- `, exitCode: 0, stderr: "" };
28084
- } catch (err) {
28085
- return {
28086
- stdout: "",
28087
- exitCode: 1,
28088
- stderr: err instanceof Error ? err.message : "greeting failed"
28089
- };
28090
- }
28091
- }
28322
+ var processTimezone = () => Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
28323
+ var effectiveWorkTimezone = (cfg) => {
28324
+ const configured = cfg?.timezone;
28325
+ return typeof configured === "string" && configured.trim() ? { timezone: configured.trim(), source: "youtrack.json" } : { timezone: processTimezone(), source: "process" };
28326
+ };
28327
+ var DEFAULT_MEETING_WORK_ITEM_TEXT = "Meetings";
28328
+ var meetingWorkItemText = (cfg, item) => {
28329
+ if (typeof item?.workItemText === "string" && item.workItemText)
28330
+ return item.workItemText;
28331
+ if (typeof cfg?.meetingWorkItemText === "string" && cfg.meetingWorkItemText)
28332
+ return cfg.meetingWorkItemText;
28333
+ return DEFAULT_MEETING_WORK_ITEM_TEXT;
28334
+ };
28092
28335
  function youTrackParseDuration(text) {
28093
28336
  const lower = String(text).toLowerCase().trim();
28094
28337
  let total = 0;
@@ -28165,7 +28408,6 @@ function parseIssueRef(input) {
28165
28408
  }
28166
28409
  var contextScripts = {
28167
28410
  config: readYouTrackContextConfig,
28168
- greeting: () => youTrackGreeting(youTrackReadOnlyConfigPath()),
28169
28411
  parseDuration: (text) => youTrackParseDuration(text),
28170
28412
  api: () => ({ error: "YouTrack context is read-only" })
28171
28413
  };
@@ -28194,7 +28436,7 @@ function meetingOptionsFromConfig(cfg) {
28194
28436
  key,
28195
28437
  issue: item.issue,
28196
28438
  label: item.label ?? item.issue,
28197
- workItemText: item.workItemText ?? "Reuniones",
28439
+ workItemText: meetingWorkItemText(cfg, item),
28198
28440
  url: item.url ?? (base && item.issue ? `${base}/issue/${item.issue}` : null)
28199
28441
  }));
28200
28442
  }
@@ -28204,7 +28446,7 @@ function meetingOptionsFromConfig(cfg) {
28204
28446
  key: "general",
28205
28447
  issue,
28206
28448
  label: "General meetings",
28207
- workItemText: "Reuniones",
28449
+ workItemText: meetingWorkItemText(cfg),
28208
28450
  url: base && issue ? `${base}/issue/${issue}` : null
28209
28451
  }
28210
28452
  ];
@@ -28226,17 +28468,9 @@ var readOnlyYouTrackConfig = (cfg) => {
28226
28468
  const baseUrl = publicUrl(cfg.baseUrl);
28227
28469
  if (baseUrl)
28228
28470
  safe.baseUrl = baseUrl;
28229
- for (const key of ["timezone", "greetingCutoff", "defaultMention", "meetingIssue"])
28471
+ for (const key of ["timezone", "meetingIssue", "meetingWorkItemText"])
28230
28472
  if (typeof cfg[key] === "string")
28231
28473
  safe[key] = cfg[key];
28232
- if (cfg.greetings && typeof cfg.greetings === "object" && !Array.isArray(cfg.greetings)) {
28233
- const greetings = {};
28234
- for (const key of ["morning", "afternoon"])
28235
- if (typeof cfg.greetings[key] === "string")
28236
- greetings[key] = cfg.greetings[key];
28237
- if (Object.keys(greetings).length)
28238
- safe.greetings = greetings;
28239
- }
28240
28474
  if (cfg.meetingIssues && typeof cfg.meetingIssues === "object" && !Array.isArray(cfg.meetingIssues)) {
28241
28475
  safe.meetingIssues = Object.fromEntries(Object.entries(cfg.meetingIssues).flatMap(([key, value]) => {
28242
28476
  if (!value || typeof value !== "object" || Array.isArray(value))
@@ -28266,16 +28500,12 @@ function context({
28266
28500
  const cfg = scripts.config();
28267
28501
  if (cfg.error)
28268
28502
  return { error: cfg.error };
28269
- const greeting = scripts.greeting();
28270
- if (greeting.exitCode !== 0) {
28271
- return { error: (greeting.stderr || greeting.stdout || "greeting failed").trim() };
28272
- }
28273
28503
  const safeConfig = readOnlyYouTrackConfig(cfg.data);
28274
28504
  const meetingOptions = meetingOptionsFromConfig(safeConfig);
28275
28505
  if (mode === "meetings" && !issue_id && !issue_url && !issue_ref) {
28276
28506
  return {
28277
28507
  config: safeConfig,
28278
- greeting: greeting.stdout.trim(),
28508
+ workTimezone: effectiveWorkTimezone(safeConfig),
28279
28509
  mode: "meetings",
28280
28510
  requiresMeetingChoice: true,
28281
28511
  meetingOptions,
@@ -28304,7 +28534,7 @@ function context({
28304
28534
  const selectedMeeting = meetingOptions.find((m) => m.issue === issue);
28305
28535
  return {
28306
28536
  config: safeConfig,
28307
- greeting: greeting.stdout.trim(),
28537
+ workTimezone: effectiveWorkTimezone(safeConfig),
28308
28538
  issueId: issue,
28309
28539
  issueUrl,
28310
28540
  mode: mode ?? (selectedMeeting ? "meetings" : "task"),