@brainervirus/workit-pi 2.6.0 → 2.8.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
@@ -13795,8 +14313,9 @@ target result; a local commit alone is not evidence of a requested remote push.
13795
14313
  Skill routing: slash aliases /wk-* load on demand. Use workit-steer for a
13796
14314
  substantial interruption or change of direction, workit-deslop when relevant
13797
14315
  to a PR-ready endpoint, workit-green-run for failing CI, workit-blast-radius
13798
- when impact is uncertain, and workit-challenge for genuinely open consequential
13799
- choices. Load workit-plan when dependencies or handoff need durable next actions.
14316
+ when impact is uncertain, workit-challenge for genuinely open consequential
14317
+ choices, workit-bdd to turn acceptance criteria into Given/When/Then tests, and
14318
+ workit-test-audit to check tests for tautologies. Load workit-plan when dependencies or handoff need durable next actions.
13800
14319
  Load the skill; never act from memory of it.
13801
14320
  `.trim();
13802
14321
 
@@ -13807,7 +14326,7 @@ function reconcileResume(view, observations = []) {
13807
14326
  const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
13808
14327
  if (!candidate.ok)
13809
14328
  return candidate;
13810
- const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
14329
+ const staleEvidenceIds = evaluateEvidence(view.task, candidate.data, treeFreshness(view.workspace.root)).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
13811
14330
  const blockers = [...view.task.progress.blockers];
13812
14331
  if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
13813
14332
  blockers.push({
@@ -13932,7 +14451,7 @@ var shellBranchPolicyViolation = (root, command) => {
13932
14451
  // packages/workit-core/src/core/external-action.ts
13933
14452
  import { execFileSync as execFileSync3 } from "node:child_process";
13934
14453
  import { realpathSync as realpathSync6 } from "node:fs";
13935
- import path12 from "node:path";
14454
+ import path14 from "node:path";
13936
14455
  var chainStepSchema = union([
13937
14456
  string2().min(1),
13938
14457
  object({ branch: string2().min(1) }).strict(),
@@ -14126,7 +14645,7 @@ var planCommitDescriptor = (content) => {
14126
14645
  if (!steps || typeof branch !== "string" || !branch)
14127
14646
  return null;
14128
14647
  const cwd = value.payload.cwd;
14129
- if (cwd !== undefined && (typeof cwd !== "string" || !path12.isAbsolute(cwd)))
14648
+ if (cwd !== undefined && (typeof cwd !== "string" || !path14.isAbsolute(cwd)))
14130
14649
  return null;
14131
14650
  const head = value.payload.resolved?.head;
14132
14651
  return {
@@ -14269,7 +14788,7 @@ var descriptorTargetRoot = (store, operation) => {
14269
14788
  const cwd = descriptor.payload?.cwd;
14270
14789
  if (cwd === undefined)
14271
14790
  return store.root;
14272
- return typeof cwd === "string" && path12.isAbsolute(cwd) ? realpathSync6(cwd) : null;
14791
+ return typeof cwd === "string" && path14.isAbsolute(cwd) ? realpathSync6(cwd) : null;
14273
14792
  } catch {
14274
14793
  return null;
14275
14794
  }
@@ -14380,7 +14899,7 @@ var branchSetupIntent = (descriptor) => {
14380
14899
  const { target_branch, resolved } = value.payload;
14381
14900
  if (typeof target_branch !== "string" || !target_branch || !resolved)
14382
14901
  return null;
14383
- if (value.payload.cwd !== undefined && (typeof value.payload.cwd !== "string" || !path12.isAbsolute(value.payload.cwd)))
14902
+ if (value.payload.cwd !== undefined && (typeof value.payload.cwd !== "string" || !path14.isAbsolute(value.payload.cwd)))
14384
14903
  return null;
14385
14904
  if (typeof resolved.base_branch !== "string" || typeof resolved.target_exists !== "boolean")
14386
14905
  return null;
@@ -14497,7 +15016,7 @@ var externalActionTargetRoot = (operation, coordinationRoot) => {
14497
15016
  const payload = descriptor.payload;
14498
15017
  const cwd = typeof payload.cwd === "string" ? payload.cwd : coordinationRoot;
14499
15018
  try {
14500
- return realpathSync6(path12.resolve(coordinationRoot, cwd));
15019
+ return realpathSync6(path14.resolve(coordinationRoot, cwd));
14501
15020
  } catch {
14502
15021
  return null;
14503
15022
  }
@@ -15746,10 +16265,7 @@ class WorkitCore {
15746
16265
  if (finding.data.disposition !== "fixed")
15747
16266
  return finding;
15748
16267
  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
- ]));
16268
+ const statuses = new Map(evaluateEvidence(withEntry, currentCandidate.data, treeFreshness(this.store.root)).map((item) => [item.evidenceId, item.status]));
15753
16269
  const verified = withEntry.evidence.some((item) => findingVerificationPasses(finding.data.candidateId, {
15754
16270
  kind: item.data.kind,
15755
16271
  candidateId: item.data.candidateId,
@@ -15764,6 +16280,71 @@ class WorkitCore {
15764
16280
  return changed;
15765
16281
  return success(changed.data.revision, null, changed.data.evidence.at(-1));
15766
16282
  }
16283
+ observeCheck(request) {
16284
+ return this.retryOmittedRevisions(request, () => this.observeCheckOnce(request));
16285
+ }
16286
+ observeCheckOnce(request) {
16287
+ const root = this.contextRootError();
16288
+ if (!root.ok)
16289
+ return root;
16290
+ if (this.context.caller.host !== "workit_cli" || (this.context.workerId ?? null) !== null)
16291
+ return failure2("permission_denied", "only the workit CLI records observed checks");
16292
+ const observation = checkObservationSchema.safeParse(request?.observation);
16293
+ if (!observation.success)
16294
+ return failure2("invalid_input", "check observation is invalid", {
16295
+ fields: observation.error.issues.map((issue) => ({
16296
+ path: issue.path.join("."),
16297
+ reason: issue.message
16298
+ }))
16299
+ });
16300
+ const task = this.store.readTask(request.taskId);
16301
+ if (!task.ok)
16302
+ return task;
16303
+ if (task.data.status === "closed")
16304
+ return failure2("invalid_transition", "closed task cannot record evidence");
16305
+ const observed = observation.data;
16306
+ let candidate = null;
16307
+ if (observed.tree === null) {
16308
+ const captured = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
16309
+ if (!captured.ok)
16310
+ return captured;
16311
+ candidate = captured.data;
16312
+ }
16313
+ const label = observed.name ?? observed.argv.join(" ");
16314
+ const data = {
16315
+ kind: "check",
16316
+ claim: `workit check ${label}`.slice(0, 500),
16317
+ requirementIds: [],
16318
+ ...candidate ? { beforeCandidateId: candidate.id, candidateId: candidate.id } : {},
16319
+ result: observed.exitCode === 0 ? "passed" : "failed",
16320
+ summary: `${observed.configured ? "configured" : "ad-hoc"} check exited ${observed.exitCode}${observed.timedOut ? " (timed out)" : ""} in ${observed.durationMs} ms`,
16321
+ refs: [],
16322
+ exitCode: observed.exitCode,
16323
+ reviewContext: null,
16324
+ observation: observed
16325
+ };
16326
+ const changed = this.store.mutateTask(task.data.id, request.expectedRevision ?? task.data.revision, (current, mutation) => {
16327
+ const candidates = candidate === null || current.candidates.some((item) => item.id === candidate.id) ? current.candidates : [...current.candidates, candidate];
16328
+ const critical = [...new Set([...current.critical ?? [], CHECK_OBSERVATION_PATH])];
16329
+ return success(mutation.revision, null, {
16330
+ ...current,
16331
+ critical,
16332
+ candidates,
16333
+ evidence: [
16334
+ ...current.evidence,
16335
+ {
16336
+ id: newId(),
16337
+ recordedAt: mutation.now,
16338
+ provenance: provenance(this.context, "host_observed"),
16339
+ data
16340
+ }
16341
+ ]
16342
+ });
16343
+ }, trustedNow(this.context));
16344
+ if (!changed.ok)
16345
+ return changed;
16346
+ return success(changed.data.revision, null, changed.data.evidence.at(-1));
16347
+ }
15767
16348
  decision(request) {
15768
16349
  return this.recordDecision(request);
15769
16350
  }
@@ -15999,7 +16580,7 @@ class WorkitCore {
15999
16580
  const current = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
16000
16581
  if (!current.ok)
16001
16582
  return current;
16002
- const evaluations = evaluateEvidence(task.data, current.data);
16583
+ const evaluations = evaluateEvidence(task.data, current.data, treeFreshness(this.store.root));
16003
16584
  const verified = evidence.some((entry) => {
16004
16585
  const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
16005
16586
  return entry.recordedAt >= finding.recordedAt && findingVerificationPasses(finding.data.candidateId, {
@@ -16703,7 +17284,8 @@ class WorkitCore {
16703
17284
  if (!current.ok)
16704
17285
  return current;
16705
17286
  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);
17287
+ const tree = treeFreshness(this.store.root);
17288
+ const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
16707
17289
  return success(task.revision, workspace.data.revision, {
16708
17290
  id: task.id,
16709
17291
  revision: task.revision,
@@ -16720,7 +17302,7 @@ class WorkitCore {
16720
17302
  writer: evaluationWorkspace.writer
16721
17303
  });
16722
17304
  }
16723
- compactContext(taskId) {
17305
+ compactContext(taskId, freshness) {
16724
17306
  const root = this.contextRootError();
16725
17307
  if (!root.ok)
16726
17308
  return root;
@@ -16730,12 +17312,12 @@ class WorkitCore {
16730
17312
  const helper = this.helperTaskGuard(task.data, true);
16731
17313
  if (!helper.ok)
16732
17314
  return helper;
16733
- const view = this.view(task.data, false);
17315
+ const view = this.view(task.data, false, freshness ?? signalFreshness(this.store.root));
16734
17316
  if (!view.ok)
16735
17317
  return view;
16736
17318
  return success(view.revision, view.workspaceRevision, compactTaskContext(view.data));
16737
17319
  }
16738
- view(task, capture = true) {
17320
+ view(task, capture = true, freshness = treeFreshness(this.store.root)) {
16739
17321
  const workspace = this.store.readWorkspace();
16740
17322
  if (!workspace.ok)
16741
17323
  return workspace;
@@ -16746,13 +17328,14 @@ class WorkitCore {
16746
17328
  if (!current.ok)
16747
17329
  return current;
16748
17330
  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);
17331
+ const tree = freshness;
17332
+ const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
16750
17333
  return success(task.revision, workspace.data.revision, {
16751
17334
  task,
16752
17335
  workspace: evaluationWorkspace,
16753
17336
  capabilities: this.context.capabilities,
16754
17337
  requirements,
16755
- evidence: evaluateEvidence(task, current.data)
17338
+ evidence: evaluateEvidence(task, current.data, tree)
16756
17339
  });
16757
17340
  }
16758
17341
  reconcileResume(view, observations = []) {
@@ -16979,13 +17562,14 @@ class WorkitCore {
16979
17562
  return current;
16980
17563
  const withCandidate = current.data;
16981
17564
  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);
17565
+ const tree = treeFreshness(this.store.root);
17566
+ const requirements = evaluateRequirements(taskForView, workspace.data, this.context.capabilities, withCandidate, this.store.root, this.context.caller, tree);
16983
17567
  const view = {
16984
17568
  task: taskForView,
16985
17569
  workspace: workspace.data,
16986
17570
  capabilities: this.context.capabilities,
16987
17571
  requirements,
16988
- evidence: evaluateEvidence(taskForView, withCandidate)
17572
+ evidence: evaluateEvidence(taskForView, withCandidate, tree)
16989
17573
  };
16990
17574
  const closure = evaluateClosure(input.outcome, view);
16991
17575
  if (!closure.ok)
@@ -17049,22 +17633,22 @@ function resolveGitRevision(root, value) {
17049
17633
  }
17050
17634
  function resolveInside(root, candidate) {
17051
17635
  const base = realpathSync8(root);
17052
- const target = path13.resolve(base, candidate);
17053
- if (target !== base && !target.startsWith(base + path13.sep)) {
17636
+ const target = path15.resolve(base, candidate);
17637
+ if (target !== base && !target.startsWith(base + path15.sep)) {
17054
17638
  throw new Error("path must stay inside repository root");
17055
17639
  }
17056
17640
  let ancestor = target;
17057
17641
  while (!existsSync6(ancestor))
17058
- ancestor = path13.dirname(ancestor);
17642
+ ancestor = path15.dirname(ancestor);
17059
17643
  const canonicalAncestor = realpathSync8(ancestor);
17060
- if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path13.sep)) {
17644
+ if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path15.sep)) {
17061
17645
  throw new Error("path must stay inside repository root");
17062
17646
  }
17063
17647
  return target;
17064
17648
  }
17065
17649
  function run2(root, executable, args, env = {}) {
17066
17650
  const cwd = realpathSync8(root);
17067
- const result = spawnSync4(executable, args, {
17651
+ const result = spawnSync6(executable, args, {
17068
17652
  cwd,
17069
17653
  encoding: "utf8",
17070
17654
  env: { ...process.env, ...env }
@@ -17101,7 +17685,7 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
17101
17685
  };
17102
17686
  });
17103
17687
  // packages/workit-core/src/hooks/context.ts
17104
- import path14 from "node:path";
17688
+ import path16 from "node:path";
17105
17689
  var boundTo = (entry, session) => entry.sessions.some((item) => item.host === session.host && item.handle === session.handle);
17106
17690
  var newestFirst = (left, right) => right.updatedAt.localeCompare(left.updatedAt);
17107
17691
  var sessionTaskEntry = (entries, session) => entries.filter((entry) => entry.status !== "closed" && boundTo(entry, session)).toSorted(newestFirst)[0] ?? null;
@@ -17126,6 +17710,19 @@ var filesKey = (files) => {
17126
17710
  return signatures.some((item) => item !== "-" && racySignature(item)) ? null : signatures.join("\x00");
17127
17711
  };
17128
17712
  var CACHE_LIMIT = 64;
17713
+ var SIGNAL_TTL_MS = 1500;
17714
+ var signals = new Map;
17715
+ var recentSignal = (root) => {
17716
+ const now = Date.now();
17717
+ const cached = signals.get(root);
17718
+ if (cached && now - cached.at < SIGNAL_TTL_MS)
17719
+ return cached.value;
17720
+ const value = worktreeSignal(root);
17721
+ signals.set(root, { at: now, value });
17722
+ if (signals.size > CACHE_LIMIT)
17723
+ signals.delete(signals.keys().next().value);
17724
+ return value;
17725
+ };
17129
17726
  var cache = new Map;
17130
17727
  function sessionCompactContext(store, session, context, selection = "session-bound") {
17131
17728
  const listed = store.listTaskIndex();
@@ -17149,18 +17746,26 @@ function entryCompactContext(store, entry, session, context) {
17149
17746
  capabilities: context.capabilities
17150
17747
  });
17151
17748
  const hit = cache.get(slot);
17152
- if (hit && hit.key === key && hit.filesKey !== null && hit.filesKey === filesKey(hit.files))
17749
+ if (hit && hit.key === key && hit.filesKey !== null && hit.filesKey === filesKey(hit.files) && (hit.signal === null || hit.signal === recentSignal(store.root)))
17153
17750
  return hit.value;
17154
17751
  const task = store.readTask(entry.id);
17155
17752
  if (!task.ok)
17156
17753
  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);
17754
+ const freshness = { mode: "signal", current: () => recentSignal(store.root) };
17755
+ const observed = task.data.evidence.some(isObservedCheck);
17756
+ const files = task.data.decisions.flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path16.resolve(store.root, ref.path)] : []));
17757
+ const observedFiles = filesKey(files);
17758
+ const compact = new WorkitCore(store, context).compactContext(entry.id, freshness);
17160
17759
  if (!compact.ok)
17161
17760
  return null;
17162
17761
  cache.delete(slot);
17163
- cache.set(slot, { key, files, filesKey: observed, value: compact.data });
17762
+ cache.set(slot, {
17763
+ key,
17764
+ files,
17765
+ filesKey: observedFiles,
17766
+ signal: observed ? freshness.current() : null,
17767
+ value: compact.data
17768
+ });
17164
17769
  if (cache.size > CACHE_LIMIT)
17165
17770
  cache.delete(cache.keys().next().value);
17166
17771
  return compact.data;
@@ -17322,14 +17927,16 @@ var WORKIT_SKILL_ALIASES = {
17322
17927
  "wk-diagram": "workit-diagram",
17323
17928
  "wk-mockup": "workit-mockup",
17324
17929
  "wk-green-run": "workit-green-run",
17325
- "wk-steer": "workit-steer"
17930
+ "wk-steer": "workit-steer",
17931
+ "wk-bdd": "workit-bdd",
17932
+ "wk-test-audit": "workit-test-audit"
17326
17933
  };
17327
17934
  if (false) {}
17328
17935
 
17329
17936
  // 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";
17937
+ import fs11 from "node:fs";
17938
+ import path17 from "node:path";
17939
+ import { spawnSync as spawnSync7 } from "node:child_process";
17333
17940
  import { pathToFileURL } from "node:url";
17334
17941
  // packages/workit-core/src/forge/pr-body.ts
17335
17942
  function parseGhIssue(value) {
@@ -17419,13 +18026,13 @@ var cliRepository = (remote, provider, cfg) => {
17419
18026
  };
17420
18027
  function whichOnPath(tool) {
17421
18028
  const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
17422
- for (const dir of (process.env.PATH ?? "").split(path15.delimiter)) {
18029
+ for (const dir of (process.env.PATH ?? "").split(path17.delimiter)) {
17423
18030
  if (!dir)
17424
18031
  continue;
17425
18032
  for (const name of names) {
17426
- const candidate = path15.join(dir, name);
18033
+ const candidate = path17.join(dir, name);
17427
18034
  try {
17428
- fs9.accessSync(candidate, fs9.constants.X_OK);
18035
+ fs11.accessSync(candidate, fs11.constants.X_OK);
17429
18036
  return candidate;
17430
18037
  } catch {}
17431
18038
  }
@@ -17434,11 +18041,11 @@ function whichOnPath(tool) {
17434
18041
  }
17435
18042
  var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
17436
18043
  function repoRoot(cwd) {
17437
- const result = spawnSync5("git", ["rev-parse", "--show-toplevel"], { cwd, encoding: "utf8" });
18044
+ const result = spawnSync7("git", ["rev-parse", "--show-toplevel"], { cwd, encoding: "utf8" });
17438
18045
  return result.status === 0 ? (result.stdout ?? "").trim() : cwd;
17439
18046
  }
17440
18047
  var pushRemote = (cwd) => {
17441
- const result = spawnSync5("git", ["remote", "get-url", "--push", "--all", "origin"], {
18048
+ const result = spawnSync7("git", ["remote", "get-url", "--push", "--all", "origin"], {
17442
18049
  cwd,
17443
18050
  encoding: "utf8"
17444
18051
  });
@@ -17449,7 +18056,7 @@ var pushRemote = (cwd) => {
17449
18056
  };
17450
18057
  var safePushUrl = (remote) => {
17451
18058
  const raw = remote.trim();
17452
- if (path15.isAbsolute(raw))
18059
+ if (path17.isAbsolute(raw))
17453
18060
  return pathToFileURL(raw).href;
17454
18061
  if (!raw.includes("://")) {
17455
18062
  const scp = /^(?:([^@\s]+)@)?([^:]+):(.+)$/u.exec(raw);
@@ -17474,7 +18081,7 @@ var pushRemoteIdentity = (remote) => {
17474
18081
  const raw = remote.trim();
17475
18082
  if (!raw)
17476
18083
  return null;
17477
- if (path15.isAbsolute(raw))
18084
+ if (path17.isAbsolute(raw))
17478
18085
  return pathToFileURL(raw).href;
17479
18086
  if (!raw.includes("://")) {
17480
18087
  const scp = /^(?:([^@\s]+)@)?([^:]+):(.+)$/u.exec(raw);
@@ -17498,7 +18105,7 @@ var pushTargetIsStable = (cwd, remote) => {
17498
18105
  const target = safePushUrl(remote);
17499
18106
  if (!target)
17500
18107
  return false;
17501
- const result = spawnSync5("git", ["config", "--get-regexp", "^url\\..*\\.(insteadof|pushinsteadof)$"], { cwd, encoding: "utf8" });
18108
+ const result = spawnSync7("git", ["config", "--get-regexp", "^url\\..*\\.(insteadof|pushinsteadof)$"], { cwd, encoding: "utf8" });
17502
18109
  if (result.status !== 0 && result.status !== 1)
17503
18110
  return false;
17504
18111
  const prefixes = (result.stdout ?? "").split(/\r?\n/u).map((line) => line.trim().slice(line.trim().indexOf(" ") + 1)).filter(Boolean);
@@ -17535,7 +18142,7 @@ function prCreate(env, cwd) {
17535
18142
  return {
17536
18143
  error: "origin must have exactly one supported push URL for a reserved hosting action"
17537
18144
  };
17538
- const localOnlyTarget = path15.isAbsolute(pushUrl) || pushUrl.startsWith("file://");
18145
+ const localOnlyTarget = path17.isAbsolute(pushUrl) || pushUrl.startsWith("file://");
17539
18146
  if ((!localOnlyTarget || env.WORKIT_EXPECTED_API_HOST) && !hostingApiHostMatches(pushUrl, identity.host))
17540
18147
  return { error: "hosting API host does not match the push destination" };
17541
18148
  if (env.WORKIT_EXPECTED_REMOTE && env.WORKIT_EXPECTED_REMOTE !== targetIdentity)
@@ -17546,12 +18153,12 @@ function prCreate(env, cwd) {
17546
18153
  const resolvedDefault = String(cfg.defaultTargetBranch ?? policy.defaultTargetBranch ?? "develop");
17547
18154
  const target = targetOverride || resolvedDefault;
17548
18155
  const title = String(env.WF_PR_TITLE ?? "");
17549
- const br = spawnSync5("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
18156
+ const br = spawnSync7("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
17550
18157
  cwd: root,
17551
18158
  encoding: "utf8"
17552
18159
  });
17553
18160
  const branch = br.status === 0 ? (br.stdout ?? "").trim() : "";
17554
- const sourceResult = branch ? spawnSync5("git", ["rev-parse", "--verify", `refs/heads/${branch}`], {
18161
+ const sourceResult = branch ? spawnSync7("git", ["rev-parse", "--verify", `refs/heads/${branch}`], {
17555
18162
  cwd: root,
17556
18163
  encoding: "utf8"
17557
18164
  }) : null;
@@ -17560,7 +18167,7 @@ function prCreate(env, cwd) {
17560
18167
  return { error: "approved PR source branch and commit are incomplete" };
17561
18168
  if (env.WORKIT_EXPECTED_SOURCE_BRANCH && env.WORKIT_EXPECTED_SOURCE_BRANCH !== branch || env.WORKIT_EXPECTED_SOURCE_COMMIT && env.WORKIT_EXPECTED_SOURCE_COMMIT !== sourceCommit)
17562
18169
  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}`], {
18170
+ const mergeBaseResult = policy.integration === "merge" ? spawnSync7("git", ["rev-parse", "--verify", `refs/heads/${target}`], {
17564
18171
  cwd: root,
17565
18172
  encoding: "utf8"
17566
18173
  }) : null;
@@ -17576,7 +18183,7 @@ function prCreate(env, cwd) {
17576
18183
  targetBranch: target,
17577
18184
  stderr: "source or target branch commit could not be resolved"
17578
18185
  };
17579
- const merge = spawnSync5("git", ["merge", "--no-ff", sourceCommit, "-m", title], {
18186
+ const merge = spawnSync7("git", ["merge", "--no-ff", sourceCommit, "-m", title], {
17580
18187
  cwd: root,
17581
18188
  encoding: "utf8"
17582
18189
  });
@@ -17587,16 +18194,16 @@ function prCreate(env, cwd) {
17587
18194
  targetBranch: target,
17588
18195
  stderr: (merge.stderr ?? "").slice(0, 800)
17589
18196
  };
17590
- const mergedCommit = spawnSync5("git", ["rev-parse", "HEAD"], {
18197
+ const mergedCommit = spawnSync7("git", ["rev-parse", "HEAD"], {
17591
18198
  cwd: root,
17592
18199
  encoding: "utf8"
17593
18200
  });
17594
18201
  if (mergedCommit.status !== 0)
17595
18202
  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" });
18203
+ const updateTarget = spawnSync7("git", ["update-ref", `refs/heads/${target}`, mergedCommit.stdout.trim(), mergeBaseCommit], { cwd: root, encoding: "utf8" });
17597
18204
  if (updateTarget.status !== 0)
17598
18205
  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 ?? "" } });
18206
+ const push = spawnSync7("git", ["push", targetUrl, `${mergedCommit.stdout.trim()}:refs/heads/${target}`], { cwd: root, encoding: "utf8", env: { ...process.env, PATH: process.env.PATH ?? "" } });
17600
18207
  if (push.status !== 0)
17601
18208
  return {
17602
18209
  error: "push failed",
@@ -17613,7 +18220,7 @@ function prCreate(env, cwd) {
17613
18220
  output: (push.stdout ?? "").trim()
17614
18221
  };
17615
18222
  };
17616
- const co = spawnSync5("git", ["checkout", "--detach", mergeBaseCommit], {
18223
+ const co = spawnSync7("git", ["checkout", "--detach", mergeBaseCommit], {
17617
18224
  cwd: root,
17618
18225
  encoding: "utf8"
17619
18226
  });
@@ -17625,7 +18232,7 @@ function prCreate(env, cwd) {
17625
18232
  stderr: (co.stderr ?? "").slice(0, 800)
17626
18233
  };
17627
18234
  const result = finish();
17628
- spawnSync5("git", ["checkout", branch], { cwd: root, encoding: "utf8" });
18235
+ spawnSync7("git", ["checkout", branch], { cwd: root, encoding: "utf8" });
17629
18236
  return result;
17630
18237
  }
17631
18238
  const pr = cfg.pr ?? {};
@@ -17633,9 +18240,9 @@ function prCreate(env, cwd) {
17633
18240
  const draft = String(env.WF_PR_DRAFT ?? "false").toLowerCase() === "true";
17634
18241
  let baseUrl = cfg.youtrack_base_url;
17635
18242
  if (!baseUrl) {
17636
- const ytCfg = process.env.WORKFLOW_YOUTRACK_CONFIG ?? path15.join(path15.dirname(String(cfg.configPath)), "youtrack.json");
18243
+ const ytCfg = process.env.WORKFLOW_YOUTRACK_CONFIG ?? path17.join(path17.dirname(String(cfg.configPath)), "youtrack.json");
17637
18244
  try {
17638
- const yt = JSON.parse(fs9.readFileSync(ytCfg, "utf8"));
18245
+ const yt = JSON.parse(fs11.readFileSync(ytCfg, "utf8"));
17639
18246
  if (yt && typeof yt === "object")
17640
18247
  baseUrl = yt.baseUrl;
17641
18248
  } catch {}
@@ -17651,7 +18258,7 @@ function prCreate(env, cwd) {
17651
18258
  const push = pr.pushBranch !== false;
17652
18259
  const skipConfirm = pr.confirmSkip !== false;
17653
18260
  const remoteBranchTip = () => {
17654
- const read = spawnSync5("git", ["ls-remote", targetUrl, `refs/heads/${branch}`], {
18261
+ const read = spawnSync7("git", ["ls-remote", targetUrl, `refs/heads/${branch}`], {
17655
18262
  cwd: root,
17656
18263
  encoding: "utf8",
17657
18264
  env: { ...process.env, PATH: process.env.PATH ?? "" }
@@ -17667,7 +18274,7 @@ function prCreate(env, cwd) {
17667
18274
  stderr: branch ? "source branch commit could not be resolved" : "empty current branch (detached HEAD or unborn HEAD)"
17668
18275
  };
17669
18276
  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 ?? "" } });
18277
+ const pushed = spawnSync7("git", ["push", "-u", targetUrl, `${sourceCommit}:refs/heads/${branch}`], { cwd: root, encoding: "utf8", env: { ...process.env, PATH: process.env.PATH ?? "" } });
17671
18278
  if (pushed.status !== 0)
17672
18279
  return { error: "push failed", provider, stderr: (pushed.stderr ?? "").slice(0, 800) };
17673
18280
  }
@@ -17712,12 +18319,12 @@ function prCreate(env, cwd) {
17712
18319
  cmd.push("--draft");
17713
18320
  cmdEnv = { ...process.env, PATH: process.env.PATH ?? "", GH_HOST: identity.host };
17714
18321
  }
17715
- const result = spawnSync5(cmd[0], cmd.slice(1), { cwd: root, encoding: "utf8", env: cmdEnv });
18322
+ const result = spawnSync7(cmd[0], cmd.slice(1), { cwd: root, encoding: "utf8", env: cmdEnv });
17716
18323
  if (result.status !== 0) {
17717
18324
  const err = (result.stderr ?? result.stdout ?? "").trim();
17718
18325
  let hint = null;
17719
18326
  if (provider === "gitlab" && (err.includes("409") || err.toLowerCase().includes("already exists"))) {
17720
- const list = spawnSync5("glab", ["mr", "list", `--source-branch=${branch}`, "--output=json"], {
18327
+ const list = spawnSync7("glab", ["mr", "list", `--source-branch=${branch}`, "--output=json"], {
17721
18328
  cwd: root,
17722
18329
  encoding: "utf8",
17723
18330
  env: cmdEnv
@@ -17776,7 +18383,7 @@ function mergePr(cwd, opts) {
17776
18383
  return { error: "approved hosting account changed before execution" };
17777
18384
  if (!/^[a-f0-9]{40,64}$/i.test(opts.sourceCommit))
17778
18385
  return { error: "approved merge source commit is invalid" };
17779
- const currentCommit = spawnSync5("git", ["rev-parse", "--verify", `refs/heads/${opts.source}`], {
18386
+ const currentCommit = spawnSync7("git", ["rev-parse", "--verify", `refs/heads/${opts.source}`], {
17780
18387
  cwd: root,
17781
18388
  encoding: "utf8"
17782
18389
  });
@@ -17790,7 +18397,7 @@ function mergePr(cwd, opts) {
17790
18397
  return { error: `workflow CLI missing (required for ${provider})` };
17791
18398
  const owner = repoPath.split("/")[0];
17792
18399
  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], {
18400
+ const listOpen = () => spawnSync7(provider === "github" ? "gh" : "glab", ["api", endpoint], {
17794
18401
  cwd: root,
17795
18402
  encoding: "utf8",
17796
18403
  env: {
@@ -17850,7 +18457,7 @@ function mergePr(cwd, opts) {
17850
18457
  cmd.push("--delete-branch");
17851
18458
  cmdEnv = { ...process.env, PATH: process.env.PATH ?? "", GH_HOST: identity.host };
17852
18459
  }
17853
- const result = spawnSync5(cmd[0], cmd.slice(1), { cwd: root, encoding: "utf8", env: cmdEnv });
18460
+ const result = spawnSync7(cmd[0], cmd.slice(1), { cwd: root, encoding: "utf8", env: cmdEnv });
17854
18461
  if (result.status !== 0)
17855
18462
  return {
17856
18463
  error: "merge failed",
@@ -17867,9 +18474,9 @@ function mergePr(cwd, opts) {
17867
18474
  }
17868
18475
 
17869
18476
  // packages/workit-core/src/core/repo-context.ts
17870
- import { spawnSync as spawnSync6 } from "node:child_process";
18477
+ import { spawnSync as spawnSync8 } from "node:child_process";
17871
18478
  import { existsSync as existsSync7, readFileSync as readFileSync8, readdirSync as readdirSync4 } from "node:fs";
17872
- import path16 from "node:path";
18479
+ import path18 from "node:path";
17873
18480
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
17874
18481
  try {
17875
18482
  gitRevisionParts(value);
@@ -17896,7 +18503,7 @@ var invalidRange = (cwd) => ({
17896
18503
  cwd
17897
18504
  });
17898
18505
  var runGit = (cwd, args) => {
17899
- const result = spawnSync6("git", args, { cwd, encoding: "utf8" });
18506
+ const result = spawnSync8("git", args, { cwd, encoding: "utf8" });
17900
18507
  return {
17901
18508
  stdout: (result.stdout ?? "").trimEnd(),
17902
18509
  stderr: (result.stderr ?? "").trimEnd(),
@@ -18003,7 +18610,7 @@ var findPrTemplate = (cwd) => {
18003
18610
  ]) {
18004
18611
  const parts = rel.split("/");
18005
18612
  const base = parts[parts.length - 1];
18006
- const dir = parts.length > 1 ? path16.join(cwd, ...parts.slice(0, -1)) : cwd;
18613
+ const dir = parts.length > 1 ? path18.join(cwd, ...parts.slice(0, -1)) : cwd;
18007
18614
  const actual = probeCaseInsensitive(dir, base);
18008
18615
  if (actual)
18009
18616
  return [...parts.slice(0, -1), actual].join("/");
@@ -18034,7 +18641,7 @@ var diffStatForRange = (cwd, range) => {
18034
18641
  return runGit(cwd, ["diff", "--stat", "--"]).stdout;
18035
18642
  };
18036
18643
  var packageScripts = (cwd, keys) => {
18037
- const pkgPath = path16.join(cwd, "package.json");
18644
+ const pkgPath = path18.join(cwd, "package.json");
18038
18645
  if (!existsSync7(pkgPath))
18039
18646
  return [];
18040
18647
  try {
@@ -18060,9 +18667,9 @@ var documentationFiles = (cwd) => {
18060
18667
  if (entry.isDirectory()) {
18061
18668
  if (excluded.has(entry.name))
18062
18669
  continue;
18063
- walk(path16.join(dir, entry.name), depth + 1);
18670
+ walk(path18.join(dir, entry.name), depth + 1);
18064
18671
  } else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
18065
- matches.push("./" + path16.relative(cwd, path16.join(dir, entry.name)).replaceAll("\\", "/"));
18672
+ matches.push("./" + path18.relative(cwd, path18.join(dir, entry.name)).replaceAll("\\", "/"));
18066
18673
  }
18067
18674
  }
18068
18675
  };
@@ -18078,7 +18685,7 @@ var readTrimmed2 = (file, maxLines) => {
18078
18685
  `);
18079
18686
  };
18080
18687
  function prReadyContext(root, range) {
18081
- const cwd = path16.resolve(repoRoot2(root));
18688
+ const cwd = path18.resolve(repoRoot2(root));
18082
18689
  if (range !== undefined && !isResolvableContextRange(cwd, range))
18083
18690
  return invalidRange(cwd);
18084
18691
  let stdout = "";
@@ -18136,7 +18743,7 @@ ${status.stderr}` : "");
18136
18743
  stdout += `template_path: ${template}
18137
18744
 
18138
18745
  `;
18139
- stdout += readTrimmed2(path16.join(cwd, template), 220);
18746
+ stdout += readTrimmed2(path18.join(cwd, template), 220);
18140
18747
  } else {
18141
18748
  stdout += `template_path: none
18142
18749
 
@@ -18146,7 +18753,7 @@ ${status.stderr}` : "");
18146
18753
  stdout += `
18147
18754
  `;
18148
18755
  stdout += printSection("Recent Validation Signals");
18149
- if (existsSync7(path16.join(cwd, "package.json"))) {
18756
+ if (existsSync7(path18.join(cwd, "package.json"))) {
18150
18757
  stdout += `package.json detected. Common scripts:
18151
18758
  `;
18152
18759
  const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
@@ -18155,7 +18762,7 @@ ${status.stderr}` : "");
18155
18762
  `) + `
18156
18763
  `;
18157
18764
  }
18158
- if (existsSync7(path16.join(cwd, "Cargo.toml")) || existsSync7(path16.join(cwd, "src-tauri/Cargo.toml"))) {
18765
+ if (existsSync7(path18.join(cwd, "Cargo.toml")) || existsSync7(path18.join(cwd, "src-tauri/Cargo.toml"))) {
18159
18766
  stdout += `Rust project detected.
18160
18767
  `;
18161
18768
  }
@@ -18188,7 +18795,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
18188
18795
  - Apply with the host-native editor, preserving the repository changelog conventions.
18189
18796
  - If Unreleased already has duplicate category headings, normalize_only first.`;
18190
18797
  function changelogContext(root, range) {
18191
- const cwd = path16.resolve(repoRoot2(root));
18798
+ const cwd = path18.resolve(repoRoot2(root));
18192
18799
  if (range !== undefined && !isResolvableContextRange(cwd, range))
18193
18800
  return invalidRange(cwd);
18194
18801
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -18206,7 +18813,7 @@ function changelogContext(root, range) {
18206
18813
  stdout += CHANGELOG_RULES + `
18207
18814
  `;
18208
18815
  stdout += printSection("Existing CHANGELOG.md");
18209
- const changelogPath = path16.join(cwd, "CHANGELOG.md");
18816
+ const changelogPath = path18.join(cwd, "CHANGELOG.md");
18210
18817
  if (existsSync7(changelogPath)) {
18211
18818
  stdout += readTrimmed2(changelogPath, 260) + `
18212
18819
  `;
@@ -18226,7 +18833,7 @@ function changelogContext(root, range) {
18226
18833
  return { stdout, stderr: "", exitCode: 0, cwd };
18227
18834
  }
18228
18835
  function docsRefreshContext(root, range) {
18229
- const cwd = path16.resolve(repoRoot2(root));
18836
+ const cwd = path18.resolve(repoRoot2(root));
18230
18837
  if (range !== undefined && !isResolvableContextRange(cwd, range))
18231
18838
  return invalidRange(cwd);
18232
18839
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -18248,7 +18855,7 @@ function docsRefreshContext(root, range) {
18248
18855
  `) + `
18249
18856
  `;
18250
18857
  stdout += printSection("README Preview");
18251
- const readme = path16.join(cwd, "README.md");
18858
+ const readme = path18.join(cwd, "README.md");
18252
18859
  if (existsSync7(readme)) {
18253
18860
  stdout += readTrimmed2(readme, 220) + `
18254
18861
  `;
@@ -18257,7 +18864,7 @@ function docsRefreshContext(root, range) {
18257
18864
  `;
18258
18865
  }
18259
18866
  stdout += printSection("Package Scripts");
18260
- const pkgPath = path16.join(cwd, "package.json");
18867
+ const pkgPath = path18.join(cwd, "package.json");
18261
18868
  if (existsSync7(pkgPath)) {
18262
18869
  try {
18263
18870
  const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
@@ -18271,7 +18878,7 @@ function docsRefreshContext(root, range) {
18271
18878
  return { stdout, stderr: "", exitCode: 0, cwd };
18272
18879
  }
18273
18880
  function releaseNotesContext(root, rangeOrTag) {
18274
- const cwd = path16.resolve(repoRoot2(root));
18881
+ const cwd = path18.resolve(repoRoot2(root));
18275
18882
  if (!rangeOrTag) {
18276
18883
  return { stdout: "", stderr: `ERROR: release tag or range required
18277
18884
  `, exitCode: 1, cwd };
@@ -18309,7 +18916,7 @@ function releaseNotesContext(root, rangeOrTag) {
18309
18916
  `;
18310
18917
  stdout += printSection("Existing Release Files");
18311
18918
  for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
18312
- if (existsSync7(path16.join(cwd, rel)))
18919
+ if (existsSync7(path18.join(cwd, rel)))
18313
18920
  stdout += rel + `
18314
18921
  `;
18315
18922
  }
@@ -18337,7 +18944,7 @@ function releaseNotesContext(root, rangeOrTag) {
18337
18944
  }
18338
18945
 
18339
18946
  // packages/workit-core/src/core/tracker-issues.ts
18340
- import { spawnSync as spawnSync7 } from "node:child_process";
18947
+ import { spawnSync as spawnSync9 } from "node:child_process";
18341
18948
  var repoPathFromRemote = (remote) => {
18342
18949
  let rest = (remote || "").trim().replace(/\/+$/, "");
18343
18950
  if (!rest)
@@ -18358,7 +18965,7 @@ var defaultCli = (bin, available) => {
18358
18965
  if (!available)
18359
18966
  return null;
18360
18967
  return (args, root) => {
18361
- const out = spawnSync7(bin, args, {
18968
+ const out = spawnSync9(bin, args, {
18362
18969
  cwd: root,
18363
18970
  encoding: "utf8",
18364
18971
  env: { ...process.env, PATH: process.env.PATH ?? "" }
@@ -18436,8 +19043,8 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
18436
19043
  };
18437
19044
 
18438
19045
  // packages/workit-core/src/core/youtrack.ts
18439
- import fs10 from "node:fs";
18440
- import path17 from "node:path";
19046
+ import fs12 from "node:fs";
19047
+ import path19 from "node:path";
18441
19048
 
18442
19049
  // packages/workit-core/src/core/templates.ts
18443
19050
  var repoRoot3 = assetRoot();
@@ -18450,22 +19057,22 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
18450
19057
  // packages/workit-core/src/core/youtrack.ts
18451
19058
  var ISSUE_RE = /^[A-Z]+-\d+$/;
18452
19059
  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");
19060
+ var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path19.join(configDir(), "youtrack.json");
19061
+ var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path19.join(resolveConfigDir(), "youtrack.json");
18455
19062
  var youTrackTokenModeOk = (p) => {
18456
19063
  if (process.platform === "win32")
18457
19064
  return true;
18458
- const mode = fs10.statSync(p).mode & 511;
19065
+ const mode = fs12.statSync(p).mode & 511;
18459
19066
  return mode === 384;
18460
19067
  };
18461
19068
  function readYouTrackConfig(required) {
18462
19069
  const cfgPath = youTrackConfigPath();
18463
- if (!fs10.existsSync(cfgPath)) {
19070
+ if (!fs12.existsSync(cfgPath)) {
18464
19071
  return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
18465
19072
  }
18466
19073
  let parsed;
18467
19074
  try {
18468
- parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
19075
+ parsed = JSON.parse(fs12.readFileSync(cfgPath, "utf8"));
18469
19076
  } catch {
18470
19077
  return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
18471
19078
  }
@@ -18476,11 +19083,11 @@ function readYouTrackConfig(required) {
18476
19083
  }
18477
19084
  var readYouTrackContextConfig = () => {
18478
19085
  const cfgPath = youTrackReadOnlyConfigPath();
18479
- if (!fs10.existsSync(cfgPath))
19086
+ if (!fs12.existsSync(cfgPath))
18480
19087
  return { data: {} };
18481
19088
  let parsed;
18482
19089
  try {
18483
- parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
19090
+ parsed = JSON.parse(fs12.readFileSync(cfgPath, "utf8"));
18484
19091
  } catch {
18485
19092
  return { error: "YouTrack configuration is unavailable" };
18486
19093
  }
@@ -18495,22 +19102,22 @@ function youTrackConfigLoad() {
18495
19102
  }
18496
19103
  const cfgPath = loaded.path;
18497
19104
  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)) {
19105
+ const tokenPath = tokenFile ? path19.isAbsolute(tokenFile) ? path19.resolve(tokenFile) : path19.resolve(process.cwd(), tokenFile) : "";
19106
+ if (!tokenPath || !fs12.existsSync(tokenPath)) {
18500
19107
  return { error: "missing youtrack.token" };
18501
19108
  }
18502
19109
  if (!youTrackTokenModeOk(tokenPath)) {
18503
19110
  return { error: "youtrack.token mode must be 0600" };
18504
19111
  }
18505
- const token = fs10.readFileSync(tokenPath, "utf8").trim();
19112
+ const token = fs12.readFileSync(tokenPath, "utf8").trim();
18506
19113
  if (!token || token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
18507
19114
  return { error: "token file still placeholder — edit locally, then /wk-status" };
18508
19115
  }
18509
19116
  const redacted = { ...loaded.config };
18510
19117
  delete redacted.tokenFile;
18511
19118
  redacted.tokenPresent = true;
18512
- redacted.configPath = path17.resolve(cfgPath);
18513
- redacted.tokenPath = path17.resolve(tokenPath);
19119
+ redacted.configPath = path19.resolve(cfgPath);
19120
+ redacted.tokenPath = path19.resolve(tokenPath);
18514
19121
  return { data: redacted };
18515
19122
  }
18516
19123
  function tzParts(date, tz) {
@@ -18557,10 +19164,10 @@ function youTrackParseDuration(text) {
18557
19164
  function youTrackWorkDateMs(dateRaw) {
18558
19165
  const cfgPath = youTrackConfigPath();
18559
19166
  let effective = effectiveWorkTimezone(null);
18560
- if (fs10.existsSync(cfgPath)) {
19167
+ if (fs12.existsSync(cfgPath)) {
18561
19168
  let parsed;
18562
19169
  try {
18563
- parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
19170
+ parsed = JSON.parse(fs12.readFileSync(cfgPath, "utf8"));
18564
19171
  } catch {
18565
19172
  return { error: `${cfgPath} is not valid JSON` };
18566
19173
  }
@@ -18601,12 +19208,12 @@ var youTrackToken = () => {
18601
19208
  if ("error" in loaded)
18602
19209
  return loaded;
18603
19210
  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))
19211
+ const tokenPath = tokenFile ? path19.isAbsolute(tokenFile) ? path19.resolve(tokenFile) : path19.resolve(process.cwd(), tokenFile) : "";
19212
+ if (!tokenPath || !fs12.existsSync(tokenPath))
18606
19213
  return { error: "missing youtrack.token" };
18607
19214
  if (!youTrackTokenModeOk(tokenPath))
18608
19215
  return { error: "youtrack.token mode must be 0600" };
18609
- const token = fs10.readFileSync(tokenPath, "utf8").trim();
19216
+ const token = fs12.readFileSync(tokenPath, "utf8").trim();
18610
19217
  if (!token)
18611
19218
  return { error: "empty token file" };
18612
19219
  if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
@@ -18733,9 +19340,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
18733
19340
  } catch {
18734
19341
  continue;
18735
19342
  }
18736
- if (!fs10.existsSync(full))
19343
+ if (!fs12.existsSync(full))
18737
19344
  continue;
18738
- const text = fs10.readFileSync(full, "utf8");
19345
+ const text = fs12.readFileSync(full, "utf8");
18739
19346
  const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
18740
19347
  if (m)
18741
19348
  return m[1];
@@ -19058,14 +19665,14 @@ async function logTimeUpdate(input, operation = defaultOperations) {
19058
19665
  }
19059
19666
 
19060
19667
  // 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";
19668
+ import fs14 from "node:fs";
19669
+ import path21 from "node:path";
19670
+ import { spawnSync as spawnSync10 } from "node:child_process";
19064
19671
 
19065
19672
  // 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";
19673
+ import fs13 from "node:fs";
19674
+ import path20 from "node:path";
19675
+ import { createHash as createHash9 } from "node:crypto";
19069
19676
  var CATEGORIES = ["Added", "Changed", "Deprecated", "Removed", "Fixed", "Security"];
19070
19677
  var CAT_RE = /^###\s+(?:Added|Changed|Deprecated|Removed|Fixed|Security)\s*$/i;
19071
19678
  var UNRELEASED_RE = /^##\s+\[Unreleased\]\s*$/i;
@@ -19082,14 +19689,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
19082
19689
  ## [Unreleased]
19083
19690
 
19084
19691
  `;
19085
- var digestBytes4 = (value) => createHash8("sha256").update(value).digest("hex");
19692
+ var digestBytes4 = (value) => createHash9("sha256").update(value).digest("hex");
19086
19693
  var readChangelogSource = (target) => {
19087
- if (!fs11.existsSync(target)) {
19694
+ if (!fs13.existsSync(target)) {
19088
19695
  const bytes = Buffer.from(SKELETON, "utf8");
19089
19696
  return { exists: false, bytes, text: SKELETON };
19090
19697
  }
19091
19698
  try {
19092
- const bytes = fs11.readFileSync(target);
19699
+ const bytes = fs13.readFileSync(target);
19093
19700
  const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
19094
19701
  return { exists: true, bytes, text };
19095
19702
  } catch {
@@ -19254,8 +19861,8 @@ function applyChangelog(target, entries, normalizeOnly) {
19254
19861
  const rendered = renderChangelog(source.text, entries, normalizeOnly);
19255
19862
  if ("error" in rendered)
19256
19863
  return { error: rendered.error };
19257
- fs11.mkdirSync(path18.dirname(target), { recursive: true });
19258
- fs11.writeFileSync(target, rendered.text, "utf8");
19864
+ fs13.mkdirSync(path20.dirname(target), { recursive: true });
19865
+ fs13.writeFileSync(target, rendered.text, "utf8");
19259
19866
  return { ...rendered.result, path: target };
19260
19867
  }
19261
19868
  function renderChangelog(source, entries, normalizeOnly) {
@@ -19288,17 +19895,17 @@ function renderChangelog(source, entries, normalizeOnly) {
19288
19895
  }
19289
19896
  var changelogTarget = (workspaceRoot, changelogPath) => {
19290
19897
  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))
19898
+ const cwd = fs13.realpathSync(resolveWorkspaceRoot(workspaceRoot));
19899
+ const target = path20.resolve(cwd, changelogPath || "CHANGELOG.md");
19900
+ if (target !== cwd && !target.startsWith(cwd + path20.sep))
19294
19901
  return { error: "changelog path must be inside workspace_root" };
19295
19902
  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))
19903
+ while (!fs13.existsSync(ancestor))
19904
+ ancestor = path20.dirname(ancestor);
19905
+ const canonicalAncestor = fs13.realpathSync(ancestor);
19906
+ if (canonicalAncestor !== cwd && !canonicalAncestor.startsWith(cwd + path20.sep))
19300
19907
  return { error: "changelog path must be inside workspace_root" };
19301
- return { cwd, target: fs11.existsSync(target) ? fs11.realpathSync(target) : target };
19908
+ return { cwd, target: fs13.existsSync(target) ? fs13.realpathSync(target) : target };
19302
19909
  } catch {
19303
19910
  return { error: "changelog path must be inside workspace_root" };
19304
19911
  }
@@ -19453,98 +20060,6 @@ function redactText(value) {
19453
20060
  `);
19454
20061
  }
19455
20062
 
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
20063
  // packages/workit-core/src/ledger.ts
19549
20064
  var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
19550
20065
  var FAILING = new Set(["failed", "blocked"]);
@@ -19659,7 +20174,7 @@ var credentialFreeRemote = (value) => pushRemoteIdentity(value);
19659
20174
  var actionRoot = (taskRoot, request) => {
19660
20175
  const chosen = "cwd" in request.payload ? request.payload.cwd : undefined;
19661
20176
  try {
19662
- const directory = fs12.realpathSync(chosen ? path19.resolve(taskRoot, chosen) : taskRoot);
20177
+ const directory = fs14.realpathSync(chosen ? path21.resolve(taskRoot, chosen) : taskRoot);
19663
20178
  if (request.operation === "context.read")
19664
20179
  return success(null, null, directory);
19665
20180
  const gitAction = request.operation.startsWith("git.") || request.operation.startsWith("hosting.");
@@ -19670,7 +20185,7 @@ var actionRoot = (taskRoot, request) => {
19670
20185
  return failure2("capability_unavailable", "action target is not a Git repository", {
19671
20186
  outcome: "not_started"
19672
20187
  });
19673
- return success(null, null, fs12.realpathSync(checkout));
20188
+ return success(null, null, fs14.realpathSync(checkout));
19674
20189
  } catch {
19675
20190
  return failure2("invalid_input", "action target directory cannot be resolved", {
19676
20191
  outcome: "not_started"
@@ -20002,7 +20517,7 @@ var hostingProvider = (root, remote) => {
20002
20517
  };
20003
20518
  var remoteBranchTip = (root, provider, host, repo, branch) => {
20004
20519
  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], {
20520
+ const response = spawnSync10(provider === "github" ? "gh" : "glab", ["api", endpoint], {
20006
20521
  cwd: root,
20007
20522
  encoding: "utf8",
20008
20523
  env: hostingCliEnv(provider, host)
@@ -20010,7 +20525,7 @@ var remoteBranchTip = (root, provider, host, repo, branch) => {
20010
20525
  if (response.status !== 0) {
20011
20526
  if (/\b404\b/.test(response.stderr ?? "")) {
20012
20527
  const project = provider === "github" ? `repos/${repo}` : `projects/${encodeURIComponent(repo)}`;
20013
- const visible = spawnSync9(provider === "github" ? "gh" : "glab", ["api", project], {
20528
+ const visible = spawnSync10(provider === "github" ? "gh" : "glab", ["api", project], {
20014
20529
  cwd: root,
20015
20530
  encoding: "utf8",
20016
20531
  env: hostingCliEnv(provider, host)
@@ -20066,7 +20581,7 @@ var mergedBranch = (root, branch) => {
20066
20581
  outcome: "not_started"
20067
20582
  });
20068
20583
  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], {
20584
+ const response = spawnSync10(provider === "github" ? "gh" : "glab", ["api", endpoint], {
20070
20585
  cwd: root,
20071
20586
  encoding: "utf8",
20072
20587
  env: hostingCliEnv(provider, auth.host)
@@ -20128,7 +20643,7 @@ var readHostingAction = async (root, resolved, actionRef) => {
20128
20643
  return unknownHostingEvidence(operation);
20129
20644
  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
20645
  const cli = cfg.provider === "github" ? "gh" : "glab";
20131
- const response = spawnSync9(cli, ["api", endpoint], {
20646
+ const response = spawnSync10(cli, ["api", endpoint], {
20132
20647
  cwd: root,
20133
20648
  encoding: "utf8",
20134
20649
  env: hostingCliEnv(cfg.provider, auth.host)
@@ -20320,7 +20835,7 @@ var readLocalAction = async (root, resolved, actionRef) => {
20320
20835
  const sdd = resolved.request.payload.sdd_dir;
20321
20836
  if (resolved.request.payload.action === "reapply_stash" && typeof sdd === "string") {
20322
20837
  try {
20323
- const manifest = JSON.parse(fs12.readFileSync(path19.join(sdd, "manifest.json"), "utf8"));
20838
+ const manifest = JSON.parse(fs14.readFileSync(path21.join(sdd, "manifest.json"), "utf8"));
20324
20839
  const stashes = gitRaw(root, ["stash", "list", "--format=%H"]);
20325
20840
  if (typeof target.stash_commit === "string" && manifest.stash_ref === undefined && stashes !== null && !stashes.split(`
20326
20841
  `).includes(target.stash_commit))
@@ -20328,7 +20843,7 @@ var readLocalAction = async (root, resolved, actionRef) => {
20328
20843
  } catch {}
20329
20844
  } 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
20845
  try {
20331
- const manifest = JSON.parse(fs12.readFileSync(path19.join(sdd, "manifest.json"), "utf8"));
20846
+ const manifest = JSON.parse(fs14.readFileSync(path21.join(sdd, "manifest.json"), "utf8"));
20332
20847
  if (manifest.branch === targetBranch)
20333
20848
  observed = { branch: targetBranch };
20334
20849
  } catch {}
@@ -20381,7 +20896,7 @@ var readDeletedBranch = async (root, resolved, actionRef) => {
20381
20896
  };
20382
20897
  var youTrackBase = () => {
20383
20898
  try {
20384
- const config = JSON.parse(fs12.readFileSync(youTrackConfigPath(), "utf8"));
20899
+ const config = JSON.parse(fs14.readFileSync(youTrackConfigPath(), "utf8"));
20385
20900
  const value = typeof config.baseUrl === "string" ? config.baseUrl.trim() : "";
20386
20901
  if (!value || /[\s#?]/.test(value))
20387
20902
  return null;
@@ -20449,7 +20964,7 @@ var resolveExternalActionAtRoot = (root, request, preservedDateMs) => {
20449
20964
  let stashTarget;
20450
20965
  if (action === "reapply_stash") {
20451
20966
  try {
20452
- const manifest = JSON.parse(fs12.readFileSync(path19.join(sdd_dir, "manifest.json"), "utf8"));
20967
+ const manifest = JSON.parse(fs14.readFileSync(path21.join(sdd_dir, "manifest.json"), "utf8"));
20453
20968
  const stash_ref = typeof manifest.stash_ref === "string" ? manifest.stash_ref : "";
20454
20969
  const stash_commit = stash_ref ? gitValue(root, ["rev-parse", stash_ref]) : null;
20455
20970
  if (stash_ref && stash_commit)
@@ -20724,7 +21239,7 @@ var resolveExternalActionAtRoot = (root, request, preservedDateMs) => {
20724
21239
  if (preview.error)
20725
21240
  return failure2("invalid_input", preview.error);
20726
21241
  const target = String(preview.path);
20727
- const relative = path19.relative(fs12.realpathSync(root), target).replaceAll(path19.sep, "/") || "CHANGELOG.md";
21242
+ const relative = path21.relative(fs14.realpathSync(root), target).replaceAll(path21.sep, "/") || "CHANGELOG.md";
20728
21243
  const normalized = {
20729
21244
  operation: request.operation,
20730
21245
  payload: {
@@ -21566,9 +22081,9 @@ import { randomUUID as randomUUID3 } from "node:crypto";
21566
22081
  // packages/workit-pi/src/worker-protocol.ts
21567
22082
  var MAX_WORKER_LINE_BYTES = 64 * 1024;
21568
22083
  var MAX_WORKER_STDERR_BYTES = 64 * 1024;
21569
- var isRecord3 = (value) => typeof value === "object" && value !== null;
22084
+ var isRecord4 = (value) => typeof value === "object" && value !== null;
21570
22085
  var parseEvent = (value) => {
21571
- if (!isRecord3(value) || typeof value.type !== "string")
22086
+ if (!isRecord4(value) || typeof value.type !== "string")
21572
22087
  return null;
21573
22088
  if (value.type === "workit_worker_ready") {
21574
22089
  if (typeof value.workerId !== "string" || typeof value.sessionId !== "string")