@brainervirus/workit-cursor 2.1.1 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21584,6 +21584,45 @@ var metadataLockSchema = object4({
21584
21584
  nonce: string5().min(1),
21585
21585
  externalAction: literal3(true).optional()
21586
21586
  }).strict();
21587
+ var INDEX_VERSION = 1;
21588
+ var hostSession = (value) => isObject3(value) && value.kind === "host" && typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
21589
+ var indexEntry = (task, file) => {
21590
+ const sessions = [];
21591
+ const intent = hostSession(task.intent.provenance.session);
21592
+ if (intent)
21593
+ sessions.push({ ...intent, workerId: null });
21594
+ for (const worker of task.workers) {
21595
+ const session = hostSession(worker.data.session);
21596
+ if (session)
21597
+ sessions.push({ ...session, workerId: worker.id });
21598
+ }
21599
+ return {
21600
+ id: task.id,
21601
+ revision: task.revision,
21602
+ status: task.status,
21603
+ createdAt: task.createdAt,
21604
+ updatedAt: task.updatedAt,
21605
+ objective: task.intent.data.objective,
21606
+ source: { host: task.intent.provenance.host, kind: task.intent.provenance.kind },
21607
+ progress: { summary: task.progress.summary, nextAction: task.progress.nextAction },
21608
+ sessions,
21609
+ file
21610
+ };
21611
+ };
21612
+ var fileSignature = (file) => {
21613
+ try {
21614
+ const stat = fs4.statSync(file, { bigint: true });
21615
+ return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
21616
+ } catch {
21617
+ return null;
21618
+ }
21619
+ };
21620
+ var RACY_WINDOW_NS = 2000000000n;
21621
+ var racySignature = (signature) => {
21622
+ const [mtime = 0n, ctime = 0n] = signature.split(":").slice(3, 5).map(BigInt);
21623
+ const changed = mtime > ctime ? mtime : ctime;
21624
+ return BigInt(Date.now()) * 1000000n - changed < RACY_WINDOW_NS;
21625
+ };
21587
21626
  var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
21588
21627
  var cachedRuntimeVersion = null;
21589
21628
  var runtimeVersion = () => {
@@ -21712,6 +21751,57 @@ class TaskStore {
21712
21751
  }
21713
21752
  return success(null, null, tasks);
21714
21753
  }
21754
+ listTaskIndex() {
21755
+ if (!fs4.existsSync(this.tasksDir))
21756
+ return success(null, null, []);
21757
+ let names;
21758
+ try {
21759
+ names = fs4.readdirSync(this.tasksDir).filter((name) => name.endsWith(".json"));
21760
+ } catch (error) {
21761
+ return failure("storage_error", `unable to list tasks: ${String(error)}`, {
21762
+ path: this.tasksDir
21763
+ });
21764
+ }
21765
+ const workspace = this.readWorkspace();
21766
+ if (!workspace.ok)
21767
+ return workspace;
21768
+ if (!workspace.data)
21769
+ return failure("recovery_required", "task workspace binding is invalid", {
21770
+ path: this.workspacePath
21771
+ });
21772
+ const stored = this.readIndex(workspace.data.id);
21773
+ const entries = [];
21774
+ let changed = Object.keys(stored).length !== names.length;
21775
+ for (const name of names.sort()) {
21776
+ const file = path4.join(this.tasksDir, name);
21777
+ const signature = fileSignature(file);
21778
+ if (signature === null) {
21779
+ changed = true;
21780
+ continue;
21781
+ }
21782
+ const cached = stored[name.slice(0, -5)];
21783
+ if (cached && cached.file === signature && !racySignature(signature)) {
21784
+ entries.push(cached);
21785
+ continue;
21786
+ }
21787
+ const item = this.readRecord(file, taskRecordSchema);
21788
+ if (!item.exists)
21789
+ continue;
21790
+ if (!item.result.ok)
21791
+ return item.result;
21792
+ if (!validId(name.slice(0, -5)) || item.result.data.id !== name.slice(0, -5))
21793
+ return failure("recovery_required", "task filename and record ID differ", { path: name });
21794
+ if (item.result.data.workspaceId !== workspace.data.id)
21795
+ return failure("recovery_required", "task workspace binding is invalid", { path: name });
21796
+ const entry = indexEntry(item.result.data, signature);
21797
+ if (!cached || canonicalJson(cached) !== canonicalJson(entry))
21798
+ changed = true;
21799
+ entries.push(entry);
21800
+ }
21801
+ if (changed)
21802
+ this.writeIndex(workspace.data.id, entries);
21803
+ return success(null, null, entries);
21804
+ }
21715
21805
  readWorkspace() {
21716
21806
  const item = this.readRecord(this.workspacePath, workspaceRecordSchema);
21717
21807
  if (!item.exists)
@@ -22391,6 +22481,8 @@ class TaskStore {
22391
22481
  fs4.renameSync(temporary, file);
22392
22482
  temporary = undefined;
22393
22483
  this.fsyncDirectory(path4.dirname(file));
22484
+ if (path4.dirname(file) === this.tasksDir)
22485
+ this.indexTaskWrite(file, value);
22394
22486
  return success(null, null, value);
22395
22487
  } catch (error) {
22396
22488
  return failure("storage_error", `snapshot replacement failed: ${String(error)}`, {
@@ -22403,6 +22495,45 @@ class TaskStore {
22403
22495
  } catch {}
22404
22496
  }
22405
22497
  }
22498
+ readIndex(workspaceId) {
22499
+ try {
22500
+ const value = JSON.parse(fs4.readFileSync(this.indexPath, "utf8"));
22501
+ if (!isObject3(value) || value.version !== INDEX_VERSION || value.workspaceId !== workspaceId || !isObject3(value.tasks))
22502
+ return {};
22503
+ const tasks = {};
22504
+ for (const [id, entry] of Object.entries(value.tasks))
22505
+ if (isObject3(entry) && entry.id === id && typeof entry.file === "string" && typeof entry.status === "string" && typeof entry.updatedAt === "string" && Array.isArray(entry.sessions) && isObject3(entry.progress) && isObject3(entry.source))
22506
+ tasks[id] = entry;
22507
+ return tasks;
22508
+ } catch {
22509
+ return {};
22510
+ }
22511
+ }
22512
+ writeIndex(workspaceId, entries) {
22513
+ const temporary = `${this.indexPath}.${process.pid}.${randomUUID2()}.tmp`;
22514
+ try {
22515
+ fs4.writeFileSync(temporary, JSON.stringify({
22516
+ version: INDEX_VERSION,
22517
+ workspaceId,
22518
+ tasks: Object.fromEntries(entries.map((entry) => [entry.id, entry]))
22519
+ }), { mode: 384, flag: "wx" });
22520
+ fs4.renameSync(temporary, this.indexPath);
22521
+ } catch {
22522
+ try {
22523
+ fs4.unlinkSync(temporary);
22524
+ } catch {}
22525
+ }
22526
+ }
22527
+ indexTaskWrite(file, task) {
22528
+ try {
22529
+ const signature = fileSignature(file);
22530
+ if (signature === null)
22531
+ return;
22532
+ const tasks = this.readIndex(task.workspaceId);
22533
+ tasks[task.id] = indexEntry(task, signature);
22534
+ this.writeIndex(task.workspaceId, Object.values(tasks));
22535
+ } catch {}
22536
+ }
22406
22537
  saveRecovery(file, bytes) {
22407
22538
  const target = path4.basename(file) === "workspace.json" ? "workspace" : "task";
22408
22539
  const id = target === "task" ? path4.basename(file, ".json") : "workspace";
@@ -22561,6 +22692,9 @@ class TaskStore {
22561
22692
  get workspacePath() {
22562
22693
  return path4.join(this.workitDir, "workspace.json");
22563
22694
  }
22695
+ get indexPath() {
22696
+ return path4.join(this.workitDir, "index.json");
22697
+ }
22564
22698
  get lockPath() {
22565
22699
  return path4.join(this.workitDir, "metadata.lock");
22566
22700
  }
@@ -22568,71 +22702,8 @@ class TaskStore {
22568
22702
  return path4.join(this.workitDir, ".gitignore");
22569
22703
  }
22570
22704
  }
22571
- // packages/workit-core/src/core/workers.ts
22572
- var same = (left, right) => {
22573
- try {
22574
- return canonicalJson(left) === canonicalJson(right);
22575
- } catch {
22576
- return false;
22577
- }
22578
- };
22579
- var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
22580
- var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
22581
- var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
22582
- var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
22583
- var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
22584
- const code = char.charCodeAt(0);
22585
- return code < 32 || code === 127;
22586
- }) && !value.split(/[\\/]/).includes("..");
22587
- var callerValue = (value) => ({ host: value.host, actor: value.actor });
22588
- var workerIdOf = (value) => value.workerId ?? null;
22589
- var currentWriterOwnsTask = (task, workspace, caller) => {
22590
- const owner = workspace.writer?.owner;
22591
- return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
22592
- };
22593
- function assertProductWriteAllowed(input) {
22594
- if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
22595
- return failure("invalid_input", "invalid product write path");
22596
- if (!input.store)
22597
- return failure("permission_denied", "write authorization requires core state");
22598
- const task = input.store.readTask(input.task.id);
22599
- if (!task.ok)
22600
- return task;
22601
- const workspace = input.store.readWorkspace();
22602
- if (!workspace.ok)
22603
- return workspace;
22604
- if (!workspace.data)
22605
- return failure("not_found", "workspace not found");
22606
- const workspaceRecord = workspace.data;
22607
- if (task.data.status !== "active")
22608
- return failure("invalid_transition", "paused or closed tasks cannot own product writes");
22609
- if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
22610
- return failure("recovery_required", "task and workspace bindings are invalid");
22611
- const writer = workspaceRecord.writer;
22612
- if (!writer)
22613
- return failure("permission_denied", "checkout has no writer owner");
22614
- if (writer.state === "uncertain")
22615
- return failure("recovery_required", "checkout writer requires recovery", {
22616
- owner: writer.owner
22617
- });
22618
- const caller = callerValue(input.caller);
22619
- const workerId = workerIdOf(input.caller);
22620
- const owner = writer.owner;
22621
- if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
22622
- return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
22623
- const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
22624
- if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
22625
- return failure("permission_denied", "only implementers can own product writes");
22626
- 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))))
22627
- return failure("recovery_required", "worker session is not an active writer");
22628
- return success(null, workspaceRecord.revision, owner);
22629
- }
22630
-
22631
- // packages/workit-core/src/core/task-evaluation.ts
22632
- import { createHash as createHash6 } from "node:crypto";
22633
- import * as fs7 from "node:fs";
22634
- import { spawnSync as spawnSync2 } from "node:child_process";
22635
- import path10 from "node:path";
22705
+ // packages/workit-core/src/core/task-engine.ts
22706
+ import { realpathSync as realpathSync6 } from "node:fs";
22636
22707
 
22637
22708
  // packages/workit-core/src/core/authority.ts
22638
22709
  import { createHash as createHash5 } from "node:crypto";
@@ -22766,7 +22837,6 @@ var LOCALE_RE = /^[a-z]{2,3}(-(?:[A-Z]{2}|[0-9]{3}))?$/;
22766
22837
  var DEFAULTS = {
22767
22838
  locale: "en",
22768
22839
  localeOptions: ["en", "es-CL", "es-MX", "es-AR", "pt-BR"],
22769
- timezone: "America/Santiago",
22770
22840
  branchPolicy: {
22771
22841
  preset: "gitflow",
22772
22842
  allowed: [...PRESETS.gitflow.allowed],
@@ -22804,7 +22874,6 @@ var parseConfigResult = (raw, file) => {
22804
22874
  config: {
22805
22875
  locale,
22806
22876
  localeOptions: Array.isArray(input.localeOptions) ? input.localeOptions : DEFAULTS.localeOptions,
22807
- timezone: input.timezone ?? DEFAULTS.timezone,
22808
22877
  branchPolicy: mergePreset(preset, {
22809
22878
  allowed: Array.isArray(input.branchPolicy?.allowed) ? input.branchPolicy.allowed : undefined,
22810
22879
  protectedNames: Array.isArray(input.branchPolicy?.protected) ? input.branchPolicy.protected : undefined
@@ -23639,6 +23708,66 @@ var verifyPushIdentity = (root, provider, account) => {
23639
23708
  return { ok: true };
23640
23709
  };
23641
23710
 
23711
+ // packages/workit-core/src/core/workers.ts
23712
+ var same = (left, right) => {
23713
+ try {
23714
+ return canonicalJson(left) === canonicalJson(right);
23715
+ } catch {
23716
+ return false;
23717
+ }
23718
+ };
23719
+ var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
23720
+ var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
23721
+ var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
23722
+ var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
23723
+ var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
23724
+ const code = char.charCodeAt(0);
23725
+ return code < 32 || code === 127;
23726
+ }) && !value.split(/[\\/]/).includes("..");
23727
+ var callerValue = (value) => ({ host: value.host, actor: value.actor });
23728
+ var workerIdOf = (value) => value.workerId ?? null;
23729
+ var currentWriterOwnsTask = (task, workspace, caller) => {
23730
+ const owner = workspace.writer?.owner;
23731
+ return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
23732
+ };
23733
+ function assertProductWriteAllowed(input) {
23734
+ if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
23735
+ return failure("invalid_input", "invalid product write path");
23736
+ if (!input.store)
23737
+ return failure("permission_denied", "write authorization requires core state");
23738
+ const task = input.store.readTask(input.task.id);
23739
+ if (!task.ok)
23740
+ return task;
23741
+ const workspace = input.store.readWorkspace();
23742
+ if (!workspace.ok)
23743
+ return workspace;
23744
+ if (!workspace.data)
23745
+ return failure("not_found", "workspace not found");
23746
+ const workspaceRecord = workspace.data;
23747
+ if (task.data.status !== "active")
23748
+ return failure("invalid_transition", "paused or closed tasks cannot own product writes");
23749
+ if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
23750
+ return failure("recovery_required", "task and workspace bindings are invalid");
23751
+ const writer = workspaceRecord.writer;
23752
+ if (!writer)
23753
+ return failure("permission_denied", "checkout has no writer owner");
23754
+ if (writer.state === "uncertain")
23755
+ return failure("recovery_required", "checkout writer requires recovery", {
23756
+ owner: writer.owner
23757
+ });
23758
+ const caller = callerValue(input.caller);
23759
+ const workerId = workerIdOf(input.caller);
23760
+ const owner = writer.owner;
23761
+ if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
23762
+ return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
23763
+ const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
23764
+ if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
23765
+ return failure("permission_denied", "only implementers can own product writes");
23766
+ 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))))
23767
+ return failure("recovery_required", "worker session is not an active writer");
23768
+ return success(null, workspaceRecord.revision, owner);
23769
+ }
23770
+
23642
23771
  // packages/workit-core/src/core/auto-approval.ts
