@brainervirus/workit-pi 2.6.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/workit.js CHANGED
@@ -2,9 +2,9 @@
2
2
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3
3
 
4
4
  // packages/workit-core/src/core.ts
5
- import { spawnSync as spawnSync4 } from "node:child_process";
5
+ import { spawnSync as spawnSync6 } from "node:child_process";
6
6
  import { existsSync as existsSync6, realpathSync as realpathSync8 } from "node:fs";
7
- import path13 from "node:path";
7
+ import path15 from "node:path";
8
8
 
9
9
  // packages/workit-core/src/core/task-contract.ts
10
10
  import { createHash, randomUUID } from "node:crypto";
@@ -8121,7 +8121,37 @@ var candidateSchema = object({
8121
8121
  path: ["id"]
8122
8122
  });
8123
8123
  });
8124
- var evidenceSchema = object({
8124
+ var checkObservationSchema = object({
8125
+ observer: literal("workit_cli"),
8126
+ name: nonEmpty.nullable(),
8127
+ configured: boolean2(),
8128
+ argv: array(text).min(1),
8129
+ shell: boolean2(),
8130
+ cwd: nonEmpty,
8131
+ exitCode: safeInteger,
8132
+ durationMs: safeInteger.min(0),
8133
+ timedOut: boolean2(),
8134
+ head: text.nullable(),
8135
+ tree: text.nullable(),
8136
+ dirty: boolean2().nullable(),
8137
+ signal: text.nullable(),
8138
+ treeAfter: text.nullable(),
8139
+ modifiedWorktree: boolean2(),
8140
+ base: text.nullable(),
8141
+ patchId: text.nullable(),
8142
+ environment: object({
8143
+ platform: nonEmpty,
8144
+ arch: nonEmpty,
8145
+ vars: record(nonEmpty, text.nullable())
8146
+ }).strict(),
8147
+ logDigest: text.nullable(),
8148
+ logRef: text.nullable(),
8149
+ logTail: array(text).max(40),
8150
+ ledgerRowId: text.nullable(),
8151
+ attestation: object({ host: nonEmpty, session: text.nullable(), agentId: text.nullable() }).strict().nullable()
8152
+ }).strict();
8153
+ var CHECK_OBSERVATION_PATH = "evidence.*.data.observation";
8154
+ var evidenceFields = {
8125
8155
  kind: _enum(["check", "review", "investigation", "artifact"]),
8126
8156
  claim: text,
8127
8157
  requirementIds: array(digest),
@@ -8132,7 +8162,9 @@ var evidenceSchema = object({
8132
8162
  refs: array(refSchema),
8133
8163
  exitCode: safeInteger.nullable(),
8134
8164
  reviewContext: refSchema.nullable()
8135
- }).strict();
8165
+ };
8166
+ var evidenceSchema = object({ ...evidenceFields, observation: checkObservationSchema.optional() }).strict();
8167
+ var reportedEvidenceSchema = object(evidenceFields).strict();
8136
8168
  var evidenceEvaluationSchema = object({
8137
8169
  evidenceId: id,
8138
8170
  status: _enum(["passed", "failed", "missing", "skipped", "stale"]),
@@ -8464,7 +8496,7 @@ var evidenceOperations = {
8464
8496
  action: literal("record"),
8465
8497
  ...taskId,
8466
8498
  expectedRevision: revision.optional(),
8467
- evidence: evidenceSchema
8499
+ evidence: reportedEvidenceSchema
8468
8500
  })
8469
8501
  };
8470
8502
  var findingOperations = {
@@ -10964,10 +10996,10 @@ function assertProductWriteAllowed(input) {
10964
10996
  }
10965
10997
 
10966
10998
  // packages/workit-core/src/core/task-evaluation.ts
10967
- import { createHash as createHash7 } from "node:crypto";
10968
- import * as fs8 from "node:fs";
10969
- import { spawnSync as spawnSync3 } from "node:child_process";
10970
- import path11 from "node:path";
10999
+ import { createHash as createHash8 } from "node:crypto";
11000
+ import * as fs10 from "node:fs";
11001
+ import { spawnSync as spawnSync5 } from "node:child_process";
11002
+ import path13 from "node:path";
10971
11003
 
10972
11004
  // packages/workit-core/src/core/authority.ts
10973
11005
  import { createHash as createHash6 } from "node:crypto";
@@ -13174,16 +13206,407 @@ function reconcileAction(input) {
13174
13206
  var bindingCovers = scopeCovers;
13175
13207
  var verifyDecisionContent = verifyContentRefs;
13176
13208
 
13209
+ // packages/workit-core/src/check-config.ts
13210
+ import { spawnSync as spawnSync3 } from "node:child_process";
13211
+ import fs8 from "node:fs";
13212
+ import path11 from "node:path";
13213
+ var SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"];
13214
+ var CHECKS_FILE = "workit.checks.json";
13215
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
13216
+ var SHELL_META = /[|&;<>()$`\\*?[\]{}~!#]/u;
13217
+ function splitCommand(command) {
13218
+ const argv = [];
13219
+ let current = "";
13220
+ let quote = null;
13221
+ let started = false;
13222
+ for (const char of command) {
13223
+ if (quote) {
13224
+ if (char === quote)
13225
+ quote = null;
13226
+ else
13227
+ current += char;
13228
+ continue;
13229
+ }
13230
+ if (char === '"' || char === "'") {
13231
+ quote = char;
13232
+ started = true;
13233
+ continue;
13234
+ }
13235
+ if (/\s/u.test(char)) {
13236
+ if (started)
13237
+ argv.push(current);
13238
+ current = "";
13239
+ started = false;
13240
+ continue;
13241
+ }
13242
+ if (SHELL_META.test(char))
13243
+ return new Error(`"${command}" uses shell syntax (${char}); configure an argv array instead`);
13244
+ current += char;
13245
+ started = true;
13246
+ }
13247
+ if (quote)
13248
+ return new Error(`"${command}" has an unterminated quote`);
13249
+ if (started)
13250
+ argv.push(current);
13251
+ return argv.length ? argv : new Error("empty command");
13252
+ }
13253
+ var git = (cwd, args) => {
13254
+ const run = spawnSync3("git", args, {
13255
+ cwd,
13256
+ encoding: "utf8",
13257
+ timeout: 20000,
13258
+ killSignal: "SIGKILL",
13259
+ windowsHide: true
13260
+ });
13261
+ const value = run.status === 0 ? (run.stdout ?? "").trim() : "";
13262
+ return value || null;
13263
+ };
13264
+ function checkRoot(cwd) {
13265
+ return path11.resolve(git(cwd, ["rev-parse", "--show-toplevel"]) ?? cwd);
13266
+ }
13267
+ var packageManager = (root) => {
13268
+ const has = (file) => fs8.existsSync(path11.join(root, file));
13269
+ if (has("bun.lock") || has("bun.lockb"))
13270
+ return "bun";
13271
+ if (has("pnpm-lock.yaml"))
13272
+ return "pnpm";
13273
+ if (has("yarn.lock"))
13274
+ return "yarn";
13275
+ return "npm";
13276
+ };
13277
+ var scriptCheck = (pm, name) => {
13278
+ const argv = [pm, "run", name];
13279
+ const accepts = [argv];
13280
+ if (pm === "pnpm" || pm === "yarn" || pm === "npm" && name === "test")
13281
+ accepts.push([pm, name]);
13282
+ return { name, argv, accepts };
13283
+ };
13284
+ var CHECK_NAME = /^[a-z0-9][a-z0-9:._-]{0,63}$/u;
13285
+ function detectedChecks(root) {
13286
+ const read = (file) => {
13287
+ try {
13288
+ return fs8.readFileSync(path11.join(root, file), "utf8");
13289
+ } catch {
13290
+ return null;
13291
+ }
13292
+ };
13293
+ const checks = [];
13294
+ const add = (check) => {
13295
+ if (!checks.some((item) => item.name === check.name))
13296
+ checks.push(check);
13297
+ };
13298
+ const manifestText = read("package.json");
13299
+ if (manifestText !== null) {
13300
+ let manifest = null;
13301
+ try {
13302
+ manifest = JSON.parse(manifestText);
13303
+ } catch {}
13304
+ const scripts = isRecord2(manifest) && isRecord2(manifest.scripts) ? manifest.scripts : {};
13305
+ const pm = packageManager(root);
13306
+ for (const name of SCRIPT_CHECKS) {
13307
+ const script = scripts[name];
13308
+ if (typeof script === "string" && script.trim() !== "")
13309
+ add(scriptCheck(pm, name));
13310
+ }
13311
+ }
13312
+ const plain = (name, argv) => ({ name, argv, accepts: [argv] });
13313
+ if (read("go.mod") !== null)
13314
+ add(plain("test", ["go", "test", "./..."]));
13315
+ if (read("Cargo.toml") !== null)
13316
+ add(plain("test", ["cargo", "test"]));
13317
+ if (read("pytest.ini") !== null || /\bpytest\b/u.test(read("pyproject.toml") ?? ""))
13318
+ add(plain("test", ["pytest"]));
13319
+ const makefile = read("Makefile") ?? read("makefile") ?? read("GNUmakefile");
13320
+ if (makefile !== null && /^test\s*:(?!=)/mu.test(makefile))
13321
+ add(plain("test", ["make", "test"]));
13322
+ return checks;
13323
+ }
13324
+ var GATES = ["testing", "verification"];
13325
+ function loadCheckConfig(cwd) {
13326
+ const root = checkRoot(cwd);
13327
+ const file = path11.join(root, CHECKS_FILE);
13328
+ const none = (error = null) => ({
13329
+ source: error ? CHECKS_FILE : "none",
13330
+ root,
13331
+ checks: [],
13332
+ gates: {},
13333
+ error
13334
+ });
13335
+ let text = null;
13336
+ try {
13337
+ text = fs8.readFileSync(file, "utf8");
13338
+ } catch (error) {
13339
+ if (error.code !== "ENOENT")
13340
+ return none(`cannot read ${CHECKS_FILE}: ${error.message}`);
13341
+ }
13342
+ if (text === null) {
13343
+ const checks = detectedChecks(root);
13344
+ return checks.length ? { source: "defaults", root, checks, gates: {}, error: null } : none();
13345
+ }
13346
+ let parsed;
13347
+ try {
13348
+ parsed = JSON.parse(text);
13349
+ } catch {
13350
+ return none(`${CHECKS_FILE} is not valid JSON`);
13351
+ }
13352
+ if (!isRecord2(parsed) || !isRecord2(parsed.checks))
13353
+ return none(`${CHECKS_FILE} must be {"checks":{"<name>":"<command>"|["argv",…]}}`);
13354
+ const checks = [];
13355
+ for (const [name, value] of Object.entries(parsed.checks)) {
13356
+ if (!CHECK_NAME.test(name))
13357
+ return none(`${CHECKS_FILE}: check name "${name}" must match ${CHECK_NAME.source}`);
13358
+ const argv = typeof value === "string" ? splitCommand(value) : Array.isArray(value) && value.length > 0 && value.every((item) => typeof item === "string" && item.length > 0) ? value : new Error("expected a command string or a non-empty argv array");
13359
+ if (argv instanceof Error)
13360
+ return none(`${CHECKS_FILE}: check "${name}": ${argv.message}`);
13361
+ checks.push({ name, argv, accepts: [argv] });
13362
+ }
13363
+ const gates = {};
13364
+ if (parsed.gates !== undefined) {
13365
+ if (!isRecord2(parsed.gates))
13366
+ return none(`${CHECKS_FILE}: gates must be {"testing":"<name>"|["<name>",…]}`);
13367
+ for (const [gate, value] of Object.entries(parsed.gates)) {
13368
+ if (!GATES.includes(gate))
13369
+ return none(`${CHECKS_FILE}: unknown gate "${gate}" (use ${GATES.join(" or ")})`);
13370
+ const names = typeof value === "string" ? [value] : value;
13371
+ if (!Array.isArray(names) || names.length === 0 || !names.every((name) => typeof name === "string" && checks.some((c) => c.name === name)))
13372
+ return none(`${CHECKS_FILE}: gates.${gate} must name configured checks`);
13373
+ gates[gate] = names;
13374
+ }
13375
+ }
13376
+ return { source: CHECKS_FILE, root, checks, gates, error: null };
13377
+ }
13378
+ var sameArgv = (left, right) => left.length === right.length && left.every((item, index) => item === right[index]);
13379
+ function matchesNamedCheck(config, name, argv, cwd) {
13380
+ if (config.error || name === null || cwd !== ".")
13381
+ return false;
13382
+ const check = config.checks.find((item) => item.name === name);
13383
+ return Boolean(check?.accepts.some((accepted) => sameArgv(accepted, argv)));
13384
+ }
13385
+ function gateCheckNames(config, dimension) {
13386
+ const explicit = config.gates[dimension];
13387
+ if (explicit)
13388
+ return explicit;
13389
+ const names = config.checks.map((check) => check.name);
13390
+ if (dimension === "testing")
13391
+ return names.includes("test") ? ["test"] : [];
13392
+ return names;
13393
+ }
13394
+ var workitOnPath;
13395
+ function workitAvailable(env = process.env) {
13396
+ if (workitOnPath !== undefined)
13397
+ return workitOnPath;
13398
+ const windows = process.platform === "win32";
13399
+ const exts = windows ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
13400
+ const dirs = (env.PATH ?? env.Path ?? "").split(path11.delimiter).filter(Boolean);
13401
+ workitOnPath = dirs.some((dir) => exts.some((ext) => {
13402
+ try {
13403
+ return fs8.statSync(path11.join(dir, `workit${ext}`)).isFile();
13404
+ } catch {
13405
+ return false;
13406
+ }
13407
+ }));
13408
+ return workitOnPath;
13409
+ }
13410
+ function checkCommand(args, version, onPath = workitAvailable()) {
13411
+ return onPath ? `workit check ${args}` : `npx -y @brainervirus/workit-cli@${version} check ${args}`;
13412
+ }
13413
+
13414
+ // packages/workit-core/src/git/rev.ts
13415
+ import { spawnSync as spawnSync4 } from "node:child_process";
13416
+ import { createHash as createHash7 } from "node:crypto";
13417
+ import fs9 from "node:fs";
13418
+ import os2 from "node:os";
13419
+ import path12 from "node:path";
13420
+ var MAX_BUFFER = 256 * 1024 * 1024;
13421
+ var GIT_TIMEOUTS = { local: 20000, worktree: 120000, network: 15000 };
13422
+ var git2 = (cwd, args, options = {}) => {
13423
+ const result = spawnSync4("git", args, {
13424
+ cwd,
13425
+ encoding: "utf8",
13426
+ env: options.env ?? process.env,
13427
+ input: options.input,
13428
+ maxBuffer: MAX_BUFFER,
13429
+ timeout: options.timeoutMs ?? GIT_TIMEOUTS.local,
13430
+ killSignal: "SIGKILL",
13431
+ windowsHide: true
13432
+ });
13433
+ const error = result.error;
13434
+ return {
13435
+ ok: result.status === 0,
13436
+ stdout: result.stdout ?? "",
13437
+ stderr: result.stderr ?? "",
13438
+ timedOut: error?.code === "ETIMEDOUT"
13439
+ };
13440
+ };
13441
+ var line = (run) => {
13442
+ if (!run.ok)
13443
+ return null;
13444
+ const value = run.stdout.trim();
13445
+ return value ? value : null;
13446
+ };
13447
+ function networkGitInvocation(cwd, args, timeoutMs) {
13448
+ const seconds = Math.max(1, Math.ceil(timeoutMs / 1000));
13449
+ const env = {
13450
+ ...process.env,
13451
+ GIT_TERMINAL_PROMPT: "0",
13452
+ GIT_ASKPASS: "",
13453
+ SSH_ASKPASS: "",
13454
+ SSH_ASKPASS_REQUIRE: "never",
13455
+ GCM_INTERACTIVE: "never"
13456
+ };
13457
+ const base = process.env.GIT_SSH_COMMAND || line(git2(cwd, ["config", "--get", "core.sshCommand"])) || (process.env.GIT_SSH ? null : "ssh");
13458
+ if (base)
13459
+ env.GIT_SSH_COMMAND = `${base} -o BatchMode=yes -o ConnectTimeout=${Math.min(10, seconds)}`;
13460
+ return {
13461
+ args: ["-c", "http.lowSpeedLimit=1", "-c", `http.lowSpeedTime=${seconds}`, ...args],
13462
+ env
13463
+ };
13464
+ }
13465
+ var WATCHDOG = `
13466
+ const cp = require("node:child_process");
13467
+ const { ms, args } = JSON.parse(process.env.WORKIT_GIT_WATCHDOG);
13468
+ delete process.env.WORKIT_GIT_WATCHDOG;
13469
+ const win = process.platform === "win32";
13470
+ const child = cp.spawn("git", args, { stdio: "inherit", detached: !win, windowsHide: true });
13471
+ const killTree = () => {
13472
+ if (!child.pid) return;
13473
+ if (win) cp.spawnSync("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
13474
+ else try { process.kill(-child.pid, "SIGKILL"); } catch {}
13475
+ };
13476
+ let timedOut = false;
13477
+ const timer = setTimeout(() => { timedOut = true; killTree(); }, ms);
13478
+ child.on("error", () => { clearTimeout(timer); process.exit(127); });
13479
+ child.on("exit", (code) => {
13480
+ clearTimeout(timer);
13481
+ if (!win) killTree();
13482
+ process.exit(timedOut ? 124 : code ?? 1);
13483
+ });
13484
+ `;
13485
+ var WATCHDOG_TIMEOUT_EXIT = 124;
13486
+ var gitNetwork = (cwd, args, timeoutMs) => {
13487
+ const invocation = networkGitInvocation(cwd, args, timeoutMs);
13488
+ const result = spawnSync4(process.execPath, ["-e", WATCHDOG], {
13489
+ cwd,
13490
+ encoding: "utf8",
13491
+ env: {
13492
+ ...invocation.env,
13493
+ WORKIT_GIT_WATCHDOG: JSON.stringify({ ms: timeoutMs, args: invocation.args })
13494
+ },
13495
+ maxBuffer: MAX_BUFFER,
13496
+ timeout: timeoutMs + 1e4,
13497
+ killSignal: "SIGKILL",
13498
+ windowsHide: true
13499
+ });
13500
+ const error = result.error;
13501
+ return {
13502
+ ok: result.status === 0,
13503
+ stdout: result.stdout ?? "",
13504
+ stderr: result.stderr ?? "",
13505
+ timedOut: result.status === WATCHDOG_TIMEOUT_EXIT || error?.code === "ETIMEDOUT"
13506
+ };
13507
+ };
13508
+ function headSha(cwd) {
13509
+ return line(git2(cwd, ["rev-parse", "--verify", "-q", "HEAD^{commit}"]));
13510
+ }
13511
+ var emptyTree = (cwd) => line(git2(cwd, ["mktree"], { input: "" }));
13512
+ var MAX_UNTRACKED_BYTES = 5 * 1024 * 1024;
13513
+ var seedIndex = (from, to) => {
13514
+ fs9.copyFileSync(from, to);
13515
+ const { atimeNs, mtimeNs } = fs9.statSync(from, { bigint: true });
13516
+ const seconds = (ns) => (Number(ns / 1000n) - 1) / 1e6;
13517
+ fs9.utimesSync(to, seconds(atimeNs), seconds(mtimeNs));
13518
+ };
13519
+ function worktreeTree(cwd, options = {}) {
13520
+ const top = line(git2(cwd, ["rev-parse", "--show-toplevel"]));
13521
+ const indexPath = line(git2(cwd, ["rev-parse", "--path-format=absolute", "--git-path", "index"]));
13522
+ if (!top || !indexPath)
13523
+ return null;
13524
+ const cap = options.maxUntrackedBytes ?? MAX_UNTRACKED_BYTES;
13525
+ const timeoutMs = options.timeoutMs ?? GIT_TIMEOUTS.worktree;
13526
+ const scratch = fs9.mkdtempSync(path12.join(os2.tmpdir(), "workit-tree-"));
13527
+ try {
13528
+ const tempIndex = path12.join(scratch, "index");
13529
+ if (fs9.existsSync(indexPath))
13530
+ seedIndex(indexPath, tempIndex);
13531
+ const env = { ...process.env, GIT_INDEX_FILE: tempIndex };
13532
+ const untracked = git2(top, ["ls-files", "--others", "--exclude-standard", "-z"], {
13533
+ env,
13534
+ timeoutMs
13535
+ });
13536
+ if (!untracked.ok)
13537
+ return null;
13538
+ const skipped = [];
13539
+ for (const file of untracked.stdout.split("\x00").filter(Boolean)) {
13540
+ try {
13541
+ const stat = fs9.statSync(path12.join(top, file));
13542
+ if (stat.isFile() && stat.size > cap)
13543
+ skipped.push({ path: file, size: stat.size, mtimeMs: Math.trunc(stat.mtimeMs) });
13544
+ } catch {}
13545
+ }
13546
+ skipped.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
13547
+ const added = skipped.length ? git2(top, ["add", "-A", "--pathspec-from-file=-", "--pathspec-file-nul"], {
13548
+ env,
13549
+ timeoutMs,
13550
+ input: [".", ...skipped.map((file) => `:(exclude,literal)${file.path}`)].join("\x00")
13551
+ }) : git2(top, ["add", "-A"], { env, timeoutMs });
13552
+ if (!added.ok)
13553
+ return null;
13554
+ const tree = line(git2(top, ["write-tree"], { env, timeoutMs }));
13555
+ if (!tree)
13556
+ return null;
13557
+ const base = line(git2(top, ["rev-parse", "--verify", "-q", "HEAD^{tree}"])) ?? emptyTree(top);
13558
+ const key = skipped.length ? `sha256:${createHash7("sha256").update(JSON.stringify({ tree, skipped })).digest("hex")}` : tree;
13559
+ return { tree, key, dirty: tree !== base || skipped.length > 0, skipped };
13560
+ } finally {
13561
+ fs9.rmSync(scratch, { recursive: true, force: true });
13562
+ }
13563
+ }
13564
+ function worktreeSignal(cwd, options = {}) {
13565
+ const top = line(git2(cwd, ["rev-parse", "--show-toplevel"]));
13566
+ if (!top)
13567
+ return null;
13568
+ const status = git2(top, [
13569
+ "status",
13570
+ "--porcelain=v1",
13571
+ "-z",
13572
+ "--untracked-files=all",
13573
+ "--no-renames",
13574
+ "--ignore-submodules=dirty"
13575
+ ], {
13576
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
13577
+ timeoutMs: options.timeoutMs ?? GIT_TIMEOUTS.worktree
13578
+ });
13579
+ if (!status.ok)
13580
+ return null;
13581
+ const hash = createHash7("sha256");
13582
+ hash.update(`${headSha(top) ?? "unborn"}\x00`);
13583
+ for (const entry of status.stdout.split("\x00")) {
13584
+ if (entry.length < 4)
13585
+ continue;
13586
+ const file = entry.slice(3);
13587
+ const absolute = path12.join(top, file);
13588
+ let stat = "-";
13589
+ try {
13590
+ const info = fs9.lstatSync(absolute, { bigint: true });
13591
+ stat = `${info.mode.toString(8)}:${info.size}:${info.mtimeNs}`;
13592
+ if (info.isDirectory())
13593
+ stat += `:${headSha(absolute) ?? "-"}`;
13594
+ } catch {}
13595
+ hash.update(`${entry.slice(0, 2)}\x00${file}\x00${stat}\x00`);
13596
+ }
13597
+ return `sig:${hash.digest("hex")}`;
13598
+ }
13599
+
13177
13600
  // packages/workit-core/src/core/task-evaluation.ts
13178
- var digestBytes3 = (value) => createHash7("sha256").update(value).digest("hex");
13179
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path11.sep}`);
13180
- var relative = (root, candidate) => path11.relative(root, candidate).split(path11.sep).join("/") || ".";
13601
+ var digestBytes3 = (value) => createHash8("sha256").update(value).digest("hex");
13602
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path13.sep}`);
13603
+ var relative = (root, candidate) => path13.relative(root, candidate).split(path13.sep).join("/") || ".";
13181
13604
  var canonicalPath = (value) => {
13182
13605
  if (typeof value !== "string" || !value)
13183
13606
  return null;
13184
13607
  if (value.includes("\\") || value.split("/").includes(".."))
13185
13608
  return null;
13186
- const normalized = path11.posix.normalize(value);
13609
+ const normalized = path13.posix.normalize(value);
13187
13610
  if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
13188
13611
  return null;
13189
13612
  return normalized === "." ? "." : normalized.replace(/\/$/, "");
@@ -13219,7 +13642,7 @@ var compareCodeUnits2 = (left, right) => {
13219
13642
  return left.length - right.length;
13220
13643
  };
13221
13644
  var gitPaths = (root) => {
13222
- const result = spawnSync3("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
13645
+ const result = spawnSync5("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
13223
13646
  cwd: root,
13224
13647
  encoding: "buffer"
13225
13648
  });
@@ -13231,21 +13654,21 @@ var gitPaths = (root) => {
13231
13654
  git: !/not a git repository/i.test(stderr)
13232
13655
  };
13233
13656
  }
13234
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path11.sep).join("/")) : [];
13235
- const stagedDeleted = spawnSync3("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
13657
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path13.sep).join("/")) : [];
13658
+ const stagedDeleted = spawnSync5("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
13236
13659
  if (stagedDeleted.status !== 0) {
13237
13660
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
13238
13661
  if (!/not a git repository/i.test(stderr))
13239
13662
  return { paths, uncertain: true, git: true };
13240
13663
  } else if (stagedDeleted.stdout) {
13241
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path11.sep).join("/")));
13664
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path13.sep).join("/")));
13242
13665
  }
13243
13666
  return { paths: [...new Set(paths)], uncertain: false, git: true };
13244
13667
  };
13245
13668
  var walk = (root, directory, output) => {
13246
13669
  let names;
13247
13670
  try {
13248
- names = fs8.readdirSync(directory);
13671
+ names = fs10.readdirSync(directory);
13249
13672
  } catch {
13250
13673
  return true;
13251
13674
  }
@@ -13253,10 +13676,10 @@ var walk = (root, directory, output) => {
13253
13676
  for (const name of names) {
13254
13677
  if (name === ".git" || name === ".workit")
13255
13678
  continue;
13256
- const target = path11.join(directory, name);
13679
+ const target = path13.join(directory, name);
13257
13680
  let stat;
13258
13681
  try {
13259
- stat = fs8.lstatSync(target);
13682
+ stat = fs10.lstatSync(target);
13260
13683
  } catch {
13261
13684
  uncertain = true;
13262
13685
  continue;
@@ -13271,14 +13694,14 @@ var walk = (root, directory, output) => {
13271
13694
  var scopeRoots = (root, scope) => {
13272
13695
  const roots = [];
13273
13696
  for (const item of scope.paths.length ? scope.paths : ["."]) {
13274
- const target = path11.resolve(root, item);
13697
+ const target = path13.resolve(root, item);
13275
13698
  if (!inside(root, target))
13276
13699
  return failure2("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` }] });