23643
23772
  var AUTO_CLASSES = ["branch", "commit", "push", "pr", "merge"];
23644
23773
  var OPERATION_CLASS = {
@@ -24329,41 +24458,18 @@ function reconcileAction(input) {
24329
24458
  var bindingCovers = scopeCovers;
24330
24459
  var verifyDecisionContent = verifyContentRefs;
24331
24460
 
24332
- // packages/workit-core/src/core/task-evaluation.ts
24333
- var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex");
24334
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`);
24335
- var relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".";
24336
- var canonicalPath = (value) => {
24337
- if (typeof value !== "string" || !value)
24338
- return null;
24339
- if (value.includes("\\") || value.split("/").includes(".."))
24340
- return null;
24341
- const normalized = path10.posix.normalize(value);
24342
- if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
24343
- return null;
24344
- return normalized === "." ? "." : normalized.replace(/\/$/, "");
24345
- };
24346
- var canonicalScope = (scope) => {
24347
- const paths = scope.paths.length ? scope.paths : ["."];
24348
- const normalizedPaths = paths.map(canonicalPath);
24349
- const normalizedExclusions = scope.exclusions.map(canonicalPath);
24350
- if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
24351
- return null;
24352
- return {
24353
- description: scope.description,
24354
- paths: [...new Set(normalizedPaths)],
24355
- exclusions: [...new Set(normalizedExclusions)]
24356
- };
24357
- };
24358
- var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
24359
- var scopeMatches = (value, scope) => {
24360
- const normalized = canonicalScope(scope);
24361
- if (!normalized)
24362
- return false;
24363
- const paths = normalized.paths;
24364
- return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
24365
- };
24366
- var pathRelevant = (value, scope) => scopeMatches(value, scope);
24461
+ // packages/workit-core/src/core/policy-resolver.ts
24462
+ var resolverInputSchema = object4({
24463
+ intent: intentSchema,
24464
+ assessment: assessmentSchema,
24465
+ constraints: array3(constraintSchema),
24466
+ prior: object4({
24467
+ decisions: array3(decisionSchema),
24468
+ findings: array3(findingSchema),
24469
+ requirements: array3(requirementSchema)
24470
+ }).strict(),
24471
+ preferences: object4({ fast: boolean5().optional(), thorough: boolean5().optional() }).strict().optional()
24472
+ }).strict();
24367
24473
  var compareCodeUnits2 = (left, right) => {
24368
24474
  const length = Math.min(left.length, right.length);
24369
24475
  for (let index = 0;index < length; index += 1) {
@@ -24373,927 +24479,970 @@ var compareCodeUnits2 = (left, right) => {
24373
24479
  }
24374
24480
  return left.length - right.length;
24375
24481
  };
24376
- var gitPaths = (root) => {
24377
- const result = spawnSync2("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
24378
- cwd: root,
24379
- encoding: "buffer"
24380
- });
24381
- if (result.status !== 0) {
24382
- const stderr = result.stderr?.toString("utf8") ?? "";
24383
- return {
24384
- paths: [],
24385
- uncertain: !/not a git repository/i.test(stderr),
24386
- git: /not a git repository/i.test(stderr) ? false : true
24387
- };
24388
- }
24389
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
24390
- const stagedDeleted = spawnSync2("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
24391
- if (stagedDeleted.status !== 0) {
24392
- const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
24393
- if (!/not a git repository/i.test(stderr))
24394
- return { paths, uncertain: true, git: true };
24395
- } else if (stagedDeleted.stdout) {
24396
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")));
24397
- }
24398
- return { paths: [...new Set(paths)], uncertain: false, git: true };
24399
- };
24400
- var walk = (root, directory, output) => {
24401
- let names;
24402
- try {
24403
- names = fs7.readdirSync(directory);
24404
- } catch {
24405
- return true;
24406
- }
24407
- let uncertain = false;
24408
- for (const name of names) {
24409
- if (name === ".git" || name === ".workit")
24410
- continue;
24411
- const target = path10.join(directory, name);
24412
- let stat;
24413
- try {
24414
- stat = fs7.lstatSync(target);
24415
- } catch {
24416
- uncertain = true;
24417
- continue;
24418
- }
24419
- const item = relative(root, target);
24420
- output.add(item);
24421
- if (stat.isDirectory() && !stat.isSymbolicLink())
24422
- uncertain = walk(root, target, output) || uncertain;
24423
- }
24424
- return uncertain;
24425
- };
24426
- var scopeRoots = (root, scope) => {
24427
- const roots = [];
24428
- for (const item of scope.paths.length ? scope.paths : ["."]) {
24429
- const target = path10.resolve(root, item);
24430
- if (!inside(root, target))
24431
- 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` }] });
24432
- let ancestor = target;
24433
- while (!fs7.existsSync(ancestor) && ancestor !== root)
24434
- ancestor = path10.dirname(ancestor);
24435
- try {
24436
- if (!inside(root, fs7.realpathSync(ancestor)))
24437
- 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` }] });
24438
- } catch {
24439
- return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
24440
- }
24441
- roots.push(target);
24442
- }
24443
- return success(null, null, roots);
24444
- };
24445
- function captureCandidate(root, scope, environment = []) {
24446
- let checkout;
24447
- try {
24448
- checkout = fs7.realpathSync(root);
24449
- } catch {
24450
- return failure("invalid_input", "candidate root does not exist");
24451
- }
24452
- const normalizedScope = canonicalScope(scope);
24453
- if (!normalizedScope)
24454
- 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" }] });
24455
- const roots = scopeRoots(checkout, normalizedScope);
24456
- if (!roots.ok)
24457
- return roots;
24458
- const git = gitPaths(checkout);
24459
- const names = new Set;
24460
- let uncertain = git.uncertain;
24461
- for (const target of roots.data) {
24462
- const item = relative(checkout, target);
24463
- if (item === ".git" || item === ".workit")
24464
- continue;
24465
- names.add(item);
24466
- if (!fs7.existsSync(target))
24467
- continue;
24468
- let stat;
24469
- try {
24470
- stat = fs7.lstatSync(target);
24471
- } catch {
24472
- uncertain = true;
24473
- continue;
24474
- }
24475
- if (!git.git && stat.isDirectory() && !stat.isSymbolicLink())
24476
- uncertain = walk(checkout, target, names) || uncertain;
24477
- }
24478
- for (const item of git.paths)
24479
- if (scopeMatches(item, normalizedScope))
24480
- names.add(item);
24481
- const files = [];
24482
- let completeness = uncertain ? "uncertain" : "known";
24483
- for (const item of [...names].sort()) {
24484
- if (!scopeMatches(item, normalizedScope))
24485
- continue;
24486
- const target = path10.join(checkout, item);
24487
- try {
24488
- const stat = fs7.lstatSync(target);
24489
- if (stat.isSymbolicLink()) {
24490
- const link = fs7.readlinkSync(target);
24491
- files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
24492
- } else if (stat.isFile()) {
24493
- files.push({
24494
- path: item,
24495
- kind: "file",
24496
- digest: digestBytes3(fs7.readFileSync(target)),
24497
- executable: (stat.mode & 73) !== 0
24498
- });
24499
- } else if (!stat.isDirectory()) {
24500
- completeness = "uncertain";
24501
- } else if (git.git && git.paths.includes(item)) {
24502
- completeness = "uncertain";
24503
- }
24504
- } catch (error) {
24505
- if (error?.code === "ENOENT") {
24506
- files.push({ path: item, kind: "absent", digest: null, executable: null });
24507
- } else
24508
- completeness = "uncertain";
24509
- }
24510
- }
24511
- 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) => ({
24482
+ var stableUnique = (values) => [...new Set(values)].sort(compareCodeUnits2);
24483
+ var stableList = (values) => [...values].sort((left, right) => compareCodeUnits2(canonicalJson(left), canonicalJson(right)));
24484
+ var normalizedScope = (scope) => ({
24485
+ description: scope.description,
24486
+ paths: stableUnique(scope.paths),
24487
+ exclusions: stableUnique(scope.exclusions)
24488
+ });
24489
+ var normalizedRefList = (values) => stableList(values);
24490
+ var normalizeAssessment = (assessment) => ({
24491
+ facts: stableList(assessment.facts.map((fact) => ({ ...fact, refs: normalizedRefList(fact.refs) }))),
24492
+ signals: Object.fromEntries(Object.entries(assessment.signals).map(([name, signal]) => [
24512
24493
  name,
24513
- value: environment[name] ?? null
24514
- }));
24515
- const namesByEnvironment = supplied.map(({ name }) => name);
24516
- if (new Set(namesByEnvironment).size !== namesByEnvironment.length || namesByEnvironment.some((name) => typeof name !== "string" || !name))
24517
- return failure("invalid_input", "candidate environment names must be unique and non-empty");
24518
- const values = supplied.sort((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
24519
- if (values.some(({ value }) => value === null))
24520
- completeness = "uncertain";
24521
- const headResult = spawnSync2("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
24522
- if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
24523
- completeness = "uncertain";
24524
- const head = headResult.status === 0 ? headResult.stdout.trim() : null;
24525
- const partial = {
24526
- id: "0".repeat(64),
24527
- scope: normalizedScope,
24528
- completeness,
24529
- files,
24530
- environment: values,
24531
- head
24494
+ { ...signal, refs: normalizedRefList(signal.refs) }
24495
+ ])),
24496
+ consequences: stableList(assessment.consequences.map((consequence) => ({
24497
+ ...consequence,
24498
+ fact: { ...consequence.fact, refs: normalizedRefList(consequence.fact.refs) }
24499
+ }))),
24500
+ verification: stableList(assessment.verification.map((verification) => ({
24501
+ ...verification,
24502
+ scope: normalizedScope(verification.scope),
24503
+ availableChecks: normalizedRefList(verification.availableChecks),
24504
+ gaps: stableUnique(verification.gaps)
24505
+ })))
24506
+ });
24507
+ var normalizeConstraint = (constraint) => ({
24508
+ ...constraint,
24509
+ source: constraint.source,
24510
+ requires: stableList(constraint.requires.map((requirement) => ({
24511
+ ...requirement,
24512
+ scope: normalizedScope(requirement.scope),
24513
+ refs: normalizedRefList(requirement.refs)
24514
+ })))
24515
+ });
24516
+ var normalizeDecision = (decision) => ({
24517
+ ...decision,
24518
+ binding: {
24519
+ ...decision.binding,
24520
+ scope: normalizedScope(decision.binding.scope),
24521
+ contentRefs: normalizedRefList(decision.binding.contentRefs)
24522
+ },
24523
+ requirementIds: stableUnique(decision.requirementIds)
24524
+ });
24525
+ var normalizeIntent = (intent) => ({
24526
+ ...intent,
24527
+ scope: normalizedScope(intent.scope),
24528
+ authorityRefs: normalizedRefList(intent.authorityRefs)
24529
+ });
24530
+ var normalizePrior = (prior) => ({
24531
+ decisions: stableList(prior.decisions.map(normalizeDecision)),
24532
+ findings: stableList(prior.findings.map((finding) => ({
24533
+ ...finding,
24534
+ scope: normalizedScope(finding.scope),
24535
+ refs: normalizedRefList(finding.refs),
24536
+ resolution: finding.resolution ? {
24537
+ ...finding.resolution,
24538
+ evidenceIds: stableUnique(finding.resolution.evidenceIds),
24539
+ decisionIds: stableUnique(finding.resolution.decisionIds)
24540
+ } : null
24541
+ }))),
24542
+ requirements: stableList(prior.requirements.map((requirement) => ({
24543
+ ...requirement,
24544
+ scope: normalizedScope(requirement.scope)
24545
+ })))
24546
+ });
24547
+ var invalidFields = (error) => ({
24548
+ fields: error.issues.map((issue) => ({
24549
+ path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
24550
+ reason: issue.message
24551
+ }))
24552
+ });
24553
+ function normalizeResolverInput(input) {
24554
+ const parsed = resolverInputSchema.safeParse(input);
24555
+ if (!parsed.success)
24556
+ return failure("invalid_input", "resolver input is invalid", invalidFields(parsed.error));
24557
+ const value = parsed.data;
24558
+ const signals = value.assessment.signals;
24559
+ if (signals.behaviorChange.value === true && signals.mechanicalLowRisk.value === true)
24560
+ return failure("invalid_input", "behavioral change cannot be wholly mechanical", {
24561
+ fields: [
24562
+ {
24563
+ path: "assessment.signals",
24564
+ reason: "contradictory behaviorChange and mechanicalLowRisk"
24565
+ }
24566
+ ]
24567
+ });
24568
+ const ids = value.constraints.map((constraint) => constraint.id);
24569
+ if (new Set(ids).size !== ids.length)
24570
+ return failure("invalid_input", "constraint IDs must be unique", {
24571
+ fields: [{ path: "constraints", reason: "duplicate constraint id" }]
24572
+ });
24573
+ if (value.preferences?.fast && value.preferences.thorough)
24574
+ return failure("invalid_input", "fast and thorough preferences contradict", {
24575
+ fields: [{ path: "preferences", reason: "fast and thorough cannot both be enabled" }]
24576
+ });
24577
+ const normalized = {
24578
+ intent: normalizeIntent(value.intent),
24579
+ assessment: normalizeAssessment(value.assessment),
24580
+ constraints: stableList(value.constraints.map(normalizeConstraint)),
24581
+ prior: normalizePrior(value.prior),
24582
+ ...value.preferences ? { preferences: { ...value.preferences } } : {},
24583
+ policyVersion: POLICY_VERSION
24532
24584
  };
24533
- const candidate = { ...partial, id: candidateDigest(partial) };
24534
- const parsed = candidateSchema.safeParse(candidate);
24535
- return parsed.success ? success(null, null, parsed.data) : failure("invalid_input", "captured candidate is invalid");
24585
+ return success(null, null, normalized);
24536
24586
  }
24537
- var fileMap = (candidate) => new Map(candidate.files.map((file) => [file.path, file]));
24538
- var changedPaths = (before, after) => {
24539
- const paths = new Set([...before.files, ...after.files].map((file) => file.path));
24540
- const left = fileMap(before);
24541
- const right = fileMap(after);
24542
- const changed = [...paths].filter((item) => JSON.stringify(left.get(item) ?? null) !== JSON.stringify(right.get(item) ?? null));
24543
- if (before.head !== after.head)
24544
- changed.push(".");
24545
- if (JSON.stringify(before.environment) !== JSON.stringify(after.environment))
24546
- changed.push(".");
24547
- return changed;
24548
- };
24549
- var sessionFromRef = (value) => {
24550
- if (typeof value !== "object" || value === null || value.kind !== "host")
24551
- return null;
24552
- return typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
24553
- };
24554
- var sameSession = (left, right) => {
24555
- const other = sessionFromRef(right);
24556
- return left !== null && other !== null && left.host === other.host && left.handle === other.handle;
24557
- };
24558
- var evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope);
24559
- function evaluateEvidence(task, candidate) {
24560
- const current = candidate ?? task.candidates.at(-1) ?? null;
24561
- return task.evidence.map((entry) => {
24562
- const evidence = entry.data;
24563
- if (evidence.result === "failed")
24564
- return {
24565
- evidenceId: entry.id,
24566
- status: "failed",
24567
- reason: "failed evidence remains historical"
24568
- };
24569
- if (evidence.kind === "check" || evidence.kind === "review") {
24570
- if (!evidence.beforeCandidateId || !evidence.candidateId)
24571
- return {
24572
- evidenceId: entry.id,
24573
- status: "stale",
24574
- reason: "check or review evidence lacks start and end candidates"
24575
- };
24576
- if (evidence.beforeCandidateId !== evidence.candidateId)
24577
- return {
24578
- evidenceId: entry.id,
24579
- status: "stale",
24580
- reason: "candidate changed during the check or review"
24581
- };
24587
+ var requirement = (spec) => ({
24588
+ ...spec,
24589
+ id: requirementId({
24590
+ ruleId: spec.ruleId,
24591
+ scope: spec.scope,
24592
+ satisfaction: spec.satisfaction,
24593
+ before: spec.before,
24594
+ dependentAction: spec.dependentAction
24595
+ })
24596
+ });
24597
+ var signalReason = (input, name) => input.assessment.signals[name].reason;
24598
+ function resolveRequirements(input) {
24599
+ const { signals } = input.assessment;
24600
+ const scope = input.intent.scope;
24601
+ const requirements = [];
24602
+ const hasUnknownSignal = Object.values(signals).some((signal) => signal.value === "unknown");
24603
+ const hasUnknownFact = [
24604
+ ...input.assessment.facts,
24605
+ ...input.assessment.consequences.map(({ fact }) => fact)
24606
+ ].some((fact) => fact.basis === "unknown");
24607
+ if (hasUnknownSignal || hasUnknownFact)
24608
+ requirements.push(requirement({
24609
+ ruleId: "consequential-unknown",
24610
+ dimension: "investigation",
24611
+ scope,
24612
+ reason: `Resolve consequential uncertainty before the dependent action: ${[
24613
+ ...Object.entries(signals).filter(([, signal]) => signal.value === "unknown").map(([name]) => name),
24614
+ ...hasUnknownFact ? ["unknown fact"] : []
24615
+ ].join(", ")}`,
24616
+ satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
24617
+ before: "dependent_action",
24618
+ dependentAction: null,
24619
+ acceptanceAllowed: false
24620
+ }));
24621
+ if (signals.productChoiceOpen.value === true)
24622
+ requirements.push(requirement({
24623
+ ruleId: "product-decision",
24624
+ dimension: "decisions",
24625
+ scope,
24626
+ reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
24627
+ satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
24628
+ before: "dependent_action",
24629
+ dependentAction: null,
24630
+ acceptanceAllowed: false
24631
+ }));
24632
+ const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
24633
+ if (signals.behaviorChange.value === true)
24634
+ requirements.push(requirement({
24635
+ ruleId: "behavioral-verification",
24636
+ dimension: "testing",
24637
+ scope,
24638
+ reason: `Behavior, side effects, permissions, or data handling may change: ${signalReason(input, "behaviorChange")}`,
24639
+ satisfaction: "A useful behavioral check passes against the affected behavior and current candidate.",
24640
+ before: "close",
24641
+ dependentAction: null,
24642
+ acceptanceAllowed: false
24643
+ }), requirement({
24644
+ ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
24645
+ dimension: "review",
24646
+ scope,
24647
+ 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.",
24648
+ 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.",
24649
+ before: "close",
24650
+ dependentAction: null,
24651
+ acceptanceAllowed: !independentBehaviorReview
24652
+ }));
24653
+ else if (signals.mechanicalLowRisk.value === true)
24654
+ requirements.push(requirement({
24655
+ ruleId: "mechanical-existing-checks",
24656
+ dimension: "verification",
24657
+ scope,
24658
+ reason: `The change is demonstrably mechanical and low-risk: ${signalReason(input, "mechanicalLowRisk")}`,
24659
+ satisfaction: "Run the relevant existing checks for affected consumers; do not add tautological tests.",
24660
+ before: "close",
24661
+ dependentAction: null,
24662
+ acceptanceAllowed: true
24663
+ }), requirement({
24664
+ ruleId: input.preferences?.thorough ? "fresh-context-review" : "self-review",
24665
+ dimension: "review",
24666
+ scope,
24667
+ 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.",
24668
+ 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.",
24669
+ before: "close",
24670
+ dependentAction: null,
24671
+ acceptanceAllowed: !input.preferences?.thorough
24672
+ }));
24673
+ if (signals.behaviorChange.value === true || signals.mechanicalLowRisk.value === true)
24674
+ requirements.push(requirement({
24675
+ ruleId: "pre-pr-cleanup",
24676
+ dimension: "verification",
24677
+ scope,
24678
+ reason: "Implementation output must be deslopped before it is delivered.",
24679
+ satisfaction: "Run the workit-deslop pass and record passing check evidence, or record an approved limitation decision waiving cleanup.",
24680
+ before: "dependent_action",
24681
+ dependentAction: "hosting.pull_request",
24682
+ acceptanceAllowed: true
24683
+ }));
24684
+ if (signals.durableAgreementNeeded.value === true)
24685
+ requirements.push(requirement({
24686
+ ruleId: "durable-spec",
24687
+ dimension: "artifacts",
24688
+ scope,
24689
+ reason: `The work needs a durable agreement about intended behavior: ${signalReason(input, "durableAgreementNeeded")}`,
24690
+ satisfaction: "Record the agreed behavior in a durable specification before implementation writes.",
24691
+ before: "write",
24692
+ dependentAction: null,
24693
+ acceptanceAllowed: true
24694
+ }));
24695
+ if (signals.coordinationPlanNeeded.value === true)
24696
+ requirements.push(requirement({
24697
+ ruleId: "coordination-plan",
24698
+ dimension: "continuity",
24699
+ scope,
24700
+ reason: `Dependencies, sequencing, or resumption need durable coordination: ${signalReason(input, "coordinationPlanNeeded")}`,
24701
+ satisfaction: "Record useful sequencing, dependencies, and the next action in a compact plan.",
24702
+ before: "dependent_action",
24703
+ dependentAction: null,
24704
+ acceptanceAllowed: true
24705
+ }));
24706
+ if (signals.helperUseful.value === true && !input.preferences?.fast)
24707
+ requirements.push(requirement({
24708
+ ruleId: "helper-usefulness",
24709
+ dimension: "delegation",
24710
+ scope,
24711
+ reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
24712
+ satisfaction: "Use a bounded helper only for the independent objective and reconcile its result.",
24713
+ before: "dependent_action",
24714
+ dependentAction: null,
24715
+ acceptanceAllowed: true
24716
+ }));
24717
+ for (const constraint of input.constraints)
24718
+ for (const obligation of constraint.requires) {
24719
+ const dimension = obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge");
24720
+ requirements.push(requirement({
24721
+ ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
24722
+ kind: obligation.kind,
24723
+ scope: normalizedScope(obligation.scope),
24724
+ refs: normalizedRefList(obligation.refs),
24725
+ method: obligation.method,
24726
+ before: obligation.before,
24727
+ dependentAction: obligation.dependentAction
24728
+ })}`,
24729
+ dimension,
24730
+ scope: obligation.scope,
24731
+ reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
24732
+ satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
24733
+ before: obligation.before,
24734
+ dependentAction: obligation.dependentAction,
24735
+ acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
24736
+ }));
24582
24737
  }
24583
- if (!evidence.candidateId || !current)
24584
- return {
24585
- evidenceId: entry.id,
24586
- status: evidence.result,
24587
- reason: "evidence has no candidate binding"
24588
- };
24589
- const observed = task.candidates.find((item) => item.id === evidence.candidateId) ?? (current.id === evidence.candidateId ? current : undefined);
24590
- if (!observed || observed.completeness === "uncertain" || current.completeness === "uncertain")
24591
- return { evidenceId: entry.id, status: "stale", reason: "candidate is missing or uncertain" };
24592
- if (observed.id === current.id)
24593
- return { evidenceId: entry.id, status: evidence.result, reason: "candidate is unchanged" };
24594
- const scopes = evidenceScopes(task, evidence.requirementIds);
24595
- const changed = changedPaths(observed, current);
24596
- const environmentChanged = JSON.stringify(observed.environment) !== JSON.stringify(current.environment);
24597
- if (!scopes.length || environmentChanged || changed.some((item) => scopes.some((scope) => pathRelevant(item, scope))))
24598
- return { evidenceId: entry.id, status: "stale", reason: "relevant candidate state changed" };
24599
- return {
24600
- evidenceId: entry.id,
24601
- status: evidence.result,
24602
- reason: "candidate changed outside evidence scope"
24603
- };
24738
+ const seen = new Set;
24739
+ return requirements.filter((item) => {
24740
+ if (seen.has(item.id))
24741
+ return false;
24742
+ seen.add(item.id);
24743
+ return true;
24604
24744
  });
24605
24745
  }