13277
13700
  let ancestor = target;
13278
- while (!fs8.existsSync(ancestor) && ancestor !== root)
13279
- ancestor = path11.dirname(ancestor);
13701
+ while (!fs10.existsSync(ancestor) && ancestor !== root)
13702
+ ancestor = path13.dirname(ancestor);
13280
13703
  try {
13281
- if (!inside(root, fs8.realpathSync(ancestor)))
13704
+ if (!inside(root, fs10.realpathSync(ancestor)))
13282
13705
  return failure2("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` }] });
13283
13706
  } catch {
13284
13707
  return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
@@ -13290,7 +13713,7 @@ var scopeRoots = (root, scope) => {
13290
13713
  function captureCandidate(root, scope, environment = []) {
13291
13714
  let checkout;
13292
13715
  try {
13293
- checkout = fs8.realpathSync(root);
13716
+ checkout = fs10.realpathSync(root);
13294
13717
  } catch {
13295
13718
  return failure2("invalid_input", "candidate root does not exist");
13296
13719
  }
@@ -13308,11 +13731,11 @@ function captureCandidate(root, scope, environment = []) {
13308
13731
  if (item === ".git" || item === ".workit")
13309
13732
  continue;
13310
13733
  names.add(item);
13311
- if (!fs8.existsSync(target))
13734
+ if (!fs10.existsSync(target))
13312
13735
  continue;
13313
13736
  let stat;
13314
13737
  try {
13315
- stat = fs8.lstatSync(target);
13738
+ stat = fs10.lstatSync(target);
13316
13739
  } catch {
13317
13740
  uncertain = true;
13318
13741
  continue;
@@ -13328,17 +13751,17 @@ function captureCandidate(root, scope, environment = []) {
13328
13751
  for (const item of [...names].toSorted()) {
13329
13752
  if (!scopeMatches(item, normalizedScope))
13330
13753
  continue;
13331
- const target = path11.join(checkout, item);
13754
+ const target = path13.join(checkout, item);
13332
13755
  try {
13333
- const stat = fs8.lstatSync(target);
13756
+ const stat = fs10.lstatSync(target);
13334
13757
  if (stat.isSymbolicLink()) {
13335
- const link = fs8.readlinkSync(target);
13758
+ const link = fs10.readlinkSync(target);
13336
13759
  files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
13337
13760
  } else if (stat.isFile()) {
13338
13761
  files.push({
13339
13762
  path: item,
13340
13763
  kind: "file",
13341
- digest: digestBytes3(fs8.readFileSync(target)),
13764
+ digest: digestBytes3(fs10.readFileSync(target)),
13342
13765
  executable: (stat.mode & 73) !== 0
13343
13766
  });
13344
13767
  } else if (!stat.isDirectory()) {
@@ -13363,7 +13786,7 @@ function captureCandidate(root, scope, environment = []) {
13363
13786
  const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
13364
13787
  if (values.some(({ value }) => value === null))
13365
13788
  completeness = "uncertain";
13366
- const headResult = spawnSync3("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
13789
+ const headResult = spawnSync5("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
13367
13790
  if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
13368
13791
  completeness = "uncertain";
13369
13792
  const head = headResult.status === 0 ? headResult.stdout.trim() : null;
@@ -13401,7 +13824,24 @@ var sameSession = (left, right) => {
13401
13824
  return left !== null && other !== null && left.host === other.host && left.handle === other.handle;
13402
13825
  };
13403
13826
  var evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope);
13404
- function evaluateEvidence(task, candidate) {
13827
+ var memo2 = (compute) => {
13828
+ let value;
13829
+ return () => {
13830
+ if (value === undefined)
13831
+ value = compute();
13832
+ return value;
13833
+ };
13834
+ };
13835
+ var treeFreshness = (root) => ({
13836
+ mode: "tree",
13837
+ current: memo2(() => worktreeTree(root)?.key ?? null)
13838
+ });
13839
+ var signalFreshness = (root) => ({
13840
+ mode: "signal",
13841
+ current: memo2(() => worktreeSignal(root))
13842
+ });
13843
+ var isObservedCheck = (entry) => entry.data.kind === "check" && entry.data.observation?.observer === "workit_cli" && entry.provenance?.kind === "host_observed" && entry.provenance.host === "workit_cli";
13844
+ function evaluateEvidence(task, candidate, freshness) {
13405
13845
  const current = candidate ?? task.candidates.at(-1) ?? null;
13406
13846
  return task.evidence.map((entry) => {
13407
13847
  const evidence = entry.data;
@@ -13411,6 +13851,25 @@ function evaluateEvidence(task, candidate) {
13411
13851
  status: "failed",
13412
13852
  reason: "failed evidence remains historical"
13413
13853
  };
13854
+ const observation = isObservedCheck(entry) ? evidence.observation : undefined;
13855
+ if (observation && observation.tree !== null) {
13856
+ if (task.status === "closed")
13857
+ return { evidenceId: entry.id, status: evidence.result, reason: "recorded before close" };
13858
+ if (observation.modifiedWorktree)
13859
+ return {
13860
+ evidenceId: entry.id,
13861
+ status: "stale",
13862
+ reason: "the check changed the worktree while it ran; ignore or clean its outputs"
13863
+ };
13864
+ const recorded = freshness?.mode === "signal" ? observation.signal : observation.tree;
13865
+ if (recorded !== null && freshness && recorded === freshness.current())
13866
+ return { evidenceId: entry.id, status: evidence.result, reason: "worktree is unchanged" };
13867
+ return {
13868
+ evidenceId: entry.id,
13869
+ status: "stale",
13870
+ reason: freshness?.mode === "signal" ? "the worktree may have changed since the check ran (quick signal; inspect re-checks exactly)" : "the worktree changed since the check ran"
13871
+ };
13872
+ }
13414
13873
  if (evidence.kind === "check" || evidence.kind === "review") {
13415
13874
  if (!evidence.beforeCandidateId || !evidence.candidateId)
13416
13875
  return {
@@ -13494,8 +13953,45 @@ var expectedKinds = (dimension, ruleId) => {
13494
13953
  return "decision";
13495
13954
  return "worker report";
13496
13955
  };
13497
- function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller) {
13498
- const evidence = evaluateEvidence(task, candidate);
13956
+ var observedGate = (requirement) => (requirement.dimension === "testing" || requirement.dimension === "verification") && requirement.before === "close";
13957
+ function evaluateObservedGate(task, requirement, statuses, config) {
13958
+ const names = gateCheckNames(config, requirement.dimension);
13959
+ const observed = task.evidence.filter(isObservedCheck);
13960
+ const fresh = observed.filter((entry) => statuses.get(entry.id) === "passed" && entry.data.observation?.exitCode === 0);
13961
+ const named = (entry) => {
13962
+ const observation = entry.data.observation;
13963
+ return observation.configured && !observation.shell && names.includes(observation.name ?? "") && matchesNamedCheck(config, observation.name, observation.argv, observation.cwd);
13964
+ };
13965
+ const considered = observed.map((entry) => entry.id);
13966
+ const run = checkCommand(names[0] ?? "<name>", runtimeVersion());
13967
+ if (config.error)
13968
+ return {
13969
+ satisfied: [],
13970
+ considered,
13971
+ reason: `check config is invalid (${config.error}); fix it, then run ${run}`
13972
+ };
13973
+ if (!names.length)
13974
+ return {
13975
+ satisfied: [],
13976
+ considered,
13977
+ reason: requirement.dimension === "testing" && config.checks.length ? `no \`test\` check is configured; add one (or gates.testing) to ${CHECKS_FILE}, then run ${checkCommand("test", runtimeVersion())}` : `no checks are configured or detected; add ${CHECKS_FILE} (committed, so it shows in the diff), e.g. {"checks":{"test":"<your test command>"}}, then run ${checkCommand("test", runtimeVersion())}`
13978
+ };
13979
+ const satisfied = fresh.filter(named);
13980
+ if (satisfied.length)
13981
+ return {
13982
+ satisfied: satisfied.map((entry) => entry.id),
13983
+ considered,
13984
+ reason: `a fresh passing configured check (${names.join("|")}) was observed by workit check`
13985
+ };
13986
+ const reported = task.evidence.some((entry) => entry.data.kind === "check" && !isObservedCheck(entry) && entry.data.requirementIds.includes(requirement.id));
13987
+ const reason = fresh.length ? `ad-hoc checks do not satisfy a configured gate; run ${run}` : observed.some((entry) => statuses.get(entry.id) === "stale") ? `observed checks are stale (the worktree changed); run ${run}` : observed.some((entry) => statuses.get(entry.id) === "failed") ? `the observed check failed; fix it and run ${run}` : reported ? `agent-reported checks are notes, not evidence; run ${run}` : `no CLI-observed check (needs kind:check observed by workit check); run ${run}`;
13988
+ return { satisfied: [], considered, reason };
13989
+ }
13990
+ function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller, freshness = treeFreshness(checkoutRoot)) {
13991
+ const evidence = evaluateEvidence(task, candidate, freshness);
13992
+ const statuses = new Map(evidence.map((item) => [item.evidenceId, item.status]));
13993
+ let checkConfig;
13994
+ const configFor = () => checkConfig ??= loadCheckConfig(checkoutRoot);
13499
13995
  if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
13500
13996
  return task.policy.requirements.map((requirement) => ({
13501
13997
  requirementId: requirement.id,
@@ -13505,8 +14001,19 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
13505
14001
  reason: "stored policy version is unsupported; reassessment is required"
13506
14002
  }));
13507
14003
  return (task.policy?.requirements ?? []).map((requirement) => {
14004
+ const gate = observedGate(requirement) ? evaluateObservedGate(task, requirement, statuses, configFor()) : null;
14005
+ if (gate?.satisfied.length)
14006
+ return {
14007
+ requirementId: requirement.id,
14008
+ status: "satisfied",
14009
+ evidenceIds: gate.satisfied,
14010
+ decisionIds: [],
14011
+ reason: gate.reason
14012
+ };
13508
14013
  const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
13509
14014
  const passed = related.filter(({ entry, evaluation }) => {
14015
+ if (gate)
14016
+ return false;
13510
14017
  if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
13511
14018
  return false;
13512
14019
  if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
@@ -13569,6 +14076,14 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
13569
14076
  reason: "an applicable approved limitation permits the missing evidence"
13570
14077
  };
13571
14078
  const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
14079
+ if (gate)
14080
+ return {
14081
+ requirementId: requirement.id,
14082
+ status: unavailable ? "unavailable" : "unsatisfied",
14083
+ evidenceIds: [...new Set([...gate.considered, ...related.map(({ entry }) => entry.id)])],
14084
+ decisionIds: [],
14085
+ reason: unavailable ? "required capability is unavailable" : gate.reason
14086
+ };
13572
14087
  if (!unavailable && related.length > 0 && !passed.length)
13573
14088
  return {
13574
14089
  requirementId: requirement.id,
@@ -13745,8 +14260,11 @@ policy, and reapplies the call, returning busy under persistent contention. A
13745
14260
  revision_conflict means a revision you passed is stale: re-read the record
13746
14261
  before deciding whether to retry.
13747
14262
  A solo edit does not need writer acquisition; use it when concurrent checkout
13748
- writers need coordination. Record only observed facts and checks. Evidence can
13749
- become stale when its bound candidate changes; reconcile findings against the
14263
+ writers need coordination. Record only observed facts and checks. Close-time
14264
+ testing and verification gates accept only a configured check the CLI ran:
14265
+ \`workit check <name>\` (\`npx -y @brainervirus/workit-cli check <name>\` when
14266
+ \`workit\` is not on PATH); a recorded check result is a note and an ad-hoc
14267
+ \`workit check -- <cmd>\` never satisfies a gate. Evidence can become stale when its bound candidate or tree changes; reconcile findings against the
13750
14268
  current candidate before recording completion.
13751
14269
 
13752
14270
  Workit validates domain policy against the actual action target, configured
@@ -13807,7 +14325,7 @@ function reconcileResume(view, observations = []) {
13807
14325
  const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
13808
14326
  if (!candidate.ok)
13809
14327
  return candidate;
13810
- const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
14328
+ const staleEvidenceIds = evaluateEvidence(view.task, candidate.data, treeFreshness(view.workspace.root)).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
13811
14329
  const blockers = [...view.task.progress.blockers];
13812
14330
  if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
13813
14331
  blockers.push({
@@ -13932,7 +14450,7 @@ var shellBranchPolicyViolation = (root, command) => {
13932
14450
  // packages/workit-core/src/core/external-action.ts
13933
14451
  import { execFileSync as execFileSync3 } from "node:child_process";
13934
14452
  import { realpathSync as realpathSync6 } from "node:fs";
13935
- import path12 from "node:path";
14453
+ import path14 from "node:path";
13936
14454
  var chainStepSchema = union([
13937
14455
  string2().min(1),
13938
14456
  object({ branch: string2().min(1) }).strict(),
@@ -14126,7 +14644,7 @@ var planCommitDescriptor = (content) => {
14126
14644
  if (!steps || typeof branch !== "string" || !branch)
14127
14645
  return null;
14128
14646
  const cwd = value.payload.cwd;
14129
- if (cwd !== undefined && (typeof cwd !== "string" || !path12.isAbsolute(cwd)))
14647
+ if (cwd !== undefined && (typeof cwd !== "string" || !path14.isAbsolute(cwd)))
14130
14648
  return null;
14131
14649
  const head = value.payload.resolved?.head;
14132
14650
  return {
@@ -14269,7 +14787,7 @@ var descriptorTargetRoot = (store, operation) => {
14269
14787
  const cwd = descriptor.payload?.cwd;
14270
14788
  if (cwd === undefined)
14271
14789
  return store.root;
14272
- return typeof cwd === "string" && path12.isAbsolute(cwd) ? realpathSync6(cwd) : null;
14790
+ return typeof cwd === "string" && path14.isAbsolute(cwd) ? realpathSync6(cwd) : null;
14273
14791
  } catch {
14274
14792
  return null;
14275
14793
  }
@@ -14380,7 +14898,7 @@ var branchSetupIntent = (descriptor) => {
14380
14898
  const { target_branch, resolved } = value.payload;
14381
14899
  if (typeof target_branch !== "string" || !target_branch || !resolved)
14382
14900
  return null;
14383
- if (value.payload.cwd !== undefined && (typeof value.payload.cwd !== "string" || !path12.isAbsolute(value.payload.cwd)))
14901
+ if (value.payload.cwd !== undefined && (typeof value.payload.cwd !== "string" || !path14.isAbsolute(value.payload.cwd)))
14384
14902
  return null;
14385
14903
  if (typeof resolved.base_branch !== "string" || typeof resolved.target_exists !== "boolean")
14386
14904
  return null;
@@ -14497,7 +15015,7 @@ var externalActionTargetRoot = (operation, coordinationRoot) => {
14497
15015
  const payload = descriptor.payload;
14498
15016
  const cwd = typeof payload.cwd === "string" ? payload.cwd : coordinationRoot;
14499
15017
  try {
14500
- return realpathSync6(path12.resolve(coordinationRoot, cwd));
15018
+ return realpathSync6(path14.resolve(coordinationRoot, cwd));
14501
15019
  } catch {
14502
15020
  return null;
14503
15021
  }
@@ -15746,10 +16264,7 @@ class WorkitCore {
15746
16264
  if (finding.data.disposition !== "fixed")
15747
16265
  return finding;
15748
16266
  const withEntry = { ...current, candidates, evidence: [...current.evidence, entry] };
15749
- const statuses = new Map(evaluateEvidence(withEntry, currentCandidate.data).map((item) => [
15750
- item.evidenceId,
15751
- item.status
15752
- ]));
16267
+ const statuses = new Map(evaluateEvidence(withEntry, currentCandidate.data, treeFreshness(this.store.root)).map((item) => [item.evidenceId, item.status]));
15753
16268
  const verified = withEntry.evidence.some((item) => findingVerificationPasses(finding.data.candidateId, {
15754
16269
  kind: item.data.kind,
15755
16270
  candidateId: item.data.candidateId,
@@ -15764,6 +16279,71 @@ class WorkitCore {
15764
16279
  return changed;
15765
16280
  return success(changed.data.revision, null, changed.data.evidence.at(-1));
15766
16281
  }
16282
+ observeCheck(request) {
16283
+ return this.retryOmittedRevisions(request, () => this.observeCheckOnce(request));
16284
+ }
16285
+ observeCheckOnce(request) {
16286
+ const root = this.contextRootError();
16287
+ if (!root.ok)
16288
+ return root;
16289
+ if (this.context.caller.host !== "workit_cli" || (this.context.workerId ?? null) !== null)
16290
+ return failure2("permission_denied", "only the workit CLI records observed checks");
16291
+ const observation = checkObservationSchema.safeParse(request?.observation);
16292
+ if (!observation.success)
16293
+ return failure2("invalid_input", "check observation is invalid", {
16294
+ fields: observation.error.issues.map((issue) => ({
16295
+ path: issue.path.join("."),
16296
+ reason: issue.message
16297
+ }))
16298
+ });
16299
+ const task = this.store.readTask(request.taskId);
16300
+ if (!task.ok)
16301
+ return task;
16302
+ if (task.data.status === "closed")
16303
+ return failure2("invalid_transition", "closed task cannot record evidence");
16304
+ const observed = observation.data;
16305
+ let candidate = null;
16306
+ if (observed.tree === null) {
16307
+ const captured = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
16308
+ if (!captured.ok)
16309
+ return captured;
16310
+ candidate = captured.data;
16311
+ }
16312
+ const label = observed.name ?? observed.argv.join(" ");
16313
+ const data = {
16314
+ kind: "check",
16315
+ claim: `workit check ${label}`.slice(0, 500),
16316
+ requirementIds: [],
16317
+ ...candidate ? { beforeCandidateId: candidate.id, candidateId: candidate.id } : {},
16318
+ result: observed.exitCode === 0 ? "passed" : "failed",
16319
+ summary: `${observed.configured ? "configured" : "ad-hoc"} check exited ${observed.exitCode}${observed.timedOut ? " (timed out)" : ""} in ${observed.durationMs} ms`,
16320
+ refs: [],
16321
+ exitCode: observed.exitCode,
16322
+ reviewContext: null,
16323
+ observation: observed
16324
+ };
16325
+ const changed = this.store.mutateTask(task.data.id, request.expectedRevision ?? task.data.revision, (current, mutation) => {
16326
+ const candidates = candidate === null || current.candidates.some((item) => item.id === candidate.id) ? current.candidates : [...current.candidates, candidate];
16327
+ const critical = [...new Set([...current.critical ?? [], CHECK_OBSERVATION_PATH])];
16328
+ return success(mutation.revision, null, {
16329
+ ...current,
16330
+ critical,
16331
+ candidates,
16332
+ evidence: [
16333
+ ...current.evidence,
16334
+ {
16335
+ id: newId(),
16336
+ recordedAt: mutation.now,
16337
+ provenance: provenance(this.context, "host_observed"),
16338
+ data
16339
+ }
16340
+ ]
16341
+ });
16342
+ }, trustedNow(this.context));
16343
+ if (!changed.ok)
16344
+ return changed;
16345
+ return success(changed.data.revision, null, changed.data.evidence.at(-1));
16346
+ }
15767
16347
  decision(request) {
15768
16348
  return this.recordDecision(request);
15769
16349
  }
@@ -15999,7 +16579,7 @@ class WorkitCore {
15999
16579
  const current = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
16000
16580
  if (!current.ok)
16001
16581
  return current;
16002
- const evaluations = evaluateEvidence(task.data, current.data);
16582
+ const evaluations = evaluateEvidence(task.data, current.data, treeFreshness(this.store.root));
16003
16583
  const verified = evidence.some((entry) => {
16004
16584
  const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
16005
16585
  return entry.recordedAt >= finding.recordedAt && findingVerificationPasses(finding.data.candidateId, {
@@ -16703,7 +17283,8 @@ class WorkitCore {
16703
17283
  if (!current.ok)
16704
17284
  return current;
16705
17285
  const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
16706
- const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller);
17286
+ const tree = treeFreshness(this.store.root);
17287
+ const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
16707
17288
  return success(task.revision, workspace.data.revision, {
16708
17289
  id: task.id,
16709
17290
  revision: task.revision,
@@ -16720,7 +17301,7 @@ class WorkitCore {
16720
17301
  writer: evaluationWorkspace.writer
16721
17302
  });
16722
17303
  }
16723
- compactContext(taskId) {
17304
+ compactContext(taskId, freshness) {
16724
17305
  const root = this.contextRootError();
16725
17306
  if (!root.ok)
16726
17307
  return root;
@@ -16730,12 +17311,12 @@ class WorkitCore {
16730
17311
  const helper = this.helperTaskGuard(task.data, true);
16731
17312
  if (!helper.ok)
16732
17313
  return helper;
16733
- const view = this.view(task.data, false);
17314
+ const view = this.view(task.data, false, freshness ?? signalFreshness(this.store.root));
16734
17315
  if (!view.ok)
16735
17316
  return view;
16736
17317
  return success(view.revision, view.workspaceRevision, compactTaskContext(view.data));
16737
17318
  }
16738
- view(task, capture = true) {
17319
+ view(task, capture = true, freshness = treeFreshness(this.store.root)) {
16739
17320
  const workspace = this.store.readWorkspace();
16740
17321
  if (!workspace.ok)
16741
17322
  return workspace;
@@ -16746,13 +17327,14 @@ class WorkitCore {
16746
17327
  if (!current.ok)
16747
17328
  return current;
16748
17329
  const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
16749
- const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller);
17330
+ const tree = freshness;
17331
+ const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
16750
17332
  return success(task.revision, workspace.data.revision, {
16751
17333
  task,
16752
17334
  workspace: evaluationWorkspace,
16753
17335
  capabilities: this.context.capabilities,
16754
17336
  requirements,
16755
- evidence: evaluateEvidence(task, current.data)
17337
+ evidence: evaluateEvidence(task, current.data, tree)
16756
17338
  });
16757
17339
  }
16758
17340
  reconcileResume(view, observations = []) {
@@ -16979,13 +17561,14 @@ class WorkitCore {
16979
17561
  return current;
16980
17562
  const withCandidate = current.data;
16981
17563
  const taskForView = withCandidate.id === task.data.candidates.at(-1)?.id ? task.data : { ...task.data, candidates: [...task.data.candidates, withCandidate] };
16982
- const requirements = evaluateRequirements(taskForView, workspace.data, this.context.capabilities, withCandidate, this.store.root, this.context.caller);
17564
+ const tree = treeFreshness(this.store.root);
17565
+ const requirements = evaluateRequirements(taskForView, workspace.data, this.context.capabilities, withCandidate, this.store.root, this.context.caller, tree);
16983
17566
  const view = {
16984
17567
  task: taskForView,
16985
17568
  workspace: workspace.data,
16986
17569
  capabilities: this.context.capabilities,
16987
17570
  requirements,
16988
- evidence: evaluateEvidence(taskForView, withCandidate)
17571
+ evidence: evaluateEvidence(taskForView, withCandidate, tree)
16989
17572
  };
16990
17573
  const closure = evaluateClosure(input.outcome, view);
16991
17574
  if (!closure.ok)
@@ -17049,22 +17632,22 @@ function resolveGitRevision(root, value) {
17049
17632
  }
17050
17633
  function resolveInside(root, candidate) {
17051
17634
  const base = realpathSync8(root);
17052
- const target = path13.resolve(base, candidate);
17053
- if (target !== base && !target.startsWith(base + path13.sep)) {
17635
+ const target = path15.resolve(base, candidate);
17636
+ if (target !== base && !target.startsWith(base + path15.sep)) {
17054
17637
  throw new Error("path must stay inside repository root");
17055
17638
  }
17056
17639
  let ancestor = target;
17057
17640
  while (!existsSync6(ancestor))
17058
- ancestor = path13.dirname(ancestor);
17641
+ ancestor = path15.dirname(ancestor);
17059
17642
  const canonicalAncestor = realpathSync8(ancestor);
17060
- if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path13.sep)) {
17643
+ if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path15.sep)) {
17061
17644
  throw new Error("path must stay inside repository root");
17062
17645
  }
17063
17646
  return target;
17064
17647
  }
17065
17648
  function run2(root, executable, args, env = {}) {
17066
17649
  const cwd = realpathSync8(root);
17067
- const result = spawnSync4(executable, args, {
17650
+ const result = spawnSync6(executable, args, {
17068
17651
  cwd,
17069
17652
  encoding: "utf8",
17070
17653
  env: { ...process.env, ...env }
@@ -17101,7 +17684,7 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
17101
17684
  };
17102
17685
  });
17103
17686
  // packages/workit-core/src/hooks/context.ts
17104
- import path14 from "node:path";
17687
+ import path16 from "node:path";
17105
17688
  var boundTo = (entry, session) => entry.sessions.some((item) => item.host === session.host && item.handle === session.handle);
17106
17689
  var newestFirst = (left, right) => right.updatedAt.localeCompare(left.updatedAt);
17107
17690
  var sessionTaskEntry = (entries, session) => entries.filter((entry) => entry.status !== "closed" && boundTo(entry, session)).toSorted(newestFirst)[0] ?? null;
@@ -17126,6 +17709,19 @@ var filesKey = (files) => {
17126
17709
  return signatures.some((item) => item !== "-" && racySignature(item)) ? null : signatures.join("\x00");
17127
17710
  };
17128
17711
  var CACHE_LIMIT = 64;
17712
+ var SIGNAL_TTL_MS = 1500;
17713
+ var signals = new Map;
17714
+ var recentSignal = (root) => {
17715
+ const now = Date.now();
17716
+ const cached = signals.get(root);
17717
+ if (cached && now - cached.at < SIGNAL_TTL_MS)
17718
+ return cached.value;
17719
+ const value = worktreeSignal(root);
17720
+ signals.set(root, { at: now, value });
17721
+ if (signals.size > CACHE_LIMIT)
17722
+ signals.delete(signals.keys().next().value);
17723
+ return value;
17724
+ };
17129
17725
  var cache = new Map;
17130
17726
  function sessionCompactContext(store, session, context, selection = "session-bound") {
17131
17727
  const listed = store.listTaskIndex();
@@ -17149,18 +17745,26 @@ function entryCompactContext(store, entry, session, context) {
17149
17745
  capabilities: context.capabilities
17150
17746
  });
17151
17747
  const hit = cache.get(slot);
17152
- if (hit && hit.key === key && hit.filesKey !== null && hit.filesKey === filesKey(hit.files))
17748
+ if (hit && hit.key === key && hit.filesKey !== null && hit.filesKey === filesKey(hit.files) && (hit.signal === null || hit.signal === recentSignal(store.root)))
17153
17749
  return hit.value;
17154
17750
  const task = store.readTask(entry.id);
17155
17751
  if (!task.ok)
17156
17752
  return null;
17157
- const files = task.data.decisions.flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path14.resolve(store.root, ref.path)] : []));
17158
- const observed = filesKey(files);
17159
- const compact = new WorkitCore(store, context).compactContext(entry.id);
17753
+ const freshness = { mode: "signal", current: () => recentSignal(store.root) };
17754
+ const observed = task.data.evidence.some(isObservedCheck);
17755
+ const files = task.data.decisions.flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path16.resolve(store.root, ref.path)] : []));
17756
+ const observedFiles = filesKey(files);
17757
+ const compact = new WorkitCore(store, context).compactContext(entry.id, freshness);
17160
17758
  if (!compact.ok)
17161
17759
  return null;
17162
17760
  cache.delete(slot);
17163
- cache.set(slot, { key, files, filesKey: observed, value: compact.data });
17761
+ cache.set(slot, {
17762
+ key,
17763
+ files,
17764
+ filesKey: observedFiles,
17765
+ signal: observed ? freshness.current() : null,
17766
+ value: compact.data
17767
+ });
17164
17768
  if (cache.size > CACHE_LIMIT)
17165
17769
  cache.delete(cache.keys().next().value);
17166
17770
  return compact.data;
@@ -17327,9 +17931,9 @@ var WORKIT_SKILL_ALIASES = {
17327
17931
  if (false) {}
17328
17932
 
17329
17933
  // packages/workit-core/src/core/pr-create.ts
17330
- import fs9 from "node:fs";
17331
- import path15 from "node:path";
17332
- import { spawnSync as spawnSync5 } from "node:child_process";
17934
+ import fs11 from "node:fs";
17935
+ import path17 from "node:path";
17936
+ import { spawnSync as spawnSync7 } from "node:child_process";
17333
17937
  import { pathToFileURL } from "node:url";
17334
17938
  // packages/workit-core/src/forge/pr-body.ts
17335
17939
  function parseGhIssue(value) {
@@ -17419,13 +18023,13 @@ var cliRepository = (remote, provider, cfg) => {
17419
18023
  };
17420
18024
  function whichOnPath(tool) {
17421
18025
  const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
17422
- for (const dir of (process.env.PATH ?? "").split(path15.delimiter)) {
18026
+ for (const dir of (process.env.PATH ?? "").split(path17.delimiter)) {
17423
18027
  if (!dir)
17424
18028
  continue;
17425
18029
  for (const name of names) {
17426
- const candidate = path15.join(dir, name);
18030
+ const candidate = path17.join(dir, name);
17427
18031
  try {
17428
- fs9.accessSync(candidate, fs9.constants.X_OK);
18032
+ fs11.accessSync(candidate, fs11.constants.X_OK);
17429
18033
  return candidate;
17430
18034
  } catch {}
17431
18035
  }
@@ -17434,11 +18038,11 @@ function whichOnPath(tool) {
17434
18038
  }
17435
18039
  var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
17436
18040
  function repoRoot(cwd) {
17437
- const result = spawnSync5("git", ["rev-parse", "--show-toplevel"], { cwd, encoding: "utf8" });
18041
+ const result = spawnSync7("git", ["rev-parse", "--show-toplevel"], { cwd, encoding: "utf8" });
17438
18042
  return result.status === 0 ? (result.stdout ?? "").trim() : cwd;
17439
18043
  }
17440
18044
  var pushRemote = (cwd) => {
17441
- const result = spawnSync5("git", ["remote", "get-url", "--push", "--all", "origin"], {
18045
+ const result = spawnSync7("git", ["remote", "get-url", "--push", "--all", "origin"], {
17442
18046
  cwd,
17443
18047
  encoding: "utf8"
17444
18048
  });
@@ -17449,7 +18053,7 @@ var pushRemote = (cwd) => {
17449
18053
  };
17450
18054
  var safePushUrl = (remote) => {
17451
18055
  const raw = remote.trim();
17452
- if (path15.isAbsolute(raw))
18056
+ if (path17.isAbsolute(raw))
17453
18057
  return pathToFileURL(raw).href;
17454
18058
  if (!raw.includes("://")) {
17455
18059
  const scp = /^(?:([^@\s]+)@)?([^:]+):(.+)$/u.exec(raw);
@@ -17474,7 +18078,7 @@ var pushRemoteIdentity = (remote) => {
17474
18078
  const raw = remote.trim();
17475
18079
  if (!raw)
17476
18080
  return null;
17477
- if (path15.isAbsolute(raw))
18081
+ if (path17.isAbsolute(raw))
17478
18082
  return pathToFileURL(raw).href;
17479
18083
  if (!raw.includes("://")) {
17480
18084
  const scp = /^(?:([^@\s]+)@)?([^:]+):(.+)$/u.exec(raw);
@@ -17498,7 +18102,7 @@ var pushTargetIsStable = (cwd, remote) => {
17498
18102
  const target = safePushUrl(remote);
17499
18103
  if (!target)
17500
18104
  return false;
17501
- const result = spawnSync5("git", ["config", "--get-regexp", "^url\\..*\\.(insteadof|pushinsteadof)$"], { cwd, encoding: "utf8" });
18105
+ const result = spawnSync7("git", ["config", "--get-regexp", "^url\\..*\\.(insteadof|pushinsteadof)$"], { cwd, encoding: "utf8" });
17502
18106
  if (result.status !== 0 && result.status !== 1)
17503
18107
  return false;
17504
18108
  const prefixes = (result.stdout ?? "").split(/\r?\n/u).map((line) => line.trim().slice(line.trim().indexOf(" ") + 1)).filter(Boolean);
@@ -17535,7 +18139,7 @@ function prCreate(env, cwd) {
17535
18139
  return {
17536
18140
  error: "origin must have exactly one supported push URL for a reserved hosting action"
17537
18141
  };
17538
- const localOnlyTarget = path15.isAbsolute(pushUrl) || pushUrl.startsWith("file://");
18142
+ const localOnlyTarget = path17.isAbsolute(pushUrl) || pushUrl.startsWith("file://");
17539
18143
  if ((!localOnlyTarget || env.WORKIT_EXPECTED_API_HOST) && !hostingApiHostMatches(pushUrl, identity.host))
17540
18144
  return { error: "hosting API host does not match the push destination" };
17541
18145
  if (env.WORKIT_EXPECTED_REMOTE && env.WORKIT_EXPECTED_REMOTE !== targetIdentity)
@@ -17546,12 +18150,12 @@ function prCreate(env, cwd) {
17546
18150
  const resolvedDefault = String(cfg.defaultTargetBranch ?? policy.defaultTargetBranch ?? "develop");
17547
18151
  const target = targetOverride || resolvedDefault;
17548
18152
  const title = String(env.WF_PR_TITLE ?? "");
17549
- const br = spawnSync5("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
18153
+ const br = spawnSync7("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
17550
18154
  cwd: root,
17551
18155
  encoding: "utf8"
17552
18156
  });
17553
18157
  const branch = br.status === 0 ? (br.stdout ?? "").trim() : "";
17554
- const sourceResult = branch ? spawnSync5("git", ["rev-parse", "--verify", `refs/heads/${branch}`], {
18158
+ const sourceResult = branch ? spawnSync7("git", ["rev-parse", "--verify", `refs/heads/${branch}`], {
17555
18159
  cwd: root,
17556
18160
  encoding: "utf8"
17557
18161
  }) : null;
@@ -17560,7 +18164,7 @@ function prCreate(env, cwd) {
17560
18164
  return { error: "approved PR source branch and commit are incomplete" };
17561
18165
  if (env.WORKIT_EXPECTED_SOURCE_BRANCH && env.WORKIT_EXPECTED_SOURCE_BRANCH !== branch || env.WORKIT_EXPECTED_SOURCE_COMMIT && env.WORKIT_EXPECTED_SOURCE_COMMIT !== sourceCommit)
17562
18166
  return { error: "approved PR source branch or commit changed before execution" };
17563
- const mergeBaseResult = policy.integration === "merge" ? spawnSync5("git", ["rev-parse", "--verify", `refs/heads/${target}`], {
18167
+ const mergeBaseResult = policy.integration === "merge" ? spawnSync7("git", ["rev-parse", "--verify", `refs/heads/${target}`], {
17564
18168
  cwd: root,
17565
18169
  encoding: "utf8"
17566
18170
  }) : null;
@@ -17576,7 +18180,7 @@ function prCreate(env, cwd) {
17576
18180
  targetBranch: target,
17577
18181
  stderr: "source or target branch commit could not be resolved"
17578
18182
  };
17579
- const merge = spawnSync5("git", ["merge", "--no-ff", sourceCommit, "-m", title], {
18183
+ const merge = spawnSync7("git", ["merge", "--no-ff", sourceCommit, "-m", title], {
17580
18184
  cwd: root,
17581
18185
  encoding: "utf8"
17582
18186
  });
@@ -17587,16 +18191,16 @@ function prCreate(env, cwd) {
17587
18191
  targetBranch: target,
17588
18192
  stderr: (merge.stderr ?? "").slice(0, 800)
17589
18193
  };
17590
- const mergedCommit = spawnSync5("git", ["rev-parse", "HEAD"], {
18194
+ const mergedCommit = spawnSync7("git", ["rev-parse", "HEAD"], {
17591
18195
  cwd: root,
17592
18196
  encoding: "utf8"
17593
18197
  });
17594
18198
  if (mergedCommit.status !== 0)
17595
18199
  return { error: "merge commit could not be resolved", mode: "merge" };
17596
- const updateTarget = spawnSync5("git", ["update-ref", `refs/heads/${target}`, mergedCommit.stdout.trim(), mergeBaseCommit], { cwd: root, encoding: "utf8" });
18200
+ const updateTarget = spawnSync7("git", ["update-ref", `refs/heads/${target}`, mergedCommit.stdout.trim(), mergeBaseCommit], { cwd: root, encoding: "utf8" });
17597
18201
  if (updateTarget.status !== 0)
17598
18202
  return { error: "merge target branch changed before update", mode: "merge" };
17599
- const push = spawnSync5("git", ["push", targetUrl, `${mergedCommit.stdout.trim()}:refs/heads/${target}`], { cwd: root, encoding: "utf8", env: { ...process.env, PATH: process.env.PATH ?? "" } });
18203
+ const push = spawnSync7("git", ["push", targetUrl, `${mergedCommit.stdout.trim()}:refs/heads/${target}`], { cwd: root, encoding: "utf8", env: { ...process.env, PATH: process.env.PATH ?? "" } });
17600
18204
  if (push.status !== 0)
17601
18205
  return {
17602
18206
  error: "push failed",
@@ -17613,7 +18217,7 @@ function prCreate(env, cwd) {
17613
18217
  output: (push.stdout ?? "").trim()
17614
18218
  };
17615
18219
  };
17616
- const co = spawnSync5("git", ["checkout", "--detach", mergeBaseCommit], {
18220
+ const co = spawnSync7("git", ["checkout", "--detach", mergeBaseCommit], {
17617
18221
  cwd: root,
17618
18222
  encoding: "utf8"
17619
18223
  });
@@ -17625,7 +18229,7 @@ function prCreate(env, cwd) {
17625
18229
  stderr: (co.stderr ?? "").slice(0, 800)
17626
18230
  };
17627
18231
  const result = finish();
17628
- spawnSync5("git", ["checkout", branch], { cwd: root, encoding: "utf8" });
18232
+ spawnSync7("git", ["checkout", branch], { cwd: root, encoding: "utf8" });
17629
18233
  return result;
17630
18234
  }
17631
18235
  const pr = cfg.pr ?? {};
@@ -17633,9 +18237,9 @@ function prCreate(env, cwd) {
17633
18237
  const draft = String(env.WF_PR_DRAFT ?? "false").toLowerCase() === "true";
17634
18238
  let baseUrl = cfg.youtrack_base_url;
17635
18239
  if (!baseUrl) {
17636
- const ytCfg = process.env.WORKFLOW_YOUTRACK_CONFIG ?? path15.join(path15.dirname(String(cfg.configPath)), "youtrack.json");
18240
+ const ytCfg = process.env.WORKFLOW_YOUTRACK_CONFIG ?? path17.join(path17.dirname(String(cfg.configPath)), "youtrack.json");
17637
18241
  try {
17638
- const yt = JSON.parse(fs9.readFileSync(ytCfg, "utf8"));
18242
+ const yt = JSON.parse(fs11.readFileSync(ytCfg, "utf8"));
17639
18243
  if (yt && typeof yt === "object")
17640
18244
  baseUrl = yt.baseUrl;
17641
18245
  } catch {}
@@ -17651,7 +18255,7 @@ function prCreate(env, cwd) {
17651
18255
  const push = pr.pushBranch !== false;
17652
18256
  const skipConfirm = pr.confirmSkip !== false;
17653
18257
  const remoteBranchTip = () => {
17654
- const read = spawnSync5("git", ["ls-remote", targetUrl, `refs/heads/${branch}`], {
18258
+ const read = spawnSync7("git", ["ls-remote", targetUrl, `refs/heads/${branch}`], {
17655
18259
  cwd: root,
17656
18260
  encoding: "utf8",
17657
18261
  env: { ...process.env, PATH: process.env.PATH ?? "" }
@@ -17667,7 +18271,7 @@ function prCreate(env, cwd) {
17667
18271
  stderr: branch ? "source branch commit could not be resolved" : "empty current branch (detached HEAD or unborn HEAD)"
17668
18272
  };
17669
18273
  if (push) {
17670
- const pushed = spawnSync5("git", ["push", "-u", targetUrl, `${sourceCommit}:refs/heads/${branch}`], { cwd: root, encoding: "utf8", env: { ...process.env, PATH: process.env.PATH ?? "" } });
18274
+ const pushed = spawnSync7("git", ["push", "-u", targetUrl, `${sourceCommit}:refs/heads/${branch}`], { cwd: root, encoding: "utf8", env: { ...process.env, PATH: process.env.PATH ?? "" } });
17671
18275
  if (pushed.status !== 0)
17672
18276
  return { error: "push failed", provider, stderr: (pushed.stderr ?? "").slice(0, 800) };
17673
18277
  }
@@ -17712,12 +18316,12 @@ function prCreate(env, cwd) {
17712
18316
  cmd.push("--draft");
17713
18317
  cmdEnv = { ...process.env, PATH: process.env.PATH ?? "", GH_HOST: identity.host };
17714
18318
  }
17715
- const result = spawnSync5(cmd[0], cmd.slice(1), { cwd: root, encoding: "utf8", env: cmdEnv });
18319
+ const result = spawnSync7(cmd[0], cmd.slice(1), { cwd: root, encoding: "utf8", env: cmdEnv });
17716
18320
  if (result.status !== 0) {
17717
18321
  const err = (result.stderr ?? result.stdout ?? "").trim();
17718
18322
  let hint = null;
17719
18323
  if (provider === "gitlab" && (err.includes("409") || err.toLowerCase().includes("already exists"))) {
17720
- const list = spawnSync5("glab", ["mr", "list", `--source-branch=${branch}`, "--output=json"], {
18324
+ const list = spawnSync7("glab", ["mr", "list", `--source-branch=${branch}`, "--output=json"], {
17721
18325
  cwd: root,
17722
18326
  encoding: "utf8",
17723
18327
  env: cmdEnv
@@ -17776,7 +18380,7 @@ function mergePr(cwd, opts) {
17776
18380
  return { error: "approved hosting account changed before execution" };
17777
18381
  if (!/^[a-f0-9]{40,64}$/i.test(opts.sourceCommit))
17778
18382
  return { error: "approved merge source commit is invalid" };
17779
- const currentCommit = spawnSync5("git", ["rev-parse", "--verify", `refs/heads/${opts.source}`], {
18383
+ const currentCommit = spawnSync7("git", ["rev-parse", "--verify", `refs/heads/${opts.source}`], {
17780
18384
  cwd: root,
17781
18385
  encoding: "utf8"
17782
18386
  });
@@ -17790,7 +18394,7 @@ function mergePr(cwd, opts) {
17790
18394
  return { error: `workflow CLI missing (required for ${provider})` };
17791
18395
  const owner = repoPath.split("/")[0];
17792
18396
  const endpoint = provider === "github" ? `repos/${repository.replace(/^[^/]+\//u, "")}/pulls?state=open&head=${encodeURIComponent(`${owner}:${opts.source}`)}&base=${encodeURIComponent(opts.target)}&per_page=100` : `projects/${encodeURIComponent(repoPath)}/merge_requests?state=opened&source_branch=${encodeURIComponent(opts.source)}&target_branch=${encodeURIComponent(opts.target)}&per_page=100`;
17793
- const listOpen = () => spawnSync5(provider === "github" ? "gh" : "glab", ["api", endpoint], {
18397
+ const listOpen = () => spawnSync7(provider === "github" ? "gh" : "glab", ["api", endpoint], {
17794
18398
  cwd: root,
17795
18399
  encoding: "utf8",
17796
18400
  env: {
@@ -17850,7 +18454,7 @@ function mergePr(cwd, opts) {
17850
18454
  cmd.push("--delete-branch");
17851
18455
  cmdEnv = { ...process.env, PATH: process.env.PATH ?? "", GH_HOST: identity.host };
17852
18456
  }
17853
- const result = spawnSync5(cmd[0], cmd.slice(1), { cwd: root, encoding: "utf8", env: cmdEnv });
18457
+ const result = spawnSync7(cmd[0], cmd.slice(1), { cwd: root, encoding: "utf8", env: cmdEnv });
17854
18458
  if (result.status !== 0)
17855
18459
  return {
17856
18460
  error: "merge failed",
@@ -17867,9 +18471,9 @@ function mergePr(cwd, opts) {
17867
18471
  }
17868
18472
 
17869
18473
  // packages/workit-core/src/core/repo-context.ts
17870
- import { spawnSync as spawnSync6 } from "node:child_process";
18474
+ import { spawnSync as spawnSync8 } from "node:child_process";
17871
18475
  import { existsSync as existsSync7, readFileSync as readFileSync8, readdirSync as readdirSync4 } from "node:fs";
17872
- import path16 from "node:path";
18476
+ import path18 from "node:path";
17873
18477
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
17874
18478
  try {
17875
18479
  gitRevisionParts(value);
@@ -17896,7 +18500,7 @@ var invalidRange = (cwd) => ({
17896
18500
  cwd
17897
18501
  });
17898
18502
  var runGit = (cwd, args) => {
17899
- const result = spawnSync6("git", args, { cwd, encoding: "utf8" });
18503
+ const result = spawnSync8("git", args, { cwd, encoding: "utf8" });
17900
18504
  return {
17901
18505
  stdout: (result.stdout ?? "").trimEnd(),
17902
18506
  stderr: (result.stderr ?? "").trimEnd(),
@@ -18003,7 +18607,7 @@ var findPrTemplate = (cwd) => {
18003
18607
  ]) {
18004
18608
  const parts = rel.split("/");
18005
18609
  const base = parts[parts.length - 1];
18006
- const dir = parts.length > 1 ? path16.join(cwd, ...parts.slice(0, -1)) : cwd;
18610
+ const dir = parts.length > 1 ? path18.join(cwd, ...parts.slice(0, -1)) : cwd;
18007
18611
  const actual = probeCaseInsensitive(dir, base);
18008
18612
  if (actual)
18009
18613
  return [...parts.slice(0, -1), actual].join("/");
@@ -18034,7 +18638,7 @@ var diffStatForRange = (cwd, range) => {
18034
18638
  return runGit(cwd, ["diff", "--stat", "--"]).stdout;
18035
18639
  };
18036
18640
  var packageScripts = (cwd, keys) => {
18037
- const pkgPath = path16.join(cwd, "package.json");
18641
+ const pkgPath = path18.join(cwd, "package.json");
18038
18642
  if (!existsSync7(pkgPath))
18039
18643
  return [];
18040
18644
  try {
@@ -18060,9 +18664,9 @@ var documentationFiles = (cwd) => {
18060
18664
  if (entry.isDirectory()) {
18061
18665
  if (excluded.has(entry.name))
18062
18666
  continue;
18063
- walk(path16.join(dir, entry.name), depth + 1);
18667
+ walk(path18.join(dir, entry.name), depth + 1);
18064
18668
  } else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
18065
- matches.push("./" + path16.relative(cwd, path16.join(dir, entry.name)).replaceAll("\\", "/"));
18669
+ matches.push("./" + path18.relative(cwd, path18.join(dir, entry.name)).replaceAll("\\", "/"));
18066
18670
  }
18067
18671
  }
18068
18672
  };
@@ -18078,7 +18682,7 @@ var readTrimmed2 = (file, maxLines) => {
18078
18682
  `);
18079
18683
  };
18080
18684
  function prReadyContext(root, range) {
18081
- const cwd = path16.resolve(repoRoot2(root));
18685
+ const cwd = path18.resolve(repoRoot2(root));
18082
18686
  if (range !== undefined && !isResolvableContextRange(cwd, range))
18083
18687
  return invalidRange(cwd);
18084
18688
  let stdout = "";
@@ -18136,7 +18740,7 @@ ${status.stderr}` : "");
18136
18740
  stdout += `template_path: ${template}
18137
18741
 
18138
18742
  `;
18139
- stdout += readTrimmed2(path16.join(cwd, template), 220);
18743
+ stdout += readTrimmed2(path18.join(cwd, template), 220);
18140
18744
  } else {
18141
18745
  stdout += `template_path: none
18142
18746
 
@@ -18146,7 +18750,7 @@ ${status.stderr}` : "");
18146
18750
  stdout += `
18147
18751
  `;
18148
18752
  stdout += printSection("Recent Validation Signals");
18149
- if (existsSync7(path16.join(cwd, "package.json"))) {
18753
+ if (existsSync7(path18.join(cwd, "package.json"))) {
18150
18754
  stdout += `package.json detected. Common scripts:
18151
18755
  `;
18152
18756
  const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
@@ -18155,7 +18759,7 @@ ${status.stderr}` : "");
18155
18759
  `) + `
18156
18760
  `;
18157
18761
  }
18158
- if (existsSync7(path16.join(cwd, "Cargo.toml")) || existsSync7(path16.join(cwd, "src-tauri/Cargo.toml"))) {
18762
+ if (existsSync7(path18.join(cwd, "Cargo.toml")) || existsSync7(path18.join(cwd, "src-tauri/Cargo.toml"))) {
18159
18763
  stdout += `Rust project detected.
18160
18764
  `;
18161
18765
  }
@@ -18188,7 +18792,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
18188
18792
  - Apply with the host-native editor, preserving the repository changelog conventions.
18189
18793
  - If Unreleased already has duplicate category headings, normalize_only first.`;
18190
18794
  function changelogContext(root, range) {
18191
- const cwd = path16.resolve(repoRoot2(root));
18795
+ const cwd = path18.resolve(repoRoot2(root));
18192
18796
  if (range !== undefined && !isResolvableContextRange(cwd, range))
18193
18797
  return invalidRange(cwd);
18194
18798
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -18206,7 +18810,7 @@ function changelogContext(root, range) {
18206
18810
  stdout += CHANGELOG_RULES + `
18207
18811
  `;
18208
18812
  stdout += printSection("Existing CHANGELOG.md");
18209
- const changelogPath = path16.join(cwd, "CHANGELOG.md");
18813
+ const changelogPath = path18.join(cwd, "CHANGELOG.md");
18210
18814
  if (existsSync7(changelogPath)) {
18211
18815
  stdout += readTrimmed2(changelogPath, 260) + `
18212
18816
  `;
@@ -18226,7 +18830,7 @@ function changelogContext(root, range) {
18226
18830
  return { stdout, stderr: "", exitCode: 0, cwd };
18227
18831
  }
18228
18832
  function docsRefreshContext(root, range) {
18229
- const cwd = path16.resolve(repoRoot2(root));
18833
+ const cwd = path18.resolve(repoRoot2(root));
18230
18834
  if (range !== undefined && !isResolvableContextRange(cwd, range))
18231
18835
  return invalidRange(cwd);
18232
18836
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -18248,7 +18852,7 @@ function docsRefreshContext(root, range) {
18248
18852
  `) + `
18249
18853
  `;
18250
18854
  stdout += printSection("README Preview");
18251
- const readme = path16.join(cwd, "README.md");
18855
+ const readme = path18.join(cwd, "README.md");
18252
18856
  if (existsSync7(readme)) {
18253
18857
  stdout += readTrimmed2(readme, 220) + `
18254
18858
  `;
@@ -18257,7 +18861,7 @@ function docsRefreshContext(root, range) {
18257
18861
  `;
18258
18862
  }
18259
18863
  stdout += printSection("Package Scripts");
18260
- const pkgPath = path16.join(cwd, "package.json");
18864
+ const pkgPath = path18.join(cwd, "package.json");
18261
18865
  if (existsSync7(pkgPath)) {
18262
18866
  try {
18263
18867
  const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
@@ -18271,7 +18875,7 @@ function docsRefreshContext(root, range) {
18271
18875
  return { stdout, stderr: "", exitCode: 0, cwd };
18272
18876
  }
18273
18877
  function releaseNotesContext(root, rangeOrTag) {
18274
- const cwd = path16.resolve(repoRoot2(root));
18878
+ const cwd = path18.resolve(repoRoot2(root));
18275
18879
  if (!rangeOrTag) {
18276
18880
  return { stdout: "", stderr: `ERROR: release tag or range required
18277
18881
  `, exitCode: 1, cwd };
@@ -18309,7 +18913,7 @@ function releaseNotesContext(root, rangeOrTag) {
18309
18913
  `;
18310
18914
  stdout += printSection("Existing Release Files");
18311
18915
  for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
18312
- if (existsSync7(path16.join(cwd, rel)))
18916
+ if (existsSync7(path18.join(cwd, rel)))
18313
18917
  stdout += rel + `
18314
18918
  `;
18315
18919
  }
@@ -18337,7 +18941,7 @@ function releaseNotesContext(root, rangeOrTag) {
18337
18941
  }
18338
18942
 
18339
18943
  // packages/workit-core/src/core/tracker-issues.ts
18340
- import { spawnSync as spawnSync7 } from "node:child_process";
18944
+ import { spawnSync as spawnSync9 } from "node:child_process";
18341
18945
  var repoPathFromRemote = (remote) => {
18342
18946
  let rest = (remote || "").trim().replace(/\/+$/, "");
18343
18947
  if (!rest)
@@ -18358,7 +18962,7 @@ var defaultCli = (bin, available) => {
18358
18962
  if (!available)
18359
18963
  return null;
18360
18964
  return (args, root) => {
18361
- const out = spawnSync7(bin, args, {
18965
+ const out = spawnSync9(bin, args, {
18362
18966
  cwd: root,
18363
18967
  encoding: "utf8",
18364
18968
  env: { ...process.env, PATH: process.env.PATH ?? "" }
@@ -18436,8 +19040,8 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
18436
19040
  };
18437
19041
 
18438
19042
  // packages/workit-core/src/core/youtrack.ts
18439
- import fs10 from "node:fs";
18440
- import path17 from "node:path";
19043
+ import fs12 from "node:fs";
19044
+ import path19 from "node:path";
18441
19045
 
18442
19046
  // packages/workit-core/src/core/templates.ts
18443
19047
  var repoRoot3 = assetRoot();
@@ -18450,22 +19054,22 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
18450
19054
  // packages/workit-core/src/core/youtrack.ts
18451
19055
  var ISSUE_RE = /^[A-Z]+-\d+$/;
18452
19056
  var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
18453
- var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path17.join(configDir(), "youtrack.json");
18454
- var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path17.join(resolveConfigDir(), "youtrack.json");
19057
+ var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path19.join(configDir(), "youtrack.json");
19058
+ var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path19.join(resolveConfigDir(), "youtrack.json");
18455
19059
  var youTrackTokenModeOk = (p) => {
18456
19060
  if (process.platform === "win32")
18457
19061
  return true;
18458
- const mode = fs10.statSync(p).mode & 511;
19062
+ const mode = fs12.statSync(p).mode & 511;
18459
19063
  return mode === 384;
18460
19064
  };
18461
19065
  function readYouTrackConfig(required) {
18462
19066
  const cfgPath = youTrackConfigPath();
18463
- if (!fs10.existsSync(cfgPath)) {
19067
+ if (!fs12.existsSync(cfgPath)) {
18464
19068
  return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
18465
19069
  }
18466
19070
  let parsed;
18467
19071
  try {
18468
- parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
19072
+ parsed = JSON.parse(fs12.readFileSync(cfgPath, "utf8"));
18469
19073
  } catch {
18470
19074
  return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
18471
19075
  }
@@ -18476,11 +19080,11 @@ function readYouTrackConfig(required) {
18476
19080
  }
18477
19081
  var readYouTrackContextConfig = () => {
18478
19082
  const cfgPath = youTrackReadOnlyConfigPath();
18479
- if (!fs10.existsSync(cfgPath))
19083
+ if (!fs12.existsSync(cfgPath))
18480
19084
  return { data: {} };
18481
19085
  let parsed;
18482
19086
  try {
18483
- parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
19087
+ parsed = JSON.parse(fs12.readFileSync(cfgPath, "utf8"));
18484
19088
  } catch {
18485
19089
  return { error: "YouTrack configuration is unavailable" };
18486
19090
  }
@@ -18495,22 +19099,22 @@ function youTrackConfigLoad() {
18495
19099
  }
18496
19100
  const cfgPath = loaded.path;
18497
19101
  const tokenFile = String(loaded.config.tokenFile ?? "");
18498
- const tokenPath = tokenFile ? path17.isAbsolute(tokenFile) ? path17.resolve(tokenFile) : path17.resolve(process.cwd(), tokenFile) : "";
18499
- if (!tokenPath || !fs10.existsSync(tokenPath)) {
19102
+ const tokenPath = tokenFile ? path19.isAbsolute(tokenFile) ? path19.resolve(tokenFile) : path19.resolve(process.cwd(), tokenFile) : "";
19103
+ if (!tokenPath || !fs12.existsSync(tokenPath)) {
18500
19104
  return { error: "missing youtrack.token" };
18501
19105
  }
18502
19106
  if (!youTrackTokenModeOk(tokenPath)) {
18503
19107
  return { error: "youtrack.token mode must be 0600" };
18504
19108
  }
18505
- const token = fs10.readFileSync(tokenPath, "utf8").trim();
19109
+ const token = fs12.readFileSync(tokenPath, "utf8").trim();
18506
19110
  if (!token || token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
18507
19111
  return { error: "token file still placeholder — edit locally, then /wk-status" };
18508
19112
  }
18509
19113
  const redacted = { ...loaded.config };
18510
19114
  delete redacted.tokenFile;
18511
19115
  redacted.tokenPresent = true;
18512
- redacted.configPath = path17.resolve(cfgPath);
18513
- redacted.tokenPath = path17.resolve(tokenPath);
19116
+ redacted.configPath = path19.resolve(cfgPath);
19117
+ redacted.tokenPath = path19.resolve(tokenPath);
18514
19118
  return { data: redacted };
18515
19119
  }
18516
19120
  function tzParts(date, tz) {
@@ -18557,10 +19161,10 @@ function youTrackParseDuration(text) {
18557
19161
  function youTrackWorkDateMs(dateRaw) {
18558
19162
  const cfgPath = youTrackConfigPath();
18559
19163
  let effective = effectiveWorkTimezone(null);
18560
- if (fs10.existsSync(cfgPath)) {
19164
+ if (fs12.existsSync(cfgPath)) {
18561
19165
  let parsed;
18562
19166
  try {
18563
- parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
19167
+ parsed = JSON.parse(fs12.readFileSync(cfgPath, "utf8"));
18564
19168
  } catch {
18565
19169
  return { error: `${cfgPath} is not valid JSON` };
18566
19170
  }
@@ -18601,12 +19205,12 @@ var youTrackToken = () => {
18601
19205
  if ("error" in loaded)
18602
19206
  return loaded;
18603
19207
  const tokenFile = String(loaded.config.tokenFile ?? "");
18604
- const tokenPath = tokenFile ? path17.isAbsolute(tokenFile) ? path17.resolve(tokenFile) : path17.resolve(process.cwd(), tokenFile) : "";
18605
- if (!tokenPath || !fs10.existsSync(tokenPath))
19208
+ const tokenPath = tokenFile ? path19.isAbsolute(tokenFile) ? path19.resolve(tokenFile) : path19.resolve(process.cwd(), tokenFile) : "";
19209
+ if (!tokenPath || !fs12.existsSync(tokenPath))
18606
19210
  return { error: "missing youtrack.token" };
18607
19211
  if (!youTrackTokenModeOk(tokenPath))
18608
19212
  return { error: "youtrack.token mode must be 0600" };
18609
- const token = fs10.readFileSync(tokenPath, "utf8").trim();
19213
+ const token = fs12.readFileSync(tokenPath, "utf8").trim();
18610
19214
  if (!token)
18611
19215
  return { error: "empty token file" };
18612
19216
  if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
@@ -18733,9 +19337,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
18733
19337
  } catch {
18734
19338
  continue;
18735
19339
  }
18736
- if (!fs10.existsSync(full))
19340
+ if (!fs12.existsSync(full))
18737
19341
  continue;
18738
- const text = fs10.readFileSync(full, "utf8");
19342
+ const text = fs12.readFileSync(full, "utf8");
18739
19343
  const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
18740
19344
  if (m)
18741
19345
  return m[1];
@@ -19058,14 +19662,14 @@ async function logTimeUpdate(input, operation = defaultOperations) {
19058
19662
  }
19059
19663
 
19060
19664
  // packages/workit-core/src/core/external-action-effects.ts
19061
- import fs12 from "node:fs";
19062
- import path19 from "node:path";
19063
- import { spawnSync as spawnSync9 } from "node:child_process";
19665
+ import fs14 from "node:fs";
19666
+ import path21 from "node:path";
19667
+ import { spawnSync as spawnSync10 } from "node:child_process";
19064
19668
 
19065
19669
  // packages/workit-core/src/core/changelog.ts
19066
- import fs11 from "node:fs";
19067
- import path18 from "node:path";
19068
- import { createHash as createHash8 } from "node:crypto";
19670
+ import fs13 from "node:fs";
19671
+ import path20 from "node:path";
19672
+ import { createHash as createHash9 } from "node:crypto";
19069
19673
  var CATEGORIES = ["Added", "Changed", "Deprecated", "Removed", "Fixed", "Security"];
19070
19674
  var CAT_RE = /^###\s+(?:Added|Changed|Deprecated|Removed|Fixed|Security)\s*$/i;
19071
19675
  var UNRELEASED_RE = /^##\s+\[Unreleased\]\s*$/i;
@@ -19082,14 +19686,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
19082
19686
  ## [Unreleased]
19083
19687
 
19084
19688
  `;
19085
- var digestBytes4 = (value) => createHash8("sha256").update(value).digest("hex");
19689
+ var digestBytes4 = (value) => createHash9("sha256").update(value).digest("hex");
19086
19690
  var readChangelogSource = (target) => {
19087
- if (!fs11.existsSync(target)) {
19691
+ if (!fs13.existsSync(target)) {
19088
19692
  const bytes = Buffer.from(SKELETON, "utf8");
19089
19693
  return { exists: false, bytes, text: SKELETON };
19090
19694
  }
19091
19695
  try {
19092
- const bytes = fs11.readFileSync(target);
19696
+ const bytes = fs13.readFileSync(target);
19093
19697
  const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
19094
19698
  return { exists: true, bytes, text };
19095
19699
  } catch {
@@ -19254,8 +19858,8 @@ function applyChangelog(target, entries, normalizeOnly) {
19254
19858
  const rendered = renderChangelog(source.text, entries, normalizeOnly);
19255
19859
  if ("error" in rendered)
19256
19860
  return { error: rendered.error };
19257
- fs11.mkdirSync(path18.dirname(target), { recursive: true });
19258
- fs11.writeFileSync(target, rendered.text, "utf8");
19861
+ fs13.mkdirSync(path20.dirname(target), { recursive: true });
19862
+ fs13.writeFileSync(target, rendered.text, "utf8");
19259
19863
  return { ...rendered.result, path: target };
19260
19864
  }
19261
19865
  function renderChangelog(source, entries, normalizeOnly) {
@@ -19288,17 +19892,17 @@ function renderChangelog(source, entries, normalizeOnly) {
19288
19892
  }
19289
19893
  var changelogTarget = (workspaceRoot, changelogPath) => {
19290
19894
  try {
19291
- const cwd = fs11.realpathSync(resolveWorkspaceRoot(workspaceRoot));
19292
- const target = path18.resolve(cwd, changelogPath || "CHANGELOG.md");
19293
- if (target !== cwd && !target.startsWith(cwd + path18.sep))
19895
+ const cwd = fs13.realpathSync(resolveWorkspaceRoot(workspaceRoot));
19896
+ const target = path20.resolve(cwd, changelogPath || "CHANGELOG.md");
19897
+ if (target !== cwd && !target.startsWith(cwd + path20.sep))
19294
19898
  return { error: "changelog path must be inside workspace_root" };
19295
19899
  let ancestor = target;
19296
- while (!fs11.existsSync(ancestor))
19297
- ancestor = path18.dirname(ancestor);
19298
- const canonicalAncestor = fs11.realpathSync(ancestor);
19299
- if (canonicalAncestor !== cwd && !canonicalAncestor.startsWith(cwd + path18.sep))
19900
+ while (!fs13.existsSync(ancestor))
19901
+ ancestor = path20.dirname(ancestor);
19902
+ const canonicalAncestor = fs13.realpathSync(ancestor);
19903
+ if (canonicalAncestor !== cwd && !canonicalAncestor.startsWith(cwd + path20.sep))
19300
19904
  return { error: "changelog path must be inside workspace_root" };
19301
- return { cwd, target: fs11.existsSync(target) ? fs11.realpathSync(target) : target };
19905
+ return { cwd, target: fs13.existsSync(target) ? fs13.realpathSync(target) : target };
19302
19906
  } catch {
19303
19907
  return { error: "changelog path must be inside workspace_root" };
19304
19908
  }
@@ -19453,98 +20057,6 @@ function redactText(value) {
19453
20057
  `);
19454
20058
  }
19455
20059
 
19456
- // packages/workit-core/src/git/rev.ts
19457
- import { spawnSync as spawnSync8 } from "node:child_process";
19458
- var MAX_BUFFER = 256 * 1024 * 1024;
19459
- var GIT_TIMEOUTS = { local: 20000, worktree: 120000, network: 15000 };
19460
- var git = (cwd, args, options = {}) => {
19461
- const result = spawnSync8("git", args, {
19462
- cwd,
19463
- encoding: "utf8",
19464
- env: options.env ?? process.env,
19465
- input: options.input,
19466
- maxBuffer: MAX_BUFFER,
19467
- timeout: options.timeoutMs ?? GIT_TIMEOUTS.local,
19468
- killSignal: "SIGKILL",
19469
- windowsHide: true
19470
- });
19471
- const error = result.error;
19472
- return {
19473
- ok: result.status === 0,
19474
- stdout: result.stdout ?? "",
19475
- stderr: result.stderr ?? "",
19476
- timedOut: error?.code === "ETIMEDOUT"
19477
- };
19478
- };
19479
- var line = (run) => {
19480
- if (!run.ok)
19481
- return null;
19482
- const value = run.stdout.trim();
19483
- return value ? value : null;
19484
- };
19485
- function networkGitInvocation(cwd, args, timeoutMs) {
19486
- const seconds = Math.max(1, Math.ceil(timeoutMs / 1000));
19487
- const env = {
19488
- ...process.env,
19489
- GIT_TERMINAL_PROMPT: "0",
19490
- GIT_ASKPASS: "",
19491
- SSH_ASKPASS: "",
19492
- SSH_ASKPASS_REQUIRE: "never",
19493
- GCM_INTERACTIVE: "never"
19494
- };
19495
- const base = process.env.GIT_SSH_COMMAND || line(git(cwd, ["config", "--get", "core.sshCommand"])) || (process.env.GIT_SSH ? null : "ssh");
19496
- if (base)
19497
- env.GIT_SSH_COMMAND = `${base} -o BatchMode=yes -o ConnectTimeout=${Math.min(10, seconds)}`;
19498
- return {
19499
- args: ["-c", "http.lowSpeedLimit=1", "-c", `http.lowSpeedTime=${seconds}`, ...args],
19500
- env
19501
- };
19502
- }
19503
- var WATCHDOG = `
19504
- const cp = require("node:child_process");
19505
- const { ms, args } = JSON.parse(process.env.WORKIT_GIT_WATCHDOG);
19506
- delete process.env.WORKIT_GIT_WATCHDOG;
19507
- const win = process.platform === "win32";
19508
- const child = cp.spawn("git", args, { stdio: "inherit", detached: !win, windowsHide: true });
19509
- const killTree = () => {
19510
- if (!child.pid) return;
19511
- if (win) cp.spawnSync("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
19512
- else try { process.kill(-child.pid, "SIGKILL"); } catch {}
19513
- };
19514
- let timedOut = false;
19515
- const timer = setTimeout(() => { timedOut = true; killTree(); }, ms);
19516
- child.on("error", () => { clearTimeout(timer); process.exit(127); });
19517
- child.on("exit", (code) => {
19518
- clearTimeout(timer);
19519
- if (!win) killTree();
19520
- process.exit(timedOut ? 124 : code ?? 1);
19521
- });
19522
- `;
19523
- var WATCHDOG_TIMEOUT_EXIT = 124;
19524
- var gitNetwork = (cwd, args, timeoutMs) => {
19525
- const invocation = networkGitInvocation(cwd, args, timeoutMs);
19526
- const result = spawnSync8(process.execPath, ["-e", WATCHDOG], {
19527
- cwd,
19528
- encoding: "utf8",
19529
- env: {
19530
- ...invocation.env,
19531
- WORKIT_GIT_WATCHDOG: JSON.stringify({ ms: timeoutMs, args: invocation.args })
19532
- },
19533
- maxBuffer: MAX_BUFFER,
19534
- timeout: timeoutMs + 1e4,
19535
- killSignal: "SIGKILL",
19536
- windowsHide: true
19537
- });
19538
- const error = result.error;
19539
- return {
19540
- ok: result.status === 0,
19541
- stdout: result.stdout ?? "",
19542
- stderr: result.stderr ?? "",
19543
- timedOut: result.status === WATCHDOG_TIMEOUT_EXIT || error?.code === "ETIMEDOUT"
19544
- };
19545
- };
19546
- var MAX_UNTRACKED_BYTES = 5 * 1024 * 1024;
19547
-
19548
20060
  // packages/workit-core/src/ledger.ts
19549
20061
  var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
19550
20062
  var FAILING = new Set(["failed", "blocked"]);
@@ -19659,7 +20171,7 @@ var credentialFreeRemote = (value) => pushRemoteIdentity(value);
19659
20171
  var actionRoot = (taskRoot, request) => {
19660
20172
  const chosen = "cwd" in request.payload ? request.payload.cwd : undefined;
19661
20173
  try {
19662
- const directory = fs12.realpathSync(chosen ? path19.resolve(taskRoot, chosen) : taskRoot);
20174
+ const directory = fs14.realpathSync(chosen ? path21.resolve(taskRoot, chosen) : taskRoot);
19663
20175
  if (request.operation === "context.read")
19664
20176
  return success(null, null, directory);
19665
20177
  const gitAction = request.operation.startsWith("git.") || request.operation.startsWith("hosting.");
@@ -19670,7 +20182,7 @@ var actionRoot = (taskRoot, request) => {
19670
20182
  return failure2("capability_unavailable", "action target is not a Git repository", {
19671
20183
  outcome: "not_started"
19672
20184
  });
19673
- return success(null, null, fs12.realpathSync(checkout));
20185
+ return success(null, null, fs14.realpathSync(checkout));
19674
20186
  } catch {
19675
20187
  return failure2("invalid_input", "action target directory cannot be resolved", {
19676
20188
  outcome: "not_started"
@@ -20002,7 +20514,7 @@ var hostingProvider = (root, remote) => {
20002
20514
  };
20003
20515
  var remoteBranchTip = (root, provider, host, repo, branch) => {
20004
20516
  const endpoint = provider === "github" ? `repos/${repo}/git/ref/heads/${branch.split("/").map(encodeURIComponent).join("/")}` : `projects/${encodeURIComponent(repo)}/repository/branches/${encodeURIComponent(branch)}`;
20005
- const response = spawnSync9(provider === "github" ? "gh" : "glab", ["api", endpoint], {
20517
+ const response = spawnSync10(provider === "github" ? "gh" : "glab", ["api", endpoint], {
20006
20518
  cwd: root,
20007
20519
  encoding: "utf8",
20008
20520
  env: hostingCliEnv(provider, host)
@@ -20010,7 +20522,7 @@ var remoteBranchTip = (root, provider, host, repo, branch) => {
20010
20522
  if (response.status !== 0) {
20011
20523
  if (/\b404\b/.test(response.stderr ?? "")) {
20012
20524
  const project = provider === "github" ? `repos/${repo}` : `projects/${encodeURIComponent(repo)}`;
20013
- const visible = spawnSync9(provider === "github" ? "gh" : "glab", ["api", project], {
20525
+ const visible = spawnSync10(provider === "github" ? "gh" : "glab", ["api", project], {
20014
20526
  cwd: root,
20015
20527
  encoding: "utf8",
20016
20528
  env: hostingCliEnv(provider, host)
@@ -20066,7 +20578,7 @@ var mergedBranch = (root, branch) => {
20066
20578
  outcome: "not_started"
20067
20579
  });
20068
20580
  const endpoint = provider === "github" ? `repos/${repo}/pulls?state=closed&head=${encodeURIComponent(`${repo.split("/")[0]}:${branch}`)}&per_page=100` : `projects/${encodeURIComponent(repo)}/merge_requests?state=merged&source_branch=${encodeURIComponent(branch)}&per_page=100`;
20069
- const response = spawnSync9(provider === "github" ? "gh" : "glab", ["api", endpoint], {
20581
+ const response = spawnSync10(provider === "github" ? "gh" : "glab", ["api", endpoint], {
20070
20582
  cwd: root,
20071
20583
  encoding: "utf8",
20072
20584
  env: hostingCliEnv(provider, auth.host)
@@ -20128,7 +20640,7 @@ var readHostingAction = async (root, resolved, actionRef) => {
20128
20640
  return unknownHostingEvidence(operation);
20129
20641
  const endpoint = cfg.provider === "github" ? `repos/${repo}/pulls?state=all&head=${encodeURIComponent(`${repo.split("/")[0]}:${branch}`)}&base=${encodeURIComponent(target)}&per_page=100&page=1` : `projects/${encodeURIComponent(repo)}/merge_requests?state=all&source_branch=${encodeURIComponent(branch)}&target_branch=${encodeURIComponent(target)}&per_page=100&page=1`;
20130
20642
  const cli = cfg.provider === "github" ? "gh" : "glab";
20131
- const response = spawnSync9(cli, ["api", endpoint], {
20643
+ const response = spawnSync10(cli, ["api", endpoint], {
20132
20644
  cwd: root,
20133
20645
  encoding: "utf8",
20134
20646
  env: hostingCliEnv(cfg.provider, auth.host)
@@ -20320,7 +20832,7 @@ var readLocalAction = async (root, resolved, actionRef) => {
20320
20832
  const sdd = resolved.request.payload.sdd_dir;
20321
20833
  if (resolved.request.payload.action === "reapply_stash" && typeof sdd === "string") {
20322
20834
  try {
20323
- const manifest = JSON.parse(fs12.readFileSync(path19.join(sdd, "manifest.json"), "utf8"));
20835
+ const manifest = JSON.parse(fs14.readFileSync(path21.join(sdd, "manifest.json"), "utf8"));
20324
20836
  const stashes = gitRaw(root, ["stash", "list", "--format=%H"]);
20325
20837
  if (typeof target.stash_commit === "string" && manifest.stash_ref === undefined && stashes !== null && !stashes.split(`
20326
20838
  `).includes(target.stash_commit))
@@ -20328,7 +20840,7 @@ var readLocalAction = async (root, resolved, actionRef) => {
20328
20840
  } catch {}
20329
20841
  } else if (typeof targetBranch === "string" && typeof sdd === "string" && gitValue(root, ["branch", "--show-current"]) === targetBranch && typeof target.target_head === "string" && gitValue(root, ["rev-parse", "HEAD"]) === target.target_head) {
20330
20842
  try {
20331
- const manifest = JSON.parse(fs12.readFileSync(path19.join(sdd, "manifest.json"), "utf8"));
20843
+ const manifest = JSON.parse(fs14.readFileSync(path21.join(sdd, "manifest.json"), "utf8"));
20332
20844
  if (manifest.branch === targetBranch)
20333
20845
  observed = { branch: targetBranch };
20334
20846
  } catch {}
@@ -20381,7 +20893,7 @@ var readDeletedBranch = async (root, resolved, actionRef) => {
20381
20893
  };
20382
20894
  var youTrackBase = () => {
20383
20895
  try {
20384
- const config = JSON.parse(fs12.readFileSync(youTrackConfigPath(), "utf8"));
20896
+ const config = JSON.parse(fs14.readFileSync(youTrackConfigPath(), "utf8"));
20385
20897
  const value = typeof config.baseUrl === "string" ? config.baseUrl.trim() : "";
20386
20898
  if (!value || /[\s#?]/.test(value))
20387
20899
  return null;
@@ -20449,7 +20961,7 @@ var resolveExternalActionAtRoot = (root, request, preservedDateMs) => {
20449
20961
  let stashTarget;
20450
20962
  if (action === "reapply_stash") {
20451
20963
  try {
20452
- const manifest = JSON.parse(fs12.readFileSync(path19.join(sdd_dir, "manifest.json"), "utf8"));
20964
+ const manifest = JSON.parse(fs14.readFileSync(path21.join(sdd_dir, "manifest.json"), "utf8"));
20453
20965
  const stash_ref = typeof manifest.stash_ref === "string" ? manifest.stash_ref : "";
20454
20966
  const stash_commit = stash_ref ? gitValue(root, ["rev-parse", stash_ref]) : null;
20455
20967
  if (stash_ref && stash_commit)
@@ -20724,7 +21236,7 @@ var resolveExternalActionAtRoot = (root, request, preservedDateMs) => {
20724
21236
  if (preview.error)
20725
21237
  return failure2("invalid_input", preview.error);
20726
21238
  const target = String(preview.path);
20727
- const relative = path19.relative(fs12.realpathSync(root), target).replaceAll(path19.sep, "/") || "CHANGELOG.md";
21239
+ const relative = path21.relative(fs14.realpathSync(root), target).replaceAll(path21.sep, "/") || "CHANGELOG.md";
20728
21240
  const normalized = {
20729
21241
  operation: request.operation,
20730
21242
  payload: {
@@ -21566,9 +22078,9 @@ import { randomUUID as randomUUID3 } from "node:crypto";
21566
22078
  // packages/workit-pi/src/worker-protocol.ts
21567
22079
  var MAX_WORKER_LINE_BYTES = 64 * 1024;
21568
22080
  var MAX_WORKER_STDERR_BYTES = 64 * 1024;
21569
- var isRecord3 = (value) => typeof value === "object" && value !== null;
22081
+ var isRecord4 = (value) => typeof value === "object" && value !== null;
21570
22082
  var parseEvent = (value) => {
21571
- if (!isRecord3(value) || typeof value.type !== "string")
22083
+ if (!isRecord4(value) || typeof value.type !== "string")
21572
22084
  return null;
21573
22085
  if (value.type === "workit_worker_ready") {
21574
22086
  if (typeof value.workerId !== "string" || typeof value.sessionId !== "string")