24606
- var scopeCovers2 = scopeCovers;
24607
- var checkPin = (assignment, beforeCandidateId, candidateId) => {
24608
- const pin = assignment.candidateId ?? null;
24609
- if (pin === null)
24610
- return true;
24611
- return (beforeCandidateId ?? null) === pin || (candidateId ?? null) === pin;
24612
- };
24613
- var resolveBinding = (evidence, pin, currentCandidateId) => {
24614
- const bound = evidence.kind === "check" || evidence.kind === "review";
24615
- const fallback = bound ? pin ?? currentCandidateId : null;
24616
- return {
24617
- beforeCandidateId: evidence.beforeCandidateId ?? fallback,
24618
- candidateId: evidence.candidateId ?? fallback
24746
+ function resolvePolicy(input) {
24747
+ const normalized = normalizeResolverInput(input);
24748
+ if (!normalized.ok)
24749
+ return normalized;
24750
+ const requirements = resolveRequirements(normalized.data);
24751
+ const policy = {
24752
+ policyVersion: POLICY_VERSION,
24753
+ inputDigest: sha256(canonicalJson(normalized.data)),
24754
+ requirements
24619
24755
  };
24620
- };
24621
- var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId != null);
24622
- 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")));
24623
- 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 }));
24624
- var evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
24625
- if (kind !== "passed")
24626
- return false;
24627
- if (dimension === "testing" || dimension === "verification")
24628
- return evidenceKind === "check" || ruleId === "pre-pr-cleanup" && evidenceKind === "artifact";
24629
- if (dimension === "review")
24630
- return evidenceKind === "review";
24631
- if (dimension === "investigation" || dimension === "challenge")
24632
- return evidenceKind === "investigation";
24633
- if (dimension === "artifacts" || dimension === "continuity")
24634
- return evidenceKind === "artifact";
24635
- return false;
24636
- };
24637
- var expectedKinds = (dimension, ruleId) => {
24638
- if (dimension === "testing")
24639
- return "check";
24640
- if (dimension === "verification")
24641
- return ruleId === "pre-pr-cleanup" ? "check or artifact" : "check";
24642
- if (dimension === "review")
24643
- return "review";
24644
- if (dimension === "investigation" || dimension === "challenge")
24645
- return "investigation";
24646
- if (dimension === "artifacts" || dimension === "continuity")
24647
- return "artifact";
24648
- if (dimension === "decisions")
24649
- return "decision";
24650
- return "worker report";
24651
- };
24652
- function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller) {
24653
- const evidence = evaluateEvidence(task, candidate);
24654
- if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
24655
- return task.policy.requirements.map((requirement) => ({
24656
- requirementId: requirement.id,
24657
- status: "unsatisfied",
24658
- evidenceIds: [],
24659
- decisionIds: [],
24660
- reason: "stored policy version is unsupported; reassessment is required"
24661
- }));
24662
- return (task.policy?.requirements ?? []).map((requirement) => {
24663
- const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
24664
- const passed = related.filter(({ entry, evaluation }) => {
24665
- if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
24666
- return false;
24667
- if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
24668
- return false;
24669
- if (requirement.dimension !== "review")
24670
- return true;
24671
- const reviewSession = sessionFromRef(entry.data.reviewContext);
24672
- const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
24673
- 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));
24674
- return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
24675
- });
24676
- if (passed.length) {
24677
- const currentId = candidate?.id ?? null;
24678
- 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;
24679
- if (withRed.length)
24680
- return {
24681
- requirementId: requirement.id,
24682
- status: "satisfied",
24683
- evidenceIds: withRed.map(({ entry }) => entry.id),
24684
- decisionIds: [],
24685
- reason: "fresh applicable evidence passed"
24686
- };
24687
- if (requirement.dimension === "testing")
24688
- return {
24689
- requirementId: requirement.id,
24690
- status: "unsatisfied",
24691
- evidenceIds: [],
24692
- decisionIds: [],
24693
- reason: "testing requirement has GREEN evidence but no preceding RED"
24694
- };
24695
- }
24696
- if (requirement.dimension === "delegation") {
24697
- const dot = (scope) => scope.paths.length > 0 ? scope : { ...scope, paths: ["."] };
24698
- 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))));
24699
- if (reconciled)
24700
- return {
24701
- requirementId: requirement.id,
24702
- status: "satisfied",
24703
- evidenceIds: [],
24704
- decisionIds: [],
24705
- reason: "bounded helper completed and its result is recorded"
24706
- };
24707
- }
24708
- const decisions = requirement.dimension === "decisions" ? applicableRequirementDecision(task, workspace, requirement, checkoutRoot) : [];
24709
- if (decisions.length)
24710
- return {
24711
- requirementId: requirement.id,
24712
- status: "satisfied",
24713
- evidenceIds: [],
24714
- decisionIds: decisions.map((decision) => decision.id),
24715
- reason: "an applicable approved or stated decision satisfies the requirement"
24716
- };
24717
- const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
24718
- if (limitations.length)
24719
- return {
24720
- requirementId: requirement.id,
24721
- status: "accepted_limitation",
24722
- evidenceIds: [],
24723
- decisionIds: limitations.map((decision) => task.decisions.find((entry) => entry.data === decision).id),
24724
- reason: "an applicable approved limitation permits the missing evidence"
24725
- };
24726
- const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
24727
- if (!unavailable && related.length > 0 && !passed.length)
24728
- return {
24729
- requirementId: requirement.id,
24730
- status: "unsatisfied",
24731
- evidenceIds: related.map(({ entry }) => entry.id),
24732
- decisionIds: [],
24733
- reason: `no passing evidence (needs kind:${expectedKinds(requirement.dimension, requirement.ruleId)})`
24734
- };
24756
+ const parsed = policySchema.safeParse(policy);
24757
+ if (!parsed.success)
24758
+ return failure("invalid_input", "resolved policy is invalid", invalidFields(parsed.error));
24759
+ return success(null, null, parsed.data);
24760
+ }
24761
+ function diffPolicy(previous, next, reason, now) {
24762
+ if (!policySchema.safeParse(previous).success && previous !== null)
24763
+ throw new TypeError("previous policy is invalid");
24764
+ if (!policySchema.safeParse(next).success)
24765
+ throw new TypeError("next policy is invalid");
24766
+ const oldIds = new Set(previous?.requirements.map((requirement) => requirement.id) ?? []);
24767
+ const newIds = new Set(next.requirements.map((requirement) => requirement.id));
24768
+ const added = [...newIds].filter((id) => !oldIds.has(id)).sort(compareCodeUnits2);
24769
+ const retired = [...oldIds].filter((id) => !newIds.has(id)).sort(compareCodeUnits2);
24770
+ if (added.length === 0 && retired.length === 0)
24771
+ return null;
24772
+ const normalizedReason = reason.trim();
24773
+ const changeReason = retired.length ? `${normalizedReason || "policy reassessed"} (retired: ${retired.join(",")})` : normalizedReason || "policy reassessed";
24774
+ const change = {
24775
+ recordedAt: now,
24776
+ fromInputDigest: previous?.inputDigest ?? null,
24777
+ toInputDigest: next.inputDigest,
24778
+ added,
24779
+ retired,
24780
+ reason: changeReason
24781
+ };
24782
+ const parsed = policyChangeSchema.safeParse(change);
24783
+ if (!parsed.success)
24784
+ throw new TypeError("policy change is invalid");
24785
+ return parsed.data;
24786
+ }
24787
+
24788
+ // packages/workit-core/src/core/task-evaluation.ts
24789
+ import { createHash as createHash6 } from "node:crypto";
24790
+ import * as fs7 from "node:fs";
24791
+ import { spawnSync as spawnSync2 } from "node:child_process";
24792
+ import path10 from "node:path";
24793
+ var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex");
24794
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`);
24795
+ var relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".";
24796
+ var canonicalPath = (value) => {
24797
+ if (typeof value !== "string" || !value)
24798
+ return null;
24799
+ if (value.includes("\\") || value.split("/").includes(".."))
24800
+ return null;
24801
+ const normalized = path10.posix.normalize(value);
24802
+ if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
24803
+ return null;
24804
+ return normalized === "." ? "." : normalized.replace(/\/$/, "");
24805
+ };
24806
+ var canonicalScope = (scope) => {
24807
+ const paths = scope.paths.length ? scope.paths : ["."];
24808
+ const normalizedPaths = paths.map(canonicalPath);
24809
+ const normalizedExclusions = scope.exclusions.map(canonicalPath);
24810
+ if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
24811
+ return null;
24812
+ return {
24813
+ description: scope.description,
24814
+ paths: [...new Set(normalizedPaths)],
24815
+ exclusions: [...new Set(normalizedExclusions)]
24816
+ };
24817
+ };
24818
+ var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
24819
+ var scopeMatches = (value, scope) => {
24820
+ const normalized = canonicalScope(scope);
24821
+ if (!normalized)
24822
+ return false;
24823
+ const paths = normalized.paths;
24824
+ return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
24825
+ };
24826
+ var pathRelevant = (value, scope) => scopeMatches(value, scope);
24827
+ var compareCodeUnits3 = (left, right) => {
24828
+ const length = Math.min(left.length, right.length);
24829
+ for (let index = 0;index < length; index += 1) {
24830
+ const difference = left.charCodeAt(index) - right.charCodeAt(index);
24831
+ if (difference !== 0)
24832
+ return difference;
24833
+ }
24834
+ return left.length - right.length;
24835
+ };
24836
+ var gitPaths = (root) => {
24837
+ const result = spawnSync2("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
24838
+ cwd: root,
24839
+ encoding: "buffer"
24840
+ });
24841
+ if (result.status !== 0) {
24842
+ const stderr = result.stderr?.toString("utf8") ?? "";
24735
24843
  return {
24736
- requirementId: requirement.id,
24737
- status: unavailable ? "unavailable" : "unsatisfied",
24738
- evidenceIds: related.map(({ entry }) => entry.id),
24739
- decisionIds: [],
24740
- reason: unavailable ? "required capability is unavailable" : "no fresh passing evidence"
24844
+ paths: [],
24845
+ uncertain: !/not a git repository/i.test(stderr),
24846
+ git: /not a git repository/i.test(stderr) ? false : true
24741
24847
  };
24742
- });
24743
- }
24744
- function evaluateClosure(requestedOutcome, view) {
24745
- if (requestedOutcome !== "stopped" && !view.task.policy)
24746
- return failure("requirements_unsatisfied", "task has not been assessed");
24747
- if (view.task.policy && view.task.policy.policyVersion !== POLICY_VERSION)
24748
- return failure("requirements_unsatisfied", "stored policy version is unsupported; reassessment is required");
24749
- const evaluations = view.requirements;
24750
- const openFindings = view.task.findings.filter((entry) => entry.data.disposition === "open");
24751
- if (requestedOutcome !== "stopped" && openFindings.length)
24752
- return failure("requirements_unsatisfied", "open findings must be resolved before closure");
24753
- if (requestedOutcome !== "stopped") {
24754
- const evidenceById = new Map(view.evidence.map((entry) => [entry.evidenceId, entry.status]));
24755
- for (const entry of view.task.findings) {
24756
- if (entry.data.disposition === "fixed") {
24757
- const verified = entry.data.resolution?.evidenceIds.some((id) => {
24758
- const evidence = view.task.evidence.find((item) => item.id === id);
24759
- if (!evidence)
24760
- return false;
24761
- return findingVerificationPasses(entry.data.candidateId, {
24762
- kind: evidence.data.kind,
24763
- candidateId: evidence.data.candidateId,
24764
- status: evidenceById.get(id) ?? "missing"
24765
- });
24766
- });
24767
- if (!verified)
24768
- return failure("requirements_unsatisfied", "fixed findings require current verification");
24769
- }
24770
- if (entry.data.disposition === "deferred") {
24771
- const valid = entry.data.resolution?.decisionIds.some((id) => {
24772
- const decisionEntry = view.task.decisions.find((item) => item.id === id);
24773
- const decision = decisionEntry?.data;
24774
- 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)));
24775
- });
24776
- if (!valid)
24777
- return failure("requirements_unsatisfied", "deferred findings require an approved limitation decision that references an acceptanceAllowed requirement and covers the finding scope");
24778
- }
24848
+ }
24849
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
24850
+ const stagedDeleted = spawnSync2("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
24851
+ if (stagedDeleted.status !== 0) {
24852
+ const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
24853
+ if (!/not a git repository/i.test(stderr))
24854
+ return { paths, uncertain: true, git: true };
24855
+ } else if (stagedDeleted.stdout) {
24856
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")));
24857
+ }
24858
+ return { paths: [...new Set(paths)], uncertain: false, git: true };
24859
+ };
24860
+ var walk = (root, directory, output) => {
24861
+ let names;
24862
+ try {
24863
+ names = fs7.readdirSync(directory);
24864
+ } catch {
24865
+ return true;
24866
+ }
24867
+ let uncertain = false;
24868
+ for (const name of names) {
24869
+ if (name === ".git" || name === ".workit")
24870
+ continue;
24871
+ const target = path10.join(directory, name);
24872
+ let stat;
24873
+ try {
24874
+ stat = fs7.lstatSync(target);
24875
+ } catch {
24876
+ uncertain = true;
24877
+ continue;
24779
24878
  }
24879
+ const item = relative(root, target);
24880
+ output.add(item);
24881
+ if (stat.isDirectory() && !stat.isSymbolicLink())
24882
+ uncertain = walk(root, target, output) || uncertain;
24780
24883
  }
24781
- const blocking = evaluations.filter((item) => {
24782
- if (item.status !== "unsatisfied" && item.status !== "unavailable")
24783
- return false;
24784
- const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
24785
- return !requirement || requirement.before === "close";
24786
- });
24787
- if (requestedOutcome !== "stopped" && blocking.length)
24788
- return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
24789
- requirementIds: blocking.map((item) => item.requirementId),
24790
- requirements: blocking.map((item) => {
24791
- const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
24792
- return {
24793
- requirementId: item.requirementId,
24794
- ruleId: requirement?.ruleId ?? "unknown",
24795
- reason: item.reason,
24796
- satisfaction: requirement?.satisfaction ?? "",
24797
- dependentAction: requirement?.dependentAction ?? null
24798
- };
24799
- })
24800
- });
24801
- const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
24802
- if (requestedOutcome === "verified" && accepted.length)
24803
- return failure("requirements_unsatisfied", "accepted limitations cannot be reported as verified", {
24804
- requirementIds: accepted.map((item) => item.requirementId)
24805
- });
24806
- const evidenceIds = [
24807
- ...new Set([
24808
- ...evaluations.flatMap((item) => item.evidenceIds),
24809
- ...view.task.findings.flatMap((item) => item.data.resolution?.evidenceIds ?? [])
24810
- ])
24811
- ];
24812
- const decisionIds = [
24813
- ...new Set([
24814
- ...evaluations.flatMap((item) => item.decisionIds),
24815
- ...view.task.findings.flatMap((item) => item.data.resolution?.decisionIds ?? [])
24816
- ])
24817
- ];
24818
- return success(null, null, {
24819
- outcome: requestedOutcome === "stopped" ? "stopped" : accepted.length ? "accepted_limitations" : "verified",
24820
- evidenceIds,
24821
- decisionIds,
24822
- requirementIds: evaluations.map((item) => item.requirementId)
24823
- });
24824
- }
24825
-
24826
- // packages/workit-core/src/core/task-context.ts
24827
- function reconcileResume(view, observations = []) {
24828
- if (observations.length > 0)
24829
- return failure("permission_denied", "worker observations require native host verification");
24830
- const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
24831
- if (!candidate.ok)
24832
- return candidate;
24833
- const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
24834
- const blockers = [...view.task.progress.blockers];
24835
- if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
24836
- blockers.push({
24837
- reason: "worker state requires reconciliation",
24838
- dependentAction: "resume",
24839
- refs: []
24840
- });
24841
- return success(null, null, {
24842
- candidate: candidate.data,
24843
- staleEvidenceIds,
24844
- workerUpdates: [],
24845
- blockers,
24846
- reassessmentRequired: view.task.policy?.policyVersion !== POLICY_VERSION
24847
- });
24848
- }
24849
- var exportDigest = (bundle) => sha256({
24850
- schemaVersion: bundle.schemaVersion,
24851
- exportedAt: bundle.exportedAt,
24852
- sourceWorkspaceId: bundle.sourceWorkspaceId,
24853
- task: bundle.task
24854
- });
24855
- // packages/workit-core/src/core/external-action.ts
24856
- var chainStepSchema = union3([
24857
- string5().min(1),
24858
- object4({ branch: string5().min(1) }).strict(),
24859
- object4({ pr: literal3(true) }).strict()
24860
- ]);
24861
- var externalActionSchema = discriminatedUnion2("operation", [
24862
- object4({
24863
- operation: literal3("git.branch_setup"),
24864
- payload: object4({
24865
- action: _enum2(["setup", "reapply_stash"]).optional(),
24866
- sdd_dir: string5().optional(),
24867
- target_branch: string5().optional(),
24868
- stash: _enum2(["yes", "no"]).optional(),
24869
- cwd: string5().min(1).optional()
24870
- }).strict()
24871
- }).strict(),
24872
- object4({
24873
- operation: literal3("git.commit"),
24874
- payload: object4({
24875
- message: string5().min(1).optional(),
24876
- plan_steps: array3(chainStepSchema).min(1).max(32).optional(),
24877
- plan_branch: string5().min(1).optional(),
24878
- cwd: string5().min(1).optional()
24879
- }).strict()
24880
- }).strict(),
24881
- object4({
24882
- operation: literal3("git.push"),
24883
- payload: object4({ branch: string5().min(1).optional(), cwd: string5().min(1).optional() }).strict()
24884
- }).strict(),
24885
- object4({
24886
- operation: literal3("hosting.pull_request"),
24887
- payload: object4({
24888
- title: string5().min(1),
24889
- body: string5().optional(),
24890
- draft: boolean5().optional(),
24891
- target_branch: string5().optional(),
24892
- babysit: boolean5().optional(),
24893
- cwd: string5().min(1).optional()
24894
- }).strict()
24895
- }).strict(),
24896
- object4({
24897
- operation: literal3("hosting.merge"),
24898
- payload: object4({
24899
- target_branch: string5().min(1).optional(),
24900
- source_branch: string5().min(1).optional(),
24901
- cwd: string5().min(1).optional()
24902
- }).strict()
24903
- }).strict(),
24904
- object4({
24905
- operation: literal3("hosting.delete_branch"),
24906
- payload: object4({ branch: string5().min(1), cwd: string5().min(1).optional() }).strict()
24907
- }).strict(),
24908
- object4({
24909
- operation: literal3("youtrack.update"),
24910
- payload: object4({
24911
- issueId: string5().min(1),
24912
- markdown: string5().min(1),
24913
- minutes: number5().positive().optional()
24914
- }).strict()
24915
- }).strict(),
24916
- object4({
24917
- operation: literal3("youtrack.time"),
24918
- payload: object4({
24919
- issueId: string5().min(1),
24920
- minutes: number5().positive(),
24921
- text: string5().optional(),
24922
- dateMs: number5().finite().optional()
24923
- }).strict()
24924
- }).strict(),
24925
- object4({
24926
- operation: literal3("youtrack.meeting"),
24927
- payload: object4({
24928
- issueId: string5().min(1),
24929
- minutes: number5().positive(),
24930
- text: string5().min(1)
24931
- }).strict()
24932
- }).strict(),
24933
- object4({
24934
- operation: literal3("changelog.apply"),
24935
- payload: object4({
24936
- entries: union3([
24937
- record3(string5(), array3(string5())),
24938
- array3(object4({ category: string5().min(1), text: string5().min(1) }).strict())
24939
- ]).optional(),
24940
- path: string5().optional(),
24941
- normalize_only: boolean5().optional()
24942
- }).strict()
24943
- }).strict(),
24944
- object4({
24945
- operation: literal3("context.read"),
24946
- payload: object4({
24947
- kind: _enum2([
24948
- "git",
24949
- "pr",
24950
- "youtrack",
24951
- "github_issue",
24952
- "gitlab_issue",
24953
- "changelog",
24954
- "release",
24955
- "affected"
24956
- ]),
24957
- range: string5().optional(),
24958
- issueId: string5().optional(),
24959
- issueUrl: string5().optional(),
24960
- issueRef: string5().optional(),
24961
- mode: string5().optional(),
24962
- specPath: string5().optional(),
24963
- planPath: string5().optional(),
24964
- cwd: string5().min(1).optional()
24965
- }).strict()
24966
- }).strict()
24967
- ]);
24968
- // packages/workit-core/src/core/task-engine.ts
24969
- import { realpathSync as realpathSync6 } from "node:fs";
24970
-
24971
- // packages/workit-core/src/core/policy-resolver.ts
24972
- var resolverInputSchema = object4({
24973
- intent: intentSchema,
24974
- assessment: assessmentSchema,
24975
- constraints: array3(constraintSchema),
24976
- prior: object4({
24977
- decisions: array3(decisionSchema),
24978
- findings: array3(findingSchema),
24979
- requirements: array3(requirementSchema)
24980
- }).strict(),
24981
- preferences: object4({ fast: boolean5().optional(), thorough: boolean5().optional() }).strict().optional()
24982
- }).strict();
24983
- var compareCodeUnits3 = (left, right) => {
24984
- const length = Math.min(left.length, right.length);
24985
- for (let index = 0;index < length; index += 1) {
24986
- const difference = left.charCodeAt(index) - right.charCodeAt(index);
24987
- if (difference !== 0)
24988
- return difference;
24989
- }
24990
- return left.length - right.length;
24884
+ return uncertain;
24991
24885
  };
24992
- var stableUnique = (values) => [...new Set(values)].sort(compareCodeUnits3);
24993
- var stableList = (values) => [...values].sort((left, right) => compareCodeUnits3(canonicalJson(left), canonicalJson(right)));
24994
- var normalizedScope = (scope) => ({
24995
- description: scope.description,
24996
- paths: stableUnique(scope.paths),
24997
- exclusions: stableUnique(scope.exclusions)
24998
- });
24999
- var normalizedRefList = (values) => stableList(values);
25000
- var normalizeAssessment = (assessment) => ({
25001
- facts: stableList(assessment.facts.map((fact) => ({ ...fact, refs: normalizedRefList(fact.refs) }))),
25002
- signals: Object.fromEntries(Object.entries(assessment.signals).map(([name, signal]) => [
25003
- name,
25004
- { ...signal, refs: normalizedRefList(signal.refs) }
25005
- ])),
25006
- consequences: stableList(assessment.consequences.map((consequence) => ({
25007
- ...consequence,
25008
- fact: { ...consequence.fact, refs: normalizedRefList(consequence.fact.refs) }
25009
- }))),
25010
- verification: stableList(assessment.verification.map((verification) => ({
25011
- ...verification,
25012
- scope: normalizedScope(verification.scope),
25013
- availableChecks: normalizedRefList(verification.availableChecks),
25014
- gaps: stableUnique(verification.gaps)
25015
- })))
25016
- });
25017
- var normalizeConstraint = (constraint) => ({
25018
- ...constraint,
25019
- source: constraint.source,
25020
- requires: stableList(constraint.requires.map((requirement) => ({
25021
- ...requirement,
25022
- scope: normalizedScope(requirement.scope),
25023
- refs: normalizedRefList(requirement.refs)
25024
- })))
25025
- });
25026
- var normalizeDecision = (decision) => ({
25027
- ...decision,
25028
- binding: {
25029
- ...decision.binding,
25030
- scope: normalizedScope(decision.binding.scope),
25031
- contentRefs: normalizedRefList(decision.binding.contentRefs)
25032
- },
25033
- requirementIds: stableUnique(decision.requirementIds)
25034
- });
25035
- var normalizeIntent = (intent) => ({
25036
- ...intent,
25037
- scope: normalizedScope(intent.scope),
25038
- authorityRefs: normalizedRefList(intent.authorityRefs)
25039
- });
25040
- var normalizePrior = (prior) => ({
25041
- decisions: stableList(prior.decisions.map(normalizeDecision)),
25042
- findings: stableList(prior.findings.map((finding) => ({
25043
- ...finding,
25044
- scope: normalizedScope(finding.scope),
25045
- refs: normalizedRefList(finding.refs),
25046
- resolution: finding.resolution ? {
25047
- ...finding.resolution,
25048
- evidenceIds: stableUnique(finding.resolution.evidenceIds),
25049
- decisionIds: stableUnique(finding.resolution.decisionIds)
25050
- } : null
25051
- }))),
25052
- requirements: stableList(prior.requirements.map((requirement) => ({
25053
- ...requirement,
25054
- scope: normalizedScope(requirement.scope)
25055
- })))
25056
- });
25057
- var invalidFields = (error) => ({
25058
- fields: error.issues.map((issue) => ({
25059
- path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
25060
- reason: issue.message
25061
- }))
25062
- });
25063
- function normalizeResolverInput(input) {
25064
- const parsed = resolverInputSchema.safeParse(input);
25065
- if (!parsed.success)
25066
- return failure("invalid_input", "resolver input is invalid", invalidFields(parsed.error));
25067
- const value = parsed.data;
25068
- const signals = value.assessment.signals;
25069
- if (signals.behaviorChange.value === true && signals.mechanicalLowRisk.value === true)
25070
- return failure("invalid_input", "behavioral change cannot be wholly mechanical", {
25071
- fields: [
25072
- {
25073
- path: "assessment.signals",
25074
- reason: "contradictory behaviorChange and mechanicalLowRisk"
25075
- }
25076
- ]
25077
- });
25078
- const ids = value.constraints.map((constraint) => constraint.id);
25079
- if (new Set(ids).size !== ids.length)
25080
- return failure("invalid_input", "constraint IDs must be unique", {
25081
- fields: [{ path: "constraints", reason: "duplicate constraint id" }]
25082
- });
25083
- if (value.preferences?.fast && value.preferences.thorough)
25084
- return failure("invalid_input", "fast and thorough preferences contradict", {
25085
- fields: [{ path: "preferences", reason: "fast and thorough cannot both be enabled" }]
25086
- });
25087
- const normalized = {
25088
- intent: normalizeIntent(value.intent),
25089
- assessment: normalizeAssessment(value.assessment),
25090
- constraints: stableList(value.constraints.map(normalizeConstraint)),
25091
- prior: normalizePrior(value.prior),
25092
- ...value.preferences ? { preferences: { ...value.preferences } } : {},
25093
- policyVersion: POLICY_VERSION
24886
+ var scopeRoots = (root, scope) => {
24887
+ const roots = [];
24888
+ for (const item of scope.paths.length ? scope.paths : ["."]) {
24889
+ const target = path10.resolve(root, item);
24890
+ if (!inside(root, target))
24891
+ 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` }] });
24892
+ let ancestor = target;
24893
+ while (!fs7.existsSync(ancestor) && ancestor !== root)
24894
+ ancestor = path10.dirname(ancestor);
24895
+ try {
24896
+ if (!inside(root, fs7.realpathSync(ancestor)))
24897
+ 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` }] });
24898
+ } catch {
24899
+ return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
24900
+ }
24901
+ roots.push(target);
24902
+ }
24903
+ return success(null, null, roots);
24904
+ };
24905
+ function captureCandidate(root, scope, environment = []) {
24906
+ let checkout;
24907
+ try {
24908
+ checkout = fs7.realpathSync(root);
24909
+ } catch {
24910
+ return failure("invalid_input", "candidate root does not exist");
24911
+ }
24912
+ const normalizedScope = canonicalScope(scope);
24913
+ if (!normalizedScope)
24914
+ 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" }] });
24915
+ const roots = scopeRoots(checkout, normalizedScope);
24916
+ if (!roots.ok)
24917
+ return roots;
24918
+ const git = gitPaths(checkout);
24919
+ const names = new Set;
24920
+ let uncertain = git.uncertain;
24921
+ for (const target of roots.data) {
24922
+ const item = relative(checkout, target);
24923
+ if (item === ".git" || item === ".workit")
24924
+ continue;
24925
+ names.add(item);
24926
+ if (!fs7.existsSync(target))
24927
+ continue;
24928
+ let stat;
24929
+ try {
24930
+ stat = fs7.lstatSync(target);
24931
+ } catch {
24932
+ uncertain = true;
24933
+ continue;
24934
+ }
24935
+ if (!git.git && stat.isDirectory() && !stat.isSymbolicLink())
24936
+ uncertain = walk(checkout, target, names) || uncertain;
24937
+ }
24938
+ for (const item of git.paths)
24939
+ if (scopeMatches(item, normalizedScope))
24940
+ names.add(item);
24941
+ const files = [];
24942
+ let completeness = uncertain ? "uncertain" : "known";
24943
+ for (const item of [...names].sort()) {
24944
+ if (!scopeMatches(item, normalizedScope))
24945
+ continue;
24946
+ const target = path10.join(checkout, item);
24947
+ try {
24948
+ const stat = fs7.lstatSync(target);
24949
+ if (stat.isSymbolicLink()) {
24950
+ const link = fs7.readlinkSync(target);
24951
+ files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
24952
+ } else if (stat.isFile()) {
24953
+ files.push({
24954
+ path: item,
24955
+ kind: "file",
24956
+ digest: digestBytes3(fs7.readFileSync(target)),
24957
+ executable: (stat.mode & 73) !== 0
24958
+ });
24959
+ } else if (!stat.isDirectory()) {
24960
+ completeness = "uncertain";
24961
+ } else if (git.git && git.paths.includes(item)) {
24962
+ completeness = "uncertain";
24963
+ }
24964
+ } catch (error) {
24965
+ if (error?.code === "ENOENT") {
24966
+ files.push({ path: item, kind: "absent", digest: null, executable: null });
24967
+ } else
24968
+ completeness = "uncertain";
24969
+ }
24970
+ }
24971
+ 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) => ({
24972
+ name,
24973
+ value: environment[name] ?? null
24974
+ }));
24975
+ const namesByEnvironment = supplied.map(({ name }) => name);
24976
+ if (new Set(namesByEnvironment).size !== namesByEnvironment.length || namesByEnvironment.some((name) => typeof name !== "string" || !name))
24977
+ return failure("invalid_input", "candidate environment names must be unique and non-empty");
24978
+ const values = supplied.sort((left, right) => compareCodeUnits3(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
24979
+ if (values.some(({ value }) => value === null))
24980
+ completeness = "uncertain";
24981
+ const headResult = spawnSync2("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
24982
+ if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
24983
+ completeness = "uncertain";
24984
+ const head = headResult.status === 0 ? headResult.stdout.trim() : null;
24985
+ const partial = {
24986
+ id: "0".repeat(64),
24987
+ scope: normalizedScope,
24988
+ completeness,
24989
+ files,
24990
+ environment: values,
24991
+ head
25094
24992
  };
25095
- return success(null, null, normalized);
24993
+ const candidate = { ...partial, id: candidateDigest(partial) };
24994
+ const parsed = candidateSchema.safeParse(candidate);
24995
+ return parsed.success ? success(null, null, parsed.data) : failure("invalid_input", "captured candidate is invalid");
25096
24996
  }
25097
- var requirement = (spec) => ({
25098
- ...spec,
25099
- id: requirementId({
25100
- ruleId: spec.ruleId,
25101
- scope: spec.scope,
25102
- satisfaction: spec.satisfaction,
25103
- before: spec.before,
25104
- dependentAction: spec.dependentAction
25105
- })
25106
- });
25107
- var signalReason = (input, name) => input.assessment.signals[name].reason;
25108
- function resolveRequirements(input) {
25109
- const { signals } = input.assessment;
25110
- const scope = input.intent.scope;
25111
- const requirements = [];
25112
- const hasUnknownSignal = Object.values(signals).some((signal) => signal.value === "unknown");
25113
- const hasUnknownFact = [
25114
- ...input.assessment.facts,
25115
- ...input.assessment.consequences.map(({ fact }) => fact)
25116
- ].some((fact) => fact.basis === "unknown");
25117
- if (hasUnknownSignal || hasUnknownFact)
25118
- requirements.push(requirement({
25119
- ruleId: "consequential-unknown",
25120
- dimension: "investigation",
25121
- scope,
25122
- reason: `Resolve consequential uncertainty before the dependent action: ${[
25123
- ...Object.entries(signals).filter(([, signal]) => signal.value === "unknown").map(([name]) => name),
25124
- ...hasUnknownFact ? ["unknown fact"] : []
25125
- ].join(", ")}`,
25126
- satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
25127
- before: "dependent_action",
25128
- dependentAction: null,
25129
- acceptanceAllowed: false
25130
- }));
25131
- if (signals.productChoiceOpen.value === true)
25132
- requirements.push(requirement({
25133
- ruleId: "product-decision",
25134
- dimension: "decisions",
25135
- scope,
25136
- reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
25137
- satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
25138
- before: "dependent_action",
25139
- dependentAction: null,
25140
- acceptanceAllowed: false
25141
- }));
25142
- const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
25143
- if (signals.behaviorChange.value === true)
25144
- requirements.push(requirement({
25145
- ruleId: "behavioral-verification",
25146
- dimension: "testing",
25147
- scope,
25148
- reason: `Behavior, side effects, permissions, or data handling may change: ${signalReason(input, "behaviorChange")}`,
25149
- satisfaction: "A useful behavioral check passes against the affected behavior and current candidate.",
25150
- before: "close",
25151
- dependentAction: null,
25152
- acceptanceAllowed: false
25153
- }), requirement({
25154
- ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
25155
- dimension: "review",
25156
- scope,
25157
- 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.",
25158
- 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.",
25159
- before: "close",
25160
- dependentAction: null,
25161
- acceptanceAllowed: !independentBehaviorReview
25162
- }));
25163
- else if (signals.mechanicalLowRisk.value === true)
25164
- requirements.push(requirement({
25165
- ruleId: "mechanical-existing-checks",
25166
- dimension: "verification",
25167
- scope,
25168
- reason: `The change is demonstrably mechanical and low-risk: ${signalReason(input, "mechanicalLowRisk")}`,
25169
- satisfaction: "Run the relevant existing checks for affected consumers; do not add tautological tests.",
25170
- before: "close",
25171
- dependentAction: null,
25172
- acceptanceAllowed: true
25173
- }), requirement({
25174
- ruleId: input.preferences?.thorough ? "fresh-context-review" : "self-review",
25175
- dimension: "review",
25176
- scope,
25177
- 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.",
25178
- 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.",
25179
- before: "close",
25180
- dependentAction: null,
25181
- acceptanceAllowed: !input.preferences?.thorough
25182
- }));
25183
- if (signals.behaviorChange.value === true || signals.mechanicalLowRisk.value === true)
25184
- requirements.push(requirement({
25185
- ruleId: "pre-pr-cleanup",
25186
- dimension: "verification",
25187
- scope,
25188
- reason: "Implementation output must be deslopped before it is delivered.",
25189
- satisfaction: "Run the workit-deslop pass and record passing check evidence, or record an approved limitation decision waiving cleanup.",
25190
- before: "dependent_action",
25191
- dependentAction: "hosting.pull_request",
25192
- acceptanceAllowed: true
25193
- }));
25194
- if (signals.durableAgreementNeeded.value === true)
25195
- requirements.push(requirement({
25196
- ruleId: "durable-spec",
25197
- dimension: "artifacts",
25198
- scope,
25199
- reason: `The work needs a durable agreement about intended behavior: ${signalReason(input, "durableAgreementNeeded")}`,
25200
- satisfaction: "Record the agreed behavior in a durable specification before implementation writes.",
25201
- before: "write",
25202
- dependentAction: null,
25203
- acceptanceAllowed: true
25204
- }));
25205
- if (signals.coordinationPlanNeeded.value === true)
25206
- requirements.push(requirement({
25207
- ruleId: "coordination-plan",
25208
- dimension: "continuity",
25209
- scope,
25210
- reason: `Dependencies, sequencing, or resumption need durable coordination: ${signalReason(input, "coordinationPlanNeeded")}`,
25211
- satisfaction: "Record useful sequencing, dependencies, and the next action in a compact plan.",
25212
- before: "dependent_action",
25213
- dependentAction: null,
25214
- acceptanceAllowed: true
25215
- }));
25216
- if (signals.helperUseful.value === true && !input.preferences?.fast)
25217
- requirements.push(requirement({
25218
- ruleId: "helper-usefulness",
25219
- dimension: "delegation",
25220
- scope,
25221
- reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
25222
- satisfaction: "Use a bounded helper only for the independent objective and reconcile its result.",
25223
- before: "dependent_action",
25224
- dependentAction: null,
25225
- acceptanceAllowed: true
24997
+ var fileMap = (candidate) => new Map(candidate.files.map((file) => [file.path, file]));
24998
+ var changedPaths = (before, after) => {
24999
+ const paths = new Set([...before.files, ...after.files].map((file) => file.path));
25000
+ const left = fileMap(before);
25001
+ const right = fileMap(after);
25002
+ const changed = [...paths].filter((item) => JSON.stringify(left.get(item) ?? null) !== JSON.stringify(right.get(item) ?? null));
25003
+ if (before.head !== after.head)
25004
+ changed.push(".");
25005
+ if (JSON.stringify(before.environment) !== JSON.stringify(after.environment))
25006
+ changed.push(".");
25007
+ return changed;
25008
+ };
25009
+ var sessionFromRef = (value) => {
25010
+ if (typeof value !== "object" || value === null || value.kind !== "host")
25011
+ return null;
25012
+ return typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
25013
+ };
25014
+ var sameSession = (left, right) => {
25015
+ const other = sessionFromRef(right);
25016
+ return left !== null && other !== null && left.host === other.host && left.handle === other.handle;
25017
+ };
25018
+ var evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope);
25019
+ function evaluateEvidence(task, candidate) {
25020
+ const current = candidate ?? task.candidates.at(-1) ?? null;
25021
+ return task.evidence.map((entry) => {
25022
+ const evidence = entry.data;
25023
+ if (evidence.result === "failed")
25024
+ return {
25025
+ evidenceId: entry.id,
25026
+ status: "failed",
25027
+ reason: "failed evidence remains historical"
25028
+ };
25029
+ if (evidence.kind === "check" || evidence.kind === "review") {
25030
+ if (!evidence.beforeCandidateId || !evidence.candidateId)
25031
+ return {
25032
+ evidenceId: entry.id,
25033
+ status: "stale",
25034
+ reason: "check or review evidence lacks start and end candidates"
25035
+ };
25036
+ if (evidence.beforeCandidateId !== evidence.candidateId)
25037
+ return {
25038
+ evidenceId: entry.id,
25039
+ status: "stale",
25040
+ reason: "candidate changed during the check or review"
25041
+ };
25042
+ }
25043
+ if (!evidence.candidateId || !current)
25044
+ return {
25045
+ evidenceId: entry.id,
25046
+ status: evidence.result,
25047
+ reason: "evidence has no candidate binding"
25048
+ };
25049
+ const observed = task.candidates.find((item) => item.id === evidence.candidateId) ?? (current.id === evidence.candidateId ? current : undefined);
25050
+ if (!observed || observed.completeness === "uncertain" || current.completeness === "uncertain")
25051
+ return { evidenceId: entry.id, status: "stale", reason: "candidate is missing or uncertain" };
25052
+ if (observed.id === current.id)
25053
+ return { evidenceId: entry.id, status: evidence.result, reason: "candidate is unchanged" };
25054
+ const scopes = evidenceScopes(task, evidence.requirementIds);
25055
+ const changed = changedPaths(observed, current);
25056
+ const environmentChanged = JSON.stringify(observed.environment) !== JSON.stringify(current.environment);
25057
+ if (!scopes.length || environmentChanged || changed.some((item) => scopes.some((scope) => pathRelevant(item, scope))))
25058
+ return { evidenceId: entry.id, status: "stale", reason: "relevant candidate state changed" };
25059
+ return {
25060
+ evidenceId: entry.id,
25061
+ status: evidence.result,
25062
+ reason: "candidate changed outside evidence scope"
25063
+ };
25064
+ });
25065
+ }
25066
+ var scopeCovers2 = scopeCovers;
25067
+ var checkPin = (assignment, beforeCandidateId, candidateId) => {
25068
+ const pin = assignment.candidateId ?? null;
25069
+ if (pin === null)
25070
+ return true;
25071
+ return (beforeCandidateId ?? null) === pin || (candidateId ?? null) === pin;
25072
+ };
25073
+ var resolveBinding = (evidence, pin, currentCandidateId) => {
25074
+ const bound = evidence.kind === "check" || evidence.kind === "review";
25075
+ const fallback = bound ? pin ?? currentCandidateId : null;
25076
+ return {
25077
+ beforeCandidateId: evidence.beforeCandidateId ?? fallback,
25078
+ candidateId: evidence.candidateId ?? fallback
25079
+ };
25080
+ };
25081
+ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId != null);
25082
+ 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")));
25083
+ 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 }));
25084
+ var evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
25085
+ if (kind !== "passed")
25086
+ return false;
25087
+ if (dimension === "testing" || dimension === "verification")
25088
+ return evidenceKind === "check" || ruleId === "pre-pr-cleanup" && evidenceKind === "artifact";
25089
+ if (dimension === "review")
25090
+ return evidenceKind === "review";
25091
+ if (dimension === "investigation" || dimension === "challenge")
25092
+ return evidenceKind === "investigation";
25093
+ if (dimension === "artifacts" || dimension === "continuity")
25094
+ return evidenceKind === "artifact";
25095
+ return false;
25096
+ };
25097
+ var expectedKinds = (dimension, ruleId) => {
25098
+ if (dimension === "testing")
25099
+ return "check";
25100
+ if (dimension === "verification")
25101
+ return ruleId === "pre-pr-cleanup" ? "check or artifact" : "check";
25102
+ if (dimension === "review")
25103
+ return "review";
25104
+ if (dimension === "investigation" || dimension === "challenge")
25105
+ return "investigation";
25106
+ if (dimension === "artifacts" || dimension === "continuity")
25107
+ return "artifact";
25108
+ if (dimension === "decisions")
25109
+ return "decision";
25110
+ return "worker report";
25111
+ };
25112
+ function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller) {
25113
+ const evidence = evaluateEvidence(task, candidate);
25114
+ if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
25115
+ return task.policy.requirements.map((requirement) => ({
25116
+ requirementId: requirement.id,
25117
+ status: "unsatisfied",
25118
+ evidenceIds: [],
25119
+ decisionIds: [],
25120
+ reason: "stored policy version is unsupported; reassessment is required"
25226
25121
  }));
25227
- for (const constraint of input.constraints)
25228
- for (const obligation of constraint.requires) {
25229
- const dimension = obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge");
25230
- requirements.push(requirement({
25231
- ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
25232
- kind: obligation.kind,
25233
- scope: normalizedScope(obligation.scope),
25234
- refs: normalizedRefList(obligation.refs),
25235
- method: obligation.method,
25236
- before: obligation.before,
25237
- dependentAction: obligation.dependentAction
25238
- })}`,
25239
- dimension,
25240
- scope: obligation.scope,
25241
- reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
25242
- satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
25243
- before: obligation.before,
25244
- dependentAction: obligation.dependentAction,
25245
- acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
25246
- }));
25122
+ return (task.policy?.requirements ?? []).map((requirement) => {
25123
+ const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
25124
+ const passed = related.filter(({ entry, evaluation }) => {
25125
+ if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
25126
+ return false;
25127
+ if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
25128
+ return false;
25129
+ if (requirement.dimension !== "review")
25130
+ return true;
25131
+ const reviewSession = sessionFromRef(entry.data.reviewContext);
25132
+ const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
25133
+ 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));
25134
+ return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
25135
+ });
25136
+ if (passed.length) {
25137
+ const currentId = candidate?.id ?? null;
25138
+ 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;
25139
+ if (withRed.length)
25140
+ return {
25141
+ requirementId: requirement.id,
25142
+ status: "satisfied",
25143
+ evidenceIds: withRed.map(({ entry }) => entry.id),
25144
+ decisionIds: [],
25145
+ reason: "fresh applicable evidence passed"
25146
+ };
25147
+ if (requirement.dimension === "testing")
25148
+ return {
25149
+ requirementId: requirement.id,
25150
+ status: "unsatisfied",
25151
+ evidenceIds: [],
25152
+ decisionIds: [],
25153
+ reason: "testing requirement has GREEN evidence but no preceding RED"
25154
+ };
25247
25155
  }
25248
- const seen = new Set;
25249
- return requirements.filter((item) => {
25250
- if (seen.has(item.id))
25156
+ if (requirement.dimension === "delegation") {
25157
+ const dot = (scope) => scope.paths.length > 0 ? scope : { ...scope, paths: ["."] };
25158
+ 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))));
25159
+ if (reconciled)
25160
+ return {
25161
+ requirementId: requirement.id,
25162
+ status: "satisfied",
25163
+ evidenceIds: [],
25164
+ decisionIds: [],
25165
+ reason: "bounded helper completed and its result is recorded"
25166
+ };
25167
+ }
25168
+ const decisions = requirement.dimension === "decisions" ? applicableRequirementDecision(task, workspace, requirement, checkoutRoot) : [];
25169
+ if (decisions.length)
25170
+ return {
25171
+ requirementId: requirement.id,
25172
+ status: "satisfied",
25173
+ evidenceIds: [],
25174
+ decisionIds: decisions.map((decision) => decision.id),
25175
+ reason: "an applicable approved or stated decision satisfies the requirement"
25176
+ };
25177
+ const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
25178
+ if (limitations.length)
25179
+ return {
25180
+ requirementId: requirement.id,
25181
+ status: "accepted_limitation",
25182
+ evidenceIds: [],
25183
+ decisionIds: limitations.map((decision) => task.decisions.find((entry) => entry.data === decision).id),
25184
+ reason: "an applicable approved limitation permits the missing evidence"
25185
+ };
25186
+ const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
25187
+ if (!unavailable && related.length > 0 && !passed.length)
25188
+ return {
25189
+ requirementId: requirement.id,
25190
+ status: "unsatisfied",
25191
+ evidenceIds: related.map(({ entry }) => entry.id),
25192
+ decisionIds: [],
25193
+ reason: `no passing evidence (needs kind:${expectedKinds(requirement.dimension, requirement.ruleId)})`
25194
+ };
25195
+ return {
25196
+ requirementId: requirement.id,
25197
+ status: unavailable ? "unavailable" : "unsatisfied",
25198
+ evidenceIds: related.map(({ entry }) => entry.id),
25199
+ decisionIds: [],
25200
+ reason: unavailable ? "required capability is unavailable" : "no fresh passing evidence"
25201
+ };
25202
+ });
25203
+ }
25204
+ function evaluateClosure(requestedOutcome, view) {
25205
+ if (requestedOutcome !== "stopped" && !view.task.policy)
25206
+ return failure("requirements_unsatisfied", "task has not been assessed");
25207
+ if (view.task.policy && view.task.policy.policyVersion !== POLICY_VERSION)
25208
+ return failure("requirements_unsatisfied", "stored policy version is unsupported; reassessment is required");
25209
+ const evaluations = view.requirements;
25210
+ const openFindings = view.task.findings.filter((entry) => entry.data.disposition === "open");
25211
+ if (requestedOutcome !== "stopped" && openFindings.length)
25212
+ return failure("requirements_unsatisfied", "open findings must be resolved before closure");
25213
+ if (requestedOutcome !== "stopped") {
25214
+ const evidenceById = new Map(view.evidence.map((entry) => [entry.evidenceId, entry.status]));
25215
+ for (const entry of view.task.findings) {
25216
+ if (entry.data.disposition === "fixed") {
25217
+ const verified = entry.data.resolution?.evidenceIds.some((id) => {
25218
+ const evidence = view.task.evidence.find((item) => item.id === id);
25219
+ if (!evidence)
25220
+ return false;
25221
+ return findingVerificationPasses(entry.data.candidateId, {
25222
+ kind: evidence.data.kind,
25223
+ candidateId: evidence.data.candidateId,
25224
+ status: evidenceById.get(id) ?? "missing"
25225
+ });
25226
+ });
25227
+ if (!verified)
25228
+ return failure("requirements_unsatisfied", "fixed findings require current verification");
25229
+ }
25230
+ if (entry.data.disposition === "deferred") {
25231
+ const valid = entry.data.resolution?.decisionIds.some((id) => {
25232
+ const decisionEntry = view.task.decisions.find((item) => item.id === id);
25233
+ const decision = decisionEntry?.data;
25234
+ 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)));
25235
+ });
25236
+ if (!valid)
25237
+ return failure("requirements_unsatisfied", "deferred findings require an approved limitation decision that references an acceptanceAllowed requirement and covers the finding scope");
25238
+ }
25239
+ }
25240
+ }
25241
+ const blocking = evaluations.filter((item) => {
25242
+ if (item.status !== "unsatisfied" && item.status !== "unavailable")
25251
25243
  return false;
25252
- seen.add(item.id);
25253
- return true;
25244
+ const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
25245
+ return !requirement || requirement.before === "close";
25246
+ });
25247
+ if (requestedOutcome !== "stopped" && blocking.length)
25248
+ return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
25249
+ requirementIds: blocking.map((item) => item.requirementId),
25250
+ requirements: blocking.map((item) => {
25251
+ const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
25252
+ return {
25253
+ requirementId: item.requirementId,
25254
+ ruleId: requirement?.ruleId ?? "unknown",
25255
+ reason: item.reason,
25256
+ satisfaction: requirement?.satisfaction ?? "",
25257
+ dependentAction: requirement?.dependentAction ?? null
25258
+ };
25259
+ })
25260
+ });
25261
+ const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
25262
+ if (requestedOutcome === "verified" && accepted.length)
25263
+ return failure("requirements_unsatisfied", "accepted limitations cannot be reported as verified", {
25264
+ requirementIds: accepted.map((item) => item.requirementId)
25265
+ });
25266
+ const evidenceIds = [
25267
+ ...new Set([
25268
+ ...evaluations.flatMap((item) => item.evidenceIds),
25269
+ ...view.task.findings.flatMap((item) => item.data.resolution?.evidenceIds ?? [])
25270
+ ])
25271
+ ];
25272
+ const decisionIds = [
25273
+ ...new Set([
25274
+ ...evaluations.flatMap((item) => item.decisionIds),
25275
+ ...view.task.findings.flatMap((item) => item.data.resolution?.decisionIds ?? [])
25276
+ ])
25277
+ ];
25278
+ return success(null, null, {
25279
+ outcome: requestedOutcome === "stopped" ? "stopped" : accepted.length ? "accepted_limitations" : "verified",
25280
+ evidenceIds,
25281
+ decisionIds,
25282
+ requirementIds: evaluations.map((item) => item.requirementId)
25254
25283
  });
25255
25284
  }
25256
- function resolvePolicy(input) {
25257
- const normalized = normalizeResolverInput(input);
25258
- if (!normalized.ok)
25259
- return normalized;
25260
- const requirements = resolveRequirements(normalized.data);
25261
- const policy = {
25262
- policyVersion: POLICY_VERSION,
25263
- inputDigest: sha256(canonicalJson(normalized.data)),
25264
- requirements
25265
- };
25266
- const parsed = policySchema.safeParse(policy);
25267
- if (!parsed.success)
25268
- return failure("invalid_input", "resolved policy is invalid", invalidFields(parsed.error));
25269
- return success(null, null, parsed.data);
25285
+
25286
+ // packages/workit-core/src/core/methods.ts
25287
+ var METHODS = {
25288
+ "workit-challenge": { dimensions: ["challenge", "decisions"] },
25289
+ "workit-behavioral-tdd": { dimensions: ["testing"], ruleIds: ["mechanical-existing-checks"] },
25290
+ "workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review", "self-review"] },
25291
+ "workit-plan": { dimensions: ["artifacts", "continuity"] },
25292
+ "workit-implement": { dimensions: ["delegation"] },
25293
+ "workit-debug": {},
25294
+ "workit-handoff": {},
25295
+ "workit-deslop": { ruleIds: ["pre-pr-cleanup"] }
25296
+ };
25297
+ var methodMatches = (definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true || definition.ruleIds?.includes(requirement.ruleId) === true;
25298
+ var assuranceFor = (id, definition, capabilities) => {
25299
+ const keys = new Set([
25300
+ id,
25301
+ ...definition.dimensions ?? [],
25302
+ ...definition.ruleIds ?? []
25303
+ ]);
25304
+ const matches = capabilities.filter((capability) => keys.has(capability.name) || keys.has(capability.surface));
25305
+ if (id === "workit-review" && matches.length === 0)
25306
+ return { assurance: "unavailable", reasons: ["independent review capability is unavailable"] };
25307
+ if (matches.some((capability) => capability.assurance === "unavailable"))
25308
+ return {
25309
+ assurance: "unavailable",
25310
+ reasons: matches.filter((capability) => capability.assurance === "unavailable").map((capability) => capability.reason)
25311
+ };
25312
+ if (matches.some((capability) => capability.assurance === "enforced"))
25313
+ return { assurance: "enforced", reasons: matches.map((capability) => capability.reason) };
25314
+ return { assurance: "agent_guided", reasons: matches.map((capability) => capability.reason) };
25315
+ };
25316
+ function selectMethods(policy, capabilities) {
25317
+ return Object.entries(METHODS).flatMap(([id, definition]) => {
25318
+ const requirements = policy.requirements.filter((requirement) => methodMatches(definition, requirement));
25319
+ if (requirements.length === 0)
25320
+ return [];
25321
+ const capability = assuranceFor(id, definition, capabilities);
25322
+ const reasons = [
25323
+ ...new Set(requirements.map((requirement) => requirement.reason)),
25324
+ ...capability.reasons
25325
+ ];
25326
+ if (capability.assurance === "unavailable")
25327
+ reasons.unshift("required capability is unavailable");
25328
+ return [{ id, reason: reasons.join("; "), assurance: capability.assurance }];
25329
+ });
25270
25330
  }
25271
- function diffPolicy(previous, next, reason, now) {
25272
- if (!policySchema.safeParse(previous).success && previous !== null)
25273
- throw new TypeError("previous policy is invalid");
25274
- if (!policySchema.safeParse(next).success)
25275
- throw new TypeError("next policy is invalid");
25276
- const oldIds = new Set(previous?.requirements.map((requirement) => requirement.id) ?? []);
25277
- const newIds = new Set(next.requirements.map((requirement) => requirement.id));
25278
- const added = [...newIds].filter((id) => !oldIds.has(id)).sort(compareCodeUnits3);
25279
- const retired = [...oldIds].filter((id) => !newIds.has(id)).sort(compareCodeUnits3);
25280
- if (added.length === 0 && retired.length === 0)
25281
- return null;
25282
- const normalizedReason = reason.trim();
25283
- const changeReason = retired.length ? `${normalizedReason || "policy reassessed"} (retired: ${retired.join(",")})` : normalizedReason || "policy reassessed";
25284
- const change = {
25285
- recordedAt: now,
25286
- fromInputDigest: previous?.inputDigest ?? null,
25287
- toInputDigest: next.inputDigest,
25288
- added,
25289
- retired,
25290
- reason: changeReason
25291
- };
25292
- const parsed = policyChangeSchema.safeParse(change);
25293
- if (!parsed.success)
25294
- throw new TypeError("policy change is invalid");
25295
- return parsed.data;
25331
+
25332
+ // packages/workit-core/src/core/task-context.ts
25333
+ function reconcileResume(view, observations = []) {
25334
+ if (observations.length > 0)
25335
+ return failure("permission_denied", "worker observations require native host verification");
25336
+ const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
25337
+ if (!candidate.ok)
25338
+ return candidate;
25339
+ const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
25340
+ const blockers = [...view.task.progress.blockers];
25341
+ if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
25342
+ blockers.push({
25343
+ reason: "worker state requires reconciliation",
25344
+ dependentAction: "resume",
25345
+ refs: []
25346
+ });
25347
+ return success(null, null, {
25348
+ candidate: candidate.data,
25349
+ staleEvidenceIds,
25350
+ workerUpdates: [],
25351
+ blockers,
25352
+ reassessmentRequired: view.task.policy?.policyVersion !== POLICY_VERSION
25353
+ });
25296
25354
  }
25355
+ var COMPACT_MAX_BYTES = 4096;
25356
+ var COMPACT_MAX_ITEMS = 8;
25357
+ var COMPACT_TEXT_BYTES = 128;
25358
+ var sensitiveUrl = /\bhttps?:\/\/[^/\s:@]+:[^@\s]+@/gi;
25359
+ var sensitiveValue = /(?:token|secret|password|passwd|api[-_ ]?key|private[-_ ]?key|authorization)\s*[:=]\s*(?:bearer\s+)?\S+|\bbearer\s+\S+/gi;
25360
+ var privateKey = /-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/gi;
25361
+ var compactText = (value, limit) => value === null ? null : value.replace(privateKey, "<redacted-private-key>").replace(sensitiveUrl, "<redacted-url>").replace(sensitiveValue, (match) => {
25362
+ const separator = match.search(/[:=]/u);
25363
+ if (separator >= 0)
25364
+ return `${match.slice(0, separator)}=<redacted>`;
25365
+ const space = match.search(/\s/u);
25366
+ return `${match.slice(0, space)} <redacted>`;
25367
+ }).slice(0, limit);
25368
+ var compactBytes = (value) => new TextEncoder().encode(value).byteLength;
25369
+ var compactTimestamp = (value) => `${value.slice(0, 19)}.${value.slice(20, -1).padEnd(9, "0")}`;
25370
+ var compactDecisionChoice = (entry) => {
25371
+ const stated = entry.data.binding.statedChoice?.text;
25372
+ const approved = entry.data.response === "approved" ? entry.data.binding.approvedContent : "";
25373
+ const source = stated?.trim() || approved.trim() || null;
25374
+ return compactText(source, COMPACT_TEXT_BYTES);
25375
+ };
25376
+ var compactReference = (ref) => {
25377
+ if (ref.kind === "file")
25378
+ return {
25379
+ kind: "file",
25380
+ path: compactText(ref.path, COMPACT_TEXT_BYTES) ?? ""
25381
+ };
25382
+ if (ref.kind === "record")
25383
+ return {
25384
+ kind: "record",
25385
+ collection: ref.collection,
25386
+ id: ref.id
25387
+ };
25388
+ return { kind: ref.kind };
25389
+ };
25390
+ function compactTaskContext(view) {
25391
+ const decisions = view.task.decisions.slice().sort((left, right) => {
25392
+ const leftAt = compactTimestamp(left.recordedAt);
25393
+ const rightAt = compactTimestamp(right.recordedAt);
25394
+ if (leftAt !== rightAt)
25395
+ return leftAt > rightAt ? -1 : 1;
25396
+ return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
25397
+ }).slice(0, COMPACT_MAX_ITEMS).map((entry) => ({
25398
+ id: entry.id,
25399
+ purpose: entry.data.purpose,
25400
+ status: entry.data.response,
25401
+ digest: entry.data.digest,
25402
+ choice: compactDecisionChoice(entry) || `task.inspect taskId=${view.task.id} view=full; decision=${entry.id}`,
25403
+ references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference)
25404
+ }));
25405
+ const methods = view.task.policy ? selectMethods(view.task.policy, view.capabilities).map((method) => ({
25406
+ id: method.id,
25407
+ assurance: method.assurance,
25408
+ reason: compactText(method.reason, COMPACT_TEXT_BYTES) ?? ""
25409
+ })) : [];
25410
+ const gaps = Array.from(new Set([
25411
+ ...view.evidence.filter((entry) => entry.status === "stale").map((entry) => `stale evidence: ${entry.evidenceId}`),
25412
+ ...view.requirements.filter((entry) => entry.status !== "satisfied").map((entry) => entry.reason),
25413
+ ...view.task.progress.blockers.map((entry) => entry.reason)
25414
+ ])).sort().slice(0, COMPACT_MAX_ITEMS).map((gap) => compactText(gap, COMPACT_TEXT_BYTES));
25415
+ const context = {
25416
+ objective: compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "",
25417
+ status: view.task.status,
25418
+ decisions,
25419
+ methods,
25420
+ gaps,
25421
+ nextAction: compactText(view.task.progress.nextAction, COMPACT_TEXT_BYTES)
25422
+ };
25423
+ let encoded = JSON.stringify(context);
25424
+ while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.methods.length || context.gaps.length)) {
25425
+ if (context.gaps.length)
25426
+ context.gaps.pop();
25427
+ else if (context.decisions.length)
25428
+ context.decisions.pop();
25429
+ else
25430
+ context.methods.pop();
25431
+ encoded = JSON.stringify(context);
25432
+ }
25433
+ if (compactBytes(encoded) > COMPACT_MAX_BYTES) {
25434
+ context.objective = compactText(context.objective, 64) ?? "";
25435
+ context.nextAction = compactText(context.nextAction, 64);
25436
+ encoded = JSON.stringify(context);
25437
+ }
25438
+ return encoded;
25439
+ }
25440
+ var exportDigest = (bundle) => sha256({
25441
+ schemaVersion: bundle.schemaVersion,
25442
+ exportedAt: bundle.exportedAt,
25443
+ sourceWorkspaceId: bundle.sourceWorkspaceId,
25444
+ task: bundle.task
25445
+ });
25297
25446
 
25298
25447
  // packages/workit-core/src/core/task-engine.ts
25299
25448
  var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
@@ -26981,14 +27130,29 @@ class WorkitCore {
26981
27130
  writer: evaluationWorkspace.writer
26982
27131
  });
26983
27132
  }
26984
- view(task) {
27133
+ compactContext(taskId) {
27134
+ const root = this.contextRootError();
27135
+ if (!root.ok)
27136
+ return root;
27137
+ const task = this.store.readTask(taskId);
27138
+ if (!task.ok)
27139
+ return task;
27140
+ const helper = this.helperTaskGuard(task.data, true);
27141
+ if (!helper.ok)
27142
+ return helper;
27143
+ const view = this.view(task.data, false);
27144
+ if (!view.ok)
27145
+ return view;
27146
+ return success(view.revision, view.workspaceRevision, compactTaskContext(view.data));
27147
+ }
27148
+ view(task, capture = true) {
26985
27149
  const workspace = this.store.readWorkspace();
26986
27150
  if (!workspace.ok)
26987
27151
  return workspace;
26988
27152
  if (!workspace.data)
26989
27153
  return failure("not_found", "workspace not found");
26990
27154
  const historical = task.status === "closed" ? task.candidates.at(-1) : undefined;
26991
- const current = historical ? success(null, null, historical) : captureCandidate(this.store.root, task.intent.data.scope, environment());
27155
+ 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);
26992
27156
  if (!current.ok)
26993
27157
  return current;
26994
27158
  const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
@@ -27260,6 +27424,122 @@ class WorkitCore {
27260
27424
  return this.summary(changed.data.task);
27261
27425
  }
27262
27426
  }
27427
+
27428
+ // packages/workit-core/src/core/session-context.ts
27429
+ var cache = new Map;
27430
+ // packages/workit-core/src/core/external-action.ts
27431
+ var chainStepSchema = union3([
27432
+ string5().min(1),
27433
+ object4({ branch: string5().min(1) }).strict(),
27434
+ object4({ pr: literal3(true) }).strict()
27435
+ ]);
27436
+ var externalActionSchema = discriminatedUnion2("operation", [
27437
+ object4({
27438
+ operation: literal3("git.branch_setup"),
27439
+ payload: object4({
27440
+ action: _enum2(["setup", "reapply_stash"]).optional(),
27441
+ sdd_dir: string5().optional(),
27442
+ target_branch: string5().optional(),
27443
+ stash: _enum2(["yes", "no"]).optional(),
27444
+ cwd: string5().min(1).optional()
27445
+ }).strict()
27446
+ }).strict(),
27447
+ object4({
27448
+ operation: literal3("git.commit"),
27449
+ payload: object4({
27450
+ message: string5().min(1).optional(),
27451
+ plan_steps: array3(chainStepSchema).min(1).max(32).optional(),
27452
+ plan_branch: string5().min(1).optional(),
27453
+ cwd: string5().min(1).optional()
27454
+ }).strict()
27455
+ }).strict(),
27456
+ object4({
27457
+ operation: literal3("git.push"),
27458
+ payload: object4({ branch: string5().min(1).optional(), cwd: string5().min(1).optional() }).strict()
27459
+ }).strict(),
27460
+ object4({
27461
+ operation: literal3("hosting.pull_request"),
27462
+ payload: object4({
27463
+ title: string5().min(1),
27464
+ body: string5().optional(),
27465
+ draft: boolean5().optional(),
27466
+ target_branch: string5().optional(),
27467
+ babysit: boolean5().optional(),
27468
+ cwd: string5().min(1).optional()
27469
+ }).strict()
27470
+ }).strict(),
27471
+ object4({
27472
+ operation: literal3("hosting.merge"),
27473
+ payload: object4({
27474
+ target_branch: string5().min(1).optional(),
27475
+ source_branch: string5().min(1).optional(),
27476
+ cwd: string5().min(1).optional()
27477
+ }).strict()
27478
+ }).strict(),
27479
+ object4({
27480
+ operation: literal3("hosting.delete_branch"),
27481
+ payload: object4({ branch: string5().min(1), cwd: string5().min(1).optional() }).strict()
27482
+ }).strict(),
27483
+ object4({
27484
+ operation: literal3("youtrack.update"),
27485
+ payload: object4({
27486
+ issueId: string5().min(1),
27487
+ markdown: string5().min(1),
27488
+ minutes: number5().positive().optional()
27489
+ }).strict()
27490
+ }).strict(),
27491
+ object4({
27492
+ operation: literal3("youtrack.time"),
27493
+ payload: object4({
27494
+ issueId: string5().min(1),
27495
+ minutes: number5().positive(),
27496
+ text: string5().optional(),
27497
+ dateMs: number5().finite().optional()
27498
+ }).strict()
27499
+ }).strict(),
27500
+ object4({
27501
+ operation: literal3("youtrack.meeting"),
27502
+ payload: object4({
27503
+ issueId: string5().min(1),
27504
+ minutes: number5().positive(),
27505
+ text: string5().min(1)
27506
+ }).strict()
27507
+ }).strict(),
27508
+ object4({
27509
+ operation: literal3("changelog.apply"),
27510
+ payload: object4({
27511
+ entries: union3([
27512
+ record3(string5(), array3(string5())),
27513
+ array3(object4({ category: string5().min(1), text: string5().min(1) }).strict())
27514
+ ]).optional(),
27515
+ path: string5().optional(),
27516
+ normalize_only: boolean5().optional()
27517
+ }).strict()
27518
+ }).strict(),
27519
+ object4({
27520
+ operation: literal3("context.read"),
27521
+ payload: object4({
27522
+ kind: _enum2([
27523
+ "git",
27524
+ "pr",
27525
+ "youtrack",
27526
+ "github_issue",
27527
+ "gitlab_issue",
27528
+ "changelog",
27529
+ "release",
27530
+ "affected"
27531
+ ]),
27532
+ range: string5().optional(),
27533
+ issueId: string5().optional(),
27534
+ issueUrl: string5().optional(),
27535
+ issueRef: string5().optional(),
27536
+ mode: string5().optional(),
27537
+ specPath: string5().optional(),
27538
+ planPath: string5().optional(),
27539
+ cwd: string5().min(1).optional()
27540
+ }).strict()
27541
+ }).strict()
27542
+ ]);
27263
27543
  // packages/workit-core/src/core.ts
27264
27544
  var revision2 = /^[A-Za-z0-9@][A-Za-z0-9@._/~^{}-]*$/;
27265
27545
  function gitRevisionParts(value) {
@@ -28030,56 +28310,19 @@ var readYouTrackContextConfig = () => {
28030
28310
  return { error: "YouTrack configuration is unavailable" };
28031
28311
  return { data: readOnlyYouTrackConfig(parsed) };
28032
28312
  };
28033
- function tzParts(date, tz) {
28034
- const parts = new Intl.DateTimeFormat("en-GB", {
28035
- timeZone: tz,
28036
- year: "numeric",
28037
- month: "2-digit",
28038
- day: "2-digit",
28039
- hour: "2-digit",
28040
- minute: "2-digit",
28041
- hourCycle: "h23"
28042
- }).formatToParts(date);
28043
- const map = Object.fromEntries(parts.map((p) => [p.type, p.value]));
28044
- return { y: map.year, m: map.month, d: map.day, hour: map.hour, minute: map.minute };
28045
- }
28046
- function youTrackGreeting(configOverride) {
28047
- const cfgPath = configOverride ?? youTrackConfigPath();
28048
- let parsed;
28049
- try {
28050
- parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
28051
- } catch {
28052
- return {
28053
- stdout: "",
28054
- exitCode: 1,
28055
- stderr: fs9.existsSync(cfgPath) ? `${cfgPath} is not valid JSON` : `missing youtrack.json: ${cfgPath}`
28056
- };
28057
- }
28058
- if (!isConfigObject(parsed)) {
28059
- return { stdout: "", exitCode: 1, stderr: `${cfgPath} is not a JSON object` };
28060
- }
28061
- const config = parsed;
28062
- try {
28063
- const tz = String(config.timezone ?? "America/Santiago");
28064
- const now = new Date;
28065
- const { y, m, d, hour, minute } = tzParts(now, tz);
28066
- const cutoff = String(config.greetingCutoff ?? "12:00").split(":");
28067
- const cutoffHour = Number(cutoff[0]);
28068
- const cutoffMinute = Number(cutoff[1] ?? 0);
28069
- const greetings = config.greetings ?? {};
28070
- const isMorning = Number(hour) < cutoffHour || Number(hour) === cutoffHour && Number(minute) < cutoffMinute;
28071
- const greeting = isMorning ? greetings.morning ?? "buenos días" : greetings.afternoon ?? "buenas tardes";
28072
- const mention = String(config.defaultMention ?? "Alejandra.Flores");
28073
- return { stdout: `@${mention} Hola, ${greeting}.
28074
- `, exitCode: 0, stderr: "" };
28075
- } catch (err) {
28076
- return {
28077
- stdout: "",
28078
- exitCode: 1,
28079
- stderr: err instanceof Error ? err.message : "greeting failed"
28080
- };
28081
- }
28082
- }
28313
+ var processTimezone = () => Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
28314
+ var effectiveWorkTimezone = (cfg) => {
28315
+ const configured = cfg?.timezone;
28316
+ return typeof configured === "string" && configured.trim() ? { timezone: configured.trim(), source: "youtrack.json" } : { timezone: processTimezone(), source: "process" };
28317
+ };
28318
+ var DEFAULT_MEETING_WORK_ITEM_TEXT = "Meetings";
28319
+ var meetingWorkItemText = (cfg, item) => {
28320
+ if (typeof item?.workItemText === "string" && item.workItemText)
28321
+ return item.workItemText;
28322
+ if (typeof cfg?.meetingWorkItemText === "string" && cfg.meetingWorkItemText)
28323
+ return cfg.meetingWorkItemText;
28324
+ return DEFAULT_MEETING_WORK_ITEM_TEXT;
28325
+ };
28083
28326
  function youTrackParseDuration(text) {
28084
28327
  const lower = String(text).toLowerCase().trim();
28085
28328
  let total = 0;
@@ -28156,7 +28399,6 @@ function parseIssueRef(input) {
28156
28399
  }
28157
28400
  var contextScripts = {
28158
28401
  config: readYouTrackContextConfig,
28159
- greeting: () => youTrackGreeting(youTrackReadOnlyConfigPath()),
28160
28402
  parseDuration: (text) => youTrackParseDuration(text),
28161
28403
  api: () => ({ error: "YouTrack context is read-only" })
28162
28404
  };
@@ -28185,7 +28427,7 @@ function meetingOptionsFromConfig(cfg) {
28185
28427
  key,
28186
28428
  issue: item.issue,
28187
28429
  label: item.label ?? item.issue,
28188
- workItemText: item.workItemText ?? "Reuniones",
28430
+ workItemText: meetingWorkItemText(cfg, item),
28189
28431
  url: item.url ?? (base && item.issue ? `${base}/issue/${item.issue}` : null)
28190
28432
  }));
28191
28433
  }
@@ -28195,7 +28437,7 @@ function meetingOptionsFromConfig(cfg) {
28195
28437
  key: "general",
28196
28438
  issue,
28197
28439
  label: "General meetings",
28198
- workItemText: "Reuniones",
28440
+ workItemText: meetingWorkItemText(cfg),
28199
28441
  url: base && issue ? `${base}/issue/${issue}` : null
28200
28442
  }
28201
28443
  ];
@@ -28217,17 +28459,9 @@ var readOnlyYouTrackConfig = (cfg) => {
28217
28459
  const baseUrl = publicUrl(cfg.baseUrl);
28218
28460
  if (baseUrl)
28219
28461
  safe.baseUrl = baseUrl;
28220
- for (const key of ["timezone", "greetingCutoff", "defaultMention", "meetingIssue"])
28462
+ for (const key of ["timezone", "meetingIssue", "meetingWorkItemText"])
28221
28463
  if (typeof cfg[key] === "string")
28222
28464
  safe[key] = cfg[key];
28223
- if (cfg.greetings && typeof cfg.greetings === "object" && !Array.isArray(cfg.greetings)) {
28224
- const greetings = {};
28225
- for (const key of ["morning", "afternoon"])
28226
- if (typeof cfg.greetings[key] === "string")
28227
- greetings[key] = cfg.greetings[key];
28228
- if (Object.keys(greetings).length)
28229
- safe.greetings = greetings;
28230
- }
28231
28465
  if (cfg.meetingIssues && typeof cfg.meetingIssues === "object" && !Array.isArray(cfg.meetingIssues)) {
28232
28466
  safe.meetingIssues = Object.fromEntries(Object.entries(cfg.meetingIssues).flatMap(([key, value]) => {
28233
28467
  if (!value || typeof value !== "object" || Array.isArray(value))
@@ -28257,16 +28491,12 @@ function context({
28257
28491
  const cfg = scripts.config();
28258
28492
  if (cfg.error)
28259
28493
  return { error: cfg.error };
28260
- const greeting = scripts.greeting();
28261
- if (greeting.exitCode !== 0) {
28262
- return { error: (greeting.stderr || greeting.stdout || "greeting failed").trim() };
28263
- }
28264
28494
  const safeConfig = readOnlyYouTrackConfig(cfg.data);
28265
28495
  const meetingOptions = meetingOptionsFromConfig(safeConfig);
28266
28496
  if (mode === "meetings" && !issue_id && !issue_url && !issue_ref) {
28267
28497
  return {
28268
28498
  config: safeConfig,
28269
- greeting: greeting.stdout.trim(),
28499
+ workTimezone: effectiveWorkTimezone(safeConfig),
28270
28500
  mode: "meetings",
28271
28501
  requiresMeetingChoice: true,
28272
28502
  meetingOptions,
@@ -28295,7 +28525,7 @@ function context({
28295
28525
  const selectedMeeting = meetingOptions.find((m) => m.issue === issue);
28296
28526
  return {
28297
28527
  config: safeConfig,
28298
- greeting: greeting.stdout.trim(),
28528
+ workTimezone: effectiveWorkTimezone(safeConfig),
28299
28529
  issueId: issue,
28300
28530
  issueUrl,
28301
28531
  mode: mode ?? (selectedMeeting ? "meetings" : "task"),